Newtype the Field so we can add serializers
This commit is contained in:
parent
8ff42be353
commit
0a17226a4c
|
@ -16,7 +16,7 @@ license-file = "mit-license.md"
|
|||
[features]
|
||||
default = []
|
||||
bench = [ "criterion", "proptest" ]
|
||||
mimc = [ "tiny-keccak", "zkp-u256" ]
|
||||
mimc = [ "zkp-u256" ]
|
||||
|
||||
[[bench]]
|
||||
name = "criterion"
|
||||
|
@ -46,10 +46,11 @@ serde = "1.0"
|
|||
sha2 = "0.10.1"
|
||||
tempfile = "3.3.0"
|
||||
thiserror = "1.0.0"
|
||||
tiny-keccak = { version = "2.0.2", optional = true }
|
||||
zkp-u256 = { version = "0.2", optional = true }
|
||||
tiny-keccak = { version = "2.0.2" }
|
||||
zkp-u256 = { version = "0.2", optional = true } # TODO: Remove
|
||||
|
||||
# Use the same `ethers-core` version as ark-circom
|
||||
# TODO: Remove
|
||||
ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
13
README.md
13
README.md
|
@ -22,18 +22,17 @@ semaphore = { git = "https://github.com/worldcoin/semaphore-rs" }
|
|||
Example as in `src/lib.rs`, run with `cargo test`.
|
||||
|
||||
```rust
|
||||
use semaphore::{identity::Identity, hash::Hash, poseidon_tree::PoseidonTree,
|
||||
use semaphore::{hash_to_field, Field, identity::Identity, poseidon_tree::PoseidonTree,
|
||||
protocol::* };
|
||||
use num_bigint::BigInt;
|
||||
|
||||
// generate identity
|
||||
let id = Identity::new(b"secret");
|
||||
let id = Identity::from_seed(b"secret");
|
||||
|
||||
// generate merkle tree
|
||||
const LEAF: Hash = Hash::from_bytes_be([0u8; 32]);
|
||||
|
||||
let mut tree = PoseidonTree::new(21, LEAF);
|
||||
tree.set(0, id.commitment().into());
|
||||
let leaf = Field::from(0);
|
||||
let mut tree = PoseidonTree::new(21, leaf);
|
||||
tree.set(0, id.commitment());
|
||||
|
||||
let merkle_proof = tree.proof(0).expect("proof should exist");
|
||||
let root = tree.root();
|
||||
|
@ -45,7 +44,7 @@ let external_nullifier_hash = hash_to_field(b"appId");
|
|||
let nullifier_hash = generate_nullifier_hash(&id, external_nullifier_hash);
|
||||
|
||||
let proof = generate_proof(&id, &merkle_proof, external_nullifier_hash, signal_hash).unwrap();
|
||||
let success = verify_proof(root.into(), nullifier_hash, signal_hash, external_nullifier_hash, &proof).unwrap();
|
||||
let success = verify_proof(root, nullifier_hash, signal_hash, external_nullifier_hash, &proof).unwrap();
|
||||
|
||||
assert!(success);
|
||||
```
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
"keccak",
|
||||
"merkle",
|
||||
"mimc",
|
||||
"mimcsponge",
|
||||
"mmaped",
|
||||
"modpow",
|
||||
"mulmod",
|
||||
"proptest",
|
||||
"Repr",
|
||||
"Seedable",
|
||||
"snarkfiles",
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
use crate::util::{keccak256, trim_hex_prefix};
|
||||
use ark_bn254::Fr as ArkField;
|
||||
use ark_ff::{BigInteger as _, PrimeField as _};
|
||||
use core::{
|
||||
fmt::{Formatter, Result as FmtResult},
|
||||
str,
|
||||
str::FromStr,
|
||||
};
|
||||
use ff::{PrimeField as _, PrimeFieldRepr as _};
|
||||
use hex::encode_to_slice;
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use poseidon_rs::Fr as PosField;
|
||||
use serde::{
|
||||
de::{Error as DeError, Visitor},
|
||||
Deserialize, Serialize, Serializer,
|
||||
};
|
||||
|
||||
/// An element of the BN254 scalar field Fr.
|
||||
///
|
||||
/// Represented as a big-endian byte vector without Montgomery reduction.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
// TODO: Make sure value is always reduced.
|
||||
pub struct Field([u8; 32]);
|
||||
|
||||
impl Field {
|
||||
/// Construct a field element from a big-endian byte vector.
|
||||
#[must_use]
|
||||
pub fn from_be_bytes_mod_order(bytes: &[u8]) -> Self {
|
||||
ArkField::from_be_bytes_mod_order(bytes).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Field {
|
||||
fn from(value: u64) -> Self {
|
||||
ArkField::from(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArkField> for Field {
|
||||
fn from(value: ArkField) -> Self {
|
||||
let mut bytes = [0_u8; 32];
|
||||
let byte_vec = value.into_repr().to_bytes_be();
|
||||
bytes.copy_from_slice(&byte_vec[..]);
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Field> for ArkField {
|
||||
fn from(value: Field) -> Self {
|
||||
Self::from_be_bytes_mod_order(&value.0[..])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PosField> for Field {
|
||||
fn from(value: PosField) -> Self {
|
||||
let mut bytes = [0u8; 32];
|
||||
value
|
||||
.into_repr()
|
||||
.write_be(&mut bytes[..])
|
||||
.expect("write to correctly sized slice always succeeds");
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Field> for PosField {
|
||||
fn from(value: Field) -> Self {
|
||||
let mut repr = <Self as ff::PrimeField>::Repr::default();
|
||||
repr.read_be(&value.0[..])
|
||||
.expect("read from correctly sized slice always succeeds");
|
||||
Self::from_repr(repr).expect("value is always in range")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Field> for BigInt {
|
||||
fn from(value: Field) -> Self {
|
||||
Self::from_bytes_be(Sign::Plus, &value.0[..])
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Field {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
if serializer.is_human_readable() {
|
||||
// Write as a 0x prefixed lower-case hex string
|
||||
let mut buffer = [0u8; 66];
|
||||
buffer[0] = b'0';
|
||||
buffer[1] = b'x';
|
||||
encode_to_slice(&self.0, &mut buffer[2..]).expect("the buffer is correctly sized");
|
||||
let string = str::from_utf8(&buffer).expect("the buffer is valid UTF-8");
|
||||
serializer.serialize_str(string)
|
||||
} else {
|
||||
// Write as bytes directly
|
||||
serializer.serialize_bytes(&self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse Hash from hex string.
|
||||
/// Hex strings can be upper/lower/mixed case and have an optional `0x` prefix
|
||||
/// but they must always be exactly 32 bytes.
|
||||
impl FromStr for Field {
|
||||
type Err = hex::FromHexError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let str = trim_hex_prefix(s);
|
||||
let mut out = [0_u8; 32];
|
||||
hex::decode_to_slice(str, &mut out)?;
|
||||
|
||||
// TODO: Reduce
|
||||
Ok(Self(out))
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize human readable hex strings or byte arrays into hashes.
|
||||
/// Hex strings can be upper/lower/mixed case and have an optional `0x` prefix
|
||||
/// but they must always be exactly 32 bytes.
|
||||
impl<'de> Deserialize<'de> for Field {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
if deserializer.is_human_readable() {
|
||||
deserializer.deserialize_str(StrVisitor)
|
||||
} else {
|
||||
// TODO: Reduce
|
||||
<[u8; 32]>::deserialize(deserializer).map(Field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StrVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for StrVisitor {
|
||||
type Value = Field;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
|
||||
formatter.write_str("a 32 byte hex string")
|
||||
}
|
||||
|
||||
fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: DeError,
|
||||
{
|
||||
Field::from_str(value).map_err(|e| E::custom(format!("Error in hex: {}", e)))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: DeError,
|
||||
{
|
||||
Field::from_str(value).map_err(|e| E::custom(format!("Error in hex: {}", e)))
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: DeError,
|
||||
{
|
||||
Field::from_str(&value).map_err(|e| E::custom(format!("Error in hex: {}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Hash arbitrary data to a field element.
|
||||
///
|
||||
/// This is used to create `signal_hash` and `external_nullifier_hash`.
|
||||
#[must_use]
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub fn hash_to_field(data: &[u8]) -> Field {
|
||||
let hash = keccak256(data);
|
||||
// Shift right one byte to make it fit in the field
|
||||
let mut bytes = [0_u8; 32];
|
||||
bytes[1..].copy_from_slice(&hash[..31]);
|
||||
Field(bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use ark_ff::Field as _;
|
||||
|
||||
#[test]
|
||||
fn test_modulus_identical() {
|
||||
assert_eq!(PosField::char().0, ArkField::characteristic());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_field_serde() {
|
||||
let value = Field::from(0x1234_5678);
|
||||
let serialized = serde_json::to_value(value).unwrap();
|
||||
let deserialized = serde_json::from_value(serialized).unwrap();
|
||||
assert_eq!(value, deserialized);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_ark_pos_ark_roundtrip() {
|
||||
// let mut rng = ChaChaRng::seed_from_u64(123);
|
||||
// for _ in 0..1000 {
|
||||
// let n = Field::rand(&mut rng);
|
||||
// let m = poseidon_to_ark(ark_to_poseidon(n));
|
||||
// assert_eq!(n, m);
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
use crate::{poseidon_hash, Field};
|
||||
use ark_ff::PrimeField;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
|
@ -8,35 +7,32 @@ pub struct Identity {
|
|||
pub nullifier: Field,
|
||||
}
|
||||
|
||||
// todo: improve
|
||||
fn sha(msg: &[u8]) -> [u8; 32] {
|
||||
/// Implements the private key derivation function from zk-kit.
|
||||
///
|
||||
/// See <https://github.com/appliedzkp/zk-kit/blob/1ea410456fc2b95877efa7c671bc390ffbfb5d36/packages/identity/src/identity.ts#L58>
|
||||
fn derive_field(seed_hex: &[u8; 64], suffix: &[u8]) -> Field {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(msg);
|
||||
let result = hasher.finalize();
|
||||
let res: [u8; 32] = result.into();
|
||||
res
|
||||
hasher.update(seed_hex);
|
||||
hasher.update(suffix);
|
||||
Field::from_be_bytes_mod_order(hasher.finalize().as_ref())
|
||||
}
|
||||
|
||||
fn seed_hex(seed: &[u8]) -> [u8; 64] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(seed);
|
||||
let bytes: [u8; 32] = hasher.finalize().into();
|
||||
let mut result = [0_u8; 64];
|
||||
hex::encode_to_slice(&bytes, &mut result[..]).expect("output buffer is correctly sized");
|
||||
result
|
||||
}
|
||||
|
||||
impl Identity {
|
||||
#[must_use]
|
||||
pub fn new(seed: &[u8]) -> Self {
|
||||
let seed_hash = &sha(seed);
|
||||
|
||||
// https://github.com/appliedzkp/zk-kit/blob/1ea410456fc2b95877efa7c671bc390ffbfb5d36/packages/identity/src/identity.ts#L58
|
||||
let trapdoor = Field::from_be_bytes_mod_order(&sha(format!(
|
||||
"{}identity_trapdoor",
|
||||
hex::encode(seed_hash)
|
||||
)
|
||||
.as_bytes()));
|
||||
let nullifier = Field::from_be_bytes_mod_order(&sha(format!(
|
||||
"{}identity_nullifier",
|
||||
hex::encode(seed_hash)
|
||||
)
|
||||
.as_bytes()));
|
||||
|
||||
pub fn from_seed(seed: &[u8]) -> Self {
|
||||
let seed_hex = seed_hex(seed);
|
||||
Self {
|
||||
trapdoor,
|
||||
nullifier,
|
||||
trapdoor: derive_field(&seed_hex, b"identity_trapdoor"),
|
||||
nullifier: derive_field(&seed_hex, b"identity_nullifier"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
56
src/lib.rs
56
src/lib.rs
|
@ -4,6 +4,7 @@
|
|||
#![allow(clippy::multiple_crate_versions)]
|
||||
|
||||
mod circuit;
|
||||
mod field;
|
||||
pub mod hash;
|
||||
pub mod identity;
|
||||
pub mod merkle_tree;
|
||||
|
@ -17,40 +18,52 @@ pub mod mimc_hash;
|
|||
#[cfg(feature = "mimc")]
|
||||
pub mod mimc_tree;
|
||||
|
||||
use ark_bn254::{Fr, Parameters};
|
||||
use ark_bn254::Parameters;
|
||||
use ark_ec::bn::Bn;
|
||||
|
||||
pub use crate::poseidon_hash::poseidon_hash;
|
||||
// Export types
|
||||
pub use crate::{
|
||||
field::{hash_to_field, Field},
|
||||
poseidon_hash::poseidon_hash,
|
||||
};
|
||||
|
||||
pub type Field = Fr;
|
||||
pub type Groth16Proof = ark_groth16::Proof<Bn<Parameters>>;
|
||||
pub type EthereumGroth16Proof = ark_circom::ethereum::Proof;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
hash::Hash,
|
||||
hash_to_field,
|
||||
identity::Identity,
|
||||
poseidon_tree::PoseidonTree,
|
||||
protocol::{generate_nullifier_hash, generate_proof, hash_to_field, verify_proof},
|
||||
protocol::{generate_nullifier_hash, generate_proof, verify_proof},
|
||||
Field,
|
||||
};
|
||||
use hex_literal::hex;
|
||||
|
||||
#[test]
|
||||
fn test_field_serde() {
|
||||
let value = Field::from(0x1234_5678);
|
||||
let serialized = serde_json::to_value(value).unwrap();
|
||||
let deserialized = serde_json::from_value(serialized).unwrap();
|
||||
assert_eq!(value, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_end_to_end() {
|
||||
const LEAF: Hash = Hash::from_bytes_be(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
));
|
||||
// const LEAF: Hash = Hash::from_bytes_be(hex!(
|
||||
// "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
// ));
|
||||
let leaf = Field::from(0);
|
||||
|
||||
// generate identity
|
||||
let id = Identity::new(b"hello");
|
||||
let id = Identity::from_seed(b"hello");
|
||||
|
||||
// generate merkle tree
|
||||
let mut tree = PoseidonTree::new(21, LEAF);
|
||||
tree.set(0, id.commitment().into());
|
||||
let mut tree = PoseidonTree::new(21, leaf);
|
||||
tree.set(0, id.commitment());
|
||||
|
||||
let merkle_proof = tree.proof(0).expect("proof should exist");
|
||||
let root = tree.root().into();
|
||||
let root = tree.root();
|
||||
|
||||
// change signal and external_nullifier here
|
||||
let signal = b"xxx";
|
||||
|
@ -79,13 +92,10 @@ mod test {
|
|||
#[cfg(feature = "bench")]
|
||||
pub mod bench {
|
||||
use crate::{
|
||||
hash::Hash,
|
||||
identity::Identity,
|
||||
poseidon_tree::PoseidonTree,
|
||||
protocol::{generate_proof, hash_to_field},
|
||||
hash_to_field, identity::Identity, poseidon_tree::PoseidonTree, protocol::generate_proof,
|
||||
Field,
|
||||
};
|
||||
use criterion::Criterion;
|
||||
use hex_literal::hex;
|
||||
|
||||
pub fn group(criterion: &mut Criterion) {
|
||||
#[cfg(feature = "mimc")]
|
||||
|
@ -96,14 +106,12 @@ pub mod bench {
|
|||
}
|
||||
|
||||
fn bench_proof(criterion: &mut Criterion) {
|
||||
const LEAF: Hash = Hash::from_bytes_be(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
));
|
||||
let leaf = Field::from(0);
|
||||
|
||||
// Create tree
|
||||
let id = Identity::new(b"hello");
|
||||
let mut tree = PoseidonTree::new(21, LEAF);
|
||||
tree.set(0, id.commitment().into());
|
||||
let id = Identity::from_seed(b"hello");
|
||||
let mut tree = PoseidonTree::new(21, leaf);
|
||||
tree.set(0, id.commitment());
|
||||
let merkle_proof = tree.proof(0).expect("proof should exist");
|
||||
|
||||
// change signal and external_nullifier here
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
//!
|
||||
//! * Instantiate a `PrimeField` to use Montgomery form.
|
||||
|
||||
use crate::util::keccak256;
|
||||
use once_cell::sync::Lazy;
|
||||
use tiny_keccak::{Hasher as _, Keccak};
|
||||
use zkp_u256::U256;
|
||||
|
||||
const NUM_ROUNDS: usize = 220;
|
||||
|
@ -21,14 +21,6 @@ static MODULUS: Lazy<U256> = Lazy::new(|| {
|
|||
.unwrap()
|
||||
});
|
||||
|
||||
fn keccak256(bytes: &[u8]) -> [u8; 32] {
|
||||
let mut output = [0; 32];
|
||||
let mut hasher = Keccak::v256();
|
||||
hasher.update(bytes);
|
||||
hasher.finalize(&mut output);
|
||||
output
|
||||
}
|
||||
|
||||
static ROUND_CONSTANTS: Lazy<[U256; NUM_ROUNDS]> = Lazy::new(|| {
|
||||
const SEED: &str = "mimcsponge";
|
||||
let mut result = [U256::ZERO; NUM_ROUNDS];
|
||||
|
|
|
@ -1,57 +1,15 @@
|
|||
use crate::Field;
|
||||
use ark_ff::{BigInteger256, PrimeField as _};
|
||||
use ff::PrimeField as _;
|
||||
use once_cell::sync::Lazy;
|
||||
use poseidon_rs::{Fr, FrRepr, Poseidon};
|
||||
use poseidon_rs::Poseidon;
|
||||
|
||||
static POSEIDON: Lazy<Poseidon> = Lazy::new(Poseidon::new);
|
||||
|
||||
#[must_use]
|
||||
fn ark_to_poseidon(n: Field) -> Fr {
|
||||
Fr::from_repr(FrRepr(n.into_repr().0)).expect("n is a valid field element")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn poseidon_to_ark(n: Fr) -> Field {
|
||||
Field::from_repr(BigInteger256(n.into_repr().0)).expect("n is a valid field element")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn poseidon_hash(input: &[Field]) -> Field {
|
||||
let input = input
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ark_to_poseidon)
|
||||
.collect::<Vec<_>>();
|
||||
let input = input.iter().copied().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
POSEIDON
|
||||
.hash(input)
|
||||
.map(poseidon_to_ark)
|
||||
.map(Into::into)
|
||||
.expect("hash with fixed input size can't fail")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{ark_to_poseidon, poseidon_to_ark};
|
||||
use crate::Field;
|
||||
use ark_ff::{Field as _, UniformRand};
|
||||
use ff::PrimeField;
|
||||
use poseidon_rs::Fr;
|
||||
use rand_chacha::ChaChaRng;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn test_modulus_identical() {
|
||||
assert_eq!(Fr::char().0, Field::characteristic());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ark_pos_ark_roundtrip() {
|
||||
let mut rng = ChaChaRng::seed_from_u64(123);
|
||||
for _ in 0..1000 {
|
||||
let n = Field::rand(&mut rng);
|
||||
let m = poseidon_to_ark(ark_to_poseidon(n));
|
||||
assert_eq!(n, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use crate::{
|
||||
hash::Hash,
|
||||
merkle_tree::{self, Hasher, MerkleTree},
|
||||
poseidon_hash, Field,
|
||||
};
|
||||
use ark_ff::{PrimeField, ToBytes};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -16,81 +14,54 @@ pub type Proof = merkle_tree::Proof<PoseidonHash>;
|
|||
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PoseidonHash;
|
||||
|
||||
#[allow(clippy::fallible_impl_from)] // TODO
|
||||
impl From<&Hash> for Field {
|
||||
fn from(hash: &Hash) -> Self {
|
||||
Self::from_be_bytes_mod_order(&hash.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::fallible_impl_from)] // TODO
|
||||
impl From<Hash> for Field {
|
||||
fn from(hash: Hash) -> Self {
|
||||
Self::from_be_bytes_mod_order(&hash.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::fallible_impl_from)] // TODO
|
||||
impl From<Field> for Hash {
|
||||
fn from(n: Field) -> Self {
|
||||
let mut bytes = [0_u8; 32];
|
||||
n.into_repr()
|
||||
.write(&mut bytes[..])
|
||||
.expect("write should succeed");
|
||||
bytes.reverse(); // Convert to big endian
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hasher for PoseidonHash {
|
||||
type Hash = Hash;
|
||||
type Hash = Field;
|
||||
|
||||
fn hash_node(left: &Self::Hash, right: &Self::Hash) -> Self::Hash {
|
||||
poseidon_hash(&[left.into(), right.into()]).into()
|
||||
poseidon_hash(&[*left, *right])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use ark_ff::UniformRand;
|
||||
use hex_literal::hex;
|
||||
use rand_chacha::ChaChaRng;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn test_ark_hash_ark_roundtrip() {
|
||||
let mut rng = ChaChaRng::seed_from_u64(123);
|
||||
for _ in 0..1000 {
|
||||
let n = Field::rand(&mut rng);
|
||||
let m = Hash::from(n).into();
|
||||
assert_eq!(n, m);
|
||||
}
|
||||
}
|
||||
// TODO: proptest
|
||||
// #[test]
|
||||
// fn test_ark_hash_ark_roundtrip() {
|
||||
// let mut rng = ChaChaRng::seed_from_u64(123);
|
||||
// for _ in 0..1000 {
|
||||
// let n = Field::rand(&mut rng);
|
||||
// let m = Hash::from(n).into();
|
||||
// assert_eq!(n, m);
|
||||
// }
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_tree_4() {
|
||||
const LEAF: Hash = Hash::from_bytes_be(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
));
|
||||
// TODO: Const constructor
|
||||
// #[test]
|
||||
// fn test_tree_4() {
|
||||
// const LEAF: Hash = Hash::from_bytes_be(hex!(
|
||||
// "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
// ));
|
||||
|
||||
let tree = PoseidonTree::new(3, LEAF);
|
||||
assert_eq!(tree.num_leaves(), 4);
|
||||
assert_eq!(
|
||||
tree.root(),
|
||||
Hash::from_bytes_be(hex!(
|
||||
"1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1"
|
||||
))
|
||||
);
|
||||
let proof = tree.proof(3).expect("proof should exist");
|
||||
assert_eq!(
|
||||
proof,
|
||||
crate::merkle_tree::Proof(vec![
|
||||
Branch::Right(LEAF),
|
||||
Branch::Right(Hash::from_bytes_be(hex!(
|
||||
"2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864"
|
||||
))),
|
||||
])
|
||||
);
|
||||
}
|
||||
// let tree = PoseidonTree::new(3, LEAF);
|
||||
// assert_eq!(tree.num_leaves(), 4);
|
||||
// assert_eq!(
|
||||
// tree.root(),
|
||||
// Hash::from_bytes_be(hex!(
|
||||
//
|
||||
// "1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1"
|
||||
// ))
|
||||
// );
|
||||
// let proof = tree.proof(3).expect("proof should exist");
|
||||
// assert_eq!(
|
||||
// proof,
|
||||
// crate::merkle_tree::Proof(vec![
|
||||
// Branch::Right(LEAF),
|
||||
// Branch::Right(Hash::from_bytes_be(hex!(
|
||||
//
|
||||
// "2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864"
|
||||
// ))),
|
||||
// ])
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -9,13 +9,10 @@ use crate::{
|
|||
use ark_bn254::{Bn254, Parameters};
|
||||
use ark_circom::CircomReduction;
|
||||
use ark_ec::bn::Bn;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_groth16::{create_proof_with_reduction_and_matrices, prepare_verifying_key, Proof};
|
||||
use ark_relations::r1cs::SynthesisError;
|
||||
use ark_std::{rand::thread_rng, UniformRand};
|
||||
use color_eyre::Result;
|
||||
use ethers_core::utils::keccak256;
|
||||
use num_bigint::{BigInt, BigUint, ToBigInt};
|
||||
use std::time::Instant;
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -26,23 +23,11 @@ fn merkle_proof_to_vec(proof: &merkle_tree::Proof<PoseidonHash>) -> Vec<Field> {
|
|||
.0
|
||||
.iter()
|
||||
.map(|x| match x {
|
||||
Branch::Left(value) | Branch::Right(value) => value.into(),
|
||||
Branch::Left(value) | Branch::Right(value) => *value,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Hash arbitrary data to a field element.
|
||||
///
|
||||
/// This is used to create `signal_hash` and `external_nullifier_hash`.
|
||||
#[must_use]
|
||||
pub fn hash_to_field(data: &[u8]) -> Field {
|
||||
let hash = keccak256(data);
|
||||
// Shift right one byte to make it fit in the field
|
||||
let mut bytes = [0_u8; 32];
|
||||
bytes[1..].copy_from_slice(&hash[..31]);
|
||||
Field::from_be_bytes_mod_order(&bytes)
|
||||
}
|
||||
|
||||
/// Generates the nullifier hash
|
||||
#[must_use]
|
||||
pub fn generate_nullifier_hash(identity: &Identity, external_nullifier: Field) -> Field {
|
||||
|
@ -59,11 +44,6 @@ pub enum ProofError {
|
|||
SynthesisError(#[from] SynthesisError),
|
||||
}
|
||||
|
||||
fn ark_to_bigint(n: Field) -> BigInt {
|
||||
let n: BigUint = n.into();
|
||||
n.to_bigint().expect("conversion always succeeds for uint")
|
||||
}
|
||||
|
||||
/// Generates a semaphore proof
|
||||
///
|
||||
/// # Errors
|
||||
|
@ -86,11 +66,7 @@ pub fn generate_proof(
|
|||
let inputs = inputs.into_iter().map(|(name, values)| {
|
||||
(
|
||||
name.to_string(),
|
||||
values
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ark_to_bigint)
|
||||
.collect::<Vec<_>>(),
|
||||
values.iter().copied().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -141,7 +117,12 @@ pub fn verify_proof(
|
|||
) -> Result<bool, ProofError> {
|
||||
let pvk = prepare_verifying_key(&ZKEY.0.vk);
|
||||
|
||||
let public_inputs = vec![root, nullifier_hash, signal_hash, external_nullifier_hash];
|
||||
let result = ark_groth16::verify_proof(&pvk, proof, &public_inputs)?;
|
||||
let public_inputs = [
|
||||
root.into(),
|
||||
nullifier_hash.into(),
|
||||
signal_hash.into(),
|
||||
external_nullifier_hash.into(),
|
||||
];
|
||||
let result = ark_groth16::verify_proof(&pvk, proof, &public_inputs[..])?;
|
||||
Ok(result)
|
||||
}
|
||||
|
|
45
src/util.rs
45
src/util.rs
|
@ -1,35 +1,18 @@
|
|||
use ff::{PrimeField, PrimeFieldRepr};
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use poseidon_rs::{Fr, FrRepr};
|
||||
use tiny_keccak::{Hasher as _, Keccak};
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::missing_panics_doc)] // TODO: Remove panics
|
||||
pub fn fr_to_bigint(fr: Fr) -> BigInt {
|
||||
let mut bytes = [0_u8; 32];
|
||||
fr.into_repr().write_be(&mut bytes[..]).unwrap();
|
||||
BigInt::from_bytes_be(Sign::Plus, &bytes)
|
||||
pub(crate) fn keccak256(bytes: &[u8]) -> [u8; 32] {
|
||||
let mut output = [0; 32];
|
||||
let mut hasher = Keccak::v256();
|
||||
hasher.update(bytes);
|
||||
hasher.finalize(&mut output);
|
||||
output
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::missing_panics_doc)] // TODO: Remove panics
|
||||
pub fn bigint_to_fr(bi: &BigInt) -> Fr {
|
||||
// dirty: have to force the point into the field manually, otherwise you get an
|
||||
// error if bi not in field
|
||||
let q = BigInt::parse_bytes(
|
||||
b"21888242871839275222246405745257275088548364400416034343698204186575808495617",
|
||||
10,
|
||||
)
|
||||
.unwrap();
|
||||
let m = bi.modpow(&BigInt::from(1), &q);
|
||||
|
||||
let mut repr = FrRepr::default();
|
||||
let (_, mut res) = m.to_bytes_be();
|
||||
|
||||
// prepend zeros
|
||||
res.reverse();
|
||||
res.resize(32, 0);
|
||||
res.reverse();
|
||||
|
||||
repr.read_be(&res[..]).unwrap();
|
||||
Fr::from_repr(repr).unwrap()
|
||||
/// Helper function to optionally remove `0x` prefix from hex strings.
|
||||
pub(crate) fn trim_hex_prefix(str: &str) -> &str {
|
||||
if str.len() >= 2 && (&str[..2] == "0x" || &str[..2] == "0X") {
|
||||
&str[2..]
|
||||
} else {
|
||||
str
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue