r255b3: schnorr signatures using ristretto255 and blake3.

This crate provides Schnorr signatures based on [Ristretto255] and [Blake3].

Note: This is not [Ed25519], if you want Ed25519, please use the excellent [ed25519-dalek] crate.

Warning! This is a beta version of a (slightly) novel cryptographic primitive. It has not been audited! The API and cryptographic construction may change! Use at your own risk!

Why?

The initial motivation was preparing for embedded versions of [converge]. We already use the Blake3 hash function for bulk data, and adding SHA512 just for Ed25519 signatures just isn't necessary.

That said, there are other benefits:

How?

For the most part the parameterization is straight-forward. However, there are two minor deviations:

Key Generation

r255b3 uses 252-bit (or so) secret keys. Rather than clamping (like Ed25519), all secret keys are scalars less than the group order $\ell$. Typically, secret keys sk are generated as 256 random bits, then reduced modulo the group order. In some circumstances this could introduce significant bias into the private keys generated, however given that $\ell$ lies very close to a power of 2, the total bias introduced is under 1/126 bits.

Signing

The first step is to generate the scalar nonce k. This is done by taking the Blake3 hash (keyed with the domain) of the secret key concatenated with the message.

k = Blake3(domain, sk | message)

Alternatively, k can be generated as a random scalar modulo the group order.

Now k multiplies the base point to get the point r, which we will recover later while verifying. r is compressed, concatenated with the message, and fed to Blake2 to used to derive the scalar e.

r = k*B e = Blake3(domain, r | message)

Finally, we multiply e and the secret key, and subtract their product from k, yielding the scalar s. This produces our signature, which is the tuple of e and s.

s = k - sk * e sig = (e, s)

Verifying

For verification, we need the public key, this is found by multiplying the base point by the private key.

pk = sk*B

Then we take our signature and reconstruct r, calling it rᵥ:

rᵥ = s*B + e*pk = (k - sk*e)*B + (e*sk*B) = k*B - sk*e*B + e*sk*B = k*B

Now that we have rᵥ, we hash the message with Blake3 exactly as was done during signing. This yields another copy of e, which we call eᵥ.

eᵥ = Blake3(domain, rᵥ | message)

Finally, we can compare eᵥ with the e from the signature, and if they match we have a valid signature.

License

This project is dedicated to the public domain, see the UNLICENSE for details.