Skip to content

Instantly share code, notes, and snippets.

@sourabhniyogi
Created July 4, 2024 14:59
Show Gist options
  • Save sourabhniyogi/7097609935fc7a584d71731acdf32027 to your computer and use it in GitHub Desktop.
Save sourabhniyogi/7097609935fc7a584d71731acdf32027 to your computer and use it in GitHub Desktop.
Working Safrole jamtestvector E_T signature verification
use ark_ec_vrfs::suites::bandersnatch::edwards as bandersnatch;
use ark_ec_vrfs::{prelude::ark_serialize, suites::bandersnatch::edwards::RingContext};
use bandersnatch::{IetfProof, Input, Output, Public, RingProof, Secret};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
const RING_SIZE: usize = 6;
// This is the IETF `Prove` procedure output as described in section 2.2
// of the Bandersnatch VRFs specification
#[derive(CanonicalSerialize, CanonicalDeserialize)]
struct IetfVrfSignature {
output: Output,
proof: IetfProof,
}
// This is the IETF `Prove` procedure output as described in section 4.2
// of the Bandersnatch VRFs specification
#[derive(CanonicalSerialize, CanonicalDeserialize)]
struct RingVrfSignature {
output: Output,
// This contains both the Pedersen proof and actual ring proof.
proof: RingProof,
}
// "Static" ring context data
fn ring_context() -> &'static RingContext {
use std::sync::OnceLock;
static RING_CTX: OnceLock<RingContext> = OnceLock::new();
RING_CTX.get_or_init(|| {
use bandersnatch::PcsParams;
use std::{fs::File, io::Read};
let manifest_dir =
std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set");
let filename = format!("{}/data/zcash-srs-2-11-uncompressed.bin", manifest_dir);
println!("ring_context loaded from {}", filename);
let mut file = File::open(filename).unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).unwrap();
let pcs_params = PcsParams::deserialize_uncompressed_unchecked(&mut &buf[..]).unwrap();
RingContext::from_srs(pcs_params, RING_SIZE).unwrap()
})
}
// Construct VRF Input Point from arbitrary data (section 1.2)
fn vrf_input_point(vrf_input_data: &[u8]) -> Input {
let point =
<bandersnatch::BandersnatchSha512Ell2 as ark_ec_vrfs::Suite>::data_to_point(vrf_input_data)
.unwrap();
Input::from(point)
}
// Prover actor.
pub struct Prover {
pub prover_idx: usize,
pub secret: Secret,
pub ring: Vec<Public>,
}
type RingCommitment = ark_ec_vrfs::ring::RingCommitment<bandersnatch::BandersnatchSha512Ell2>;
// Verifier actor.
pub struct Verifier {
pub commitment: RingCommitment,
pub ring: Vec<Public>,
}
impl Verifier {
pub fn new(ring: Vec<Public>) -> Self {
// Backend currently requires the wrapped type (plain affine points)
let pts: Vec<_> = ring.iter().map(|pk| pk.0).collect();
let verifier_key = ring_context().verifier_key(&pts);
let commitment = verifier_key.commitment();
Self { ring, commitment }
}
/// Anonymous VRF signature verification.
///
/// Used for tickets verification.
pub fn ring_vrf_verify(
&self,
vrf_input_data: &[u8],
aux_data: &[u8],
signature: &[u8],
) -> bool {
use ark_ec_vrfs::ring::prelude::fflonk::pcs::PcsParams;
use ark_ec_vrfs::ring::Verifier as _;
use bandersnatch::VerifierKey;
let signature = RingVrfSignature::deserialize_compressed(signature).unwrap();
let input = vrf_input_point(vrf_input_data);
let output = signature.output;
let ring_ctx = ring_context();
// The verifier key is reconstructed from the commitment and the constant
// verifier key component of the SRS in order to verify some proof.
// As an alternative we can construct the verifier key using the
// RingContext::verifier_key() method, but is more expensive.
// In other words, we prefer computing the commitment once, when the keyset changes.
let verifier_key = VerifierKey::from_commitment_and_kzg_vk(
self.commitment.clone(),
ring_ctx.pcs_params.raw_vk(),
);
let verifier = ring_ctx.verifier(verifier_key);
let result = Public::verify(input, output, aux_data, &signature.proof, &verifier).is_ok();
// Print vrf_input_data, aux_data, and signature in hex form
println!("vrf_input_data: {}", hex::encode(vrf_input_data));
println!("aux_data: {}", hex::encode(aux_data));
let mut signature_buf = Vec::new();
signature.serialize_compressed(&mut signature_buf).unwrap();
println!("signature: {}", hex::encode(signature_buf));
if !result {
println!("Ring signature verification failure");
return result;
}
println!("Ring signature verified");
// This truncated hash is the actual value used as ticket-id/score
println!(" vrf-output-hash: {}", hex::encode(&output.hash()[..32]));
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tiny_extrinsic() {
// build ring from next_epoch pub keys in gamma_k
// https://github.com/w3f/jamtestvectors/blob/826e64f74a352a059f8b172447af7927f02e0fae/safrole/tiny/publish-tickets-no-mark-2.json#L108-L144
let ring_set_hex = [
"5e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d",
"3d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d0",
"aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bc",
"7f6190116d118d643a98878e294ccf62b509e214299931aad8ff9764181a4e33",
"48e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e3",
"f16e5352840afb47e206b5c89f560f2611835855cf2e6ebad1acc9520a72591d",
];
let ring_set: Vec<Public> = ring_set_hex
.iter()
.map(|hex_str| {
let bytes = hex::decode(hex_str).expect("Decoding hex string failed");
let point = bandersnatch::Public::deserialize_compressed(&bytes[..])
.expect("Deserialization failed");
point
})
.collect();
// eta_2 from https://github.com/w3f/jamtestvectors/blob/1ff3f1ad9ae5df30157d5b11f9808849de246877/safrole/tiny/publish-tickets-no-mark-2.json#L214
let mut vrf_input_data = Vec::from(b"jam_ticket_seal");
vrf_input_data.extend_from_slice(
&hex::decode("bb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa")
.expect("Decoding hex string failed"),
);
// attempt 1 from https://github.com/w3f/jamtestvectors/blob/826e64f74a352a059f8b172447af7927f02e0fae/safrole/tiny/publish-tickets-no-mark-2.json#L8
vrf_input_data.push(1);
// no data
let aux_data = vec![];
// https://github.com/w3f/jamtestvectors/blob/1ff3f1ad9ae5df30157d5b11f9808849de246877/safrole/tiny/publish-tickets-no-mark-2.json#L8C31-L9C13
let signature_hex = "b342bf8f6fa69c745daad2e99c92929b1da2b840f67e5e8015ac22dd1076343ea95c5bb4b69c197bfdc1b7d2f484fe455fb19bba7e8d17fcaf309ba5814bf54f3a74d75b408da8d3b99bf07f7cde373e4fd757061b1c99e0aac4847f1e393e892b566c14a7f8643a5d976ced0a18d12e32c660d59c66c271332138269cb0fe9c2462d5b3c1a6e9f5ed330ff0d70f64218010ff337b0b69b531f916c67ec564097cd842306df1b4b44534c95ff4efb73b17a14476057fdf8678683b251dc78b0b94712179345c794b6bd99aa54b564933651aee88c93b648e91a613c87bc3f445fff571452241e03e7d03151600a6ee259051a23086b408adec7c112dd94bd8123cf0bed88fddac46b7f891f34c29f13bf883771725aa234d398b13c39fd2a871894f1b1e2dbc7fffbc9c65c49d1e9fd5ee0da133bef363d4ebebe63de2b50328b5d7e020303499d55c07cae617091e33a1ee72ba1b65f940852e93e2905fdf577adcf62be9c74ebda9af59d3f11bece8996773f392a2b35693a45a5a042d88a3dc816b689fe596762d4ea7c6024da713304f56dc928be6e8048c651766952b6c40d0f48afc067ca7cbd77763a2d4f11e88e16033b3343f39bf519fe734db8a139d148ccead4331817d46cf469befa64ae153b5923869144dfa669da36171c20e1f757ed5231fa5a08827d83f7b478ddfb44c9bceb5c6c920b8761ff1e3edb03de48fb55884351f0ac5a7a1805b9b6c49c0529deb97e994deaf2dfd008825e8704cdc04b621f316b505fde26ab71b31af7becbc1154f9979e43e135d35720b93b367bedbe6c6182bb6ed99051f28a3ad6d348ba5b178e3ea0ec0bb4a03fe36604a9eeb609857f8334d3b4b34867361ed2ff9163acd9a27fa20303abe9fc29f2d6c921a8ee779f7f77d940b48bc4fce70a58eed83a206fb7db4c1c7ebe7658603495bb40f6a581dd9e235ba0583165b1569052f8fb4a3e604f2dd74ad84531c6b96723c867b06b6fdd1c4ba150cf9080aa6bbf44cc29041090973d56913b9dc755960371568ef1cf03f127fe8eca209db5d18829f5bfb5826f98833e3f42472b47fad995a9a8bb0e41a1df45ead20285a8";
// https://github.com/w3f/jamtestvectors/blob/1ff3f1ad9ae5df30157d5b11f9808849de246877/safrole/tiny/publish-tickets-no-mark-2.json#L201
let signature_bytes = hex::decode(signature_hex).expect("Decoding hex string failed");
// Create the Verifier
let verifier = Verifier::new(ring_set);
let expected_gamma_z = "96a4b479612d8d2770d6a4785fa2f44e0befca6f008b176de51e309e2ee796d2b596e315fcb044495b75c3cb5c7fd4cdae0959758cac93d4ab8789c6aec4ba8f683c6b103cf6888f70edfcb8dcbbc1d85a8fa3832e0cd4503c7a1796c8d0c3f792e630ae2b14e758ab0960e372172203f4c9a41777dadd529971d7ab9d23ab29fe0e9c85ec450505dde7f5ac038274cf";
let mut buf2 = Vec::new();
verifier.commitment.serialize_compressed(&mut buf2).unwrap();
let gamma_z = hex::encode(buf2);
assert_eq!(
expected_gamma_z, gamma_z,
"The gamma_z does not match the expected value!"
);
println!("gamma_z (commitment): {}", gamma_z);
let res = verifier.ring_vrf_verify(&vrf_input_data, &aux_data, &signature_bytes);
assert!(res);
println!("Verification result: {}", res);
}
}
/*
# cargo test -- --nocapture
Compiling bandersnatch v0.1.0 (/root/go/src/github.com/colorfulnotion/jam/bandersnatch)
Finished test [unoptimized + debuginfo] target(s) in 2.14s
Running unittests src/lib.rs (target/debug/deps/bandersnatch-fc73acc45ed52f71)
running 1 test
ring_context loaded from /root/go/src/github.com/colorfulnotion/jam/bandersnatch/data/zcash-srs-2-11-uncompressed.bin
gamma_z (commitment): 96a4b479612d8d2770d6a4785fa2f44e0befca6f008b176de51e309e2ee796d2b596e315fcb044495b75c3cb5c7fd4cdae0959758cac93d4ab8789c6aec4ba8f683c6b103cf6888f70edfcb8dcbbc1d85a8fa3832e0cd4503c7a1796c8d0c3f792e630ae2b14e758ab0960e372172203f4c9a41777dadd529971d7ab9d23ab29fe0e9c85ec450505dde7f5ac038274cf
vrf_input_data: 6a616d5f7469636b65745f7365616cbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa01
aux_data:
signature: b342bf8f6fa69c745daad2e99c92929b1da2b840f67e5e8015ac22dd1076343ea95c5bb4b69c197bfdc1b7d2f484fe455fb19bba7e8d17fcaf309ba5814bf54f3a74d75b408da8d3b99bf07f7cde373e4fd757061b1c99e0aac4847f1e393e892b566c14a7f8643a5d976ced0a18d12e32c660d59c66c271332138269cb0fe9c2462d5b3c1a6e9f5ed330ff0d70f64218010ff337b0b69b531f916c67ec564097cd842306df1b4b44534c95ff4efb73b17a14476057fdf8678683b251dc78b0b94712179345c794b6bd99aa54b564933651aee88c93b648e91a613c87bc3f445fff571452241e03e7d03151600a6ee259051a23086b408adec7c112dd94bd8123cf0bed88fddac46b7f891f34c29f13bf883771725aa234d398b13c39fd2a871894f1b1e2dbc7fffbc9c65c49d1e9fd5ee0da133bef363d4ebebe63de2b50328b5d7e020303499d55c07cae617091e33a1ee72ba1b65f940852e93e2905fdf577adcf62be9c74ebda9af59d3f11bece8996773f392a2b35693a45a5a042d88a3dc816b689fe596762d4ea7c6024da713304f56dc928be6e8048c651766952b6c40d0f48afc067ca7cbd77763a2d4f11e88e16033b3343f39bf519fe734db8a139d148ccead4331817d46cf469befa64ae153b5923869144dfa669da36171c20e1f757ed5231fa5a08827d83f7b478ddfb44c9bceb5c6c920b8761ff1e3edb03de48fb55884351f0ac5a7a1805b9b6c49c0529deb97e994deaf2dfd008825e8704cdc04b621f316b505fde26ab71b31af7becbc1154f9979e43e135d35720b93b367bedbe6c6182bb6ed99051f28a3ad6d348ba5b178e3ea0ec0bb4a03fe36604a9eeb609857f8334d3b4b34867361ed2ff9163acd9a27fa20303abe9fc29f2d6c921a8ee779f7f77d940b48bc4fce70a58eed83a206fb7db4c1c7ebe7658603495bb40f6a581dd9e235ba0583165b1569052f8fb4a3e604f2dd74ad84531c6b96723c867b06b6fdd1c4ba150cf9080aa6bbf44cc29041090973d56913b9dc755960371568ef1cf03f127fe8eca209db5d18829f5bfb5826f98833e3f42472b47fad995a9a8bb0e41a1df45ead20285a8
Ring signature verified
vrf-output-hash: 09a696b142112c0af1cd2b5f91726f2c050112078e3ef733198c5f43daa20d2b
Verification result: true
test tests::test_tiny_extrinsic ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.63s
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment