feat(rln): add extended keygen APIs for Semaphore-compatible credentials (#85)

* refactor(rln): update APIs based on updated rln circuit design

* chore(rln): update rln vendor submodule

* fix(ci): update ci to not ignore rln resources changes

* feat(rln): add extended keygen APIs

* refactor(rln): rename id_secret/id_key to identity_secret_hash as per RFC

* fix(rln): cargo fmt
This commit is contained in:
G 2022-12-12 00:48:22 +01:00 committed by GitHub
parent e69f6a67d8
commit 32f3202e9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 408 additions and 112 deletions

View File

@ -97,7 +97,7 @@ rln.key_gen(&mut buffer).unwrap();
// We deserialize the keygen output to obtain
// the identiy_secret and id_commitment
let (identity_secret, id_commitment) = deserialize_identity_pair(buffer.into_inner());
let (identity_secret_hash, id_commitment) = deserialize_identity_pair(buffer.into_inner());
```
### Add ID commitment to the RLN Merkle tree
@ -139,7 +139,7 @@ Input buffer is serialized as `[ identity_key | id_index | epoch | signal_len |
```rust
// We prepare input to the proof generation routine
let proof_input = prepare_prove_input(identity_secret, id_index, epoch, signal);
let proof_input = prepare_prove_input(identity_secret_hash, id_index, epoch, signal);
```
We are now ready to generate a RLN ZK proof along with the _public outputs_ of the ZK circuit evaluation.

View File

@ -297,6 +297,44 @@ pub extern "C" fn seeded_key_gen(
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
pub extern "C" fn extended_key_gen(ctx: *const RLN, output_buffer: *mut Buffer) -> bool {
let rln = unsafe { &*ctx };
let mut output_data: Vec<u8> = Vec::new();
if rln.extended_key_gen(&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)]
#[no_mangle]
pub extern "C" fn seeded_extended_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_extended_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)]
#[no_mangle]
pub extern "C" fn recover_id_secret(
@ -623,8 +661,8 @@ mod test {
let rln_pointer = unsafe { &mut *rln_pointer.assume_init() };
// generate identity
let identity_secret = hash_to_field(b"test-merkle-proof");
let id_commitment = poseidon_hash(&vec![identity_secret]);
let identity_secret_hash = hash_to_field(b"test-merkle-proof");
let id_commitment = poseidon_hash(&vec![identity_secret_hash]);
// We prepare id_commitment and we set the leaf at provided index
let leaf_ser = fr_to_bytes_le(&id_commitment);
@ -924,7 +962,7 @@ mod test {
assert!(success, "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 (identity_secret_hash, read) = bytes_le_to_fr(&result_data);
let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec());
// We set as leaf id_commitment, its index would be equal to no_of_leaves
@ -944,9 +982,9 @@ mod test {
let epoch = hash_to_field(b"test-epoch");
// We prepare input for generate_rln_proof API
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret));
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut identity_index.to_le_bytes().to_vec());
serialized.append(&mut fr_to_bytes_le(&epoch));
serialized.append(&mut signal_len.to_le_bytes().to_vec());
@ -1009,7 +1047,7 @@ mod test {
assert!(success, "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 (identity_secret_hash, read) = bytes_le_to_fr(&result_data);
let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec());
// We set as leaf id_commitment, its index would be equal to no_of_leaves
@ -1029,9 +1067,9 @@ mod test {
let epoch = hash_to_field(b"test-epoch");
// We prepare input for generate_rln_proof API
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret));
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut identity_index.to_le_bytes().to_vec());
serialized.append(&mut fr_to_bytes_le(&epoch));
serialized.append(&mut signal_len.to_le_bytes().to_vec());
@ -1122,7 +1160,7 @@ mod test {
assert!(success, "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 (identity_secret_hash, read) = bytes_le_to_fr(&result_data);
let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec());
// We set as leaf id_commitment, its index would be equal to 0 since tree is empty
@ -1148,9 +1186,9 @@ mod test {
let epoch = hash_to_field(b"test-epoch");
// We prepare input for generate_rln_proof API
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized1: Vec<u8> = Vec::new();
serialized1.append(&mut fr_to_bytes_le(&identity_secret));
serialized1.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized1.append(&mut identity_index.to_le_bytes().to_vec());
serialized1.append(&mut fr_to_bytes_le(&epoch));
@ -1194,17 +1232,17 @@ mod test {
);
assert!(success, "recover id secret call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let serialized_id_secret = <&[u8]>::from(&output_buffer).to_vec();
let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec();
// We passed two shares for the same secret, so recovery should be successful
// To check it, we ensure that recovered_id_secret is non-empty
assert!(!serialized_id_secret.is_empty());
// To check it, we ensure that recovered identity secret hash is empty
assert!(!serialized_identity_secret_hash.is_empty());
// We check if the recovered id secret corresponds to the original one
let (recovered_id_secret, _) = bytes_le_to_fr(&serialized_id_secret);
assert_eq!(recovered_id_secret, identity_secret);
// We check if the recovered identity secret hash corresponds to the original one
let (recovered_identity_secret_hash, _) = bytes_le_to_fr(&serialized_identity_secret_hash);
assert_eq!(recovered_identity_secret_hash, identity_secret_hash);
// We now test that computing_id_secret is unsuccessful if shares computed from two different id secrets but within same epoch are passed
// We now test that computing identity_secret_hash is unsuccessful if shares computed from two different identity secret hashes but within same epoch are passed
// We generate a new identity pair
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
@ -1212,7 +1250,7 @@ mod test {
assert!(success, "key gen call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let result_data = <&[u8]>::from(&output_buffer).to_vec();
let (identity_secret_new, read) = bytes_le_to_fr(&result_data);
let (identity_secret_hash_new, read) = bytes_le_to_fr(&result_data);
let (id_commitment_new, _) = bytes_le_to_fr(&result_data[read..].to_vec());
// We set as leaf id_commitment, its index would be equal to 1 since at 0 there is id_commitment
@ -1228,10 +1266,10 @@ mod test {
let signal3_len = u64::try_from(signal3.len()).unwrap();
// We prepare input for generate_rln_proof API
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// Note that epoch is the same as before
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret_new));
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash_new));
serialized.append(&mut identity_index_new.to_le_bytes().to_vec());
serialized.append(&mut fr_to_bytes_le(&epoch));
serialized.append(&mut signal3_len.to_le_bytes().to_vec());
@ -1246,7 +1284,7 @@ mod test {
// result_data is [ proof<128> | share_y<32> | nullifier<32> | root<32> | epoch<32> | share_x<32> | rln_identifier<32> ]
let proof_data_3 = <&[u8]>::from(&output_buffer).to_vec();
// We attempt to recover the secret using share1 (coming from identity_secret) and share3 (coming from identity_secret_new)
// We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new)
let input_proof_buffer_1 = &Buffer::from(proof_data_1.as_ref());
let input_proof_buffer_3 = &Buffer::from(proof_data_3.as_ref());
@ -1259,11 +1297,11 @@ mod test {
);
assert!(success, "recover id secret call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let serialized_id_secret = <&[u8]>::from(&output_buffer).to_vec();
let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec();
// We passed two shares for different secrets, so recovery should be not successful
// To check it, we ensure that recovered_id_secret is empty
assert!(serialized_id_secret.is_empty());
// To check it, we ensure that recovered identity secret hash is empty
assert!(serialized_identity_secret_hash.is_empty());
}
#[test]
@ -1286,11 +1324,11 @@ mod test {
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 (identity_secret_hash, 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(
let expected_identity_secret_hash_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
@ -1299,7 +1337,61 @@ mod test {
16,
);
assert_eq!(identity_secret, expected_identity_secret_seed_bytes);
assert_eq!(
identity_secret_hash,
expected_identity_secret_hash_seed_bytes
);
assert_eq!(id_commitment, expected_id_commitment_seed_bytes);
}
#[test]
// Tests hash to field using FFI APIs
fn test_seeded_extended_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 tuple 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_extended_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_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) =
deserialize_identity_tuple(result_data);
// We check against expected values
let expected_identity_trapdoor_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
let expected_identity_nullifier_seed_bytes = str_to_fr(
"0x1f18714c7bc83b5bca9e89d404cf6f2f585bc4c0f7ed8b53742b7e2b298f50b4",
16,
);
let expected_identity_secret_hash_seed_bytes = str_to_fr(
"0x2aca62aaa7abaf3686fff2caf00f55ab9462dc12db5b5d4bcf3994e671f8e521",
16,
);
let expected_id_commitment_seed_bytes = str_to_fr(
"0x68b66aa0a8320d2e56842581553285393188714c48f9b17acd198b4f1734c5c",
16,
);
assert_eq!(identity_trapdoor, expected_identity_trapdoor_seed_bytes);
assert_eq!(identity_nullifier, expected_identity_nullifier_seed_bytes);
assert_eq!(
identity_secret_hash,
expected_identity_secret_hash_seed_bytes
);
assert_eq!(id_commitment, expected_id_commitment_seed_bytes);
}

View File

@ -179,8 +179,8 @@ mod test {
let leaf_index = 3;
// generate identity
let identity_secret = hash_to_field(b"test-merkle-proof");
let id_commitment = poseidon_hash(&vec![identity_secret]);
let identity_secret_hash = hash_to_field(b"test-merkle-proof");
let id_commitment = poseidon_hash(&vec![identity_secret_hash]);
// generate merkle tree
let default_leaf = Fr::from(0);
@ -365,7 +365,7 @@ mod test {
let leaf_index = 3;
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
let (identity_secret_hash, id_commitment) = keygen();
//// generate merkle tree
let default_leaf = Fr::from(0);
@ -382,7 +382,7 @@ mod test {
//let rln_identifier = hash_to_field(b"test-rln-identifier");
let rln_witness: RLNWitnessInput = rln_witness_from_values(
identity_secret,
identity_secret_hash,
&merkle_proof,
x,
epoch, /*, rln_identifier*/
@ -436,10 +436,10 @@ mod test {
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());
let (identity_secret_hash, id_commitment) = seeded_keygen(seed_phrase.as_bytes());
// We check against expected values
let expected_identity_secret_seed_phrase = str_to_fr(
let expected_identity_secret_hash_seed_phrase = str_to_fr(
"0x20df38f3f00496f19fe7c6535492543b21798ed7cb91aebe4af8012db884eda3",
16,
);
@ -448,15 +448,18 @@ mod test {
16,
);
assert_eq!(identity_secret, expected_identity_secret_seed_phrase);
assert_eq!(
identity_secret_hash,
expected_identity_secret_hash_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);
let (identity_secret_hash, id_commitment) = seeded_keygen(seed_bytes);
// We check against expected values
let expected_identity_secret_seed_bytes = str_to_fr(
let expected_identity_secret_hash_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
@ -465,13 +468,19 @@ mod test {
16,
);
assert_eq!(identity_secret, expected_identity_secret_seed_bytes);
assert_eq!(
identity_secret_hash,
expected_identity_secret_hash_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());
let (identity_secret_hash, id_commitment) = seeded_keygen(seed_phrase.as_bytes());
assert_eq!(identity_secret, expected_identity_secret_seed_phrase);
assert_eq!(
identity_secret_hash,
expected_identity_secret_hash_seed_phrase
);
assert_eq!(id_commitment, expected_id_commitment_seed_phrase);
}
}

View File

@ -63,10 +63,32 @@ pub fn deserialize_field_element(serialized: Vec<u8>) -> Fr {
}
pub fn deserialize_identity_pair(serialized: Vec<u8>) -> (Fr, Fr) {
let (identity_secret, read) = bytes_le_to_fr(&serialized);
let (identity_secret_hash, read) = bytes_le_to_fr(&serialized);
let (id_commitment, _) = bytes_le_to_fr(&serialized[read..].to_vec());
return (identity_secret, id_commitment);
return (identity_secret_hash, id_commitment);
}
pub fn deserialize_identity_tuple(serialized: Vec<u8>) -> (Fr, Fr, Fr, Fr) {
let mut all_read = 0;
let (identity_trapdoor, read) = bytes_le_to_fr(&serialized[all_read..].to_vec());
all_read += read;
let (identity_nullifier, read) = bytes_le_to_fr(&serialized[all_read..].to_vec());
all_read += read;
let (identity_secret_hash, read) = bytes_le_to_fr(&serialized[all_read..].to_vec());
all_read += read;
let (identity_commitment, _) = bytes_le_to_fr(&serialized[all_read..].to_vec());
return (
identity_trapdoor,
identity_nullifier,
identity_secret_hash,
identity_commitment,
);
}
pub fn serialize_witness(rln_witness: &RLNWitnessInput) -> Vec<u8> {
@ -121,7 +143,7 @@ pub fn deserialize_witness(serialized: &[u8]) -> (RLNWitnessInput, usize) {
// This function deserializes input for kilic's rln generate_proof public API
// https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L148
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// return value is a rln witness populated according to this information
pub fn proof_inputs_to_rln_witness(
tree: &mut PoseidonTree,
@ -387,18 +409,38 @@ pub fn compute_tree_root(
// Protocol utility functions
///////////////////////////////////////////////////////
// Generates a tupe (identity_secret, id_commitment) where
// identity_secret is random and id_commitment = PoseidonHash(identity_secret)
// Generates a tuple (identity_secret_hash, id_commitment) where
// identity_secret_hash is random and id_commitment = PoseidonHash(identity_secret_hash)
// RNG is instantiated using thread_rng()
pub fn keygen() -> (Fr, Fr) {
let mut rng = thread_rng();
let identity_secret = Fr::rand(&mut rng);
let id_commitment = poseidon_hash(&[identity_secret]);
(identity_secret, id_commitment)
let identity_secret_hash = Fr::rand(&mut rng);
let id_commitment = poseidon_hash(&[identity_secret_hash]);
(identity_secret_hash, id_commitment)
}
// Generates a tupe (identity_secret, id_commitment) where
// identity_secret is random and id_commitment = PoseidonHash(identity_secret)
// Generates a tuple (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) where
// identity_trapdoor and identity_nullifier are random,
// identity_secret_hash = PoseidonHash(identity_trapdoor, identity_nullifier),
// id_commitment = PoseidonHash(identity_secret_hash),
// RNG is instantiated using thread_rng()
// Generated credentials are compatible with Semaphore credentials
pub fn extended_keygen() -> (Fr, Fr, Fr, Fr) {
let mut rng = thread_rng();
let identity_trapdoor = Fr::rand(&mut rng);
let identity_nullifier = Fr::rand(&mut rng);
let identity_secret_hash = poseidon_hash(&[identity_trapdoor, identity_nullifier]);
let id_commitment = poseidon_hash(&[identity_secret_hash]);
(
identity_trapdoor,
identity_nullifier,
identity_secret_hash,
id_commitment,
)
}
// Generates a tuple (identity_secret_hash, id_commitment) where
// identity_secret_hash is random and id_commitment = PoseidonHash(identity_secret_hash)
// 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.
@ -409,9 +451,36 @@ pub fn seeded_keygen(signal: &[u8]) -> (Fr, Fr) {
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)
let identity_secret_hash = Fr::rand(&mut rng);
let id_commitment = poseidon_hash(&[identity_secret_hash]);
(identity_secret_hash, id_commitment)
}
// Generates a tuple (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) where
// identity_trapdoor and identity_nullifier are random,
// identity_secret_hash = PoseidonHash(identity_trapdoor, identity_nullifier),
// id_commitment = PoseidonHash(identity_secret_hash),
// RNG is instantiated using 20 rounds of ChaCha seeded with the hash of the input
// Generated credentials are compatible with Semaphore credentials
pub fn extended_seeded_keygen(signal: &[u8]) -> (Fr, Fr, 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_trapdoor = Fr::rand(&mut rng);
let identity_nullifier = Fr::rand(&mut rng);
let identity_secret_hash = poseidon_hash(&[identity_trapdoor, identity_nullifier]);
let id_commitment = poseidon_hash(&[identity_secret_hash]);
(
identity_trapdoor,
identity_nullifier,
identity_secret_hash,
id_commitment,
)
}
// Hashes arbitrary signal to the underlying prime field
@ -452,7 +521,7 @@ pub fn compute_id_secret(
// We successfully recovered the identity secret
return Ok(a_0);
} else {
return Err("Cannot recover id_secret from provided shares".into());
return Err("Cannot recover identity_secret_hash from provided shares".into());
}
}

View File

@ -177,8 +177,8 @@ impl RLN<'_> {
/// ```
/// use crate::protocol::*;
///
/// // We generate a random id secret and commitment pair
/// let (identity_secret, id_commitment) = keygen();
/// // We generate a random identity secret hash and commitment pair
/// let (identity_secret_hash, id_commitment) = keygen();
///
/// // We define the tree index where id_commitment will be added
/// let id_index = 10;
@ -489,7 +489,7 @@ impl RLN<'_> {
/// Computes a zkSNARK RLN proof from the identity secret, the Merkle tree index, the epoch and signal.
///
/// Input values are:
/// - `input_data`: a reader for the serialization of `[ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]`
/// - `input_data`: a reader for the serialization of `[ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]`
///
/// Output values are:
/// - `output_data`: a writer receiving the serialization of the zkSNARK proof and the circuit evaluations outputs, i.e. `[ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ]`
@ -500,7 +500,7 @@ impl RLN<'_> {
/// use rln::utils::*;
///
/// // Generate identity pair
/// let (identity_secret, id_commitment) = keygen();
/// let (identity_secret_hash, id_commitment) = keygen();
///
/// // We set as leaf id_commitment after storing its index
/// let identity_index = 10;
@ -516,9 +516,9 @@ impl RLN<'_> {
/// let epoch = hash_to_field(b"test-epoch");
///
/// // We prepare input for generate_rln_proof API
/// // input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
/// // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
/// let mut serialized: Vec<u8> = Vec::new();
/// serialized.append(&mut fr_to_bytes_le(&identity_secret));
/// serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
/// serialized.append(&mut identity_index.to_le_bytes().to_vec());
/// serialized.append(&mut fr_to_bytes_le(&epoch));
/// serialized.append(&mut signal_len.to_le_bytes().to_vec());
@ -782,12 +782,45 @@ impl RLN<'_> {
/// rln.key_gen(&mut buffer).unwrap();
///
/// // We deserialize the keygen output
/// let (identity_secret, id_commitment) = deserialize_identity_pair(buffer.into_inner());
/// let (identity_secret_hash, id_commitment) = deserialize_identity_pair(buffer.into_inner());
/// ```
pub fn key_gen<W: Write>(&self, mut output_data: W) -> io::Result<()> {
let (id_key, id_commitment_key) = keygen();
output_data.write_all(&fr_to_bytes_le(&id_key))?;
output_data.write_all(&fr_to_bytes_le(&id_commitment_key))?;
let (identity_secret_hash, id_commitment) = keygen();
output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?;
output_data.write_all(&fr_to_bytes_le(&id_commitment))?;
Ok(())
}
/// Returns an identity trapdoor, nullifier, secret and commitment tuple.
///
/// The identity secret is the Poseidon hash of the identity trapdoor and identity nullifier.
///
/// The identity commitment is the Poseidon hash of the identity secret.
///
/// Generated credentials are compatible with [Semaphore](https://semaphore.appliedzkp.org/docs/guides/identities)'s credentials.
///
/// Output values are:
/// - `output_data`: a writer receiving the serialization of the identity tapdoor, identity nullifier, identity secret and identity commitment (serialization done with `rln::utils::fr_to_bytes_le`)
///
/// Example
/// ```
/// use rln::protocol::*;
///
/// // We generate an identity tuple
/// let mut buffer = Cursor::new(Vec::<u8>::new());
/// rln.extended_key_gen(&mut buffer).unwrap();
///
/// // We deserialize the keygen output
/// let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = deserialize_identity_tuple(buffer.into_inner());
/// ```
pub fn extended_key_gen<W: Write>(&self, mut output_data: W) -> io::Result<()> {
let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) =
extended_keygen();
output_data.write_all(&fr_to_bytes_le(&identity_trapdoor))?;
output_data.write_all(&fr_to_bytes_le(&identity_nullifier))?;
output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?;
output_data.write_all(&fr_to_bytes_le(&id_commitment))?;
Ok(())
}
@ -814,7 +847,7 @@ impl RLN<'_> {
/// .unwrap();
///
/// // We deserialize the keygen output
/// let (identity_secret, id_commitment) = deserialize_identity_pair(output_buffer.into_inner());
/// let (identity_secret_hash, id_commitment) = deserialize_identity_pair(output_buffer.into_inner());
/// ```
pub fn seeded_key_gen<R: Read, W: Write>(
&self,
@ -824,9 +857,55 @@ impl RLN<'_> {
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))?;
let (identity_secret_hash, id_commitment) = seeded_keygen(&serialized);
output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?;
output_data.write_all(&fr_to_bytes_le(&id_commitment))?;
Ok(())
}
/// Returns an identity trapdoor, nullifier, secret and commitment tuple generated using a seed.
///
/// The identity secret is the Poseidon hash of the identity trapdoor and identity nullifier.
///
/// The identity commitment is the Poseidon hash of the identity secret.
///
/// Generated credentials are compatible with [Semaphore](https://semaphore.appliedzkp.org/docs/guides/identities)'s credentials.
///
/// Input values are:
/// - `input_data`: a reader for the byte vector containing the seed
///
/// Output values are:
/// - `output_data`: a writer receiving the serialization of the identity tapdoor, identity nullifier, identity secret and identity commitment (serialization done with `rln::utils::fr_to_bytes_le`)
///
/// Example
/// ```
/// use rln::protocol::*;
///
/// 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();
///
/// // We deserialize the keygen output
/// let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = deserialize_identity_tuple(buffer.into_inner());
/// ```
pub fn seeded_extended_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 (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) =
extended_seeded_keygen(&serialized);
output_data.write_all(&fr_to_bytes_le(&identity_trapdoor))?;
output_data.write_all(&fr_to_bytes_le(&identity_nullifier))?;
output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?;
output_data.write_all(&fr_to_bytes_le(&id_commitment))?;
Ok(())
}
@ -838,11 +917,11 @@ impl RLN<'_> {
/// - `input_proof_data_2`: same as `input_proof_data_1`
///
/// Output values are:
/// - `output_data`: a writer receiving the serialization of the recovered identity secret field element if correctly recovered (serialization done with [`rln::utils::fr_to_bytes_le`](crate::utils::fr_to_bytes_le)), a writer receiving an empty byte vector if not.
/// - `output_data`: a writer receiving the serialization of the recovered identity secret hash field element if correctly recovered (serialization done with [`rln::utils::fr_to_bytes_le`](crate::utils::fr_to_bytes_le)), a writer receiving an empty byte vector if not.
///
/// Example
/// ```
/// // identity_secret, proof_data_1 and proof_data_2 are computed as in the example code snippet provided for rln::public::RLN::generate_rln_proof using same identity secret and epoch (but not necessarily same signal)
/// // identity_secret_hash, proof_data_1 and proof_data_2 are computed as in the example code snippet provided for rln::public::RLN::generate_rln_proof using same identity secret and epoch (but not necessarily same signal)
///
/// let mut input_proof_data_1 = Cursor::new(proof_data_1);
/// let mut input_proof_data_2 = Cursor::new(proof_data_2);
@ -854,14 +933,14 @@ impl RLN<'_> {
/// )
/// .unwrap();
///
/// let serialized_id_secret = output_buffer.into_inner();
/// let serialized_identity_secret_hash = output_buffer.into_inner();
///
/// // We ensure that a non-empty value is written to output_buffer
/// assert!(!serialized_id_secret.is_empty());
/// assert!(!serialized_identity_secret_hash.is_empty());
///
/// // We check if the recovered id secret corresponds to the original one
/// let (recovered_id_secret, _) = bytes_le_to_fr(&serialized_id_secret);
/// assert_eq!(recovered_id_secret, identity_secret);
/// // We check if the recovered identity secret hash corresponds to the original one
/// let (recovered_identity_secret_hash, _) = bytes_le_to_fr(&serialized_identity_secret_hash);
/// assert_eq!(recovered_identity_secret_hash, identity_secret_hash);
/// ```
pub fn recover_id_secret<R: Read, W: Write>(
&self,
@ -894,12 +973,13 @@ impl RLN<'_> {
let share2 = (proof_values_2.x, proof_values_2.y);
// We recover the secret
let recovered_id_secret = compute_id_secret(share1, share2, external_nullifier_1);
let recovered_identity_secret_hash =
compute_id_secret(share1, share2, external_nullifier_1);
// If an id secret is recovered, we write it to output_data, otherwise nothing will be written.
if recovered_id_secret.is_ok() {
let id_secret = recovered_id_secret.unwrap();
output_data.write_all(&fr_to_bytes_le(&id_secret))?;
// If an identity secret hash is recovered, we write it to output_data, otherwise nothing will be written.
if recovered_identity_secret_hash.is_ok() {
let identity_secret_hash = recovered_identity_secret_hash.unwrap();
output_data.write_all(&fr_to_bytes_le(&identity_secret_hash))?;
}
}
@ -941,7 +1021,7 @@ impl RLN<'_> {
/// Returns the serialization of a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) populated from the identity secret, the Merkle tree index, the epoch and signal.
///
/// Input values are:
/// - `input_data`: a reader for the serialization of `[ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]`
/// - `input_data`: a reader for the serialization of `[ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]`
///
/// The function returns the corresponding [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object serialized using [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness)).
pub fn get_serialized_rln_witness<R: Read>(&mut self, mut input_data: R) -> Vec<u8> {
@ -1197,8 +1277,8 @@ mod test {
let mut rln = RLN::new(tree_height, input_buffer);
// generate identity
let identity_secret = hash_to_field(b"test-merkle-proof");
let id_commitment = poseidon_hash(&vec![identity_secret]);
let identity_secret_hash = hash_to_field(b"test-merkle-proof");
let id_commitment = poseidon_hash(&vec![identity_secret_hash]);
// We pass id_commitment as Read buffer to RLN's set_leaf
let mut buffer = Cursor::new(fr_to_bytes_le(&id_commitment));
@ -1412,7 +1492,7 @@ mod test {
rln.init_tree_with_leaves(&mut buffer).unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
let (identity_secret_hash, id_commitment) = keygen();
// We set as leaf id_commitment after storing its index
let identity_index = u64::try_from(rln.tree.leaves_set()).unwrap();
@ -1428,9 +1508,9 @@ mod test {
let epoch = hash_to_field(b"test-epoch");
// We prepare input for generate_rln_proof API
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret));
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut identity_index.to_le_bytes().to_vec());
serialized.append(&mut fr_to_bytes_le(&epoch));
serialized.append(&mut signal_len.to_le_bytes().to_vec());
@ -1477,7 +1557,7 @@ mod test {
rln.init_tree_with_leaves(&mut buffer).unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
let (identity_secret_hash, id_commitment) = keygen();
// We set as leaf id_commitment after storing its index
let identity_index = u64::try_from(rln.tree.leaves_set()).unwrap();
@ -1493,9 +1573,9 @@ mod test {
let epoch = hash_to_field(b"test-epoch");
// We prepare input for generate_rln_proof API
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret));
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut identity_index.to_le_bytes().to_vec());
serialized.append(&mut fr_to_bytes_le(&epoch));
serialized.append(&mut signal_len.to_le_bytes().to_vec());
@ -1564,11 +1644,11 @@ mod test {
.unwrap();
let serialized_output = output_buffer.into_inner();
let (identity_secret, read) = bytes_le_to_fr(&serialized_output);
let (identity_secret_hash, 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(
let expected_identity_secret_hash_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
@ -1577,7 +1657,53 @@ mod test {
16,
);
assert_eq!(identity_secret, expected_identity_secret_seed_bytes);
assert_eq!(
identity_secret_hash,
expected_identity_secret_hash_seed_bytes
);
assert_eq!(id_commitment, expected_id_commitment_seed_bytes);
}
#[test]
fn test_seeded_extended_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_extended_key_gen(&mut input_buffer, &mut output_buffer)
.unwrap();
let serialized_output = output_buffer.into_inner();
let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) =
deserialize_identity_tuple(serialized_output);
// We check against expected values
let expected_identity_trapdoor_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
let expected_identity_nullifier_seed_bytes = str_to_fr(
"0x1f18714c7bc83b5bca9e89d404cf6f2f585bc4c0f7ed8b53742b7e2b298f50b4",
16,
);
let expected_identity_secret_hash_seed_bytes = str_to_fr(
"0x2aca62aaa7abaf3686fff2caf00f55ab9462dc12db5b5d4bcf3994e671f8e521",
16,
);
let expected_id_commitment_seed_bytes = str_to_fr(
"0x68b66aa0a8320d2e56842581553285393188714c48f9b17acd198b4f1734c5c",
16,
);
assert_eq!(identity_trapdoor, expected_identity_trapdoor_seed_bytes);
assert_eq!(identity_nullifier, expected_identity_nullifier_seed_bytes);
assert_eq!(
identity_secret_hash,
expected_identity_secret_hash_seed_bytes
);
assert_eq!(id_commitment, expected_id_commitment_seed_bytes);
}
@ -1622,7 +1748,7 @@ mod test {
rln.init_tree_with_leaves(&mut buffer).unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
let (identity_secret_hash, id_commitment) = keygen();
// We set as leaf id_commitment after storing its index
let identity_index = u64::try_from(rln.tree.leaves_set()).unwrap();
@ -1638,9 +1764,9 @@ mod test {
let epoch = hash_to_field(b"test-epoch");
// We prepare input for generate_rln_proof API
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret));
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut identity_index.to_le_bytes().to_vec());
serialized.append(&mut fr_to_bytes_le(&epoch));
serialized.append(&mut signal_len.to_le_bytes().to_vec());
@ -1705,7 +1831,7 @@ mod test {
let mut rln = RLN::new(tree_height, input_buffer);
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
let (identity_secret_hash, id_commitment) = keygen();
// We set as leaf id_commitment after storing its index
let identity_index = u64::try_from(rln.tree.leaves_set()).unwrap();
@ -1726,9 +1852,9 @@ mod test {
// We generate two proofs using same epoch but different signals.
// We prepare input for generate_rln_proof API
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized1: Vec<u8> = Vec::new();
serialized1.append(&mut fr_to_bytes_le(&identity_secret));
serialized1.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized1.append(&mut identity_index.to_le_bytes().to_vec());
serialized1.append(&mut fr_to_bytes_le(&epoch));
@ -1767,19 +1893,19 @@ mod test {
)
.unwrap();
let serialized_id_secret = output_buffer.into_inner();
let serialized_identity_secret_hash = output_buffer.into_inner();
// We ensure that a non-empty value is written to output_buffer
assert!(!serialized_id_secret.is_empty());
assert!(!serialized_identity_secret_hash.is_empty());
// We check if the recovered id secret corresponds to the original one
let (recovered_id_secret, _) = bytes_le_to_fr(&serialized_id_secret);
assert_eq!(recovered_id_secret, identity_secret);
// We check if the recovered identity secret hash corresponds to the original one
let (recovered_identity_secret_hash, _) = bytes_le_to_fr(&serialized_identity_secret_hash);
assert_eq!(recovered_identity_secret_hash, identity_secret_hash);
// We now test that computing_id_secret is unsuccessful if shares computed from two different id secrets but within same epoch are passed
// We now test that computing identity_secret_hash is unsuccessful if shares computed from two different identity secret hashes but within same epoch are passed
// We generate a new identity pair
let (identity_secret_new, id_commitment_new) = keygen();
let (identity_secret_hash_new, id_commitment_new) = keygen();
// We add it to the tree
let identity_index_new = u64::try_from(rln.tree.leaves_set()).unwrap();
@ -1791,9 +1917,9 @@ mod test {
let signal3_len = u64::try_from(signal3.len()).unwrap();
// We prepare proof input. Note that epoch is the same as before
// input_data is [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized3: Vec<u8> = Vec::new();
serialized3.append(&mut fr_to_bytes_le(&identity_secret_new));
serialized3.append(&mut fr_to_bytes_le(&identity_secret_hash_new));
serialized3.append(&mut identity_index_new.to_le_bytes().to_vec());
serialized3.append(&mut fr_to_bytes_le(&epoch));
serialized3.append(&mut signal3_len.to_le_bytes().to_vec());
@ -1806,7 +1932,7 @@ mod test {
.unwrap();
let proof_data_3 = output_buffer.into_inner();
// We attempt to recover the secret using share1 (coming from identity_secret) and share3 (coming from identity_secret_new)
// We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new)
let mut input_proof_data_1 = Cursor::new(proof_data_1.clone());
let mut input_proof_data_3 = Cursor::new(proof_data_3);
@ -1818,9 +1944,9 @@ mod test {
)
.unwrap();
let serialized_id_secret = output_buffer.into_inner();
let serialized_identity_secret_hash = output_buffer.into_inner();
// We ensure that an empty value was written to output_buffer, i.e. no secret is recovered
assert!(serialized_id_secret.is_empty());
assert!(serialized_identity_secret_hash.is_empty());
}
}