Skip to content

Commit

Permalink
Bandersnatch test vectors
Browse files Browse the repository at this point in the history
  • Loading branch information
davxy committed Jun 20, 2024
1 parent ef15032 commit f8b54aa
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 9 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
Expand Down
74 changes: 74 additions & 0 deletions data/bandersnatch_vectors.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
199 changes: 190 additions & 9 deletions src/ietf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,52 +132,233 @@ pub mod testing {

pub const TEST_FLAG_SKIP_PROOF_CHECK: u8 = 1 << 0;

pub struct TestVector2<S: Suite> {
pub sk: ScalarField<S>,
pub pk: AffinePoint<S>,
pub alpha: Vec<u8>,
pub ad: Vec<u8>,
pub h: AffinePoint<S>,
pub gamma: AffinePoint<S>,
pub beta: Vec<u8>,
pub c: ScalarField<S>,
pub s: ScalarField<S>,
pub flags: u8,
}

impl<S: IetfSuite + std::fmt::Debug> TestVector2<S> {
pub fn new(seed: &[u8], alpha: &[u8], salt: Option<&[u8]>, ad: &[u8], flags: u8) -> Self {
let sk = Secret::<S>::from_seed(seed);
let pk = sk.public().0;

let salt = salt
.map(|v| v.to_vec())
.unwrap_or_else(|| utils::encode_point::<S>(&pk));

let h2c_data = [&salt[..], alpha].concat();
let h = <S as Suite>::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::<S>::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::<S>(&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::<S>::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<S: IetfSuite> core::fmt::Debug for TestVector2<S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let sk = hex::encode(utils::encode_scalar::<S>(&self.sk));
let pk = hex::encode(utils::encode_point::<S>(&self.pk));
let alpha = hex::encode(&self.alpha);
let ad = hex::encode(&self.ad);
let h = hex::encode(utils::encode_point::<S>(&self.h));
let gamma = hex::encode(utils::encode_point::<S>(&self.gamma));
let beta = hex::encode(&self.beta);
let c = hex::encode(utils::encode_scalar::<S>(&self.c));
let s = hex::encode(utils::encode_scalar::<S>(&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<String, String>);

impl<S: IetfSuite> From<TestVector2<S>> for TestVectorMap {
fn from(v: TestVector2<S>) -> Self {
let items = [
("sk", hex::encode(utils::encode_scalar::<S>(&v.sk))),
("pk", hex::encode(utils::encode_point::<S>(&v.pk))),
("alpha", hex::encode(&v.alpha)),
("ad", hex::encode(&v.ad)),
("h", hex::encode(utils::encode_point::<S>(&v.h))),
("gamma", hex::encode(utils::encode_point::<S>(&v.gamma))),
("beta", hex::encode(&v.beta)),
("proof_c", hex::encode(utils::encode_scalar::<S>(&v.c))),
("proof_s", hex::encode(utils::encode_scalar::<S>(&v.s))),
("flags", hex::encode(&[v.flags])),
];
let map: std::collections::BTreeMap<String, String> =
items.into_iter().map(|(k, v)| (k.to_string(), v)).collect();
Self(map)
}
}

impl<S: IetfSuite> From<TestVectorMap> for TestVector2<S> {
fn from(map: TestVectorMap) -> Self {
let item_bytes = |field| hex::decode(map.0.get(field).unwrap()).unwrap();
let sk = utils::decode_scalar::<S>(&item_bytes("sk"));
let pk = utils::decode_point::<S>(&item_bytes("pk"));
let alpha = item_bytes("alpha");
let ad = item_bytes("ad");
let h = utils::decode_point::<S>(&item_bytes("h"));
let gamma = utils::decode_point::<S>(&item_bytes("gamma"));
let beta = item_bytes("beta");
let c = utils::decode_scalar::<S>(&item_bytes("proof_c"));
let s = utils::decode_scalar::<S>(&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<S: IetfSuite>(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::<S>::from_scalar(s);

let pk_bytes = utils::encode_point::<S>(&sk.public.0);
assert_eq!(v.pk, hex::encode(&pk_bytes));
let pk = sk.public();
let pk_bytes = utils::encode_point::<S>(&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::<S>(&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::<S>(&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::<S>(&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::<S>(&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());
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ pub trait Suite: Copy + Clone {
pt.serialize_compressed(buf).unwrap();
}

fn point_decode(buf: &[u8]) -> AffinePoint<Self> {
AffinePoint::<Self>::deserialize_compressed(buf).unwrap()
}

fn scalar_encode(sc: &ScalarField<Self>, buf: &mut Vec<u8>) {
sc.serialize_compressed(buf).unwrap();
}
Expand Down
57 changes: 57 additions & 0 deletions src/suites/bandersnatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestVectorMap> = serde_json::from_reader(reader).unwrap();

for vector_map in vector_maps {
let vector = TestVector2::<S>::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::<S>::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();
}
}
Loading

0 comments on commit f8b54aa

Please sign in to comment.