feat(RLN): Proof verification using provided roots (#61)

* chore(rln): better comments

* feat(rln): add verification with roots
This commit is contained in:
G 2022-10-07 18:04:48 +02:00 committed by GitHub
parent 5d429ca031
commit b77facc5e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 329 additions and 3 deletions

View File

@ -19,7 +19,7 @@ pub struct Buffer {
impl From<&[u8]> for Buffer { impl From<&[u8]> for Buffer {
fn from(src: &[u8]) -> Self { fn from(src: &[u8]) -> Self {
Self { Self {
ptr: &src[0] as *const u8, ptr: src.as_ptr(),
len: src.len(), len: src.len(),
} }
} }
@ -225,6 +225,28 @@ pub extern "C" fn verify_rln_proof(
true true
} }
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
pub extern "C" fn verify_with_roots(
ctx: *const RLN,
proof_buffer: *const Buffer,
roots_buffer: *const Buffer,
proof_is_valid_ptr: *mut bool,
) -> bool {
let rln = unsafe { &*ctx };
let proof_data = <&[u8]>::from(unsafe { &*proof_buffer });
let roots_data = <&[u8]>::from(unsafe { &*roots_buffer });
if match rln.verify_with_roots(proof_data, roots_data) {
Ok(verified) => verified,
Err(_) => return false,
} {
unsafe { *proof_is_valid_ptr = true };
} else {
unsafe { *proof_is_valid_ptr = false };
};
true
}
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
// Utils // Utils
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
@ -769,7 +791,7 @@ mod test {
proof_data.append(&mut signal_len.to_le_bytes().to_vec()); proof_data.append(&mut signal_len.to_le_bytes().to_vec());
proof_data.append(&mut signal.to_vec()); proof_data.append(&mut signal.to_vec());
// We call generate_rln_proof // We call verify_rln_proof
let input_buffer = &Buffer::from(proof_data.as_ref()); let input_buffer = &Buffer::from(proof_data.as_ref());
let mut proof_is_valid: bool = false; let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
@ -778,6 +800,134 @@ mod test {
assert_eq!(proof_is_valid, true); assert_eq!(proof_is_valid, true);
} }
#[test]
// Computes and verifies an RLN ZK proof by checking proof's root against an input roots buffer
fn test_verify_with_roots() {
// First part similar to test_rln_proof_ffi
let tree_height = TEST_TREE_HEIGHT;
let no_of_leaves = 256;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..no_of_leaves {
leaves.push(Fr::rand(&mut rng));
}
// 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 add leaves in a batch into the tree
let leaves_ser = vec_fr_to_bytes_le(&leaves);
let input_buffer = &Buffer::from(leaves_ser.as_ref());
let success = set_leaves(rln_pointer, input_buffer);
assert!(success, "set leaves call failed");
// We generate a new identity pair
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = key_gen(rln_pointer, output_buffer.as_mut_ptr());
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 (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
let leaf_ser = fr_to_bytes_le(&id_commitment);
let input_buffer = &Buffer::from(leaf_ser.as_ref());
let success = set_next_leaf(rln_pointer, input_buffer);
assert!(success, "set next leaf call failed");
let identity_index: u64 = no_of_leaves;
// We generate a random signal
let mut rng = rand::thread_rng();
let signal: [u8; 32] = rng.gen();
let signal_len = u64::try_from(signal.len()).unwrap();
// We generate a random epoch
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> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret));
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());
serialized.append(&mut signal.to_vec());
// We call generate_rln_proof
let input_buffer = &Buffer::from(serialized.as_ref());
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = generate_rln_proof(rln_pointer, input_buffer, output_buffer.as_mut_ptr());
assert!(success, "set leaves call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
// result_data is [ proof<128> | share_y<32> | nullifier<32> | root<32> | epoch<32> | share_x<32> | rln_identifier<32> ]
let mut proof_data = <&[u8]>::from(&output_buffer).to_vec();
// We prepare input for verify_rln_proof API
// input_data is [ proof<128> | share_y<32> | nullifier<32> | root<32> | epoch<32> | share_x<32> | rln_identifier<32> | signal_len<8> | signal<var> ]
// that is [ proof_data | signal_len<8> | signal<var> ]
proof_data.append(&mut signal_len.to_le_bytes().to_vec());
proof_data.append(&mut signal.to_vec());
// We test verify_with_roots
// We first try to verify against an empty buffer of roots.
// In this case, since no root is provided, proof's root check is skipped and proof is verified if other proof values are valid
let mut roots_data: Vec<u8> = Vec::new();
let input_buffer = &Buffer::from(proof_data.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
let success =
verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr);
assert!(success, "verify call failed");
// Proof should be valid
assert_eq!(proof_is_valid, true);
// We then try to verify against some random values not containing the correct one.
for _ in 0..5 {
roots_data.append(&mut fr_to_bytes_le(&Fr::rand(&mut rng)));
}
let input_buffer = &Buffer::from(proof_data.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
let success =
verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr);
assert!(success, "verify call failed");
// Proof should be invalid.
assert_eq!(proof_is_valid, false);
// We finally include the correct root
// We get the root of the tree obtained adding one leaf per time
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = get_root(rln_pointer, output_buffer.as_mut_ptr());
assert!(success, "get root call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let result_data = <&[u8]>::from(&output_buffer).to_vec();
let (root, _) = bytes_le_to_fr(&result_data);
// We include the root and verify the proof
roots_data.append(&mut fr_to_bytes_le(&root));
let input_buffer = &Buffer::from(proof_data.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
let success =
verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr);
assert!(success, "verify call failed");
// Proof should be valid.
assert_eq!(proof_is_valid, true);
}
#[test] #[test]
// Tests hash to field using FFI APIs // Tests hash to field using FFI APIs
fn test_seeded_keygen_ffi() { fn test_seeded_keygen_ffi() {

View File

@ -210,7 +210,8 @@ impl RLN<'_> {
pub fn verify<R: Read>(&self, mut input_data: R) -> io::Result<bool> { pub fn verify<R: Read>(&self, mut input_data: R) -> io::Result<bool> {
// Input data is serialized for Curve as: // Input data is serialized for Curve as:
// serialized_proof (compressed, 4*32 bytes) || serialized_proof_values (6*32 bytes) // serialized_proof (compressed, 4*32 bytes) || serialized_proof_values (6*32 bytes), i.e.
// [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ]
let mut input_byte: Vec<u8> = Vec::new(); let mut input_byte: Vec<u8> = Vec::new();
input_data.read_to_end(&mut input_byte)?; input_data.read_to_end(&mut input_byte)?;
let proof = ArkProof::deserialize(&mut Cursor::new(&input_byte[..128].to_vec())).unwrap(); let proof = ArkProof::deserialize(&mut Cursor::new(&input_byte[..128].to_vec())).unwrap();
@ -301,6 +302,7 @@ impl RLN<'_> {
// Input data is serialized for Curve as: // Input data is serialized for Curve as:
// [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal<var> ] // [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal<var> ]
// Note that in contrast to verify, this function takes as input the signal and further verifies: the tree root, the X coordinate, the RLN identifier
pub fn verify_rln_proof<R: Read>(&self, mut input_data: R) -> io::Result<bool> { pub fn verify_rln_proof<R: Read>(&self, mut input_data: R) -> io::Result<bool> {
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)?;
@ -331,6 +333,84 @@ impl RLN<'_> {
&& (proof_values.rln_identifier == hash_to_field(RLN_IDENTIFIER))) && (proof_values.rln_identifier == hash_to_field(RLN_IDENTIFIER)))
} }
// This function verifies a proof against a sequence of input valid roots to allow external validation of the tree state.
// Input data is serialized for Curve as:
// [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal<var> ]
// roots_data is serialized as (arbitrary long, even empty) sequence of Fr elements serialized in little endian (32 bytes each for Bn254)
pub fn verify_with_roots<R: Read>(
&self,
mut input_data: R,
mut roots_data: R,
) -> io::Result<bool> {
let mut serialized: Vec<u8> = Vec::new();
input_data.read_to_end(&mut serialized)?;
let mut all_read = 0;
let proof = ArkProof::deserialize(&mut Cursor::new(&serialized[..128].to_vec())).unwrap();
all_read += 128;
let (proof_values, read) = deserialize_proof_values(&serialized[all_read..].to_vec());
all_read += read;
let signal_len =
u64::from_le_bytes(serialized[all_read..all_read + 8].try_into().unwrap()) as usize;
all_read += 8;
let signal: Vec<u8> = serialized[all_read..all_read + signal_len].to_vec();
let verified = verify_proof(
self.verification_key.as_ref().unwrap(),
&proof,
&proof_values,
)
.unwrap();
// First consistency checks to counter proof tampering
let x = hash_to_field(&signal);
let partial_result = verified
&& (x == proof_values.x)
&& (proof_values.rln_identifier == hash_to_field(RLN_IDENTIFIER));
// We skip root validation if proof is already invalid
if partial_result == false {
return Ok(partial_result);
}
// We read passed roots
let mut roots_serialized: Vec<u8> = Vec::new();
roots_data.read_to_end(&mut roots_serialized)?;
// The vector where we'll store read roots
let mut roots: Vec<Fr> = Vec::new();
// We expect each root to be fr_byte_size() bytes long.
let fr_size = fr_byte_size();
println!(
"Fr size {:#?}, roots_serialized len {:#?}",
fr_size,
roots_serialized.len()
);
// We read the buffer and convert to Fr as much as we can
all_read = 0;
while all_read + fr_size <= roots_serialized.len() {
let (root, read) = bytes_le_to_fr(&roots_serialized[all_read..]);
all_read += read;
roots.push(root);
}
// We validate the root
let roots_verified: bool;
if roots.is_empty() {
// If no root is passed in roots_buffer, we skip proof's root check
roots_verified = true;
} else {
// Otherwise we check if proof's root is contained in the passed buffer
roots_verified = roots.contains(&proof_values.root);
}
// We combine all checks
Ok(partial_result && roots_verified)
}
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
// Utils // Utils
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
@ -887,4 +967,100 @@ mod test {
assert_eq!(hash1, hash2); assert_eq!(hash1, hash2);
} }
#[test]
fn proof_verification_with_roots() {
// The first part is similar to test_rln_with_witness
let tree_height = TEST_TREE_HEIGHT;
let no_of_leaves = 256;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..no_of_leaves {
leaves.push(Fr::rand(&mut rng));
}
// We create a new RLN instance
let input_buffer = Cursor::new(TEST_RESOURCES_FOLDER);
let mut rln = RLN::new(tree_height, input_buffer);
// We add leaves in a batch into the tree
let mut buffer = Cursor::new(vec_fr_to_bytes_le(&leaves));
rln.set_leaves(&mut buffer).unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
// We set as leaf id_commitment after storing its index
let identity_index = u64::try_from(rln.tree.leaves_set()).unwrap();
let mut buffer = Cursor::new(fr_to_bytes_le(&id_commitment));
rln.set_next_leaf(&mut buffer).unwrap();
// We generate a random signal
let mut rng = rand::thread_rng();
let signal: [u8; 32] = rng.gen();
let signal_len = u64::try_from(signal.len()).unwrap();
// We generate a random epoch
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> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret));
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());
serialized.append(&mut signal.to_vec());
let mut input_buffer = Cursor::new(serialized);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.generate_rln_proof(&mut input_buffer, &mut output_buffer)
.unwrap();
// output_data is [ proof<128> | share_y<32> | nullifier<32> | root<32> | epoch<32> | share_x<32> | rln_identifier<32> ]
let mut proof_data = output_buffer.into_inner();
// We prepare input for verify_rln_proof API
// input_data is [ proof<128> | share_y<32> | nullifier<32> | root<32> | epoch<32> | share_x<32> | rln_identifier<32> | signal_len<8> | signal<var> ]
// that is [ proof_data || signal_len<8> | signal<var> ]
proof_data.append(&mut signal_len.to_le_bytes().to_vec());
proof_data.append(&mut signal.to_vec());
let input_buffer = Cursor::new(proof_data);
// If no roots is provided, proof validation is skipped and if the remaining proof values are valid, the proof will be correctly verified
let mut roots_serialized: Vec<u8> = Vec::new();
let mut roots_buffer = Cursor::new(roots_serialized.clone());
let verified = rln
.verify_with_roots(&mut input_buffer.clone(), &mut roots_buffer)
.unwrap();
assert!(verified);
// We serialize in the roots buffer some random values and we check that the proof is not verified since doesn't contain the correct root the proof refers to
for _ in 0..5 {
roots_serialized.append(&mut fr_to_bytes_le(&Fr::rand(&mut rng)));
}
roots_buffer = Cursor::new(roots_serialized.clone());
let verified = rln
.verify_with_roots(&mut input_buffer.clone(), &mut roots_buffer)
.unwrap();
assert!(verified == false);
// We get the root of the tree obtained adding one leaf per time
let mut buffer = Cursor::new(Vec::<u8>::new());
rln.get_root(&mut buffer).unwrap();
let (root, _) = bytes_le_to_fr(&buffer.into_inner());
// We add the real root and we check if now the proof is verified
roots_serialized.append(&mut fr_to_bytes_le(&root));
roots_buffer = Cursor::new(roots_serialized.clone());
let verified = rln
.verify_with_roots(&mut input_buffer.clone(), &mut roots_buffer)
.unwrap();
assert!(verified);
}
} }