mirror of https://github.com/vacp2p/zerokit.git
feat(RLN): Proof verification using provided roots (#61)
* chore(rln): better comments * feat(rln): add verification with roots
This commit is contained in:
parent
5d429ca031
commit
b77facc5e9
154
rln/src/ffi.rs
154
rln/src/ffi.rs
|
@ -19,7 +19,7 @@ pub struct Buffer {
|
|||
impl From<&[u8]> for Buffer {
|
||||
fn from(src: &[u8]) -> Self {
|
||||
Self {
|
||||
ptr: &src[0] as *const u8,
|
||||
ptr: src.as_ptr(),
|
||||
len: src.len(),
|
||||
}
|
||||
}
|
||||
|
@ -225,6 +225,28 @@ pub extern "C" fn verify_rln_proof(
|
|||
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
|
||||
////////////////////////////////////////////////////////
|
||||
|
@ -769,7 +791,7 @@ mod test {
|
|||
proof_data.append(&mut signal_len.to_le_bytes().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 mut proof_is_valid: bool = false;
|
||||
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
|
||||
|
@ -778,6 +800,134 @@ mod test {
|
|||
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]
|
||||
// Tests hash to field using FFI APIs
|
||||
fn test_seeded_keygen_ffi() {
|
||||
|
|
|
@ -210,7 +210,8 @@ impl RLN<'_> {
|
|||
|
||||
pub fn verify<R: Read>(&self, mut input_data: R) -> io::Result<bool> {
|
||||
// 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();
|
||||
input_data.read_to_end(&mut input_byte)?;
|
||||
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:
|
||||
// [ 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> {
|
||||
let mut serialized: Vec<u8> = Vec::new();
|
||||
input_data.read_to_end(&mut serialized)?;
|
||||
|
@ -331,6 +333,84 @@ impl RLN<'_> {
|
|||
&& (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
|
||||
////////////////////////////////////////////////////////
|
||||
|
@ -887,4 +967,100 @@ mod test {
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue