From f8b54aac453926c8e4c79f3c2afaa3e9f4d7a745 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 20 Jun 2024 15:54:12 +0200 Subject: [PATCH] Bandersnatch test vectors --- Cargo.toml | 2 + data/bandersnatch_vectors.json | 74 ++++++++++++ src/ietf.rs | 199 +++++++++++++++++++++++++++++++-- src/lib.rs | 4 + src/suites/bandersnatch.rs | 57 ++++++++++ src/suites/secp256.rs | 3 + src/utils.rs | 8 ++ 7 files changed, 338 insertions(+), 9 deletions(-) create mode 100644 data/bandersnatch_vectors.json diff --git a/Cargo.toml b/Cargo.toml index 244d063..03a61e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,8 @@ ring-proof = { package = "ring", git = "https://github.com/w3f/ring-proof", rev [dev-dependencies] ark-ed25519 = "0.4" hex = "0.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [features] default = [ "std" ] diff --git a/data/bandersnatch_vectors.json b/data/bandersnatch_vectors.json new file mode 100644 index 0000000..f7ad067 --- /dev/null +++ b/data/bandersnatch_vectors.json @@ -0,0 +1,74 @@ +[ + { + "ad": "", + "alpha": "", + "beta": "bfe3cce020f4ed87b86ca5e855b24f731256d72ef741fa28021a85c90785207b9bcb07a8d763133b7bc07ca1a1ddece2033cf1ba3016678f9287ffe7dec0aec1", + "flags": "00", + "gamma": "4168f6407d5caccbe46820af3d032ff88952b824643acd1688cbef176e30572e", + "h": "839b7fb1019a640e6d7a2d6b20f3ec38ceb91ffc72cf3b86866cc659d95d39c1", + "pk": "76adde367eebc8b21f7ef37e327243a77e34e30f9a211fda05409b49f16f3473", + "proof_c": "bb7f81b613a2fbf6ff8af9c0118f0459e52b85fe0538b5a5fe54958681ac2303", + "proof_s": "2817d61674ce9e87a56c836504c1179bcb502091e32290840455e37e88d0ba05", + "sk": "2bd8776e6ca6a43d51987f756be88b643ab4431b523132f675c8f0004f5d5a17" + }, + { + "ad": "", + "alpha": "0a", + "beta": "86bb79d0600666564499071115fda4a0a775516b0fcd7a418c50224bc05e4436039327c35a9ffc7ba37d79403007330cfa28b67c924a3b4779cc8afc3f08047c", + "flags": "00", + "gamma": "059d738d0721dc7f1509b43d58959b127d2d09ed3696e5c8bd734cb608dc51e1", + "h": "df672c03b4cc0480a3fcb7951b2707d40fad72ade6e79870e4e0cee6ea16d1d3", + "pk": "a1b1da71cc4682e159b7da23050d8b6261eb11a3247c89b07ef56ccd002fd38b", + "proof_c": "7c0949571bd9231172592bfc85d262727f0a4737334f0daace4991adfb86fd03", + "proof_s": "b4d492d97a2283e56e403e650bc30031fec3ff888f35816a6ebbfc9121fab11b", + "sk": "3d6406500d4009fdf2604546093665911e753f2213570a29521fd88bc30ede18" + }, + { + "ad": "0b8c", + "alpha": "", + "beta": "7c263cc2a83841440fa85f4d352832d97f5d1b54a684df6efc3cd1743cc7d59f40d282271e900b8e4fba693be1b6b46e9544e64fed628984b257cb0557b0d8d5", + "flags": "00", + "gamma": "eb2d6a9a60b3acc1258ed35d815aaf6932fe76388fae3dbfc9594d5bee09c73f", + "h": "48adf7aec95508704959f279dc00a3430c8fb55f39d0d7598f4d02ef51abaabc", + "pk": "5ebfe047f421e1a3e1d9bbb163839812657bbb3e4ffe9856a725b2b405844cf3", + "proof_c": "55ea5766affceb74132d413f40bd35b45b7fe803bb08fda041fffd2a98bbe008", + "proof_s": "55f2199be6174666e2bd3d7f1b731e48cc3267f934460fd598c15f045c04da01", + "sk": "8b9063872331dda4c3c282f7d813fb3c13e7339b7dc9635fdc764e32cc57cb15" + }, + { + "ad": "", + "alpha": "73616d706c65", + "beta": "df36d83a3d0a0e07def33b2be7bf2872bcc0cdb9fabe069a4d124ee062369450198c56526711aacc5994040e0d67bebd2a4e7f1968e38fc5f19e1cfda3dbe850", + "flags": "00", + "gamma": "779f7e5e173d34ce011edf8f009396fd1164466d37dfdf983af41b421bc0c1e5", + "h": "ae61160b30625ec0b35a7bdfcdecae3da89fb3ca0fce295d38e435e7ec71ba21", + "pk": "9d97151298a5339866ddd3539d16696e19e6b68ac731562c807fe63a1ca49506", + "proof_c": "79cea3ed5c701f2cd5694934263320c614e5b0f0e64a16ff805f48e7cc520e1b", + "proof_s": "7c7f25be26291ed5ab59af6ae3be049fb699d849f712276eacb98fa4d45a4515", + "sk": "6db187202f69e627e432296ae1d0f166ae6ac3c1222585b6ceae80ea07670b14" + }, + { + "ad": "", + "alpha": "42616e646572736e6174636820766563746f72", + "beta": "b0992cf128b1d611d1e4b5fe12bd3c502513d2db938afc701ccb4f29da4a283ec824e267c3cce7c62c689337ffca45ef377fd23d106a8fff18f81860502ad819", + "flags": "00", + "gamma": "1c7ed6a09bc8d5a4b350e0071e0b78013179d7539655304f4b73390dc3586b51", + "h": "fa9d66cf9f5ba4c78e223ead5dca3a1bef508a281c6053af23148c37d9c20464", + "pk": "dc2de7312c2850a9f6c103289c64fbd76e2ebd2fa8b5734708eb2c76c0fb2d99", + "proof_c": "7b58ff009e6457ea41c2562cee63200b69103af58a548841660fc73954c59a04", + "proof_s": "6cb5f5f92b6fb10f3370fbfb5a62bdb618185bdd345dd22320690b7c8269ba05", + "sk": "b56cc204f1b6c2323709012cb16c72f3021035ce935fbe69b600a88d842c7407" + }, + { + "ad": "73616d706c65", + "alpha": "42616e646572736e6174636820766563746f72", + "beta": "ad227f825b97de4538b70438f5915f0bf4a597dfb34404700c2cbcc29983fdb594058b30df691e0b89d500b5ed1d449c335be4405a8c5e4c025e63f68acd86c2", + "flags": "00", + "gamma": "25c46b117dcfeceb2debbb14fd976e403eff1cf6f6f945f53cbeb49e7e214925", + "h": "9fd95dfca41d55e20ca783c4792ae8d35f20b56b4113945a1f3e411e733bc99a", + "pk": "decb0151cbeb49f76f10419ab6a96242bdc87baac8a474e5161123de4304ac29", + "proof_c": "f1aa703dbd38cb28586d42dd671b1967153e3410308b7f97dfcdfa00f0fd1919", + "proof_s": "3e26dd7e5cbc1f8b5d38ad8c39085446aee4acf76ce57e5f8eca1d38fc30af10", + "sk": "da36359bf1bfd1694d3ed359e7340bd02a6a5e54827d94db1384df29f5bdd302" + } +] \ No newline at end of file diff --git a/src/ietf.rs b/src/ietf.rs index e7b0adc..b62c730 100644 --- a/src/ietf.rs +++ b/src/ietf.rs @@ -132,52 +132,233 @@ pub mod testing { pub const TEST_FLAG_SKIP_PROOF_CHECK: u8 = 1 << 0; + pub struct TestVector2 { + pub sk: ScalarField, + pub pk: AffinePoint, + pub alpha: Vec, + pub ad: Vec, + pub h: AffinePoint, + pub gamma: AffinePoint, + pub beta: Vec, + pub c: ScalarField, + pub s: ScalarField, + pub flags: u8, + } + + impl TestVector2 { + pub fn new(seed: &[u8], alpha: &[u8], salt: Option<&[u8]>, ad: &[u8], flags: u8) -> Self { + let sk = Secret::::from_seed(seed); + let pk = sk.public().0; + + let salt = salt + .map(|v| v.to_vec()) + .unwrap_or_else(|| utils::encode_point::(&pk)); + + let h2c_data = [&salt[..], alpha].concat(); + let h = ::data_to_point(&h2c_data).unwrap(); + let input = Input::from(h); + + let alpha = alpha.to_vec(); + let output = sk.output(input); + let gamma = output.0; + let beta = output.hash().to_vec(); + + let proof = sk.prove(input, output, ad); + + TestVector2 { + sk: sk.scalar, + pk, + alpha, + ad: ad.to_vec(), + h, + gamma, + beta, + c: proof.c, + s: proof.s, + flags, + } + } + + pub fn run(&self) { + let sk = Secret::::from_scalar(self.sk); + + let pk = sk.public(); + assert_eq!(self.pk, pk.0, "public key ('pk') mismatch"); + + // Prepare hash_to_curve data = salt || alpha + // Salt is defined to be pk (adjust it to make the encoding to match) + let pk_bytes = utils::encode_point::(&pk.0); + let h2c_data = [&pk_bytes[..], &self.alpha[..]].concat(); + + let h = S::data_to_point(&h2c_data).unwrap(); + assert_eq!(self.h, h, "hash-to-curve ('h') mismatch"); + let input = Input::::from(h); + + let output = sk.output(input); + assert_eq!(self.gamma, output.0, "VRF pre-output ('gamma') mismatch"); + + let beta = output.hash().to_vec(); + assert_eq!(self.beta, beta, "VRF output ('beta') mismatch"); + + if self.flags & TEST_FLAG_SKIP_PROOF_CHECK != 0 { + return; + } + + let proof = sk.prove(input, output, &self.ad); + assert_eq!(self.c, proof.c, "VRF proof challenge ('c') mismatch"); + assert_eq!(self.s, proof.s, "VRF proof response ('s') mismatch"); + + assert!(pk.verify(input, output, &self.ad, &proof).is_ok()); + } + } + + impl core::fmt::Debug for TestVector2 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let sk = hex::encode(utils::encode_scalar::(&self.sk)); + let pk = hex::encode(utils::encode_point::(&self.pk)); + let alpha = hex::encode(&self.alpha); + let ad = hex::encode(&self.ad); + let h = hex::encode(utils::encode_point::(&self.h)); + let gamma = hex::encode(utils::encode_point::(&self.gamma)); + let beta = hex::encode(&self.beta); + let c = hex::encode(utils::encode_scalar::(&self.c)); + let s = hex::encode(utils::encode_scalar::(&self.s)); + f.debug_struct("TestVector") + .field("sk", &sk) + .field("pk", &pk) + .field("alpha", &alpha) + .field("ad", &ad) + .field("h", &h) + .field("gamma", &gamma) + .field("beta", &beta) + .field("proof_c", &c) + .field("proof_s", &s) + .field("flags", &self.flags) + .finish() + } + } + + #[derive(Debug, serde::Serialize, serde::Deserialize)] + pub struct TestVectorMap(std::collections::BTreeMap); + + impl From> for TestVectorMap { + fn from(v: TestVector2) -> Self { + let items = [ + ("sk", hex::encode(utils::encode_scalar::(&v.sk))), + ("pk", hex::encode(utils::encode_point::(&v.pk))), + ("alpha", hex::encode(&v.alpha)), + ("ad", hex::encode(&v.ad)), + ("h", hex::encode(utils::encode_point::(&v.h))), + ("gamma", hex::encode(utils::encode_point::(&v.gamma))), + ("beta", hex::encode(&v.beta)), + ("proof_c", hex::encode(utils::encode_scalar::(&v.c))), + ("proof_s", hex::encode(utils::encode_scalar::(&v.s))), + ("flags", hex::encode(&[v.flags])), + ]; + let map: std::collections::BTreeMap = + items.into_iter().map(|(k, v)| (k.to_string(), v)).collect(); + Self(map) + } + } + + impl From for TestVector2 { + fn from(map: TestVectorMap) -> Self { + let item_bytes = |field| hex::decode(map.0.get(field).unwrap()).unwrap(); + let sk = utils::decode_scalar::(&item_bytes("sk")); + let pk = utils::decode_point::(&item_bytes("pk")); + let alpha = item_bytes("alpha"); + let ad = item_bytes("ad"); + let h = utils::decode_point::(&item_bytes("h")); + let gamma = utils::decode_point::(&item_bytes("gamma")); + let beta = item_bytes("beta"); + let c = utils::decode_scalar::(&item_bytes("proof_c")); + let s = utils::decode_scalar::(&item_bytes("proof_s")); + let flags = item_bytes("flags")[0]; + + Self { + sk, + pk, + alpha, + ad, + h, + gamma, + beta, + c, + s, + flags, + } + } + } + pub struct TestVector { pub flags: u8, + /// Secret key pub sk: &'static str, + /// Public key pub pk: &'static str, + /// VRF input string pub alpha: &'static [u8], + /// VRF output hash pub beta: &'static str, + /// Hash to curve (salt||alpha), salt=encode(pk) pub h: &'static str, pub gamma: &'static str, pub c: &'static str, pub s: &'static str, + pub ad: &'static str, } pub fn run_test_vector(v: &TestVector) { + let ad = hex::decode(v.ad).unwrap(); + let sk_bytes = hex::decode(v.sk).unwrap(); let s = S::scalar_decode(&sk_bytes); let sk = Secret::::from_scalar(s); - let pk_bytes = utils::encode_point::(&sk.public.0); - assert_eq!(v.pk, hex::encode(&pk_bytes)); + let pk = sk.public(); + let pk_bytes = utils::encode_point::(&pk.0); + assert_eq!(v.pk, hex::encode(&pk_bytes), "public key ('pk') mismatch"); // Prepare hash_to_curve data = salt || alpha // Salt is defined to be pk (adjust it to make the encoding to match) let h2c_data = [&pk_bytes[..], v.alpha].concat(); let h = S::data_to_point(&h2c_data).unwrap(); let h_bytes = utils::encode_point::(&h); - assert_eq!(v.h, hex::encode(h_bytes)); + assert_eq!(v.h, hex::encode(h_bytes), "hash-to-curve ('h') mismatch"); let input = Input::from(h); let output = sk.output(input); - let proof = sk.prove(input, output, []); + let proof = sk.prove(input, output, &ad); let gamma_bytes = utils::encode_point::(&output.0); - assert_eq!(v.gamma, hex::encode(gamma_bytes)); + assert_eq!( + v.gamma, + hex::encode(gamma_bytes), + "VRF pre-output ('gamma') mismatch" + ); + + let beta = output.hash(); + assert_eq!(v.beta, hex::encode(beta), "VRF output ('beta') mismatch"); if v.flags & TEST_FLAG_SKIP_PROOF_CHECK != 0 { return; } let c_bytes = utils::encode_scalar::(&proof.c); - assert_eq!(v.c, hex::encode(c_bytes)); + assert_eq!( + v.c, + hex::encode(c_bytes), + "VRF proof challenge ('c') mismatch" + ); let s_bytes = utils::encode_scalar::(&proof.s); - assert_eq!(v.s, hex::encode(s_bytes)); + assert_eq!( + v.s, + hex::encode(s_bytes), + "VRF proof response ('s') mismatch" + ); - let beta = output.hash(); - assert_eq!(v.beta, hex::encode(beta)); + assert!(pk.verify(input, output, &ad, &proof).is_ok()); } } diff --git a/src/lib.rs b/src/lib.rs index e6090d8..e6019d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,10 @@ pub trait Suite: Copy + Clone { pt.serialize_compressed(buf).unwrap(); } + fn point_decode(buf: &[u8]) -> AffinePoint { + AffinePoint::::deserialize_compressed(buf).unwrap() + } + fn scalar_encode(sc: &ScalarField, buf: &mut Vec) { sc.serialize_compressed(buf).unwrap(); } diff --git a/src/suites/bandersnatch.rs b/src/suites/bandersnatch.rs index 6f5b640..aed7731 100644 --- a/src/suites/bandersnatch.rs +++ b/src/suites/bandersnatch.rs @@ -241,3 +241,60 @@ mod tests { assert!(sw_point.is_on_curve()); } } + +#[cfg(test)] +mod test_vectors_edwards { + use super::edwards::*; + use crate::ietf::testing::*; + + type S = BandersnatchSha512Ell2; + + const TEST_VECTORS_FILE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/data/bandersnatch_vectors.json" + ); + + #[test] + fn test_vectors_process() { + use std::{fs::File, io::BufReader}; + + let file = File::open(TEST_VECTORS_FILE).unwrap(); + let reader = BufReader::new(file); + + let vector_maps: Vec = serde_json::from_reader(reader).unwrap(); + + for vector_map in vector_maps { + let vector = TestVector2::::from(vector_map); + vector.run(); + } + } + + #[test] + fn test_vectors_generate() { + use std::{fs::File, io::Write}; + // ("alpha", "ad")) + let var_data: Vec<(&[u8], &[u8])> = vec![ + (b"", b""), + (b"0a", b""), + (b"", b"0b8c"), + (b"73616D706C65", b""), + (b"42616E646572736E6174636820766563746F72", b""), + (b"42616E646572736E6174636820766563746F72", b"73616D706C65"), + ]; + + let mut vector_maps = Vec::with_capacity(var_data.len()); + + for (i, var_data) in var_data.iter().enumerate() { + let alpha = hex::decode(var_data.0).unwrap(); + let ad = hex::decode(var_data.1).unwrap(); + let vector = TestVector2::::new(&[i as u8], &alpha, None, &ad, 0); + println!("{:#?}", vector); + vector.run(); + vector_maps.push(TestVectorMap::from(vector)); + } + + let mut file = File::create(TEST_VECTORS_FILE).unwrap(); + let json = serde_json::to_string_pretty(&vector_maps).unwrap(); + file.write_all(json.as_bytes()).unwrap(); + } +} diff --git a/src/suites/secp256.rs b/src/suites/secp256.rs index d924b7f..bd4b126 100644 --- a/src/suites/secp256.rs +++ b/src/suites/secp256.rs @@ -136,6 +136,7 @@ mod test_vectors { // Skip these checks as test vector looks like is not correct c: "", s: "", + ad: "", }; run_test_vector::(&v); @@ -153,6 +154,7 @@ mod test_vectors { gamma: "034dac60aba508ba0c01aa9be80377ebd7562c4a52d74722e0abae7dc3080ddb56", c: "00000000000000000000000000000000c19e067b15a8a8174905b13617804534", s: "214f935b94c2287f797e393eb0816969d864f37625b443f30f1a5a33f2b3c854", + ad: "", }; run_test_vector::(&v); @@ -170,6 +172,7 @@ mod test_vectors { gamma: "03d03398bf53aa23831d7d1b2937e005fb0062cbefa06796579f2a1fc7e7b8c667", c: "00000000000000000000000000000000d091c00b0f5c3619d10ecea44363b5a5", s: "99cadc5b2957e223fec62e81f7b4825fc799a771a3d7334b9186bdbee87316b1", + ad: "", }; run_test_vector::(&v); diff --git a/src/utils.rs b/src/utils.rs index c44ab43..0eb92f1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -237,12 +237,20 @@ pub fn encode_point(pt: &AffinePoint) -> Vec { buf } +pub fn decode_point(buf: &[u8]) -> AffinePoint { + S::point_decode(buf) +} + pub fn encode_scalar(sc: &ScalarField) -> Vec { let mut buf = Vec::new(); S::scalar_encode(sc, &mut buf); buf } +pub fn decode_scalar(buf: &[u8]) -> ScalarField { + S::scalar_decode(buf) +} + // Upcoming Arkworks features. pub(crate) mod ark_next { use ark_ec::{