Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rln): add seeded keygen #56

Merged
merged 4 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rln/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ num-bigint = { version = "0.4.3", default-features = false, features = ["rand"]
num-traits = "0.2.11"
once_cell = "1.14.0"
rand = "0.8"
rand_chacha = "0.3.1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am worried about the new dependency here. How does it affect the wasm blob size? I intended to add some size checking as part of the CI but it was not as straightforward as I hoped because cargo make is used instead of npm.

Cc @richard-ramos

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this also depends on how much this library delegates to the web crypto subtle API

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now building this branch, i end up with 530KB which is way better than the 838KB of the version published https://unpkg.com/@waku/[email protected]/bundle/assets/rln_wasm_bg-0185b546.wasm

-rw-rw-r-- 1 richard richard  17K sep 24 12:15 rln_wasm_bg.js
-rw-rw-r-- 1 richard richard 530K sep 30 08:42 rln_wasm_bg.wasm
-rw-rw-r-- 1 richard richard 1.1K sep 30 08:42 rln_wasm_bg.wasm.d.ts
-rw-rw-r-- 1 richard richard 3.2K sep 30 08:42 rln_wasm.d.ts
-rw-rw-r-- 1 richard richard  20K sep 30 08:42 rln_wasm.js

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What changed ???

Copy link
Contributor Author

@s1fr0 s1fr0 Sep 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the poseidon constants that now are generated on the fly while before were hardcoded (~300KB less)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fryorcraken if ok for you, I'll merge!

tiny-keccak = { version = "2.0.2", features = ["keccak"] }
utils = { path = "../utils/", default-features = false }

Expand Down
57 changes: 57 additions & 0 deletions rln/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
#[no_mangle]
pub extern "C" fn hash(
Expand Down Expand Up @@ -758,6 +778,43 @@ mod test {
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]
// Tests hash to field using FFI APIs
fn test_hash_to_field_ffi() {
Expand Down
45 changes: 45 additions & 0 deletions rln/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,4 +429,49 @@ mod test {
let (deser, _) = deserialize_proof_values(&ser);
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);
}
}
21 changes: 20 additions & 1 deletion rln/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use ark_relations::r1cs::SynthesisError;
use ark_std::{rand::thread_rng, UniformRand};
use color_eyre::Result;
use num_bigint::BigInt;
use rand::Rng;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Mutex;
#[cfg(debug_assertions)]
Expand Down Expand Up @@ -338,13 +339,31 @@ pub fn compute_tree_root(

// Generates a tupe (identity_secret, id_commitment) where
// identity_secret is random and id_commitment = PoseidonHash(identity_secret)
// 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)
}

// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function can accept a 1 byte signal, which I assume, is not secure.

I'd suggest to fix the signal size (and I am guessing that it should be 32 bytes).

Copy link
Contributor Author

@s1fr0 s1fr0 Sep 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of course it won't, although any input would be hashed to become a 32 bytes seed. If we allow users to choose the seed, we're delegating the entropy level and credential security. How does differ passing one byte from passing e.g. an all-0 32 bytes array? They're both unsecure regardless of the input size.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS: can also accept empty signals :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point.

// 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
pub fn hash_to_field(signal: &[u8]) -> Fr {
// We hash the input signal using Keccak256
Expand Down
45 changes: 45 additions & 0 deletions rln/src/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,21 @@ impl RLN<'_> {
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<()> {
let mut serialized: Vec<u8> = Vec::new();
input_data.read_to_end(&mut serialized)?;
Expand Down Expand Up @@ -824,6 +839,36 @@ mod test {
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]
fn test_hash_to_field() {
let rln = RLN::default();
Expand Down
2 changes: 1 addition & 1 deletion utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] }

[features]
default = ["parallel"]
parallel = ["ark-ff/parallel"]
parallel = ["ark-ff/parallel"]