Browse Source

Merge pull request #34 from ebfull/output-circuit

WIP circuit development
master
ebfull 7 years ago committed by GitHub
parent
commit
4f4a2d63db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      Cargo.toml
  2. 359
      src/circuit/boolean.rs
  3. 37
      src/circuit/ecc.rs
  4. 716
      src/circuit/mod.rs
  5. 258
      src/circuit/num.rs
  6. 5
      src/circuit/pedersen_hash.rs
  7. 187
      src/circuit/test/mod.rs
  8. 90
      src/jubjub/edwards.rs
  9. 14
      src/jubjub/mod.rs
  10. 16
      src/jubjub/tests.rs
  11. 6
      src/lib.rs

5
Cargo.toml

@ -18,6 +18,11 @@ blake2 = "0.7"
digest = "0.7"
bellman = "0.0.8"
byteorder = "1"
[dev-dependencies]
hex-literal = "0.1"
[features]
default = ["u128-support"]
u128-support = ["pairing/u128-support"]

359
src/circuit/boolean.rs

@ -2,7 +2,6 @@ use pairing::{
Engine,
Field,
PrimeField,
PrimeFieldRepr,
BitIterator
};
@ -34,6 +33,44 @@ impl AllocatedBit {
self.variable
}
/// Allocate a variable in the constraint system which can only be a
/// boolean value. Further, constrain that the boolean is false
/// unless the condition is false.
pub fn alloc_conditionally<E, CS>(
mut cs: CS,
value: Option<bool>,
must_be_false: &AllocatedBit
) -> Result<Self, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
let var = cs.alloc(|| "boolean", || {
if *value.get()? {
Ok(E::Fr::one())
} else {
Ok(E::Fr::zero())
}
})?;
// Constrain: (1 - must_be_false - a) * a = 0
// if must_be_false is true, the equation
// reduces to -a * a = 0, which implies a = 0.
// if must_be_false is false, the equation
// reduces to (1 - a) * a = 0, which is a
// traditional boolean constraint.
cs.enforce(
|| "boolean constraint",
|lc| lc + CS::one() - must_be_false.variable - var,
|lc| lc + var,
|lc| lc
);
Ok(AllocatedBit {
variable: var,
value: value
})
}
/// Allocate a variable in the constraint system which can only be a
/// boolean value.
pub fn alloc<E, CS>(
@ -328,7 +365,34 @@ impl Boolean {
{
let c = Self::xor(&mut cs, a, b)?;
Self::enforce_nand(&mut cs, &[c])
match c {
Boolean::Constant(false) => {
Ok(())
},
Boolean::Constant(true) => {
Err(SynthesisError::Unsatisfiable)
},
Boolean::Is(ref res) => {
cs.enforce(
|| "enforce equals zero",
|lc| lc,
|lc| lc,
|lc| lc + res.get_variable()
);
Ok(())
},
Boolean::Not(ref res) => {
cs.enforce(
|| "enforce equals one",
|lc| lc,
|lc| lc,
|lc| lc + CS::one() - res.get_variable()
);
Ok(())
},
}
}
pub fn get_value(&self) -> Option<bool> {
@ -431,140 +495,6 @@ impl Boolean {
}
}
}
pub fn kary_and<E, CS>(
mut cs: CS,
bits: &[Self]
) -> Result<Self, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
assert!(bits.len() > 0);
let mut bits = bits.iter();
let mut cur: Self = bits.next().unwrap().clone();
let mut i = 0;
while let Some(next) = bits.next() {
cur = Boolean::and(cs.namespace(|| format!("AND {}", i)), &cur, next)?;
i += 1;
}
Ok(cur)
}
/// Asserts that at least one operand is false.
pub fn enforce_nand<E, CS>(
mut cs: CS,
bits: &[Self]
) -> Result<(), SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
let res = Self::kary_and(&mut cs, bits)?;
match res {
Boolean::Constant(false) => {
Ok(())
},
Boolean::Constant(true) => {
Err(SynthesisError::Unsatisfiable)
},
Boolean::Is(ref res) => {
cs.enforce(
|| "enforce nand",
|lc| lc,
|lc| lc,
|lc| lc + res.get_variable()
);
Ok(())
},
Boolean::Not(ref res) => {
cs.enforce(
|| "enforce nand",
|lc| lc,
|lc| lc,
|lc| lc + CS::one() - res.get_variable()
);
Ok(())
},
}
}
/// Asserts that this bit representation is "in
/// the field" when interpreted in big endian.
pub fn enforce_in_field<E, CS, F: PrimeField>(
mut cs: CS,
bits: &[Self]
) -> Result<(), SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
assert_eq!(bits.len(), F::NUM_BITS as usize);
let mut a = bits.iter();
// b = char() - 1
let mut b = F::char();
b.sub_noborrow(&1.into());
// Runs of ones in r
let mut last_run = Boolean::constant(true);
let mut current_run = vec![];
let mut found_one = false;
let mut run_i = 0;
let mut nand_i = 0;
for b in BitIterator::new(b) {
// Skip over unset bits at the beginning
found_one |= b;
if !found_one {
continue;
}
let a = a.next().unwrap();
if b {
// This is part of a run of ones.
current_run.push(a.clone());
} else {
if current_run.len() > 0 {
// This is the start of a run of zeros, but we need
// to k-ary AND against `last_run` first.
current_run.push(last_run.clone());
last_run = Self::kary_and(
cs.namespace(|| format!("run {}", run_i)),
&current_run
)?;
run_i += 1;
current_run.truncate(0);
}
// If `last_run` is true, `a` must be false, or it would
// not be in the field.
//
// If `last_run` is false, `a` can be true or false.
//
// Ergo, at least one of `last_run` and `a` must be false.
Self::enforce_nand(
cs.namespace(|| format!("nand {}", nand_i)),
&[last_run.clone(), a.clone()]
)?;
nand_i += 1;
}
}
// We should always end in a "run" of zeros, because
// the characteristic is an odd prime. So, this should
// be empty.
assert_eq!(current_run.len(), 0);
Ok(())
}
}
impl From<AllocatedBit> for Boolean {
@ -575,10 +505,9 @@ impl From<AllocatedBit> for Boolean {
#[cfg(test)]
mod test {
use rand::{SeedableRng, Rand, XorShiftRng};
use bellman::{ConstraintSystem};
use pairing::bls12_381::{Bls12, Fr};
use pairing::{Field, PrimeField, PrimeFieldRepr, BitIterator};
use pairing::{Field, PrimeField};
use ::circuit::test::*;
use super::{
AllocatedBit,
@ -1052,160 +981,6 @@ mod test {
}
}
#[test]
fn test_enforce_in_field() {
{
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut bits = vec![];
for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() {
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
Some(b)
).unwrap()));
}
Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap();
assert!(!cs.is_satisfied());
}
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let r = Fr::rand(&mut rng);
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut bits = vec![];
for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() {
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
Some(b)
).unwrap()));
}
Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap();
assert!(cs.is_satisfied());
}
for _ in 0..1000 {
// Sample a random element not in the field
let r = loop {
let mut a = Fr::rand(&mut rng).into_repr();
let b = Fr::rand(&mut rng).into_repr();
a.add_nocarry(&b);
// we're shaving off the high bit later
a.as_mut()[3] &= 0x7fffffffffffffff;
if Fr::from_repr(a).is_err() {
break a;
}
};
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut bits = vec![];
for (i, b) in BitIterator::new(r).skip(1).enumerate() {
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
Some(b)
).unwrap()));
}
Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap();
assert!(!cs.is_satisfied());
}
}
#[test]
fn test_enforce_nand() {
{
let mut cs = TestConstraintSystem::<Bls12>::new();
Boolean::enforce_nand(&mut cs, &[Boolean::constant(false)]).is_ok();
Boolean::enforce_nand(&mut cs, &[Boolean::constant(true)]).is_err();
}
for i in 1..5 {
// with every possible assignment for them
for mut b in 0..(1 << i) {
// with every possible negation
for mut n in 0..(1 << i) {
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut expected = true;
let mut bits = vec![];
for j in 0..i {
expected &= b & 1 == 1;
if n & 1 == 1 {
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", j)),
Some(b & 1 == 1)
).unwrap()));
} else {
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", j)),
Some(b & 1 == 0)
).unwrap()).not());
}
b >>= 1;
n >>= 1;
}
let expected = !expected;
Boolean::enforce_nand(&mut cs, &bits).unwrap();
if expected {
assert!(cs.is_satisfied());
} else {
assert!(!cs.is_satisfied());
}
}
}
}
}
#[test]
fn test_kary_and() {
// test different numbers of operands
for i in 1..15 {
// with every possible assignment for them
for mut b in 0..(1 << i) {
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut expected = true;
let mut bits = vec![];
for j in 0..i {
expected &= b & 1 == 1;
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", j)),
Some(b & 1 == 1)
).unwrap()));
b >>= 1;
}
let r = Boolean::kary_and(&mut cs, &bits).unwrap();
assert!(cs.is_satisfied());
match r {
Boolean::Is(ref r) => {
assert_eq!(r.value.unwrap(), expected);
},
_ => unreachable!()
}
}
}
}
#[test]
fn test_u64_into_allocated_bits_be() {
let mut cs = TestConstraintSystem::<Bls12>::new();

37
src/circuit/ecc.rs

@ -30,20 +30,12 @@ use super::lookup::{
use super::boolean::Boolean;
#[derive(Clone)]
pub struct EdwardsPoint<E: Engine> {
pub x: AllocatedNum<E>,
pub y: AllocatedNum<E>
}
impl<E: Engine> Clone for EdwardsPoint<E> {
fn clone(&self) -> Self {
EdwardsPoint {
x: self.x.clone(),
y: self.y.clone()
}
}
}
/// Perform a fixed-base scalar multiplication with
/// `by` being in little-endian bit order.
pub fn fixed_base_multiplication<E, CS>(
@ -92,6 +84,33 @@ pub fn fixed_base_multiplication<E, CS>(
}
impl<E: JubjubEngine> EdwardsPoint<E> {
/// This converts the point into a representation.
pub fn repr<CS>(
&self,
mut cs: CS
) -> Result<Vec<Boolean>, SynthesisError>
where CS: ConstraintSystem<E>
{
let mut tmp = vec![];
let mut x = self.x.into_bits_strict(
cs.namespace(|| "unpack x")
)?;
let mut y = self.y.into_bits_strict(
cs.namespace(|| "unpack y")
)?;
// We want the representation in little endian bit order
x.reverse();
y.reverse();
tmp.extend(y);
tmp.push(x[0].clone());
Ok(tmp)
}
/// This 'witnesses' a point inside the constraint system.
/// It guarantees the point is on the curve.
pub fn witness<Order, CS>(

716
src/circuit/mod.rs

@ -9,7 +9,23 @@ pub mod lookup;
pub mod ecc;
pub mod pedersen_hash;
use bellman::SynthesisError;
use pairing::{
PrimeField,
PrimeFieldRepr,
};
use bellman::{
SynthesisError,
ConstraintSystem,
Circuit
};
use jubjub::{
JubjubEngine,
Unknown,
FixedGenerators,
edwards
};
trait Assignment<T> {
fn get(&self) -> Result<&T, SynthesisError>;
@ -23,3 +39,701 @@ impl<T> Assignment<T> for Option<T> {
}
}
}
pub struct Spend<'a, E: JubjubEngine> {
pub params: &'a E::Params,
/// Value of the note being spent
pub value: Option<u64>,
/// Randomness that will hide the value
pub value_randomness: Option<E::Fs>,
/// Key which allows the proof to be constructed
/// as defense-in-depth against a flaw in the
/// protocol that would otherwise be exploitable
/// by a holder of a viewing key.
pub rsk: Option<E::Fs>,
/// The public key that will be re-randomized for
/// use as a nullifier and signing key for the
/// transaction.
pub ak: Option<edwards::Point<E, Unknown>>,
/// The diversified base used to compute pk_d.
pub g_d: Option<edwards::Point<E, Unknown>>,
/// The randomness used to hide the note commitment data
pub commitment_randomness: Option<E::Fs>,
/// The authentication path of the commitment in the tree
pub auth_path: Vec<Option<(E::Fr, bool)>>
}
impl<'a, E: JubjubEngine> Circuit<E> for Spend<'a, E> {
fn synthesize<CS: ConstraintSystem<E>>(self, cs: &mut CS) -> Result<(), SynthesisError>
{
// Booleanize the value into little-endian bit order
let value_bits = boolean::u64_into_allocated_bits_be(
cs.namespace(|| "value"),
self.value
)?
.into_iter()
.rev() // Little endian bit order
.map(|e| boolean::Boolean::from(e))
.collect::<Vec<_>>();
{
let gv = ecc::fixed_base_multiplication(
cs.namespace(|| "compute the value in the exponent"),
FixedGenerators::ValueCommitmentValue,
&value_bits,
self.params
)?;
// Booleanize the randomness
let hr = boolean::field_into_allocated_bits_be(
cs.namespace(|| "hr"),
self.value_randomness
)?
.into_iter()
.rev() // Little endian bit order
.map(|e| boolean::Boolean::from(e))
.collect::<Vec<_>>();
let hr = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of randomization for value commitment"),
FixedGenerators::ValueCommitmentRandomness,
&hr,
self.params
)?;
let gvhr = gv.add(
cs.namespace(|| "computation of value commitment"),
&hr,
self.params
)?;
// Expose the value commitment publicly
let value_commitment_x = cs.alloc_input(
|| "value commitment x",
|| {
Ok(*gvhr.x.get_value().get()?)
}
)?;
cs.enforce(
|| "value commitment x equals input",
|lc| lc + value_commitment_x,
|lc| lc + CS::one(),
|lc| lc + gvhr.x.get_variable()
);
let value_commitment_y = cs.alloc_input(
|| "value commitment y",
|| {
Ok(*gvhr.y.get_value().get()?)
}
)?;
cs.enforce(
|| "value commitment y equals input",
|lc| lc + value_commitment_y,
|lc| lc + CS::one(),
|lc| lc + gvhr.y.get_variable()
);
}
// Compute rk = [rsk] ProvingPublicKey
let rk;
{
// Witness rsk as bits
let rsk = boolean::field_into_allocated_bits_be(
cs.namespace(|| "rsk"),
self.rsk
)?
.into_iter()
.rev() // We need it in little endian bit order
.map(|e| boolean::Boolean::from(e)).collect::<Vec<_>>();
// NB: We don't ensure that the bit representation of rsk
// is "in the field" (Fs) because it's not used except to
// demonstrate the prover knows it. If they know a
// congruency then that's equivalent.
rk = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of rk"),
FixedGenerators::ProvingPublicKey,
&rsk,
self.params
)?;
}
// Prover witnesses ak (ensures that it's on the curve)
let ak = ecc::EdwardsPoint::witness(
cs.namespace(|| "ak"),
self.ak,
self.params
)?;
// Unpack ak and rk for input to BLAKE2s
let mut vk = vec![];
let mut rho_preimage = vec![];
vk.extend(
ak.repr(cs.namespace(|| "representation of ak"))?
);
{
let repr_rk = rk.repr(
cs.namespace(|| "representation of rk")
)?;
vk.extend(repr_rk.iter().cloned());
rho_preimage.extend(repr_rk);
}
assert_eq!(vk.len(), 512);
// Compute the incoming viewing key
let mut ivk = blake2s::blake2s(
cs.namespace(|| "computation of ivk"),
&vk
)?;
// Little endian bit order
ivk.reverse();
ivk.truncate(E::Fs::CAPACITY as usize); // drop_5
// Witness g_d
let g_d = ecc::EdwardsPoint::witness(
cs.namespace(|| "witness g_d"),
self.g_d,
self.params
)?;
// Compute pk_d
let pk_d = g_d.mul(
cs.namespace(|| "compute pk_d"),
&ivk,
self.params
)?;
// Compute note contents
let mut note_contents = vec![];
note_contents.extend(value_bits);
note_contents.extend(
g_d.repr(cs.namespace(|| "representation of g_d"))?
);
note_contents.extend(
pk_d.repr(cs.namespace(|| "representation of pk_d"))?
);
assert_eq!(
note_contents.len(),
64 + // value
256 + // g_d
256 // p_d
);
// Compute the hash of the note contents
let mut cm = pedersen_hash::pedersen_hash(
cs.namespace(|| "note content hash"),
pedersen_hash::Personalization::NoteCommitment,
&note_contents,
self.params
)?;
{
// Booleanize the randomness
let cmr = boolean::field_into_allocated_bits_be(
cs.namespace(|| "cmr"),
self.commitment_randomness
)?
.into_iter()
.rev() // We need it in little endian bit order
.map(|e| boolean::Boolean::from(e))
.collect::<Vec<_>>();
let cmr = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of commitment randomness"),
FixedGenerators::NoteCommitmentRandomness,
&cmr,
self.params
)?;
cm = cm.add(
cs.namespace(|| "randomization of note commitment"),
&cmr,
self.params
)?;
}
let tree_depth = self.auth_path.len();
let mut position_bits = vec![];
// Injective encoding.
let mut cur = cm.x.clone();
for (i, e) in self.auth_path.into_iter().enumerate() {
let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i));
let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc(
cs.namespace(|| "position bit"),
e.map(|e| e.1)
)?);
position_bits.push(cur_is_right.clone());
let path_element = num::AllocatedNum::alloc(
cs.namespace(|| "path element"),
|| {
Ok(e.get()?.0)
}
)?;
let (xl, xr) = num::AllocatedNum::conditionally_reverse(
cs.namespace(|| "conditional reversal of preimage"),
&cur,
&path_element,
&cur_is_right
)?;
// We don't need to be strict, because the function is
// collision-resistant.
let mut preimage = vec![];
preimage.extend(xl.into_bits(cs.namespace(|| "xl into bits"))?);
preimage.extend(xr.into_bits(cs.namespace(|| "xr into bits"))?);
cur = pedersen_hash::pedersen_hash(
cs.namespace(|| "computation of pedersen hash"),
pedersen_hash::Personalization::MerkleTree(tree_depth - i),
&preimage,
self.params
)?.x; // Injective encoding
}
assert_eq!(position_bits.len(), tree_depth);
{
// Expose the anchor
let anchor = cs.alloc_input(
|| "anchor x",
|| {
Ok(*cur.get_value().get()?)
}
)?;
cs.enforce(
|| "anchor x equals anchor",
|lc| lc + anchor,
|lc| lc + CS::one(),
|lc| lc + cur.get_variable()
);
}
{
let position = ecc::fixed_base_multiplication(
cs.namespace(|| "g^position"),
FixedGenerators::NullifierPosition,
&position_bits,
self.params
)?;
cm = cm.add(
cs.namespace(|| "faerie gold prevention"),
&position,
self.params
)?;
}
// Let's compute rho = BLAKE2s(rk || cm + position)
rho_preimage.extend(
cm.repr(cs.namespace(|| "representation of cm"))?
);
assert_eq!(rho_preimage.len(), 512);
let mut rho = blake2s::blake2s(
cs.namespace(|| "rho computation"),
&rho_preimage
)?;
// Little endian bit order
rho.reverse();
rho.truncate(251); // drop_5
// Compute nullifier
let nf = ak.mul(
cs.namespace(|| "computation of nf"),
&rho,
self.params
)?;
{
// Expose the nullifier publicly
let nf_x = cs.alloc_input(
|| "nf_x",
|| {
Ok(*nf.x.get_value().get()?)
}
)?;
cs.enforce(
|| "nf_x equals input",
|lc| lc + nf_x,
|lc| lc + CS::one(),
|lc| lc + nf.x.get_variable()
);
let nf_y = cs.alloc_input(
|| "nf_y",
|| {
Ok(*nf.y.get_value().get()?)
}
)?;
cs.enforce(
|| "nf_y equals input",
|lc| lc + nf_y,
|lc| lc + CS::one(),
|lc| lc + nf.y.get_variable()
);
}
Ok(())
}
}
/// This is an output circuit instance.
pub struct Output<'a, E: JubjubEngine> {
pub params: &'a E::Params,
/// Value of the note being created
pub value: Option<u64>,
/// Randomness that will hide the value
pub value_randomness: Option<E::Fs>,
/// The diversified base, computed by GH(d)
pub g_d: Option<edwards::Point<E, Unknown>>,
/// The diversified address point, computed by GH(d)^ivk
pub p_d: Option<edwards::Point<E, Unknown>>,
/// The randomness used to hide the note commitment data
pub commitment_randomness: Option<E::Fs>,
/// The ephemeral secret key for DH with recipient
pub esk: Option<E::Fs>
}
impl<'a, E: JubjubEngine> Circuit<E> for Output<'a, E> {
fn synthesize<CS: ConstraintSystem<E>>(self, cs: &mut CS) -> Result<(), SynthesisError>
{
// Booleanize the value into little-endian bit order
let value_bits = boolean::u64_into_allocated_bits_be(
cs.namespace(|| "value"),
self.value
)?
.into_iter()
.rev() // Little endian bit order
.map(|e| boolean::Boolean::from(e))
.collect::<Vec<_>>();
{
let gv = ecc::fixed_base_multiplication(
cs.namespace(|| "compute the value in the exponent"),
FixedGenerators::ValueCommitmentValue,
&value_bits,
self.params
)?;
// Booleanize the randomness
let hr = boolean::field_into_allocated_bits_be(
cs.namespace(|| "hr"),
self.value_randomness
)?
.into_iter()
.rev() // Little endian bit order
.map(|e| boolean::Boolean::from(e))
.collect::<Vec<_>>();
let hr = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of randomization for value commitment"),
FixedGenerators::ValueCommitmentRandomness,
&hr,
self.params
)?;
let gvhr = gv.add(
cs.namespace(|| "computation of value commitment"),
&hr,
self.params
)?;
// Expose the value commitment publicly
let value_commitment_x = cs.alloc_input(
|| "value commitment x",
|| {
Ok(*gvhr.x.get_value().get()?)
}
)?;
cs.enforce(
|| "value commitment x equals input",
|lc| lc + value_commitment_x,
|lc| lc + CS::one(),
|lc| lc + gvhr.x.get_variable()
);
let value_commitment_y = cs.alloc_input(
|| "value commitment y",
|| {
Ok(*gvhr.y.get_value().get()?)
}
)?;
cs.enforce(
|| "value commitment y equals input",
|lc| lc + value_commitment_y,
|lc| lc + CS::one(),
|lc| lc + gvhr.y.get_variable()
);
}
// Let's start to construct our note
let mut note_contents = vec![];
note_contents.extend(value_bits);
// Let's deal with g_d
{
let g_d = ecc::EdwardsPoint::witness(
cs.namespace(|| "witness g_d"),
self.g_d,
self.params
)?;
// Check that g_d is not of small order
{
let g_d = g_d.double(
cs.namespace(|| "first doubling of g_d"),
self.params
)?;
let g_d = g_d.double(
cs.namespace(|| "second doubling of g_d"),
self.params
)?;
let g_d = g_d.double(
cs.namespace(|| "third doubling of g_d"),
self.params
)?;
// (0, -1) is a small order point, but won't ever appear here
// because cofactor is 2^3, and we performed three doublings.
// (0, 1) is the neutral element, so checking if x is nonzero
// is sufficient to prevent small order points here.
g_d.x.assert_nonzero(cs.namespace(|| "check not inf"))?;
}
note_contents.extend(
g_d.repr(cs.namespace(|| "representation of g_d"))?
);
// Compute epk from esk
let esk = boolean::field_into_allocated_bits_be(
cs.namespace(|| "esk"),
self.esk
)?
.into_iter()
.rev() // We need it in little endian bit order
.map(|e| boolean::Boolean::from(e))
.collect::<Vec<_>>();
let epk = g_d.mul(
cs.namespace(|| "epk computation"),
&esk,
self.params
)?;
// Expose epk publicly
let epk_x = cs.alloc_input(
|| "epk x",
|| {
Ok(*epk.x.get_value().get()?)
}
)?;
cs.enforce(
|| "epk x equals input",
|lc| lc + epk_x,
|lc| lc + CS::one(),
|lc| lc + epk.x.get_variable()
);
let epk_y = cs.alloc_input(
|| "epk y",
|| {
Ok(*epk.y.get_value().get()?)
}
)?;
cs.enforce(
|| "epk y equals input",
|lc| lc + epk_y,
|lc| lc + CS::one(),
|lc| lc + epk.y.get_variable()
);
}
// Now let's deal with p_d. We don't do any checks and
// essentially allow the prover to witness any 256 bits
// they would like.
{
let p_d = self.p_d.map(|e| e.into_xy());
let y_contents = boolean::field_into_allocated_bits_be(
cs.namespace(|| "p_d bits of y"),
p_d.map(|e| e.1)
)?
.into_iter()
.rev() // We need it in little endian bit order
.map(|e| boolean::Boolean::from(e))
.collect::<Vec<_>>();
let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc(
cs.namespace(|| "p_d bit of x"),
p_d.map(|e| e.0.into_repr().is_odd())
)?);
note_contents.extend(y_contents);
note_contents.push(sign_bit);
}
assert_eq!(
note_contents.len(),
64 + // value
256 + // g_d
256 // p_d
);
// Compute the hash of the note contents
let mut cm = pedersen_hash::pedersen_hash(
cs.namespace(|| "note content hash"),
pedersen_hash::Personalization::NoteCommitment,
&note_contents,
self.params
)?;
{
// Booleanize the randomness
let cmr = boolean::field_into_allocated_bits_be(
cs.namespace(|| "cmr"),
self.commitment_randomness
)?
.into_iter()
.rev() // We need it in little endian bit order
.map(|e| boolean::Boolean::from(e))
.collect::<Vec<_>>();
let cmr = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of commitment randomness"),
FixedGenerators::NoteCommitmentRandomness,
&cmr,
self.params
)?;
cm = cm.add(
cs.namespace(|| "randomization of note commitment"),
&cmr,
self.params
)?;
}
// Only the x-coordinate of the output is revealed,
// since we know it is prime order, and we know that
// the x-coordinate is an injective encoding for
// prime-order elements.
let commitment_input = cs.alloc_input(
|| "commitment input",
|| {
Ok(*cm.x.get_value().get()?)
}
)?;
cs.enforce(
|| "commitment input correct",
|lc| lc + commitment_input,
|lc| lc + CS::one(),
|lc| lc + cm.x.get_variable()
);
Ok(())
}
}
#[test]
fn test_input_circuit_with_bls12_381() {
use pairing::bls12_381::*;
use rand::{SeedableRng, Rng, XorShiftRng};
use ::circuit::test::*;
use jubjub::{JubjubBls12, fs};
let params = &JubjubBls12::new();
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let tree_depth = 29;
let value: u64 = 1;
let value_randomness: fs::Fs = rng.gen();
let ak: edwards::Point<Bls12, Unknown> = edwards::Point::rand(rng, params);
let g_d: edwards::Point<Bls12, Unknown> = edwards::Point::rand(rng, params);
let commitment_randomness: fs::Fs = rng.gen();
let rsk: fs::Fs = rng.gen();
let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth];
{
let mut cs = TestConstraintSystem::<Bls12>::new();
let instance = Spend {
params: params,
value: Some(value),
value_randomness: Some(value_randomness),
rsk: Some(rsk),
ak: Some(ak),
g_d: Some(g_d),
commitment_randomness: Some(commitment_randomness),
auth_path: auth_path
};
instance.synthesize(&mut cs).unwrap();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 97379);
assert_eq!(cs.hash(), "4d8e71c91a621e41599ea488ee89f035c892a260a595d3c85a20a82daa2d1654");
}
}
#[test]
fn test_output_circuit_with_bls12_381() {
use pairing::bls12_381::*;
use rand::{SeedableRng, Rng, XorShiftRng};
use ::circuit::test::*;
use jubjub::{JubjubBls12, fs};
let params = &JubjubBls12::new();
let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let value: u64 = 1;
let value_randomness: fs::Fs = rng.gen();
let g_d: edwards::Point<Bls12, Unknown> = edwards::Point::rand(rng, params);
let p_d: edwards::Point<Bls12, Unknown> = edwards::Point::rand(rng, params);
let commitment_randomness: fs::Fs = rng.gen();
let esk: fs::Fs = rng.gen();
{
let mut cs = TestConstraintSystem::<Bls12>::new();
let instance = Output {
params: params,
value: Some(value),
value_randomness: Some(value_randomness),
g_d: Some(g_d.clone()),
p_d: Some(p_d.clone()),
commitment_randomness: Some(commitment_randomness),
esk: Some(esk.clone())
};
instance.synthesize(&mut cs).unwrap();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 7827);
assert_eq!(cs.hash(), "225a2df7e21b9af8b436ffb9dadd645e4df843a5151c7481b0553422d5eaa793");
}
}

258
src/circuit/num.rs

@ -2,6 +2,8 @@ use pairing::{
Engine,
Field,
PrimeField,
PrimeFieldRepr,
BitIterator
};
use bellman::{
@ -18,6 +20,7 @@ use super::{
use super::boolean::{
self,
Boolean,
AllocatedBit
};
pub struct AllocatedNum<E: Engine> {
@ -63,27 +66,111 @@ impl<E: Engine> AllocatedNum<E> {
) -> Result<Vec<Boolean>, SynthesisError>
where CS: ConstraintSystem<E>
{
let bits = self.into_bits(&mut cs)?;
Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, &bits)?;
pub fn kary_and<E, CS>(
mut cs: CS,
v: &[AllocatedBit]
) -> Result<AllocatedBit, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
assert!(v.len() > 0);
Ok(bits)
}
// Let's keep this simple for now and just AND them all
// manually
let mut cur = None;
pub fn into_bits<CS>(
&self,
mut cs: CS
) -> Result<Vec<Boolean>, SynthesisError>
where CS: ConstraintSystem<E>
{
let bits = boolean::field_into_allocated_bits_be(
&mut cs,
self.value
)?;
for (i, v) in v.iter().enumerate() {
if cur.is_none() {
cur = Some(v.clone());
} else {
cur = Some(AllocatedBit::and(
cs.namespace(|| format!("and {}", i)),
cur.as_ref().unwrap(),
v
)?);
}
}
Ok(cur.expect("v.len() > 0"))
}
// We want to ensure that the bit representation of a is
// less than or equal to r - 1.
let mut a = self.value.map(|e| BitIterator::new(e.into_repr()));
let mut b = E::Fr::char();
b.sub_noborrow(&1.into());
let mut result = vec![];
// Runs of ones in r
let mut last_run = None;
let mut current_run = vec![];
let mut found_one = false;
let mut i = 0;
for b in BitIterator::new(b) {
let a_bit = a.as_mut().map(|e| e.next().unwrap());
// Skip over unset bits at the beginning
found_one |= b;
if !found_one {
// a_bit should also be false
a_bit.map(|e| assert!(!e));
continue;
}
if b {
// This is part of a run of ones. Let's just
// allocate the boolean with the expected value.
let a_bit = AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
a_bit
)?;
// ... and add it to the current run of ones.
current_run.push(a_bit.clone());
result.push(a_bit);
} else {
if current_run.len() > 0 {
// This is the start of a run of zeros, but we need
// to k-ary AND against `last_run` first.
if last_run.is_some() {
current_run.push(last_run.clone().unwrap());
}
last_run = Some(kary_and(
cs.namespace(|| format!("run ending at {}", i)),
&current_run
)?);
current_run.truncate(0);
}
// If `last_run` is true, `a` must be false, or it would
// not be in the field.
//
// If `last_run` is false, `a` can be true or false.
let a_bit = AllocatedBit::alloc_conditionally(
cs.namespace(|| format!("bit {}", i)),
a_bit,
&last_run.as_ref().expect("char always starts with a one")
)?;
result.push(a_bit);
}
i += 1;
}
// char is prime, so we'll always end on
// a run of zeros.
assert_eq!(current_run.len(), 0);
// Now, we have `result` in big-endian order.
// However, now we have to unpack self!
let mut lc = LinearCombination::zero();
let mut coeff = E::Fr::one();
for bit in bits.iter().rev() {
for bit in result.iter().rev() {
lc = lc + (coeff, bit.get_variable());
coeff.double();
@ -98,76 +185,39 @@ impl<E: Engine> AllocatedNum<E> {
|_| lc
);
Ok(bits.into_iter().map(|b| Boolean::from(b)).collect())
Ok(result.into_iter().map(|b| Boolean::from(b)).collect())
}
pub fn from_bits_strict<CS>(
mut cs: CS,
bits: &[Boolean]
) -> Result<Self, SynthesisError>
pub fn into_bits<CS>(
&self,
mut cs: CS
) -> Result<Vec<Boolean>, SynthesisError>
where CS: ConstraintSystem<E>
{
assert_eq!(bits.len(), E::Fr::NUM_BITS as usize);
Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, bits)?;
let bits = boolean::field_into_allocated_bits_be(
&mut cs,
self.value
)?;
let one = CS::one();
let mut lc = LinearCombination::<E>::zero();
let mut lc = LinearCombination::zero();
let mut coeff = E::Fr::one();
let mut value = Some(E::Fr::zero());
for bit in bits.iter().rev() {
match bit {
&Boolean::Constant(false) => {},
&Boolean::Constant(true) => {
value.as_mut().map(|value| value.add_assign(&coeff));
lc = lc + (coeff, one);
},
&Boolean::Is(ref bit) => {
match bit.get_value() {
Some(bit) => {
if bit {
value.as_mut().map(|value| value.add_assign(&coeff));
}
},
None => {
value = None;
}
}
lc = lc + (coeff, bit.get_variable());
},
&Boolean::Not(ref bit) => {
match bit.get_value() {
Some(bit) => {
if !bit {
value.as_mut().map(|value| value.add_assign(&coeff));
}
},
None => {
value = None;
}
}
lc = lc + (coeff, one) - (coeff, bit.get_variable());
}
}
for bit in bits.iter().rev() {
lc = lc + (coeff, bit.get_variable());
coeff.double();
}
let num = Self::alloc(&mut cs, || value.get().map(|v| *v))?;
lc = lc - num.get_variable();
lc = lc - self.variable;
cs.enforce(
|| "packing constraint",
|| "unpacking constraint",
|lc| lc,
|lc| lc,
|_| lc
);
Ok(num)
Ok(bits.into_iter().map(|b| Boolean::from(b)).collect())
}
pub fn mul<CS>(
@ -384,7 +434,6 @@ mod test {
use pairing::{Field, PrimeField, BitIterator};
use ::circuit::test::*;
use super::{AllocatedNum, Boolean};
use super::super::boolean::AllocatedBit;
#[test]
fn test_allocated_num() {
@ -491,31 +540,25 @@ mod test {
// make the bit representation the characteristic
cs.set("bit 254/boolean", Fr::one());
// this makes the unpacking constraint fail
assert_eq!(cs.which_is_unsatisfied().unwrap(), "unpacking constraint");
// fix it by making the number zero (congruent to the characteristic)
cs.set("num", Fr::zero());
// and constraint is disturbed during enforce in field check
assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/AND 0/and constraint");
cs.set("nand 121/AND 0/and result", Fr::one());
// now the nand should fail (enforce in field is working)
assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/enforce nand");
// this makes the conditional boolean constraint fail
assert_eq!(cs.which_is_unsatisfied().unwrap(), "bit 254/boolean constraint");
}
#[test]
fn test_into_bits() {
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..100 {
for i in 0..200 {
let r = Fr::rand(&mut rng);
let mut cs = TestConstraintSystem::<Bls12>::new();
let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap();
let bits = n.into_bits(&mut cs).unwrap();
let bits = if i % 2 == 0 {
n.into_bits(&mut cs).unwrap()
} else {
n.into_bits_strict(&mut cs).unwrap()
};
assert!(cs.is_satisfied());
@ -544,55 +587,4 @@ mod test {
}
}
}
#[test]
fn test_from_bits_strict() {
{
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut bits = vec![];
for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() {
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
Some(b)
).unwrap()));
}
let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap();
assert!(num.value.unwrap().is_zero());
assert!(!cs.is_satisfied());
}
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let r = Fr::rand(&mut rng);
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut bits = vec![];
for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() {
let parity: bool = rng.gen();
if parity {
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
Some(b)
).unwrap()));
} else {
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
Some(!b)
).unwrap()).not());
}
}
let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap();
assert!(cs.is_satisfied());
assert_eq!(num.value.unwrap(), r);
assert_eq!(cs.get("num"), r);
cs.set("num", Fr::rand(&mut rng));
assert_eq!(cs.which_is_unsatisfied().unwrap(), "packing constraint");
}
}
}

5
src/circuit/pedersen_hash.rs

@ -13,7 +13,8 @@ use super::lookup::*;
// TODO: ensure these match the spec
pub enum Personalization {
NoteCommitment,
AnotherPersonalization
AnotherPersonalization,
MerkleTree(usize)
}
impl Personalization {
@ -30,6 +31,8 @@ impl Personalization {
vec![false, false, false, false, false, false],
Personalization::AnotherPersonalization =>
vec![false, false, false, false, false, true],
Personalization::MerkleTree(_) =>
vec![false, false, false, false, true, false],
}
}
}

187
src/circuit/test/mod.rs

@ -1,6 +1,8 @@
use pairing::{
Engine,
Field
Field,
PrimeField,
PrimeFieldRepr
};
use bellman::{
@ -12,6 +14,13 @@ use bellman::{
};
use std::collections::HashMap;
use std::fmt::Write;
use blake2::{Blake2s};
use digest::{FixedOutput, Input};
use byteorder::{BigEndian, ByteOrder};
use std::cmp::Ordering;
use std::collections::BTreeMap;
#[derive(Debug)]
enum NamedObject {
@ -34,6 +43,90 @@ pub struct TestConstraintSystem<E: Engine> {
aux: Vec<(E::Fr, String)>
}
#[derive(Clone, Copy)]
struct OrderedVariable(Variable);
impl Eq for OrderedVariable {}
impl PartialEq for OrderedVariable {
fn eq(&self, other: &OrderedVariable) -> bool {
match (self.0.get_unchecked(), other.0.get_unchecked()) {
(Index::Input(ref a), Index::Input(ref b)) => a == b,
(Index::Aux(ref a), Index::Aux(ref b)) => a == b,
_ => false
}
}
}
impl PartialOrd for OrderedVariable {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OrderedVariable {
fn cmp(&self, other: &Self) -> Ordering {
match (self.0.get_unchecked(), other.0.get_unchecked()) {
(Index::Input(ref a), Index::Input(ref b)) => a.cmp(b),
(Index::Aux(ref a), Index::Aux(ref b)) => a.cmp(b),
(Index::Input(_), Index::Aux(_)) => Ordering::Less,
(Index::Aux(_), Index::Input(_)) => Ordering::Greater
}
}
}
fn proc_lc<E: Engine>(
terms: &[(Variable, E::Fr)],
) -> BTreeMap<OrderedVariable, E::Fr>
{
let mut map = BTreeMap::new();
for &(var, coeff) in terms {
map.entry(OrderedVariable(var))
.or_insert(E::Fr::zero())
.add_assign(&coeff);
}
// Remove terms that have a zero coefficient to normalize
let mut to_remove = vec![];
for (var, coeff) in map.iter() {
if coeff.is_zero() {
to_remove.push(var.clone())
}
}
for var in to_remove {
map.remove(&var);
}
map
}
fn hash_lc<E: Engine>(
terms: &[(Variable, E::Fr)],
h: &mut Blake2s
)
{
let map = proc_lc::<E>(terms);
let mut buf = [0u8; 9 + 32];
BigEndian::write_u64(&mut buf[0..8], map.len() as u64);
h.process(&buf[0..8]);
for (var, coeff) in map {
match var.0.get_unchecked() {
Index::Input(i) => {
buf[0] = b'I';
BigEndian::write_u64(&mut buf[1..9], i as u64);
},
Index::Aux(i) => {
buf[0] = b'A';
BigEndian::write_u64(&mut buf[1..9], i as u64);
}
}
coeff.into_repr().write_be(&mut buf[9..]).unwrap();
h.process(&buf);
}
}
fn eval_lc<E: Engine>(
terms: &[(Variable, E::Fr)],
inputs: &[(E::Fr, String)],
@ -69,6 +162,98 @@ impl<E: Engine> TestConstraintSystem<E> {
}
}
pub fn pretty_print(&self) -> String {
let mut s = String::new();
let negone = {
let mut tmp = E::Fr::one();
tmp.negate();
tmp
};
let powers_of_two = (0..E::Fr::NUM_BITS).map(|i| {
E::Fr::from_str("2").unwrap().pow(&[i as u64])
}).collect::<Vec<_>>();
let pp = |s: &mut String, lc: &LinearCombination<E>| {
write!(s, "(").unwrap();
let mut is_first = true;
for (var, coeff) in proc_lc::<E>(lc.as_ref()) {
if coeff == negone {
write!(s, " - ").unwrap();
} else if !is_first {
write!(s, " + ").unwrap();
}
is_first = false;
if coeff != E::Fr::one() && coeff != negone {
for (i, x) in powers_of_two.iter().enumerate() {
if x == &coeff {
write!(s, "2^{} . ", i).unwrap();
break;
}
}
write!(s, "{} . ", coeff).unwrap();
}
match var.0.get_unchecked() {
Index::Input(i) => {
write!(s, "`{}`", &self.inputs[i].1).unwrap();
},
Index::Aux(i) => {
write!(s, "`{}`", &self.aux[i].1).unwrap();
}
}
}
if is_first {
// Nothing was visited, print 0.
write!(s, "0").unwrap();
}
write!(s, ")").unwrap();
};
for &(ref a, ref b, ref c, ref name) in &self.constraints {
write!(&mut s, "\n").unwrap();
write!(&mut s, "{}: ", name).unwrap();
pp(&mut s, a);
write!(&mut s, " * ").unwrap();
pp(&mut s, b);
write!(&mut s, " = ").unwrap();
pp(&mut s, c);
}
write!(&mut s, "\n").unwrap();
s
}
pub fn hash(&self) -> String {
let mut h = Blake2s::new_keyed(&[], 32);
{
let mut buf = [0u8; 24];
BigEndian::write_u64(&mut buf[0..8], self.inputs.len() as u64);
BigEndian::write_u64(&mut buf[8..16], self.aux.len() as u64);
BigEndian::write_u64(&mut buf[16..24], self.constraints.len() as u64);
h.process(&buf);
}
for constraint in &self.constraints {
hash_lc::<E>(constraint.0.as_ref(), &mut h);
hash_lc::<E>(constraint.1.as_ref(), &mut h);
hash_lc::<E>(constraint.2.as_ref(), &mut h);
}
let mut s = String::new();
for b in h.fixed_result().as_ref() {
s += &format!("{:02x}", b);
}
s
}
pub fn which_is_unsatisfied(&self) -> Option<&str> {
for &(ref a, ref b, ref c, ref path) in &self.constraints {
let mut a = eval_lc::<E>(a.as_ref(), &self.inputs, &self.aux);

90
src/jubjub/edwards.rs

@ -20,6 +20,12 @@ use rand::{
use std::marker::PhantomData;
use std::io::{
self,
Write,
Read
};
// Represents the affine point (X/Z, Y/Z) via the extended
// twisted Edwards coordinates.
pub struct Point<E: JubjubEngine, Subgroup> {
@ -80,7 +86,67 @@ impl<E: JubjubEngine, Subgroup> PartialEq for Point<E, Subgroup> {
}
}
fn swap_bits_u64(x: u64) -> u64
{
let mut tmp = 0;
for i in 0..64 {
tmp |= ((x >> i) & 1) << (63 - i);
}
tmp
}
#[test]
fn test_swap_bits_u64() {
assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111);
assert_eq!(swap_bits_u64(15135675916470734665), 0b1001001011110010001101010010001110110000100111010011000001001011);
assert_eq!(swap_bits_u64(6724233301461108393), 0b1001010101100000100011100001010111110001011000101000101010111010);
assert_eq!(swap_bits_u64(206708183275952289), 0b1000010100011010001010100011101011111111111110100111101101000000);
assert_eq!(swap_bits_u64(12712751566144824320), 0b0000000000100110010110111000001110001100001000110011011000001101);
let mut a = 15863238721320035327u64;
for _ in 0..1000 {
a = a.wrapping_mul(a);
let swapped = swap_bits_u64(a);
let unswapped = swap_bits_u64(swapped);
assert_eq!(a, unswapped);
}
}
impl<E: JubjubEngine> Point<E, Unknown> {
pub fn read<R: Read>(
reader: R,
params: &E::Params
) -> io::Result<Self>
{
let mut y_repr = <E::Fr as PrimeField>::Repr::default();
y_repr.read_be(reader)?;
y_repr.as_mut().reverse();
for b in y_repr.as_mut() {
*b = swap_bits_u64(*b);
}
let x_sign = (y_repr.as_ref()[3] >> 63) == 1;
y_repr.as_mut()[3] &= 0x7fffffffffffffff;
match E::Fr::from_repr(y_repr) {
Ok(y) => {
match Self::get_for_y(y, x_sign, params) {
Some(p) => Ok(p),
None => {
Err(io::Error::new(io::ErrorKind::InvalidInput, "not on curve"))
}
}
},
Err(_) => {
Err(io::Error::new(io::ErrorKind::InvalidInput, "y is not in field"))
}
}
}
pub fn get_for_y(y: E::Fr, sign: bool, params: &E::Params) -> Option<Self>
{
// Given a y on the curve, x^2 = (y^2 - 1) / (dy^2 + 1)
@ -151,6 +217,30 @@ impl<E: JubjubEngine> Point<E, Unknown> {
}
impl<E: JubjubEngine, Subgroup> Point<E, Subgroup> {
pub fn write<W: Write>(
&self,
writer: W
) -> io::Result<()>
{
let (x, y) = self.into_xy();
assert_eq!(E::Fr::NUM_BITS, 255);
let x_repr = x.into_repr();
let mut y_repr = y.into_repr();
if x_repr.is_odd() {
y_repr.as_mut()[3] |= 0x8000000000000000u64;
}
y_repr.as_mut().reverse();
for b in y_repr.as_mut() {
*b = swap_bits_u64(*b);
}
y_repr.write_be(writer)
}
/// Convert from a Montgomery point
pub fn from_montgomery(
m: &montgomery::Point<E, Subgroup>,

14
src/jubjub/mod.rs

@ -71,7 +71,9 @@ pub enum FixedGenerators {
ProvingPublicKey = 1,
ValueCommitmentValue = 2,
ValueCommitmentRandomness = 3,
Max = 4
NullifierPosition = 4,
SpendingKeyGenerator = 5,
Max = 6
}
pub struct JubjubBls12 {
@ -235,4 +237,14 @@ fn test_jubjub_bls12() {
let params = JubjubBls12::new();
tests::test_suite::<Bls12>(&params);
let test_repr = hex!("b9481dd1103b7d1f8578078eb429d3c476472f53e88c0eaefdf51334c7c8b98c");
let p = edwards::Point::<Bls12, _>::read(&test_repr[..], &params).unwrap();
let q = edwards::Point::<Bls12, _>::get_for_y(
Fr::from_str("22440861827555040311190986994816762244378363690614952020532787748720529117853").unwrap(),
false,
&params
).unwrap();
assert!(p == q);
}

16
src/jubjub/tests.rs

@ -26,6 +26,7 @@ pub fn test_suite<E: JubjubEngine>(params: &E::Params) {
test_order::<E>(params);
test_mul_associativity::<E>(params);
test_loworder::<E>(params);
test_read_write::<E>(params);
}
fn is_on_mont_curve<E: JubjubEngine, P: JubjubParams<E>>(
@ -245,6 +246,21 @@ fn test_get_for<E: JubjubEngine>(params: &E::Params) {
}
}
fn test_read_write<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let e = edwards::Point::<E, _>::rand(rng, params);
let mut v = vec![];
e.write(&mut v).unwrap();
let e2 = edwards::Point::read(&v[..], params).unwrap();
assert!(e == e2);
}
}
fn test_rand<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);

6
src/lib.rs

@ -4,6 +4,12 @@ extern crate blake2;
extern crate digest;
extern crate rand;
extern crate byteorder;
#[cfg(test)]
#[macro_use]
extern crate hex_literal;
pub mod jubjub;
pub mod circuit;
pub mod group_hash;

Loading…
Cancel
Save