From 51bb5f0f709422db4b959c173117ce77da098a95 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:53:36 -0600 Subject: [PATCH 1/9] Implement UInt32::shr() for SHA256. --- src/circuit/uint32.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index 4714724..ad2dc84 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -155,6 +155,25 @@ impl UInt32 { } } + pub fn shr(&self, by: usize) -> Self { + let by = by % 32; + + let fill = Boolean::constant(false); + + let new_bits = self.bits + .iter() // The bits are least significant first + .skip(by) // Skip the bits that will be lost during the shift + .chain(Some(&fill).into_iter().cycle()) // Rest will be zeros + .take(32) // Only 32 bits needed! + .cloned() + .collect(); + + UInt32 { + bits: new_bits, + value: self.value.map(|v| v >> by as u32) + } + } + /// XOR this `UInt32` with another `UInt32` pub fn xor( &self, @@ -483,6 +502,7 @@ mod test { for i in 0..32 { let b = a.rotr(i); + assert_eq!(a.bits.len(), b.bits.len()); assert!(b.value.unwrap() == num); @@ -501,4 +521,24 @@ mod test { num = num.rotate_right(1); } } + + #[test] + fn test_uint32_shr() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..50 { + for i in 0..60 { + let num = rng.gen(); + let a = UInt32::constant(num).shr(i); + let b = UInt32::constant(num >> i); + + assert_eq!(a.value.unwrap(), num >> i); + + assert_eq!(a.bits.len(), b.bits.len()); + for (a, b) in a.bits.iter().zip(b.bits.iter()) { + assert_eq!(a.get_value().unwrap(), b.get_value().unwrap()); + } + } + } + } } From 4f0a553fbb36802dbe592cd198a69ce6938e98af Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:57:02 -0600 Subject: [PATCH 2/9] Implement UInt32 encoding/decoding with big-endian representation. --- src/circuit/uint32.rs | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index ad2dc84..6bb4847 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -87,6 +87,31 @@ impl UInt32 { }) } + pub fn into_bits_be(&self) -> Vec { + self.bits.iter().rev().cloned().collect() + } + + pub fn from_bits_be(bits: &[Boolean]) -> Self { + assert_eq!(bits.len(), 32); + + let mut value = Some(0u32); + for b in bits { + value.as_mut().map(|v| *v <<= 1); + + match b.get_value() { + Some(true) => { value.as_mut().map(|v| *v |= 1); }, + Some(false) => {}, + None => { value = None; } + } + } + + UInt32 { + value: value, + bits: bits.iter().rev().cloned().collect() + } + } + + /// Turns this `UInt32` into its little-endian byte order representation. pub fn into_bits(&self) -> Vec { self.bits.chunks(8) @@ -323,6 +348,37 @@ mod test { use bellman::{ConstraintSystem}; use circuit::multieq::MultiEq; + #[test] + fn test_uint32_from_bits_be() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]); + + for _ in 0..1000 { + let mut v = (0..32).map(|_| Boolean::constant(rng.gen())).collect::>(); + + let b = UInt32::from_bits_be(&v); + + for (i, bit) in b.bits.iter().enumerate() { + match bit { + &Boolean::Constant(bit) => { + assert!(bit == ((b.value.unwrap() >> i) & 1 == 1)); + }, + _ => unreachable!() + } + } + + let expected_to_be_same = b.into_bits_be(); + + for x in v.iter().zip(expected_to_be_same.iter()) + { + match x { + (&Boolean::Constant(true), &Boolean::Constant(true)) => {}, + (&Boolean::Constant(false), &Boolean::Constant(false)) => {}, + _ => unreachable!() + } + } + } + } + #[test] fn test_uint32_from_bits() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]); From abca61401e43239c2b70a9664e3aa176d22a8b88 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:01:15 -0600 Subject: [PATCH 3/9] Implementation of SHA256 choice operation for Boolean. --- src/circuit/boolean.rs | 234 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 22eb9b0..f5d9827 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -523,6 +523,132 @@ impl Boolean { } } } + + /// Computes (a and b) xor ((not a) and c) + pub fn sha256_ch<'a, E, CS>( + mut cs: CS, + a: &'a Self, + b: &'a Self, + c: &'a Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let ch_value = match (a.get_value(), b.get_value(), c.get_value()) { + (Some(a), Some(b), Some(c)) => { + // (a and b) xor ((not a) and c) + Some((a & b) ^ ((!a) & c)) + }, + _ => None + }; + + match (a, b, c) { + (&Boolean::Constant(_), + &Boolean::Constant(_), + &Boolean::Constant(_)) => { + // They're all constants, so we can just compute the value. + + return Ok(Boolean::Constant(ch_value.expect("they're all constants"))); + }, + (&Boolean::Constant(false), _, c) => { + // If a is false + // (a and b) xor ((not a) and c) + // equals + // (false) xor (c) + // equals + // c + return Ok(c.clone()); + }, + (a, &Boolean::Constant(false), c) => { + // If b is false + // (a and b) xor ((not a) and c) + // equals + // ((not a) and c) + return Boolean::and( + cs, + &a.not(), + &c + ); + }, + (a, b, &Boolean::Constant(false)) => { + // If c is false + // (a and b) xor ((not a) and c) + // equals + // (a and b) + return Boolean::and( + cs, + &a, + &b + ); + }, + (a, b, &Boolean::Constant(true)) => { + // If c is true + // (a and b) xor ((not a) and c) + // equals + // (a and b) xor (not a) + // equals + // not (a and (not b)) + return Ok(Boolean::and( + cs, + &a, + &b.not() + )?.not()); + }, + (a, &Boolean::Constant(true), c) => { + // If b is true + // (a and b) xor ((not a) and c) + // equals + // a xor ((not a) and c) + // equals + // not ((not a) and (not c)) + return Ok(Boolean::and( + cs, + &a.not(), + &c.not() + )?.not()); + }, + (&Boolean::Constant(true), _, _) => { + // If a is true + // (a and b) xor ((not a) and c) + // equals + // b xor ((not a) and c) + // So we just continue! + }, + (&Boolean::Is(_), &Boolean::Is(_), &Boolean::Is(_)) | + (&Boolean::Is(_), &Boolean::Is(_), &Boolean::Not(_)) | + (&Boolean::Is(_), &Boolean::Not(_), &Boolean::Is(_)) | + (&Boolean::Is(_), &Boolean::Not(_), &Boolean::Not(_)) | + (&Boolean::Not(_), &Boolean::Is(_), &Boolean::Is(_)) | + (&Boolean::Not(_), &Boolean::Is(_), &Boolean::Not(_)) | + (&Boolean::Not(_), &Boolean::Not(_), &Boolean::Is(_)) | + (&Boolean::Not(_), &Boolean::Not(_), &Boolean::Not(_)) + => {} + } + + let ch = cs.alloc(|| "ch", || { + ch_value.get().map(|v| { + if *v { + E::Fr::one() + } else { + E::Fr::zero() + } + }) + })?; + + // a(b - c) = ch - c + cs.enforce( + || "ch computation", + |_| b.lc(CS::one(), E::Fr::one()) + - &c.lc(CS::one(), E::Fr::one()), + |_| a.lc(CS::one(), E::Fr::one()), + |lc| lc + ch - &c.lc(CS::one(), E::Fr::one()) + ); + + Ok(AllocatedBit { + value: ch_value, + variable: ch + }.into()) + } } impl From for Boolean { @@ -797,6 +923,31 @@ mod test { NegatedAllocatedFalse } + impl OperandType { + fn is_constant(&self) -> bool { + match *self { + OperandType::True => true, + OperandType::False => true, + OperandType::AllocatedTrue => false, + OperandType::AllocatedFalse => false, + OperandType::NegatedAllocatedTrue => false, + OperandType::NegatedAllocatedFalse => false + } + } + + fn val(&self) -> bool { + match *self { + OperandType::True => true, + OperandType::False => false, + OperandType::AllocatedTrue => true, + OperandType::AllocatedFalse => false, + OperandType::NegatedAllocatedTrue => false, + OperandType::NegatedAllocatedFalse => true + } + } + } + + #[test] fn test_boolean_xor() { let variants = [ @@ -1115,4 +1266,87 @@ mod test { assert_eq!(bits[254 - 20].value.unwrap(), true); assert_eq!(bits[254 - 23].value.unwrap(), true); } + + #[test] + fn test_boolean_sha256_ch() { + let variants = [ + OperandType::True, + OperandType::False, + OperandType::AllocatedTrue, + OperandType::AllocatedFalse, + OperandType::NegatedAllocatedTrue, + OperandType::NegatedAllocatedFalse + ]; + + for first_operand in variants.iter().cloned() { + for second_operand in variants.iter().cloned() { + for third_operand in variants.iter().cloned() { + let mut cs = TestConstraintSystem::::new(); + + let a; + let b; + let c; + + // ch = (a and b) xor ((not a) and c) + let expected = (first_operand.val() & second_operand.val()) ^ + ((!first_operand.val()) & third_operand.val()); + + { + let mut dyn_construct = |operand, name| { + let cs = cs.namespace(|| name); + + match operand { + OperandType::True => Boolean::constant(true), + OperandType::False => Boolean::constant(false), + OperandType::AllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()), + OperandType::AllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()), + OperandType::NegatedAllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()).not(), + OperandType::NegatedAllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()).not(), + } + }; + + a = dyn_construct(first_operand, "a"); + b = dyn_construct(second_operand, "b"); + c = dyn_construct(third_operand, "c"); + } + + let maj = Boolean::sha256_ch(&mut cs, &a, &b, &c).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(maj.get_value().unwrap(), expected); + + if first_operand.is_constant() || + second_operand.is_constant() || + third_operand.is_constant() + { + if first_operand.is_constant() && + second_operand.is_constant() && + third_operand.is_constant() + { + assert_eq!(cs.num_constraints(), 0); + } + } + else + { + assert_eq!(cs.get("ch"), { + if expected { + Fr::one() + } else { + Fr::zero() + } + }); + cs.set("ch", { + if expected { + Fr::zero() + } else { + Fr::one() + } + }); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "ch computation"); + } + } + } + } + } } From 36a6b5fd90fed1925dd574279a803116644f4730 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:03:18 -0600 Subject: [PATCH 4/9] Implementation of SHA256 majority operation for Boolean. --- src/circuit/boolean.rs | 230 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index f5d9827..08f407e 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -649,6 +649,152 @@ impl Boolean { variable: ch }.into()) } + + /// Computes (a and b) xor (a and c) xor (b and c) + pub fn sha256_maj<'a, E, CS>( + mut cs: CS, + a: &'a Self, + b: &'a Self, + c: &'a Self, + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let maj_value = match (a.get_value(), b.get_value(), c.get_value()) { + (Some(a), Some(b), Some(c)) => { + // (a and b) xor (a and c) xor (b and c) + Some((a & b) ^ (a & c) ^ (b & c)) + }, + _ => None + }; + + match (a, b, c) { + (&Boolean::Constant(_), + &Boolean::Constant(_), + &Boolean::Constant(_)) => { + // They're all constants, so we can just compute the value. + + return Ok(Boolean::Constant(maj_value.expect("they're all constants"))); + }, + (&Boolean::Constant(false), b, c) => { + // If a is false, + // (a and b) xor (a and c) xor (b and c) + // equals + // (b and c) + return Boolean::and( + cs, + b, + c + ); + }, + (a, &Boolean::Constant(false), c) => { + // If b is false, + // (a and b) xor (a and c) xor (b and c) + // equals + // (a and c) + return Boolean::and( + cs, + a, + c + ); + }, + (a, b, &Boolean::Constant(false)) => { + // If c is false, + // (a and b) xor (a and c) xor (b and c) + // equals + // (a and b) + return Boolean::and( + cs, + a, + b + ); + }, + (a, b, &Boolean::Constant(true)) => { + // If c is true, + // (a and b) xor (a and c) xor (b and c) + // equals + // (a and b) xor (a) xor (b) + // equals + // not ((not a) and (not b)) + return Ok(Boolean::and( + cs, + &a.not(), + &b.not() + )?.not()); + }, + (a, &Boolean::Constant(true), c) => { + // If b is true, + // (a and b) xor (a and c) xor (b and c) + // equals + // (a) xor (a and c) xor (c) + return Ok(Boolean::and( + cs, + &a.not(), + &c.not() + )?.not()); + }, + (&Boolean::Constant(true), b, c) => { + // If a is true, + // (a and b) xor (a and c) xor (b and c) + // equals + // (b) xor (c) xor (b and c) + return Ok(Boolean::and( + cs, + &b.not(), + &c.not() + )?.not()); + }, + (&Boolean::Is(_), &Boolean::Is(_), &Boolean::Is(_)) | + (&Boolean::Is(_), &Boolean::Is(_), &Boolean::Not(_)) | + (&Boolean::Is(_), &Boolean::Not(_), &Boolean::Is(_)) | + (&Boolean::Is(_), &Boolean::Not(_), &Boolean::Not(_)) | + (&Boolean::Not(_), &Boolean::Is(_), &Boolean::Is(_)) | + (&Boolean::Not(_), &Boolean::Is(_), &Boolean::Not(_)) | + (&Boolean::Not(_), &Boolean::Not(_), &Boolean::Is(_)) | + (&Boolean::Not(_), &Boolean::Not(_), &Boolean::Not(_)) + => {} + } + + let maj = cs.alloc(|| "maj", || { + maj_value.get().map(|v| { + if *v { + E::Fr::one() + } else { + E::Fr::zero() + } + }) + })?; + + // ¬(¬a ∧ ¬b) ∧ ¬(¬a ∧ ¬c) ∧ ¬(¬b ∧ ¬c) + // (1 - ((1 - a) * (1 - b))) * (1 - ((1 - a) * (1 - c))) * (1 - ((1 - b) * (1 - c))) + // (a + b - ab) * (a + c - ac) * (b + c - bc) + // -2abc + ab + ac + bc + // a (-2bc + b + c) + bc + // + // (b) * (c) = (bc) + // (2bc - b - c) * (a) = bc - maj + + let bc = Self::and( + cs.namespace(|| "b and c"), + b, + c + )?; + + cs.enforce( + || "maj computation", + |_| bc.lc(CS::one(), E::Fr::one()) + + &bc.lc(CS::one(), E::Fr::one()) + - &b.lc(CS::one(), E::Fr::one()) + - &c.lc(CS::one(), E::Fr::one()), + |_| a.lc(CS::one(), E::Fr::one()), + |_| bc.lc(CS::one(), E::Fr::one()) - maj + ); + + Ok(AllocatedBit { + value: maj_value, + variable: maj + }.into()) + } } impl From for Boolean { @@ -1349,4 +1495,88 @@ mod test { } } } + + #[test] + fn test_boolean_sha256_maj() { + let variants = [ + OperandType::True, + OperandType::False, + OperandType::AllocatedTrue, + OperandType::AllocatedFalse, + OperandType::NegatedAllocatedTrue, + OperandType::NegatedAllocatedFalse + ]; + + for first_operand in variants.iter().cloned() { + for second_operand in variants.iter().cloned() { + for third_operand in variants.iter().cloned() { + let mut cs = TestConstraintSystem::::new(); + + let a; + let b; + let c; + + // maj = (a and b) xor (a and c) xor (b and c) + let expected = (first_operand.val() & second_operand.val()) ^ + (first_operand.val() & third_operand.val()) ^ + (second_operand.val() & third_operand.val()); + + { + let mut dyn_construct = |operand, name| { + let cs = cs.namespace(|| name); + + match operand { + OperandType::True => Boolean::constant(true), + OperandType::False => Boolean::constant(false), + OperandType::AllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()), + OperandType::AllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()), + OperandType::NegatedAllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()).not(), + OperandType::NegatedAllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()).not(), + } + }; + + a = dyn_construct(first_operand, "a"); + b = dyn_construct(second_operand, "b"); + c = dyn_construct(third_operand, "c"); + } + + let maj = Boolean::sha256_maj(&mut cs, &a, &b, &c).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(maj.get_value().unwrap(), expected); + + if first_operand.is_constant() || + second_operand.is_constant() || + third_operand.is_constant() + { + if first_operand.is_constant() && + second_operand.is_constant() && + third_operand.is_constant() + { + assert_eq!(cs.num_constraints(), 0); + } + } + else + { + assert_eq!(cs.get("maj"), { + if expected { + Fr::one() + } else { + Fr::zero() + } + }); + cs.set("maj", { + if expected { + Fr::zero() + } else { + Fr::one() + } + }); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "maj computation"); + } + } + } + } + } } From 75c5269d3b1b1d2d0c2e91e2eaf0ff1670e09b52 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:05:51 -0600 Subject: [PATCH 5/9] Implementation of SHA256 choice/majority for UInt32. --- src/circuit/uint32.rs | 161 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index 6bb4847..e254132 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -199,6 +199,85 @@ impl UInt32 { } } + fn triop( + mut cs: CS, + a: &Self, + b: &Self, + c: &Self, + tri_fn: F, + circuit_fn: U + ) -> Result + where E: Engine, + CS: ConstraintSystem, + F: Fn(u32, u32, u32) -> u32, + U: Fn(&mut CS, usize, &Boolean, &Boolean, &Boolean) -> Result + { + let new_value = match (a.value, b.value, c.value) { + (Some(a), Some(b), Some(c)) => { + Some(tri_fn(a, b, c)) + }, + _ => None + }; + + let bits = a.bits.iter() + .zip(b.bits.iter()) + .zip(c.bits.iter()) + .enumerate() + .map(|(i, ((a, b), c))| circuit_fn(&mut cs, i, a, b, c)) + .collect::>()?; + + Ok(UInt32 { + bits: bits, + value: new_value + }) + } + + /// Compute the `maj` value (a and b) xor (a and c) xor (b and c) + /// during SHA256. + pub fn sha256_maj( + cs: CS, + a: &Self, + b: &Self, + c: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + Self::triop(cs, a, b, c, |a, b, c| (a & b) ^ (a & c) ^ (b & c), + |cs, i, a, b, c| { + Boolean::sha256_maj( + cs.namespace(|| format!("maj {}", i)), + a, + b, + c + ) + } + ) + } + + /// Compute the `ch` value `(a and b) xor ((not a) and c)` + /// during SHA256. + pub fn sha256_ch( + cs: CS, + a: &Self, + b: &Self, + c: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + Self::triop(cs, a, b, c, |a, b, c| (a & b) ^ ((!a) & c), + |cs, i, a, b, c| { + Boolean::sha256_ch( + cs.namespace(|| format!("ch {}", i)), + a, + b, + c + ) + } + ) + } + /// XOR this `UInt32` with another `UInt32` pub fn xor( &self, @@ -597,4 +676,86 @@ mod test { } } } + + #[test] + fn test_uint32_sha256_maj() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let a: u32 = rng.gen(); + let b: u32 = rng.gen(); + let c: u32 = rng.gen(); + + let mut expected = (a & b) ^ (a & c) ^ (b & c); + + let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap(); + let b_bit = UInt32::constant(b); + let c_bit = UInt32::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap(); + + let r = UInt32::sha256_maj(&mut cs, &a_bit, &b_bit, &c_bit).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + match b { + &Boolean::Is(ref b) => { + assert!(b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Not(ref b) => { + assert!(!b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Constant(b) => { + assert!(b == (expected & 1 == 1)); + } + } + + expected >>= 1; + } + } + } + + #[test] + fn test_uint32_sha256_ch() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let a: u32 = rng.gen(); + let b: u32 = rng.gen(); + let c: u32 = rng.gen(); + + let mut expected = (a & b) ^ ((!a) & c); + + let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap(); + let b_bit = UInt32::constant(b); + let c_bit = UInt32::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap(); + + let r = UInt32::sha256_ch(&mut cs, &a_bit, &b_bit, &c_bit).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + match b { + &Boolean::Is(ref b) => { + assert!(b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Not(ref b) => { + assert!(!b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Constant(b) => { + assert!(b == (expected & 1 == 1)); + } + } + + expected >>= 1; + } + } + } } From ac13cb05bcff634621eb0d9d8b478ee9d2fee5f4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:08:12 -0600 Subject: [PATCH 6/9] Implementation of SHA256. --- Cargo.toml | 1 + src/circuit/mod.rs | 1 + src/circuit/sha256.rs | 417 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + 4 files changed, 422 insertions(+) create mode 100644 src/circuit/sha256.rs diff --git a/Cargo.toml b/Cargo.toml index 4a2aefd..b32997d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9" [dev-dependencies] hex-literal = "0.1" +rust-crypto = "0.2" [features] default = ["u128-support"] diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 4cde222..a82cb44 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -10,6 +10,7 @@ pub mod lookup; pub mod ecc; pub mod pedersen_hash; pub mod multipack; +pub mod sha256; pub mod sapling; diff --git a/src/circuit/sha256.rs b/src/circuit/sha256.rs new file mode 100644 index 0000000..7b55fc8 --- /dev/null +++ b/src/circuit/sha256.rs @@ -0,0 +1,417 @@ +use super::uint32::UInt32; +use super::multieq::MultiEq; +use super::boolean::Boolean; +use bellman::{ConstraintSystem, SynthesisError}; +use pairing::Engine; + +const ROUND_CONSTANTS: [u32; 64] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +]; + +const IV: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +]; + +pub fn sha256_block_no_padding( + mut cs: CS, + input: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + assert_eq!(input.len(), 512); + + Ok(sha256_compression_function( + &mut cs, + &input, + &get_sha256_iv() + )? + .into_iter() + .flat_map(|e| e.into_bits_be()) + .collect()) +} + +pub fn sha256( + mut cs: CS, + input: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + assert!(input.len() % 8 == 0); + + let mut padded = input.to_vec(); + let plen = padded.len() as u64; + // append a single '1' bit + padded.push(Boolean::constant(true)); + // append K '0' bits, where K is the minimum number >= 0 such that L + 1 + K + 64 is a multiple of 512 + while (padded.len() + 64) % 512 != 0 { + padded.push(Boolean::constant(false)); + } + // append L as a 64-bit big-endian integer, making the total post-processed length a multiple of 512 bits + for b in (0..64).rev().map(|i| (plen >> i) & 1 == 1) { + padded.push(Boolean::constant(b)); + } + assert!(padded.len() % 512 == 0); + + let mut cur = get_sha256_iv(); + for (i, block) in padded.chunks(512).enumerate() { + cur = sha256_compression_function( + cs.namespace(|| format!("block {}", i)), + block, + &cur + )?; + } + + Ok(cur.into_iter() + .flat_map(|e| e.into_bits_be()) + .collect()) +} + +fn get_sha256_iv() -> Vec { + IV.iter().map(|&v| UInt32::constant(v)).collect() +} + +fn sha256_compression_function( + cs: CS, + input: &[Boolean], + current_hash_value: &[UInt32] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + assert_eq!(input.len(), 512); + assert_eq!(current_hash_value.len(), 8); + + let mut w = input.chunks(32) + .map(|e| UInt32::from_bits_be(e)) + .collect::>(); + + // We can save some constraints by combining some of + // the constraints in different u32 additions + let mut cs = MultiEq::new(cs); + + for i in 16..64 { + let cs = &mut cs.namespace(|| format!("w extension {}", i)); + + // s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3) + let mut s0 = w[i-15].rotr(7); + s0 = s0.xor( + cs.namespace(|| "first xor for s0"), + &w[i-15].rotr(18) + )?; + s0 = s0.xor( + cs.namespace(|| "second xor for s0"), + &w[i-15].shr(3) + )?; + + // s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor (w[i-2] rightshift 10) + let mut s1 = w[i-2].rotr(17); + s1 = s1.xor( + cs.namespace(|| "first xor for s1"), + &w[i-2].rotr(19) + )?; + s1 = s1.xor( + cs.namespace(|| "second xor for s1"), + &w[i-2].shr(10) + )?; + + let tmp = UInt32::addmany( + cs.namespace(|| "computation of w[i]"), + &[w[i-16].clone(), s0, w[i-7].clone(), s1] + )?; + + // w[i] := w[i-16] + s0 + w[i-7] + s1 + w.push(tmp); + } + + assert_eq!(w.len(), 64); + + enum Maybe { + Deferred(Vec), + Concrete(UInt32) + } + + impl Maybe { + fn compute( + self, + cs: M, + others: &[UInt32] + ) -> Result + where E: Engine, + CS: ConstraintSystem, + M: ConstraintSystem> + { + Ok(match self { + Maybe::Concrete(ref v) => { + return Ok(v.clone()) + }, + Maybe::Deferred(mut v) => { + v.extend(others.into_iter().cloned()); + UInt32::addmany( + cs, + &v + )? + } + }) + } + } + + let mut a = Maybe::Concrete(current_hash_value[0].clone()); + let mut b = current_hash_value[1].clone(); + let mut c = current_hash_value[2].clone(); + let mut d = current_hash_value[3].clone(); + let mut e = Maybe::Concrete(current_hash_value[4].clone()); + let mut f = current_hash_value[5].clone(); + let mut g = current_hash_value[6].clone(); + let mut h = current_hash_value[7].clone(); + + for i in 0..64 { + let cs = &mut cs.namespace(|| format!("compression round {}", i)); + + // S1 := (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25) + let new_e = e.compute(cs.namespace(|| "deferred e computation"), &[])?; + let mut s1 = new_e.rotr(6); + s1 = s1.xor( + cs.namespace(|| "first xor for s1"), + &new_e.rotr(11) + )?; + s1 = s1.xor( + cs.namespace(|| "second xor for s1"), + &new_e.rotr(25) + )?; + + // ch := (e and f) xor ((not e) and g) + let ch = UInt32::sha256_ch( + cs.namespace(|| "ch"), + &new_e, + &f, + &g + )?; + + // temp1 := h + S1 + ch + k[i] + w[i] + let temp1 = vec![ + h.clone(), + s1, + ch, + UInt32::constant(ROUND_CONSTANTS[i]), + w[i].clone() + ]; + + // S0 := (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22) + let new_a = a.compute(cs.namespace(|| "deferred a computation"), &[])?; + let mut s0 = new_a.rotr(2); + s0 = s0.xor( + cs.namespace(|| "first xor for s0"), + &new_a.rotr(13) + )?; + s0 = s0.xor( + cs.namespace(|| "second xor for s0"), + &new_a.rotr(22) + )?; + + // maj := (a and b) xor (a and c) xor (b and c) + let maj = UInt32::sha256_maj( + cs.namespace(|| "maj"), + &new_a, + &b, + &c + )?; + + // temp2 := S0 + maj + let temp2 = vec![s0, maj]; + + /* + h := g + g := f + f := e + e := d + temp1 + d := c + c := b + b := a + a := temp1 + temp2 + */ + + h = g; + g = f; + f = new_e; + e = Maybe::Deferred(temp1.iter().cloned().chain(Some(d)).collect::>()); + d = c; + c = b; + b = new_a; + a = Maybe::Deferred(temp1.iter().cloned().chain(temp2.iter().cloned()).collect::>()); + } + + /* + Add the compressed chunk to the current hash value: + h0 := h0 + a + h1 := h1 + b + h2 := h2 + c + h3 := h3 + d + h4 := h4 + e + h5 := h5 + f + h6 := h6 + g + h7 := h7 + h + */ + + let h0 = a.compute( + cs.namespace(|| "deferred h0 computation"), + &[current_hash_value[0].clone()] + )?; + + let h1 = UInt32::addmany( + cs.namespace(|| "new h1"), + &[current_hash_value[1].clone(), b] + )?; + + let h2 = UInt32::addmany( + cs.namespace(|| "new h2"), + &[current_hash_value[2].clone(), c] + )?; + + let h3 = UInt32::addmany( + cs.namespace(|| "new h3"), + &[current_hash_value[3].clone(), d] + )?; + + let h4 = e.compute( + cs.namespace(|| "deferred h4 computation"), + &[current_hash_value[4].clone()] + )?; + + let h5 = UInt32::addmany( + cs.namespace(|| "new h5"), + &[current_hash_value[5].clone(), f] + )?; + + let h6 = UInt32::addmany( + cs.namespace(|| "new h6"), + &[current_hash_value[6].clone(), g] + )?; + + let h7 = UInt32::addmany( + cs.namespace(|| "new h7"), + &[current_hash_value[7].clone(), h] + )?; + + Ok(vec![h0, h1, h2, h3, h4, h5, h6, h7]) +} + +#[cfg(test)] +mod test { + use super::*; + use circuit::boolean::AllocatedBit; + use pairing::bls12_381::Bls12; + use circuit::test::TestConstraintSystem; + use rand::{XorShiftRng, SeedableRng, Rng}; + + #[test] + fn test_blank_hash() { + let iv = get_sha256_iv(); + + let mut cs = TestConstraintSystem::::new(); + let mut input_bits: Vec<_> = (0..512).map(|_| Boolean::Constant(false)).collect(); + input_bits[0] = Boolean::Constant(true); + let out = sha256_compression_function( + &mut cs, + &input_bits, + &iv + ).unwrap(); + let out_bits: Vec<_> = out.into_iter().flat_map(|e| e.into_bits_be()).collect(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 0); + + let expected = hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + + let mut out = out_bits.into_iter(); + for b in expected.into_iter() { + for i in (0..8).rev() { + let c = out.next().unwrap().get_value().unwrap(); + + assert_eq!(c, (b >> i) & 1u8 == 1u8); + } + } + } + + #[test] + fn test_full_block() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let iv = get_sha256_iv(); + + let mut cs = TestConstraintSystem::::new(); + let input_bits: Vec<_> = (0..512).map(|i| { + Boolean::from( + AllocatedBit::alloc( + cs.namespace(|| format!("input bit {}", i)), + Some(rng.gen()) + ).unwrap() + ) + }).collect(); + + sha256_compression_function( + cs.namespace(|| "sha256"), + &input_bits, + &iv + ).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints() - 512, 25840); + } + + #[test] + fn test_against_vectors() { + use crypto::sha2::Sha256; + use crypto::digest::Digest; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0)) + { + let mut h = Sha256::new(); + let data: Vec = (0..input_len).map(|_| rng.gen()).collect(); + h.input(&data); + let mut hash_result = [0u8; 32]; + h.result(&mut hash_result[..]); + + let mut cs = TestConstraintSystem::::new(); + let mut input_bits = vec![]; + + for (byte_i, input_byte) in data.into_iter().enumerate() { + for bit_i in (0..8).rev() { + let cs = cs.namespace(|| format!("input bit {} {}", byte_i, bit_i)); + + input_bits.push(AllocatedBit::alloc(cs, Some((input_byte >> bit_i) & 1u8 == 1u8)).unwrap().into()); + } + } + + let r = sha256(&mut cs, &input_bits).unwrap(); + + assert!(cs.is_satisfied()); + + let mut s = hash_result.as_ref().iter() + .flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1u8 == 1u8)); + + for b in r { + match b { + Boolean::Is(b) => { + assert!(s.next().unwrap() == b.get_value().unwrap()); + }, + Boolean::Not(b) => { + assert!(s.next().unwrap() != b.get_value().unwrap()); + }, + Boolean::Constant(b) => { + assert!(input_len == 0); + assert!(s.next().unwrap() == b); + } + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index a053dd1..44e10c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,9 @@ extern crate byteorder; #[macro_use] extern crate hex_literal; +#[cfg(test)] +extern crate crypto; + pub mod jubjub; pub mod group_hash; pub mod circuit; From 162a3877e5e262aa9fec1dac1ca361dd9305ff38 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:10:29 -0600 Subject: [PATCH 7/9] JoinSplit circuit implementation for Sprout. --- src/circuit/mod.rs | 1 + src/circuit/sprout/commitment.rs | 43 ++++ src/circuit/sprout/input.rs | 226 +++++++++++++++++ src/circuit/sprout/mod.rs | 414 +++++++++++++++++++++++++++++++ src/circuit/sprout/output.rs | 54 ++++ src/circuit/sprout/prfs.rs | 79 ++++++ 6 files changed, 817 insertions(+) create mode 100644 src/circuit/sprout/commitment.rs create mode 100644 src/circuit/sprout/input.rs create mode 100644 src/circuit/sprout/mod.rs create mode 100644 src/circuit/sprout/output.rs create mode 100644 src/circuit/sprout/prfs.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index a82cb44..fe0fe50 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -13,6 +13,7 @@ pub mod multipack; pub mod sha256; pub mod sapling; +pub mod sprout; use bellman::{ SynthesisError diff --git a/src/circuit/sprout/commitment.rs b/src/circuit/sprout/commitment.rs new file mode 100644 index 0000000..be8528e --- /dev/null +++ b/src/circuit/sprout/commitment.rs @@ -0,0 +1,43 @@ +use pairing::{Engine}; +use bellman::{ConstraintSystem, SynthesisError}; +use circuit::sha256::{ + sha256 +}; +use circuit::boolean::{ + Boolean +}; + +pub fn note_comm( + cs: CS, + a_pk: &[Boolean], + value: &[Boolean], + rho: &[Boolean], + r: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + assert_eq!(a_pk.len(), 256); + assert_eq!(value.len(), 64); + assert_eq!(rho.len(), 256); + assert_eq!(r.len(), 256); + + let mut image = vec![]; + image.push(Boolean::constant(true)); + image.push(Boolean::constant(false)); + image.push(Boolean::constant(true)); + image.push(Boolean::constant(true)); + image.push(Boolean::constant(false)); + image.push(Boolean::constant(false)); + image.push(Boolean::constant(false)); + image.push(Boolean::constant(false)); + image.extend(a_pk.iter().cloned()); + image.extend(rho.iter().cloned()); + image.extend(value.iter().cloned()); + image.extend(rho.iter().cloned()); + image.extend(r.iter().cloned()); + + sha256( + cs, + &image + ) +} diff --git a/src/circuit/sprout/input.rs b/src/circuit/sprout/input.rs new file mode 100644 index 0000000..ce69bc0 --- /dev/null +++ b/src/circuit/sprout/input.rs @@ -0,0 +1,226 @@ +use pairing::{Engine}; +use bellman::{ConstraintSystem, SynthesisError}; +use circuit::sha256::{ + sha256_block_no_padding +}; +use circuit::boolean::{ + AllocatedBit, + Boolean +}; + +use super::*; +use super::prfs::*; +use super::commitment::note_comm; + +pub struct InputNote { + pub nf: Vec, + pub mac: Vec, +} + +impl InputNote { + pub fn compute( + mut cs: CS, + a_sk: Option, + rho: Option, + r: Option, + value: &NoteValue, + h_sig: &[Boolean], + nonce: bool, + auth_path: [Option<([u8; 32], bool)>; TREE_DEPTH], + rt: &[Boolean] + ) -> Result + where E: Engine, CS: ConstraintSystem + { + let a_sk = witness_u252( + cs.namespace(|| "a_sk"), + a_sk.as_ref().map(|a_sk| &a_sk.0[..]) + )?; + + let rho = witness_u256( + cs.namespace(|| "rho"), + rho.as_ref().map(|rho| &rho.0[..]) + )?; + + let r = witness_u256( + cs.namespace(|| "r"), + r.as_ref().map(|r| &r.0[..]) + )?; + + let a_pk = prf_a_pk( + cs.namespace(|| "a_pk computation"), + &a_sk + )?; + + let nf = prf_nf( + cs.namespace(|| "nf computation"), + &a_sk, + &rho + )?; + + let mac = prf_pk( + cs.namespace(|| "mac computation"), + &a_sk, + h_sig, + nonce + )?; + + let cm = note_comm( + cs.namespace(|| "cm computation"), + &a_pk, + &value.bits_le(), + &rho, + &r + )?; + + // Witness into the merkle tree + let mut cur = cm.clone(); + + for (i, layer) in auth_path.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("layer {}", i)); + + let cur_is_right = AllocatedBit::alloc( + cs.namespace(|| "cur is right"), + layer.as_ref().map(|&(_, p)| p) + )?; + + let lhs = cur; + let rhs = witness_u256( + cs.namespace(|| "sibling"), + layer.as_ref().map(|&(ref sibling, _)| &sibling[..]) + )?; + + // Conditionally swap if cur is right + let preimage = conditionally_swap_u256( + cs.namespace(|| "conditional swap"), + &lhs[..], + &rhs[..], + &cur_is_right + )?; + + cur = sha256_block_no_padding( + cs.namespace(|| "hash of this layer"), + &preimage + )?; + } + + // enforce must be true if the value is nonzero + let enforce = AllocatedBit::alloc( + cs.namespace(|| "enforce"), + value.get_value().map(|n| n != 0) + )?; + + // value * (1 - enforce) = 0 + // If `value` is zero, `enforce` _can_ be zero. + // If `value` is nonzero, `enforce` _must_ be one. + cs.enforce( + || "enforce validity", + |_| value.lc(), + |lc| lc + CS::one() - enforce.get_variable(), + |lc| lc + ); + + assert_eq!(cur.len(), rt.len()); + + // Check that the anchor (exposed as a public input) + // is equal to the merkle tree root that we calculated + // for this note + for (i, (cur, rt)) in cur.into_iter().zip(rt.iter()).enumerate() { + // (cur - rt) * enforce = 0 + // if enforce is zero, cur and rt can be different + // if enforce is one, they must be equal + cs.enforce( + || format!("conditionally enforce correct root for bit {}", i), + |_| cur.lc(CS::one(), E::Fr::one()) - &rt.lc(CS::one(), E::Fr::one()), + |lc| lc + enforce.get_variable(), + |lc| lc + ); + } + + Ok(InputNote { + mac: mac, + nf: nf + }) + } +} + +/// Swaps two 256-bit blobs conditionally, returning the +/// 512-bit concatenation. +pub fn conditionally_swap_u256( + mut cs: CS, + lhs: &[Boolean], + rhs: &[Boolean], + condition: &AllocatedBit +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem, +{ + assert_eq!(lhs.len(), 256); + assert_eq!(rhs.len(), 256); + + let mut new_lhs = vec![]; + let mut new_rhs = vec![]; + + for (i, (lhs, rhs)) in lhs.iter().zip(rhs.iter()).enumerate() { + let cs = &mut cs.namespace(|| format!("bit {}", i)); + + let x = Boolean::from(AllocatedBit::alloc( + cs.namespace(|| "x"), + condition.get_value().and_then(|v| { + if v { + rhs.get_value() + } else { + lhs.get_value() + } + }) + )?); + + // x = (1-condition)lhs + (condition)rhs + // x = lhs - lhs(condition) + rhs(condition) + // x - lhs = condition (rhs - lhs) + // if condition is zero, we don't swap, so + // x - lhs = 0 + // x = lhs + // if condition is one, we do swap, so + // x - lhs = rhs - lhs + // x = rhs + cs.enforce( + || "conditional swap for x", + |lc| lc + &rhs.lc(CS::one(), E::Fr::one()) + - &lhs.lc(CS::one(), E::Fr::one()), + |lc| lc + condition.get_variable(), + |lc| lc + &x.lc(CS::one(), E::Fr::one()) + - &lhs.lc(CS::one(), E::Fr::one()) + ); + + let y = Boolean::from(AllocatedBit::alloc( + cs.namespace(|| "y"), + condition.get_value().and_then(|v| { + if v { + lhs.get_value() + } else { + rhs.get_value() + } + }) + )?); + + // y = (1-condition)rhs + (condition)lhs + // y - rhs = condition (lhs - rhs) + cs.enforce( + || "conditional swap for y", + |lc| lc + &lhs.lc(CS::one(), E::Fr::one()) + - &rhs.lc(CS::one(), E::Fr::one()), + |lc| lc + condition.get_variable(), + |lc| lc + &y.lc(CS::one(), E::Fr::one()) + - &rhs.lc(CS::one(), E::Fr::one()) + ); + + new_lhs.push(x); + new_rhs.push(y); + } + + let mut f = new_lhs; + f.extend(new_rhs); + + assert_eq!(f.len(), 512); + + Ok(f) +} diff --git a/src/circuit/sprout/mod.rs b/src/circuit/sprout/mod.rs new file mode 100644 index 0000000..5ec9420 --- /dev/null +++ b/src/circuit/sprout/mod.rs @@ -0,0 +1,414 @@ +use pairing::{Engine, Field}; +use bellman::{ConstraintSystem, SynthesisError, Circuit, LinearCombination}; +use circuit::boolean::{ + AllocatedBit, + Boolean +}; +use circuit::multipack::pack_into_inputs; + +mod prfs; +mod commitment; +mod input; +mod output; + +use self::input::*; +use self::output::*; + +pub const TREE_DEPTH: usize = 29; + +pub struct SpendingKey(pub [u8; 32]); +pub struct PayingKey(pub [u8; 32]); +pub struct UniqueRandomness(pub [u8; 32]); +pub struct CommitmentRandomness(pub [u8; 32]); + +pub struct JoinSplit { + pub vpub_old: Option, + pub vpub_new: Option, + pub h_sig: Option<[u8; 32]>, + pub phi: Option<[u8; 32]>, + pub inputs: Vec, + pub outputs: Vec, + pub rt: Option<[u8; 32]>, +} + +pub struct JSInput { + pub value: Option, + pub a_sk: Option, + pub rho: Option, + pub r: Option, + pub auth_path: [Option<([u8; 32], bool)>; TREE_DEPTH] +} + +pub struct JSOutput { + pub value: Option, + pub a_pk: Option, + pub r: Option +} + +impl Circuit for JoinSplit { + fn synthesize>( + self, + cs: &mut CS + ) -> Result<(), SynthesisError> + { + assert_eq!(self.inputs.len(), 2); + assert_eq!(self.outputs.len(), 2); + + // vpub_old is the value entering the + // JoinSplit from the "outside" value + // pool + let vpub_old = NoteValue::new( + cs.namespace(|| "vpub_old"), + self.vpub_old + )?; + + // vpub_new is the value leaving the + // JoinSplit into the "outside" value + // pool + let vpub_new = NoteValue::new( + cs.namespace(|| "vpub_new"), + self.vpub_new + )?; + + // The left hand side of the balance equation + // vpub_old + inputs[0].value + inputs[1].value + let mut lhs = vpub_old.lc(); + + // The right hand side of the balance equation + // vpub_old + inputs[0].value + inputs[1].value + let mut rhs = vpub_new.lc(); + + // Witness rt (merkle tree root) + let rt = witness_u256( + cs.namespace(|| "rt"), + self.rt.as_ref().map(|v| &v[..]) + ).unwrap(); + + // Witness h_sig + let h_sig = witness_u256( + cs.namespace(|| "h_sig"), + self.h_sig.as_ref().map(|v| &v[..]) + ).unwrap(); + + // Witness phi + let phi = witness_u252( + cs.namespace(|| "phi"), + self.phi.as_ref().map(|v| &v[..]) + ).unwrap(); + + let mut input_notes = vec![]; + let mut lhs_total = self.vpub_old; + + // Iterate over the JoinSplit inputs + for (i, input) in self.inputs.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("input {}", i)); + + // Accumulate the value of the left hand side + if let Some(value) = input.value { + lhs_total = lhs_total.map(|v| v.wrapping_add(value)); + } + + // Allocate the value of the note + let value = NoteValue::new( + cs.namespace(|| "value"), + input.value + )?; + + // Compute the nonce (for PRF inputs) which is false + // for the first input, and true for the second input. + let nonce = match i { + 0 => false, + 1 => true, + _ => unreachable!() + }; + + // Perform input note computations + input_notes.push(InputNote::compute( + cs.namespace(|| "note"), + input.a_sk, + input.rho, + input.r, + &value, + &h_sig, + nonce, + input.auth_path, + &rt + )?); + + // Add the note value to the left hand side of + // the balance equation + lhs = lhs + &value.lc(); + } + + // Rebind lhs so that it isn't mutable anymore + let lhs = lhs; + + // See zcash/zcash/issues/854 + { + // Expected sum of the left hand side of the balance + // equation, expressed as a 64-bit unsigned integer + let lhs_total = NoteValue::new( + cs.namespace(|| "total value of left hand side"), + lhs_total + )?; + + // Enforce that the left hand side can be expressed as a 64-bit + // integer + cs.enforce( + || "left hand side can be expressed as a 64-bit unsigned integer", + |_| lhs.clone(), + |lc| lc + CS::one(), + |_| lhs_total.lc() + ); + } + + let mut output_notes = vec![]; + + // Iterate over the JoinSplit outputs + for (i, output) in self.outputs.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("output {}", i)); + + let value = NoteValue::new( + cs.namespace(|| "value"), + output.value + )?; + + // Compute the nonce (for PRF inputs) which is false + // for the first output, and true for the second output. + let nonce = match i { + 0 => false, + 1 => true, + _ => unreachable!() + }; + + // Perform output note computations + output_notes.push(OutputNote::compute( + cs.namespace(|| "note"), + output.a_pk, + &value, + output.r, + &phi, + &h_sig, + nonce + )?); + + // Add the note value to the right hand side of + // the balance equation + rhs = rhs + &value.lc(); + } + + // Enforce that balance is equal + cs.enforce( + || "balance equation", + |_| lhs.clone(), + |lc| lc + CS::one(), + |_| rhs + ); + + let mut public_inputs = vec![]; + public_inputs.extend(rt); + public_inputs.extend(h_sig); + + for note in input_notes { + public_inputs.extend(note.nf); + public_inputs.extend(note.mac); + } + + for note in output_notes { + public_inputs.extend(note.cm); + } + + public_inputs.extend(vpub_old.bits_le()); + public_inputs.extend(vpub_new.bits_le()); + + pack_into_inputs(cs.namespace(|| "input packing"), &public_inputs) + } +} + +pub struct NoteValue { + value: Option, + // Least significant digit first + bits: Vec +} + +impl NoteValue { + fn new( + mut cs: CS, + value: Option + ) -> Result + where E: Engine, CS: ConstraintSystem, + { + let mut values; + match value { + Some(mut val) => { + values = vec![]; + for _ in 0..64 { + values.push(Some(val & 1 == 1)); + val >>= 1; + } + }, + None => { + values = vec![None; 64]; + } + } + + let mut bits = vec![]; + for (i, value) in values.into_iter().enumerate() { + bits.push( + AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + value + )? + ); + } + + Ok(NoteValue { + value: value, + bits: bits + }) + } + + /// Encodes the bits of the value into little-endian + /// byte order. + fn bits_le(&self) -> Vec { + self.bits.chunks(8) + .flat_map(|v| v.iter().rev()) + .cloned() + .map(|e| Boolean::from(e)) + .collect() + } + + /// Computes this value as a linear combination of + /// its bits. + fn lc(&self) -> LinearCombination { + let mut tmp = LinearCombination::zero(); + + let mut coeff = E::Fr::one(); + for b in &self.bits { + tmp = tmp + (coeff, b.get_variable()); + coeff.double(); + } + + tmp + } + + fn get_value(&self) -> Option { + self.value + } +} + +/// Witnesses some bytes in the constraint system, +/// skipping the first `skip_bits`. +fn witness_bits( + mut cs: CS, + value: Option<&[u8]>, + num_bits: usize, + skip_bits: usize +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem, +{ + let bit_values = if let Some(value) = value { + let mut tmp = vec![]; + for b in value.iter() + .flat_map(|&m| (0..8).rev().map(move |i| m >> i & 1 == 1)) + .skip(skip_bits) + { + tmp.push(Some(b)); + } + tmp + } else { + vec![None; num_bits] + }; + assert_eq!(bit_values.len(), num_bits); + + let mut bits = vec![]; + + for (i, value) in bit_values.into_iter().enumerate() { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + value + )?)); + } + + Ok(bits) +} + +fn witness_u256( + cs: CS, + value: Option<&[u8]>, +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem, +{ + witness_bits(cs, value, 256, 0) +} + +fn witness_u252( + cs: CS, + value: Option<&[u8]>, +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem, +{ + witness_bits(cs, value, 252, 4) +} + +#[test] +fn test_sprout_constraints() { + use pairing::bls12_381::{Bls12}; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + + let rng = &mut XorShiftRng::from_seed([0x3dbe6257, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let mut cs = TestConstraintSystem::::new(); + + let mut inputs = vec![]; + inputs.push( + JSInput { + value: Some(0), + a_sk: Some(SpendingKey(rng.gen())), + rho: Some(UniqueRandomness(rng.gen())), + r: Some(CommitmentRandomness(rng.gen())), + auth_path: [Some(rng.gen()); TREE_DEPTH] + } + ); + inputs.push( + JSInput { + value: Some(0), + a_sk: Some(SpendingKey(rng.gen())), + rho: Some(UniqueRandomness(rng.gen())), + r: Some(CommitmentRandomness(rng.gen())), + auth_path: [Some(rng.gen()); TREE_DEPTH] + } + ); + let mut outputs = vec![]; + outputs.push( + JSOutput { + value: Some(50), + a_pk: Some(PayingKey(rng.gen())), + r: Some(CommitmentRandomness(rng.gen())) + } + ); + outputs.push( + JSOutput { + value: Some(50), + a_pk: Some(PayingKey(rng.gen())), + r: Some(CommitmentRandomness(rng.gen())) + } + ); + + let js = JoinSplit { + vpub_old: Some(100), + vpub_new: Some(0), + h_sig: Some(rng.gen()), + phi: Some(rng.gen()), + inputs: inputs, + outputs: outputs, + rt: Some(rng.gen()) + }; + + js.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 2091901); + assert_eq!(cs.num_inputs(), 10); + assert_eq!(cs.hash(), "9d2d7bcffe5bd838009493ecf57a0401dc9b57e71d016e83744c2e1d32dc00c3"); +} diff --git a/src/circuit/sprout/output.rs b/src/circuit/sprout/output.rs new file mode 100644 index 0000000..9cdbf52 --- /dev/null +++ b/src/circuit/sprout/output.rs @@ -0,0 +1,54 @@ +use pairing::{Engine}; +use bellman::{ConstraintSystem, SynthesisError}; +use circuit::boolean::{Boolean}; + +use super::*; +use super::prfs::*; +use super::commitment::note_comm; + +pub struct OutputNote { + pub cm: Vec +} + +impl OutputNote { + pub fn compute<'a, E, CS>( + mut cs: CS, + a_pk: Option, + value: &NoteValue, + r: Option, + phi: &[Boolean], + h_sig: &[Boolean], + nonce: bool + ) -> Result + where E: Engine, CS: ConstraintSystem, + { + let rho = prf_rho( + cs.namespace(|| "rho"), + phi, + h_sig, + nonce + )?; + + let a_pk = witness_u256( + cs.namespace(|| "a_pk"), + a_pk.as_ref().map(|a_pk| &a_pk.0[..]) + )?; + + let r = witness_u256( + cs.namespace(|| "r"), + r.as_ref().map(|r| &r.0[..]) + )?; + + let cm = note_comm( + cs.namespace(|| "cm computation"), + &a_pk, + &value.bits_le(), + &rho, + &r + )?; + + Ok(OutputNote { + cm: cm + }) + } +} diff --git a/src/circuit/sprout/prfs.rs b/src/circuit/sprout/prfs.rs new file mode 100644 index 0000000..fff8648 --- /dev/null +++ b/src/circuit/sprout/prfs.rs @@ -0,0 +1,79 @@ +use pairing::{Engine}; +use bellman::{ConstraintSystem, SynthesisError}; +use circuit::sha256::{ + sha256_block_no_padding +}; +use circuit::boolean::{ + Boolean +}; + +fn prf( + cs: CS, + a: bool, + b: bool, + c: bool, + d: bool, + x: &[Boolean], + y: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + assert_eq!(x.len(), 252); + assert_eq!(y.len(), 256); + + let mut image = vec![]; + image.push(Boolean::constant(a)); + image.push(Boolean::constant(b)); + image.push(Boolean::constant(c)); + image.push(Boolean::constant(d)); + image.extend(x.iter().cloned()); + image.extend(y.iter().cloned()); + + assert_eq!(image.len(), 512); + + sha256_block_no_padding( + cs, + &image + ) +} + +pub fn prf_a_pk( + cs: CS, + a_sk: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + prf(cs, true, true, false, false, a_sk, &(0..256).map(|_| Boolean::constant(false)).collect::>()) +} + +pub fn prf_nf( + cs: CS, + a_sk: &[Boolean], + rho: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + prf(cs, true, true, true, false, a_sk, rho) +} + +pub fn prf_pk( + cs: CS, + a_sk: &[Boolean], + h_sig: &[Boolean], + nonce: bool +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + prf(cs, false, nonce, false, false, a_sk, h_sig) +} + +pub fn prf_rho( + cs: CS, + phi: &[Boolean], + h_sig: &[Boolean], + nonce: bool +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + prf(cs, false, nonce, true, false, phi, h_sig) +} From 4de908b1e5483c54a5c1c9e5433a52321343b926 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Mar 2018 23:45:13 -0600 Subject: [PATCH 8/9] Fix bug in circuit uncovered by test vector. --- src/circuit/sprout/commitment.rs | 1 - src/circuit/sprout/mod.rs | 152 +++++++++++++++++++++++-------- 2 files changed, 113 insertions(+), 40 deletions(-) diff --git a/src/circuit/sprout/commitment.rs b/src/circuit/sprout/commitment.rs index be8528e..a32f05c 100644 --- a/src/circuit/sprout/commitment.rs +++ b/src/circuit/sprout/commitment.rs @@ -31,7 +31,6 @@ pub fn note_comm( image.push(Boolean::constant(false)); image.push(Boolean::constant(false)); image.extend(a_pk.iter().cloned()); - image.extend(rho.iter().cloned()); image.extend(value.iter().cloned()); image.extend(rho.iter().cloned()); image.extend(r.iter().cloned()); diff --git a/src/circuit/sprout/mod.rs b/src/circuit/sprout/mod.rs index 5ec9420..3ae8a17 100644 --- a/src/circuit/sprout/mod.rs +++ b/src/circuit/sprout/mod.rs @@ -354,61 +354,135 @@ fn witness_u252( #[test] fn test_sprout_constraints() { use pairing::bls12_381::{Bls12}; - use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; - let rng = &mut XorShiftRng::from_seed([0x3dbe6257, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian}; + let mut cs = TestConstraintSystem::::new(); + let test_vector = hex!("0da71d04d1a5fa649239c73de9c516d2f54958f0f92118113671633872346a31d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd2591e6b61f9f84d6f70fc34838a5a898df04c9a13f1fbfc2b1433de718046efd7d61d20c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b51120c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f502008f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe20925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d5212058a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a120f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd1620bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a20671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff200323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a205145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a2011aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd05820bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd70920bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d20507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4205dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022207333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c152089a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c123320c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e201ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c002268220a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298208c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e2040460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f203f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325200109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e312026b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab203f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f9220dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c20da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d82000000000000000000000000000000000000000000000000000000000000000000000000000000000788445f3120af7846f3625a24ce18250905d9007150e276795271f2572293b20000000000000000082dbed5b3b4048851fb78feb4601ff03be53d6a4b93e97599f4fb6a23db710c41d7193cecb40f4601fc2214b14e0435646a61f0ad1eced90d69911c29850661c074dfe1a712c29b36441a71969c34c7ef1d1c6ec89dfe3c7ceb647c4a2c891061d20c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b51120c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f502008f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe20925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d5212058a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a120f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd1620bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a20671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff200323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a205145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a2011aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd05820bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd70920bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d20507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4205dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022207333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c152089a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c123320c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e201ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c002268220a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298208c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e2040460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f203f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325200109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e312026b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab203f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f9220dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c20da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d8200000000000000000000000000000000000000000000000000000000000000000000000000000000074a32261e497b4a90a786b3c86593d1538d1cebfca0c558c1f00a15cd1726df000000000000000002ce6a5a2b90f0d84f0abefe276a9306b30e4efc3cc4c1a3d2461d5fe5aef48491e1a35d1e0c1b8bd124fec233b0e5e6446b33b998b5c055161239e740459581606241488b3b9a0cc5ce17f7c08a2862b3cb64328de743eef32215210c57d657f037ae0cb7390166b0b327db98b06eb8b018a948a952e9a2b1772552f60738be200000000000000004f04a021600858d805c9ec555dbfe37eb7680bec562848e346495989e99617a691bff31ffb4ae2009e3106a8e23e5ed365b6aa05f27ce43c709dc9977a71678e3853150ec9e5782948eae5cd6c6b92f9e52e9e7f340dbf9ed50a4ea91456150a00000000000000006669719d78ac8126c22afb145d1bc7bd4c35fcb0363ffd1cd42e01a67abb4339f84507f6918b18c984574a9c4ee5a0fa192fb64a26054dfec97a487ee3a0b1720000000000000000000000000000000026c601601d75c28aaa786fb4f3b97fdb21f2d6fd5ff7a1ddf698f262f9be2e98d0184d08ee099711504094a639f4e65fd4c11e003b6ef9547465cec7452ad06435999ba8a4eb6018747ed1cac7e6dbda8260d4d4467572a7e5440d4337eeb0d994c1aca55e1dfe0b4dd7664ee652c6c4ffbdf9f284f3932fdc89b1aef23559fe4fa50b383248c24e50b8193406a97e6d709a6a9d076656b77b000b3ef178084da307423830ffe8afffdf18656cf5ebc197c5e140a952bebe8d52fbb5474fb30a"); + let mut test_vector = &test_vector[..]; + + fn get_u256(mut reader: R) -> [u8; 32] { + let mut result = [0u8; 32]; + + for i in 0..32 { + result[i] = reader.read_u8().unwrap(); + } + + result + } + + let phi = Some(get_u256(&mut test_vector)); + let rt = Some(get_u256(&mut test_vector)); + let h_sig = Some(get_u256(&mut test_vector)); + let mut inputs = vec![]; - inputs.push( - JSInput { - value: Some(0), - a_sk: Some(SpendingKey(rng.gen())), - rho: Some(UniqueRandomness(rng.gen())), - r: Some(CommitmentRandomness(rng.gen())), - auth_path: [Some(rng.gen()); TREE_DEPTH] + for _ in 0..2 { + test_vector.read_u8().unwrap(); + + let mut auth_path = [None; TREE_DEPTH]; + for i in (0..TREE_DEPTH).rev() { + test_vector.read_u8().unwrap(); + + let sibling = get_u256(&mut test_vector); + + auth_path[i] = Some((sibling, false)); } - ); - inputs.push( - JSInput { - value: Some(0), - a_sk: Some(SpendingKey(rng.gen())), - rho: Some(UniqueRandomness(rng.gen())), - r: Some(CommitmentRandomness(rng.gen())), - auth_path: [Some(rng.gen()); TREE_DEPTH] + let mut position = test_vector.read_u64::().unwrap(); + for i in (0..TREE_DEPTH).rev() { + auth_path[i].as_mut().map(|p| { + p.1 = (position & 1) == 1 + }); + + position >>= 1; } - ); + + // a_pk + let _ = Some(SpendingKey(get_u256(&mut test_vector))); + let value = Some(test_vector.read_u64::().unwrap()); + let rho = Some(UniqueRandomness(get_u256(&mut test_vector))); + let r = Some(CommitmentRandomness(get_u256(&mut test_vector))); + let a_sk = Some(SpendingKey(get_u256(&mut test_vector))); + + inputs.push( + JSInput { + value: value, + a_sk: a_sk, + rho: rho, + r: r, + auth_path: auth_path + } + ); + } + let mut outputs = vec![]; - outputs.push( - JSOutput { - value: Some(50), - a_pk: Some(PayingKey(rng.gen())), - r: Some(CommitmentRandomness(rng.gen())) - } - ); - outputs.push( - JSOutput { - value: Some(50), - a_pk: Some(PayingKey(rng.gen())), - r: Some(CommitmentRandomness(rng.gen())) - } - ); + + for _ in 0..2 { + let a_pk = Some(PayingKey(get_u256(&mut test_vector))); + let value = Some(test_vector.read_u64::().unwrap()); + get_u256(&mut test_vector); + let r = Some(CommitmentRandomness(get_u256(&mut test_vector))); + + outputs.push( + JSOutput { + value: value, + a_pk: a_pk, + r: r + } + ); + } + + let vpub_old = Some(test_vector.read_u64::().unwrap()); + let vpub_new = Some(test_vector.read_u64::().unwrap()); + + let nf1 = get_u256(&mut test_vector); + let nf2 = get_u256(&mut test_vector); + + let cm1 = get_u256(&mut test_vector); + let cm2 = get_u256(&mut test_vector); + + let mac1 = get_u256(&mut test_vector); + let mac2 = get_u256(&mut test_vector); + + assert_eq!(test_vector.len(), 0); let js = JoinSplit { - vpub_old: Some(100), - vpub_new: Some(0), - h_sig: Some(rng.gen()), - phi: Some(rng.gen()), + vpub_old: vpub_old, + vpub_new: vpub_new, + h_sig: h_sig, + phi: phi, inputs: inputs, outputs: outputs, - rt: Some(rng.gen()) + rt: rt }; js.synthesize(&mut cs).unwrap(); + if let Some(s) = cs.which_is_unsatisfied() { + panic!("{:?}", s); + } assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 2091901); + assert_eq!(cs.num_constraints(), 1989085); assert_eq!(cs.num_inputs(), 10); - assert_eq!(cs.hash(), "9d2d7bcffe5bd838009493ecf57a0401dc9b57e71d016e83744c2e1d32dc00c3"); + assert_eq!(cs.hash(), "1a228d3c6377130d1778c7885811dc8b8864049cb5af8aff7e6cd46c5bc4b84c"); + + let mut expected_inputs = vec![]; + expected_inputs.extend(rt.unwrap().to_vec()); + expected_inputs.extend(h_sig.unwrap().to_vec()); + expected_inputs.extend(nf1.to_vec()); + expected_inputs.extend(mac1.to_vec()); + expected_inputs.extend(nf2.to_vec()); + expected_inputs.extend(mac2.to_vec()); + expected_inputs.extend(cm1.to_vec()); + expected_inputs.extend(cm2.to_vec()); + expected_inputs.write_u64::(vpub_old.unwrap()).unwrap(); + expected_inputs.write_u64::(vpub_new.unwrap()).unwrap(); + + use circuit::multipack; + + let expected_inputs = multipack::bytes_to_bits(&expected_inputs); + let expected_inputs = multipack::compute_multipacking::(&expected_inputs); + + assert!(cs.verify(&expected_inputs)); } From cfd378685f7df8054bf9e2ece457f05975e29d6a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 26 Mar 2018 21:47:40 -0600 Subject: [PATCH 9/9] Add more test vectors to Sprout circuit implementation. --- src/circuit/sprout/mod.rs | 210 ++++++++++++++-------------- src/circuit/sprout/test_vectors.dat | Bin 0 -> 10864 bytes 2 files changed, 105 insertions(+), 105 deletions(-) create mode 100644 src/circuit/sprout/test_vectors.dat diff --git a/src/circuit/sprout/mod.rs b/src/circuit/sprout/mod.rs index 3ae8a17..586de8c 100644 --- a/src/circuit/sprout/mod.rs +++ b/src/circuit/sprout/mod.rs @@ -358,9 +358,7 @@ fn test_sprout_constraints() { use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian}; - let mut cs = TestConstraintSystem::::new(); - - let test_vector = hex!("0da71d04d1a5fa649239c73de9c516d2f54958f0f92118113671633872346a31d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd2591e6b61f9f84d6f70fc34838a5a898df04c9a13f1fbfc2b1433de718046efd7d61d20c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b51120c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f502008f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe20925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d5212058a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a120f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd1620bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a20671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff200323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a205145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a2011aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd05820bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd70920bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d20507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4205dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022207333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c152089a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c123320c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e201ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c002268220a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298208c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e2040460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f203f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325200109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e312026b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab203f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f9220dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c20da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d82000000000000000000000000000000000000000000000000000000000000000000000000000000000788445f3120af7846f3625a24ce18250905d9007150e276795271f2572293b20000000000000000082dbed5b3b4048851fb78feb4601ff03be53d6a4b93e97599f4fb6a23db710c41d7193cecb40f4601fc2214b14e0435646a61f0ad1eced90d69911c29850661c074dfe1a712c29b36441a71969c34c7ef1d1c6ec89dfe3c7ceb647c4a2c891061d20c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b51120c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f502008f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe20925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d5212058a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a120f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd1620bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a20671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff200323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a205145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a2011aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd05820bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd70920bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d20507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4205dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022207333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c152089a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c123320c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e201ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c002268220a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298208c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e2040460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f203f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325200109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e312026b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab203f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f9220dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c20da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d8200000000000000000000000000000000000000000000000000000000000000000000000000000000074a32261e497b4a90a786b3c86593d1538d1cebfca0c558c1f00a15cd1726df000000000000000002ce6a5a2b90f0d84f0abefe276a9306b30e4efc3cc4c1a3d2461d5fe5aef48491e1a35d1e0c1b8bd124fec233b0e5e6446b33b998b5c055161239e740459581606241488b3b9a0cc5ce17f7c08a2862b3cb64328de743eef32215210c57d657f037ae0cb7390166b0b327db98b06eb8b018a948a952e9a2b1772552f60738be200000000000000004f04a021600858d805c9ec555dbfe37eb7680bec562848e346495989e99617a691bff31ffb4ae2009e3106a8e23e5ed365b6aa05f27ce43c709dc9977a71678e3853150ec9e5782948eae5cd6c6b92f9e52e9e7f340dbf9ed50a4ea91456150a00000000000000006669719d78ac8126c22afb145d1bc7bd4c35fcb0363ffd1cd42e01a67abb4339f84507f6918b18c984574a9c4ee5a0fa192fb64a26054dfec97a487ee3a0b1720000000000000000000000000000000026c601601d75c28aaa786fb4f3b97fdb21f2d6fd5ff7a1ddf698f262f9be2e98d0184d08ee099711504094a639f4e65fd4c11e003b6ef9547465cec7452ad06435999ba8a4eb6018747ed1cac7e6dbda8260d4d4467572a7e5440d4337eeb0d994c1aca55e1dfe0b4dd7664ee652c6c4ffbdf9f284f3932fdc89b1aef23559fe4fa50b383248c24e50b8193406a97e6d709a6a9d076656b77b000b3ef178084da307423830ffe8afffdf18656cf5ebc197c5e140a952bebe8d52fbb5474fb30a"); + let test_vector = include_bytes!("test_vectors.dat"); let mut test_vector = &test_vector[..]; fn get_u256(mut reader: R) -> [u8; 32] { @@ -373,116 +371,118 @@ fn test_sprout_constraints() { result } - let phi = Some(get_u256(&mut test_vector)); - let rt = Some(get_u256(&mut test_vector)); - let h_sig = Some(get_u256(&mut test_vector)); + while test_vector.len() != 0 { + let mut cs = TestConstraintSystem::::new(); - let mut inputs = vec![]; - for _ in 0..2 { - test_vector.read_u8().unwrap(); + let phi = Some(get_u256(&mut test_vector)); + let rt = Some(get_u256(&mut test_vector)); + let h_sig = Some(get_u256(&mut test_vector)); - let mut auth_path = [None; TREE_DEPTH]; - for i in (0..TREE_DEPTH).rev() { + let mut inputs = vec![]; + for _ in 0..2 { test_vector.read_u8().unwrap(); - let sibling = get_u256(&mut test_vector); + let mut auth_path = [None; TREE_DEPTH]; + for i in (0..TREE_DEPTH).rev() { + test_vector.read_u8().unwrap(); - auth_path[i] = Some((sibling, false)); - } - let mut position = test_vector.read_u64::().unwrap(); - for i in (0..TREE_DEPTH).rev() { - auth_path[i].as_mut().map(|p| { - p.1 = (position & 1) == 1 - }); + let sibling = get_u256(&mut test_vector); - position >>= 1; + auth_path[i] = Some((sibling, false)); + } + let mut position = test_vector.read_u64::().unwrap(); + for i in 0..TREE_DEPTH { + auth_path[i].as_mut().map(|p| { + p.1 = (position & 1) == 1 + }); + + position >>= 1; + } + + // a_pk + let _ = Some(SpendingKey(get_u256(&mut test_vector))); + let value = Some(test_vector.read_u64::().unwrap()); + let rho = Some(UniqueRandomness(get_u256(&mut test_vector))); + let r = Some(CommitmentRandomness(get_u256(&mut test_vector))); + let a_sk = Some(SpendingKey(get_u256(&mut test_vector))); + + inputs.push( + JSInput { + value: value, + a_sk: a_sk, + rho: rho, + r: r, + auth_path: auth_path + } + ); } - // a_pk - let _ = Some(SpendingKey(get_u256(&mut test_vector))); - let value = Some(test_vector.read_u64::().unwrap()); - let rho = Some(UniqueRandomness(get_u256(&mut test_vector))); - let r = Some(CommitmentRandomness(get_u256(&mut test_vector))); - let a_sk = Some(SpendingKey(get_u256(&mut test_vector))); + let mut outputs = vec![]; - inputs.push( - JSInput { - value: value, - a_sk: a_sk, - rho: rho, - r: r, - auth_path: auth_path - } - ); + for _ in 0..2 { + let a_pk = Some(PayingKey(get_u256(&mut test_vector))); + let value = Some(test_vector.read_u64::().unwrap()); + get_u256(&mut test_vector); + let r = Some(CommitmentRandomness(get_u256(&mut test_vector))); + + outputs.push( + JSOutput { + value: value, + a_pk: a_pk, + r: r + } + ); + } + + let vpub_old = Some(test_vector.read_u64::().unwrap()); + let vpub_new = Some(test_vector.read_u64::().unwrap()); + + let nf1 = get_u256(&mut test_vector); + let nf2 = get_u256(&mut test_vector); + + let cm1 = get_u256(&mut test_vector); + let cm2 = get_u256(&mut test_vector); + + let mac1 = get_u256(&mut test_vector); + let mac2 = get_u256(&mut test_vector); + + let js = JoinSplit { + vpub_old: vpub_old, + vpub_new: vpub_new, + h_sig: h_sig, + phi: phi, + inputs: inputs, + outputs: outputs, + rt: rt + }; + + js.synthesize(&mut cs).unwrap(); + + if let Some(s) = cs.which_is_unsatisfied() { + panic!("{:?}", s); + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 1989085); + assert_eq!(cs.num_inputs(), 10); + assert_eq!(cs.hash(), "1a228d3c6377130d1778c7885811dc8b8864049cb5af8aff7e6cd46c5bc4b84c"); + + let mut expected_inputs = vec![]; + expected_inputs.extend(rt.unwrap().to_vec()); + expected_inputs.extend(h_sig.unwrap().to_vec()); + expected_inputs.extend(nf1.to_vec()); + expected_inputs.extend(mac1.to_vec()); + expected_inputs.extend(nf2.to_vec()); + expected_inputs.extend(mac2.to_vec()); + expected_inputs.extend(cm1.to_vec()); + expected_inputs.extend(cm2.to_vec()); + expected_inputs.write_u64::(vpub_old.unwrap()).unwrap(); + expected_inputs.write_u64::(vpub_new.unwrap()).unwrap(); + + use circuit::multipack; + + let expected_inputs = multipack::bytes_to_bits(&expected_inputs); + let expected_inputs = multipack::compute_multipacking::(&expected_inputs); + + assert!(cs.verify(&expected_inputs)); } - - let mut outputs = vec![]; - - for _ in 0..2 { - let a_pk = Some(PayingKey(get_u256(&mut test_vector))); - let value = Some(test_vector.read_u64::().unwrap()); - get_u256(&mut test_vector); - let r = Some(CommitmentRandomness(get_u256(&mut test_vector))); - - outputs.push( - JSOutput { - value: value, - a_pk: a_pk, - r: r - } - ); - } - - let vpub_old = Some(test_vector.read_u64::().unwrap()); - let vpub_new = Some(test_vector.read_u64::().unwrap()); - - let nf1 = get_u256(&mut test_vector); - let nf2 = get_u256(&mut test_vector); - - let cm1 = get_u256(&mut test_vector); - let cm2 = get_u256(&mut test_vector); - - let mac1 = get_u256(&mut test_vector); - let mac2 = get_u256(&mut test_vector); - - assert_eq!(test_vector.len(), 0); - - let js = JoinSplit { - vpub_old: vpub_old, - vpub_new: vpub_new, - h_sig: h_sig, - phi: phi, - inputs: inputs, - outputs: outputs, - rt: rt - }; - - js.synthesize(&mut cs).unwrap(); - - if let Some(s) = cs.which_is_unsatisfied() { - panic!("{:?}", s); - } - assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 1989085); - assert_eq!(cs.num_inputs(), 10); - assert_eq!(cs.hash(), "1a228d3c6377130d1778c7885811dc8b8864049cb5af8aff7e6cd46c5bc4b84c"); - - let mut expected_inputs = vec![]; - expected_inputs.extend(rt.unwrap().to_vec()); - expected_inputs.extend(h_sig.unwrap().to_vec()); - expected_inputs.extend(nf1.to_vec()); - expected_inputs.extend(mac1.to_vec()); - expected_inputs.extend(nf2.to_vec()); - expected_inputs.extend(mac2.to_vec()); - expected_inputs.extend(cm1.to_vec()); - expected_inputs.extend(cm2.to_vec()); - expected_inputs.write_u64::(vpub_old.unwrap()).unwrap(); - expected_inputs.write_u64::(vpub_new.unwrap()).unwrap(); - - use circuit::multipack; - - let expected_inputs = multipack::bytes_to_bits(&expected_inputs); - let expected_inputs = multipack::compute_multipacking::(&expected_inputs); - - assert!(cs.verify(&expected_inputs)); } diff --git a/src/circuit/sprout/test_vectors.dat b/src/circuit/sprout/test_vectors.dat new file mode 100644 index 0000000000000000000000000000000000000000..1316955771eb17e9a3e11352f1252b6591c151da GIT binary patch literal 10864 zcmeI%S2UdK{s-_;MrRO=M946rLJz}0ZS_NQuyIr$(oo}1j?QxA=XgM6P2xnMZ?hKGGJeg;P?^$J zdV6G=9MX)b;uQ|>_xJG+v>^x$9{KUrv(r$a4aDzJ4UoJkWCGFlr7jB?{I!hwEkUzA zpg_=3D@PkNSFA*%K@`=#q~8TGDGk=BS8;f9sC?z=n*k0MwOy6sG38&Tr_jRc25o;1 zh>OVVc{APCE7j~ODFWH9E>Q^YN8}=3ZU|O|87V=<2zik!T6W|6w5{*(I{T;}Bpb=8 zJ{q~WiPL|IsmD6fi$FRAVkk}J^l-b(4IJdx+k9&55G^&UzFX6a-QGDmV(AdpxtS*Z zB7wD^#|x2>hIi$hJj7RDG%<}7S19eb5po4WGJ}+Cf~MNk*?9o9*03-u_=*D)8`-as>sO>fSLEGKOpUNqdv11G&c2@ zXH49L5Rm_9LvtCN2Psp=c!jI1c<~tQN(+6eE17J)JAk_{4B;vVa=kuP_)%iZtt4UmYJR;p6iQOLzf0eCFMg5fVzAV92c5&uYrAwBeLMa=eS$sw@s!| ziTXKb$BkjvMV7;DdIC;fV8${+xr@4gyG2nW1C0DPTMQ1c=MkdYVWBOdZ2cNkHk(QU z)QK84(O_;YOOE!Y=bJ;=jSL20N%+p|{(XKBcIIQq+%HsyRdG-0T*vB`uYpfShE<_k z^|t!+x-G=T92J85Z^Ny7?dHliaQ#%c+&t z&=Z!_FNksg+t@=HOQlnRi*qja>(gJ|ZYOXMocI^MfIrK#qk<7U6wqgfPS2x^F|;oB z`(6d@BuA20(i}6=dBxKUczV6Y6Qq8N8nR$zm2BB&!o$K&)R|ixX?BW|Wc40!exyn4 zE1MU!t9LouSFp+o{WSr6v)qjNM!p>wLiQOyKNxPP8v7318#0#Nzk5<< zaMMwm<_6Rq5*oW;kh>&!1nS4kPx z?{>hUt3<{+HT#|jOZP~L9v7DoD@=>MeDfolAhpoBru5aYJC9w3Cj+0EmE~{hX;2oC z0rmoJ#FPYy!aG^3mqZiKtoENd-Uj8OM$YNCW@EwdTQ>|}T=%;lYMEC|%$F5PFNseR ze460X5W=Wb#7a9(1WSEwFdUC3% ziWS1tPGqMQ-a>r}-Ib}M39>IA3h%SE5Sx5SG*vhp!S}|tRvS>Q81W_l% zZNNh^e>&oYa4A(slVl*1*4w@J((h;Vnvpf+xdqlV9U0&?!E1uoNRykD zUDJ-)I+Sx=^;Iz-_7T!s$h`gNhi6Yxz`{-QO-hl529-ASHsM&2L;y$Fs!zfl`)hyB zK@4F32=HyfT3(GL@q|Xv6|Pu>Idz=&_sJJOW~I;3WB>YswZ}HvWk8wJ2)P~ZDe6#V zI$@b1jx|zvd-HB`YbNDo_YnO1fnyjVvOU<1vvOjbedx5|-;^Dfr}vCp20G1c*k(G^ zY?GWFe+V8pk{zK*i)H$MyJLzD^5d$FH#ll7XzXEXHh-Q&eDV4K0(~n&UBNG5E$^8l zc)wD)EYErt)D^eC4zS2H>QH>$7|aUse>?ta(Zatl(bdmY$nhgnEC9!Zsx0RzH2(gs zTC`G&kA-6LrtaoQg=ih@xolQMa()3{UJ##QYodGIf z$CZ_Bwp#02-x^psm$qi9_VSoj>O5SQr&HDY+Z&<+SLem{y_EgF!;)X-Xz_CHVwfyb z;ilzcjl}D7H;JJk0>Dl>ql&0soK6UoIxF4KkZ-_Bj8>))j~VR}H;J}|JHh}~(S{7sJ4V+ok2_nZdZ%aU92@KzuP zUK6|~cuoFSHMy+I5dB3>{;sZenI;Mdix$_(bn}L2O&aPu*c)X#9xZ(Y-}|~NgLW)$ z)J9*-f#`{pmUa}&f1D=62d{8j_pwBO=;?Ca@Pa|!I?gv&2OY-r+=lf6?(+-Wq27bX z`E_bf?E5tsBt<2A?*|Z=U8d*x|1BcGa)ibTG*nlg+ka0Y!GS!+v_w@3N7ryl87x~` zGf{u<7yv-7exa@5EeMqn;~RomCXSMXpf-fF6HG$AM|+DdO6kSi@U-w6MAxctD!sb6 zuM5D3P!Q)SLHwmaY%u^7>sNPM`yN|&{P#FX=BU92O|aO{9UEOQ#t-w|NzlB9wthVK zdKlYVB2osfg8D$)WNcHnL&&)Jj1VPr1V+KMOhRNIltHn{YX=9xS5Twg-2)E*-w*GQ zMyIs#xG6o1p912T4&Jz8$Ab1c*TfuOha9Ggk*j9U#T5>s35W!Aw2yy|k8WuBDy~$% z?{w@xIgl-({&;J0O0SRqBmn(^3p{88qzNqQdbF`Fd4!()Y^mt#!J}hi0{<3#Pb2S| z_M&{u^o*h9)qEsxQheHn(eSdAJ(G*g+$Dh|!&>37q_ z7OjB?md(oK>AzNWyS__W!lwy7P4H>*|C1&rRA%PUyvmvjAz-ldqz}8CMx4<*HHNOz zM;62!7{mI@|LI?*%f&bNe@by8#yIUwSu;9>g;ZCrfwf;}ICR!&dVix7If;?f8m_`? zg4YDE$^WV*mvtH7U+kuTS692Nn~tEVJyJWN3{M5**u7nycG$b3GK(aGJ~LQIk0H1O z9WJ__>vLN&hJYd7$PS$R_RDW{NR{F2)Wg(_k|DRUWH>cQN25)4v(lh|iYT`2_AL1Q zH8`wMv&n&$>)ZoJAoavXrOQ36E;bP;xM?xeP37_&hh2>wVuZR+I8#wEQAGG(+yl;8 zi;!K_cp^xv$Bz|XD_2C{A3H0tPPiqNz0(+*f+fLGXVP{%w__C;p|K03RELG|@aOY) zGN~nH>Y1SRq!;J>xf25dTqL%J6-AU9gK`AZC zA%i&4QYQ@#4Q?d}O}FU!dCiMj&t1QLk<&hGp`vkhunt!~^j76b5&B$&+-3G2=#Rx? ztNLXBcvr5;+di^GVchjZ@jlgW+^^R?>VZ}rgw3HR+;#in*EQH%ft@e5zY32wB2DuD E1!z(1KmY&$ literal 0 HcmV?d00001