This gem implements a signature scheme known as one-time ring signatures. The algorithm for one-time ring signatures was originally described in section 4.4 of the CryptoNote Whitepaper.
Ring signatures are a special type of digital signature that allows the signer to achieve unconditional unlinkability between their signature and their public key. Signers sign a message using their private key and an arbitrary set of foreign public keys. Verifiers are given the full set of public keys that a message was signed with. Verifiers can prove that one of the private keys signed the message, but they cannot determine which private key was actually used for signing.
The signatures produced by this gem are said to be one-time ring signatures, because the signature includes a Key Image, which is the same for all messages signed with the same private key. Therefore, if a signer signs multiple messages with the same private key, the signatures can be linked.
This gem does not use any randomness. All the algorithms are deterministic, and do not require any sort of external source of randomness. When signing, a seed is computed from a hash of the message and the private key. That seed is used along with the hash algorithm and a nonce anywhere the signing algorithm calls for randomness. As a result, the same inputs will always generate the same signature.
- This gem is not optimized for speed. All elliptical curve arithmetic is computed in pure ruby. As a result, the sign and verify operations are slow. Future versions will hopefully be better optimized for speed.
- This gem was not written by a cryptography expert. It is provided "as is" and it is the user's responsibility to make sure it will be suitable for the desired purpose.
This library is distributed as a gem named ring_sig at RubyGems.org. To install it, run:
gem install ring_sig
First, require the gem:
require 'ring_sig'
Instantiate a hasher. This specifies the ECDSA::Group
, and the hash algorithm
that will be used for signing.
hasher = RingSig::Hasher::Secp256k1_Sha256
Create a private key for signing. For our example, we'll just use the
private key 1
. In the wild, you'll want to utilize a securely generated key.
key = RingSig::PrivateKey.new(1, hasher)
Instantiate a set a foreign public keys for signing. To demonstrate that any
arbitrary keys can be used, we'll use the public keys from the coinbase
transactions of the first three blocks on the bitcoin blockchain (we can do this
since our hasher uses Secp256k1 as its ECDSA::Group
).
foreign_keys = %w{
04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f
0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee
047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77
}.map {|s| RingSig::PublicKey.from_hex(s, hasher) }
Next, we sign the message. This will assign a RingSig::Signature
to the sig
variable, and a deterministically shuffled Array of RingSig::PublicKey
s to the
public_keys
variable.
sig, public_keys = key.sign("Hello World!", foreign_keys)
You can encode the contents of the signature using the to_hex
method:
sig_hex = sig.to_hex
In order to verify the signature, the verifier will need the signature, and the ordered set of public keys.
In order to instantiate a signature object from a hex string, you can use the
from_hex
method:
sig2 = RingSig::Signature.from_hex(sig_hex, hasher)
Finally, verify the signature:
sig2.verify("Hello World!", public_keys)
The above example signature used Secp256k1 for the ECDSA group, and SHA256 for the hash algorithm. You can specify alternates if you'd like, by using a different hasher:
key = RingSig::PrivateKey.new(1, RingSig::Hasher::Secp384r1_Sha384)
You can also instantiate your own hasher directly, rather than using the pre-defined constants:
hasher = RingSig::Hasher.new(ECDSA::Group::Secp384r1, OpenSSL::Digest::SHA384)
Note that the byte-length of the group's order and the digest method must match, or else the signatures generated will leak the position of the true signer.
There currently aren't any standards around Ring Signatures that I know of. This gem attempts to make sensible choices such that the exact same algorithm can easily be implemented in other languages.
I'd love to see standards emerge around this powerful cryptographic primitive. If you have any feedback about how to make the implementation of the algorithms in this gem more inter-operable with other systems, I'd love to hear it!
To submit a bug, please go to this gem's github page and create a new issue.
If you'd like to contribute code, these are the general steps:
- Fork and clone the repository
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -m 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a Pull Request
Please make sure to write tests for your new code, and follow the existing code standards wherever possible.
Special thanks to the authors of the very excellent ECDSA gem. Not only is it a dependency of this gem, I also used it to gain a much better understanding of elliptical curve crypto, and used it as inspiration for this gem.
Ruby 1.9.3 and above, including jruby-19mode.
For complete documentation, see the RingSig page on RubyDoc.info.