feat(rln): add seeded keygen (#56)

* refactor(rln/zerokit): move poseidon to separate utils crate

* refactor(rln/zerokit): move merkle tree to utils crate

* feat(rln): add seeded keygen
This commit is contained in:
G 2022-09-30 17:27:55 +02:00 committed by GitHub
parent bbacc9dcce
commit a5aa4e8d4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 169 additions and 2 deletions

View File

@ -33,6 +33,7 @@ num-bigint = { version = "0.4.3", default-features = false, features = ["rand"]
num-traits = "0.2.11" num-traits = "0.2.11"
once_cell = "1.14.0" once_cell = "1.14.0"
rand = "0.8" rand = "0.8"
rand_chacha = "0.3.1"
tiny-keccak = { version = "2.0.2", features = ["keccak"] } tiny-keccak = { version = "2.0.2", features = ["keccak"] }
utils = { path = "../utils/", default-features = false } utils = { path = "../utils/", default-features = false }

View File

@ -243,6 +243,26 @@ pub extern "C" fn key_gen(ctx: *const RLN, output_buffer: *mut Buffer) -> bool {
} }
} }
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
pub extern "C" fn seeded_key_gen(
ctx: *const RLN,
input_buffer: *const Buffer,
output_buffer: *mut Buffer,
) -> bool {
let rln = unsafe { &*ctx };
let input_data = <&[u8]>::from(unsafe { &*input_buffer });
let mut output_data: Vec<u8> = Vec::new();
if rln.seeded_key_gen(input_data, &mut output_data).is_ok() {
unsafe { *output_buffer = Buffer::from(&output_data[..]) };
std::mem::forget(output_data);
true
} else {
std::mem::forget(output_data);
false
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle] #[no_mangle]
pub extern "C" fn hash( pub extern "C" fn hash(
@ -758,6 +778,43 @@ mod test {
assert_eq!(proof_is_valid, true); assert_eq!(proof_is_valid, true);
} }
#[test]
// Tests hash to field using FFI APIs
fn test_seeded_keygen_ffi() {
let tree_height = TEST_TREE_HEIGHT;
// We create a RLN instance
let mut rln_pointer = MaybeUninit::<*mut RLN>::uninit();
let input_buffer = &Buffer::from(TEST_RESOURCES_FOLDER.as_bytes());
let success = new(tree_height, input_buffer, rln_pointer.as_mut_ptr());
assert!(success, "RLN object creation failed");
let rln_pointer = unsafe { &mut *rln_pointer.assume_init() };
// We generate a new identity pair from an input seed
let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let input_buffer = &Buffer::from(seed_bytes);
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = seeded_key_gen(rln_pointer, input_buffer, output_buffer.as_mut_ptr());
assert!(success, "seeded key gen call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let result_data = <&[u8]>::from(&output_buffer).to_vec();
let (identity_secret, read) = bytes_le_to_fr(&result_data);
let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec());
// We check against expected values
let expected_identity_secret_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
let expected_id_commitment_seed_bytes = str_to_fr(
"0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f",
16,
);
assert_eq!(identity_secret, expected_identity_secret_seed_bytes);
assert_eq!(id_commitment, expected_id_commitment_seed_bytes);
}
#[test] #[test]
// Tests hash to field using FFI APIs // Tests hash to field using FFI APIs
fn test_hash_to_field_ffi() { fn test_hash_to_field_ffi() {

View File

@ -429,4 +429,49 @@ mod test {
let (deser, _) = deserialize_proof_values(&ser); let (deser, _) = deserialize_proof_values(&ser);
assert_eq!(proof_values, deser); assert_eq!(proof_values, deser);
} }
#[test]
// Tests seeded keygen
// Note that hardcoded values are only valid for Bn254
fn test_seeded_keygen() {
// Generate identity pair using a seed phrase
let seed_phrase: &str = "A seed phrase example";
let (identity_secret, id_commitment) = seeded_keygen(seed_phrase.as_bytes());
// We check against expected values
let expected_identity_secret_seed_phrase = str_to_fr(
"0x20df38f3f00496f19fe7c6535492543b21798ed7cb91aebe4af8012db884eda3",
16,
);
let expected_id_commitment_seed_phrase = str_to_fr(
"0x1223a78a5d66043a7f9863e14507dc80720a5602b2a894923e5b5147d5a9c325",
16,
);
assert_eq!(identity_secret, expected_identity_secret_seed_phrase);
assert_eq!(id_commitment, expected_id_commitment_seed_phrase);
// Generate identity pair using an byte array
let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let (identity_secret, id_commitment) = seeded_keygen(seed_bytes);
// We check against expected values
let expected_identity_secret_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
let expected_id_commitment_seed_bytes = str_to_fr(
"0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f",
16,
);
assert_eq!(identity_secret, expected_identity_secret_seed_bytes);
assert_eq!(id_commitment, expected_id_commitment_seed_bytes);
// We check again if the identity pair generated with the same seed phrase corresponds to the previously generated one
let (identity_secret, id_commitment) = seeded_keygen(seed_phrase.as_bytes());
assert_eq!(identity_secret, expected_identity_secret_seed_phrase);
assert_eq!(id_commitment, expected_id_commitment_seed_phrase);
}
} }

View File

@ -10,7 +10,8 @@ use ark_relations::r1cs::SynthesisError;
use ark_std::{rand::thread_rng, UniformRand}; use ark_std::{rand::thread_rng, UniformRand};
use color_eyre::Result; use color_eyre::Result;
use num_bigint::BigInt; use num_bigint::BigInt;
use rand::Rng; use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use std::sync::Mutex; use std::sync::Mutex;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -338,6 +339,7 @@ pub fn compute_tree_root(
// Generates a tupe (identity_secret, id_commitment) where // Generates a tupe (identity_secret, id_commitment) where
// identity_secret is random and id_commitment = PoseidonHash(identity_secret) // identity_secret is random and id_commitment = PoseidonHash(identity_secret)
// RNG is instantiated using thread_rng()
pub fn keygen() -> (Fr, Fr) { pub fn keygen() -> (Fr, Fr) {
let mut rng = thread_rng(); let mut rng = thread_rng();
let identity_secret = Fr::rand(&mut rng); let identity_secret = Fr::rand(&mut rng);
@ -345,6 +347,23 @@ pub fn keygen() -> (Fr, Fr) {
(identity_secret, id_commitment) (identity_secret, id_commitment)
} }
// Generates a tupe (identity_secret, id_commitment) where
// identity_secret is random and id_commitment = PoseidonHash(identity_secret)
// RNG is instantiated using 20 rounds of ChaCha seeded with the hash of the input
pub fn seeded_keygen(signal: &[u8]) -> (Fr, Fr) {
// ChaCha20 requires a seed of exactly 32 bytes.
// We first hash the input seed signal to a 32 bytes array and pass this as seed to ChaCha20
let mut seed = [0; 32];
let mut hasher = Keccak::v256();
hasher.update(signal);
hasher.finalize(&mut seed);
let mut rng = ChaCha20Rng::from_seed(seed);
let identity_secret = Fr::rand(&mut rng);
let id_commitment = poseidon_hash(&[identity_secret]);
(identity_secret, id_commitment)
}
// Hashes arbitrary signal to the underlying prime field // Hashes arbitrary signal to the underlying prime field
pub fn hash_to_field(signal: &[u8]) -> Fr { pub fn hash_to_field(signal: &[u8]) -> Fr {
// We hash the input signal using Keccak256 // We hash the input signal using Keccak256

View File

@ -343,6 +343,21 @@ impl RLN<'_> {
Ok(()) Ok(())
} }
pub fn seeded_key_gen<R: Read, W: Write>(
&self,
mut input_data: R,
mut output_data: W,
) -> io::Result<()> {
let mut serialized: Vec<u8> = Vec::new();
input_data.read_to_end(&mut serialized)?;
let (id_key, id_commitment_key) = seeded_keygen(&serialized);
output_data.write_all(&fr_to_bytes_le(&id_key))?;
output_data.write_all(&fr_to_bytes_le(&id_commitment_key))?;
Ok(())
}
pub fn hash<R: Read, W: Write>(&self, mut input_data: R, mut output_data: W) -> io::Result<()> { pub fn hash<R: Read, W: Write>(&self, mut input_data: R, mut output_data: W) -> io::Result<()> {
let mut serialized: Vec<u8> = Vec::new(); let mut serialized: Vec<u8> = Vec::new();
input_data.read_to_end(&mut serialized)?; input_data.read_to_end(&mut serialized)?;
@ -824,6 +839,36 @@ mod test {
assert!(verified); assert!(verified);
} }
#[test]
fn test_seeded_keygen() {
let rln = RLN::default();
let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let mut input_buffer = Cursor::new(&seed_bytes);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.seeded_key_gen(&mut input_buffer, &mut output_buffer)
.unwrap();
let serialized_output = output_buffer.into_inner();
let (identity_secret, read) = bytes_le_to_fr(&serialized_output);
let (id_commitment, _) = bytes_le_to_fr(&serialized_output[read..].to_vec());
// We check against expected values
let expected_identity_secret_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
let expected_id_commitment_seed_bytes = str_to_fr(
"0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f",
16,
);
assert_eq!(identity_secret, expected_identity_secret_seed_bytes);
assert_eq!(id_commitment, expected_id_commitment_seed_bytes);
}
#[test] #[test]
fn test_hash_to_field() { fn test_hash_to_field() {
let rln = RLN::default(); let rln = RLN::default();

View File

@ -15,4 +15,4 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] }
[features] [features]
default = ["parallel"] default = ["parallel"]
parallel = ["ark-ff/parallel"] parallel = ["ark-ff/parallel"]