diff --git a/src/jubjub/fs.rs b/src/jubjub/fs.rs index 051978b..eb10e65 100644 --- a/src/jubjub/fs.rs +++ b/src/jubjub/fs.rs @@ -1,7 +1,10 @@ -use pairing::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError, LegendreSymbol}; +use byteorder::{ByteOrder, LittleEndian}; +use pairing::{BitIterator, Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError, LegendreSymbol}; use pairing::LegendreSymbol::*; use pairing::{adc, sbb, mac_with_carry}; +use super::ToUniform; + // s = 6554484396890773809930967563523245729705921265872317281365359162392183254199 const MODULUS: FsRepr = FsRepr([0xd0970e5ed6f72cb7, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9]); @@ -548,6 +551,31 @@ impl Fs { (self.0).0[3] = r7; self.reduce(); } + + fn mul_bits>(&self, bits: BitIterator) -> Self { + let mut res = Self::zero(); + for bit in bits { + res.double(); + + if bit { + res.add_assign(self) + } + } + res + } +} + +impl ToUniform for Fs { + /// Convert a little endian byte string into a uniform + /// field element. The number is reduced mod s. The caller + /// is responsible for ensuring the input is 64 bytes of + /// Random Oracle output. + fn to_uniform(digest: &[u8]) -> Self { + assert_eq!(digest.len(), 64); + let mut repr: [u64; 8] = [0; 8]; + LittleEndian::read_u64_into(digest, &mut repr); + Self::one().mul_bits(BitIterator::new(repr)) + } } impl SqrtField for Fs { diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 3b786c1..1c21192 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -85,12 +85,16 @@ pub enum FixedGenerators { Max = 6 } +pub trait ToUniform { + fn to_uniform(digest: &[u8]) -> Self; +} + /// This is an extension to the pairing Engine trait which /// offers a scalar field for the embedded curve (Jubjub) /// and some pre-computed parameters. pub trait JubjubEngine: Engine { /// The scalar field of the Jubjub curve - type Fs: PrimeField + SqrtField; + type Fs: PrimeField + SqrtField + ToUniform; /// The parameters of Jubjub and the Sapling protocol type Params: JubjubParams; } diff --git a/src/lib.rs b/src/lib.rs index 44e10c0..7000292 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,4 +18,5 @@ pub mod circuit; pub mod pedersen_hash; pub mod primitives; pub mod constants; +pub mod redjubjub; mod util; diff --git a/src/redjubjub.rs b/src/redjubjub.rs new file mode 100644 index 0000000..f8c13d4 --- /dev/null +++ b/src/redjubjub.rs @@ -0,0 +1,137 @@ +//! Implementation of RedJubjub, a specialization of RedDSA to the Jubjub curve. +//! See section 5.4.6 of the Sapling protocol specification. + +use pairing::Field; +use rand::Rng; + +use jubjub::{FixedGenerators, JubjubEngine, JubjubParams, Unknown, edwards::Point}; +use util::hash_to_scalar; + +fn h_star(a: &[u8], b: &[u8]) -> E::Fs { + hash_to_scalar::(b"Zcash_RedJubjubH", a, b) +} + +pub struct Signature { + r: Point, + s: E::Fs, +} + +pub struct PrivateKey(E::Fs); + +pub struct PublicKey(Point); + +impl PrivateKey { + pub fn randomize(&self, alpha: E::Fs) -> Self { + let mut tmp = self.0; + tmp.add_assign(&alpha); + PrivateKey(tmp) + } + + pub fn sign(&self, msg: &[u8], rng: &mut R, params: &E::Params) -> Signature { + // T = (l_H + 128) bits of randomness + // For H*, l_H = 512 bits + let mut t = [0u8; 80]; + rng.fill_bytes(&mut t[..]); + + // r = H*(T || M) + let r = h_star::(&t[..], msg); + + // R = r . G + let r_g = params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(r, params); + let mut rbar = [0u8; 32]; + r_g.write(&mut rbar[..]) + .expect("Jubjub points should serialize to 32 bytes"); + + // S = r + H*(Rbar || M) . sk + let mut s = h_star::(&rbar[..], msg); + s.mul_assign(&self.0); + s.add_assign(&r); + + Signature { r: r_g.into(), s } + } +} + +impl PublicKey { + pub fn from_private(privkey: &PrivateKey, params: &E::Params) -> Self { + let res = params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(privkey.0, params) + .into(); + PublicKey(res) + } + + pub fn randomize(&self, alpha: E::Fs, params: &E::Params) -> Self { + let res: Point = params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(alpha, params) + .into(); + let res = res.add(&self.0, params); + PublicKey(res) + } + + // Pre-conditions: + // - rbar was the canonical representation of a point on the curve. + // - sig.s < order(G) + // TODO(str4d): Enforce these during deserialization of Signature + pub fn verify(&self, msg: &[u8], sig: &Signature, params: &E::Params) -> bool { + // c = H*(Rbar || M) + let mut rbar = [0u8; 32]; + sig.r + .write(&mut rbar[..]) + .expect("Jubjub points should serialize to 32 bytes"); + let c = h_star::(&rbar[..], msg); + + // S . G = R + c . vk + self.0.mul(c, params).add(&sig.r, params) + == params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(sig.s, params) + .into() + } +} + +#[cfg(test)] +mod tests { + use pairing::bls12_381::Bls12; + use rand::thread_rng; + + use jubjub::JubjubBls12; + + use super::*; + + #[test] + fn random_signatures() { + let rng = &mut thread_rng(); + let params = &JubjubBls12::new(); + + for _ in 0..1000 { + let sk = PrivateKey::(rng.gen()); + let vk = PublicKey::from_private(&sk, params); + + let msg1 = b"Foo bar"; + let msg2 = b"Spam eggs"; + + let sig1 = sk.sign(msg1, rng, params); + let sig2 = sk.sign(msg2, rng, params); + + assert!(vk.verify(msg1, &sig1, params)); + assert!(vk.verify(msg2, &sig2, params)); + assert!(!vk.verify(msg1, &sig2, params)); + assert!(!vk.verify(msg2, &sig1, params)); + + let alpha = rng.gen(); + let rsk = sk.randomize(alpha); + let rvk = vk.randomize(alpha, params); + + let sig1 = rsk.sign(msg1, rng, params); + let sig2 = rsk.sign(msg2, rng, params); + + assert!(rvk.verify(msg1, &sig1, params)); + assert!(rvk.verify(msg2, &sig2, params)); + assert!(!rvk.verify(msg1, &sig2, params)); + assert!(!rvk.verify(msg2, &sig1, params)); + } + } +} diff --git a/src/util.rs b/src/util.rs index 5291fef..b5cc4e8 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,7 @@ +use blake2_rfc::blake2b::Blake2b; + +use jubjub::{JubjubEngine, ToUniform}; + pub fn swap_bits_u64(x: u64) -> u64 { let mut tmp = 0; @@ -7,6 +11,14 @@ pub fn swap_bits_u64(x: u64) -> u64 tmp } +pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> E::Fs { + let mut hasher = Blake2b::with_params(64, &[], &[], persona); + hasher.update(a); + hasher.update(b); + let ret = hasher.finalize(); + E::Fs::to_uniform(ret.as_ref()) +} + #[test] fn test_swap_bits_u64() { assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111);