Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IETF VRF Test vectors #19

Merged
merged 6 commits into from
Jun 20, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Bandersnatch test vectors
davxy committed Jun 20, 2024
commit f8b54aac453926c8e4c79f3c2afaa3e9f4d7a745
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" ]
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
@@ -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());
}
}

4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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();
}
57 changes: 57 additions & 0 deletions src/suites/bandersnatch.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
3 changes: 3 additions & 0 deletions src/suites/secp256.rs
Original file line number Diff line number Diff line change
@@ -136,6 +136,7 @@ mod test_vectors {
// Skip these checks as test vector looks like is not correct
c: "",
s: "",
ad: "",
};

run_test_vector::<P256Sha256Tai>(&v);
@@ -153,6 +154,7 @@ mod test_vectors {
gamma: "034dac60aba508ba0c01aa9be80377ebd7562c4a52d74722e0abae7dc3080ddb56",
c: "00000000000000000000000000000000c19e067b15a8a8174905b13617804534",
s: "214f935b94c2287f797e393eb0816969d864f37625b443f30f1a5a33f2b3c854",
ad: "",
};

run_test_vector::<P256Sha256Tai>(&v);
@@ -170,6 +172,7 @@ mod test_vectors {
gamma: "03d03398bf53aa23831d7d1b2937e005fb0062cbefa06796579f2a1fc7e7b8c667",
c: "00000000000000000000000000000000d091c00b0f5c3619d10ecea44363b5a5",
s: "99cadc5b2957e223fec62e81f7b4825fc799a771a3d7334b9186bdbee87316b1",
ad: "",
};

run_test_vector::<P256Sha256Tai>(&v);
8 changes: 8 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -237,12 +237,20 @@ pub fn encode_point<S: Suite>(pt: &AffinePoint<S>) -> Vec<u8> {
buf
}

pub fn decode_point<S: Suite>(buf: &[u8]) -> AffinePoint<S> {
S::point_decode(buf)
}

pub fn encode_scalar<S: Suite>(sc: &ScalarField<S>) -> Vec<u8> {
let mut buf = Vec::new();
S::scalar_encode(sc, &mut buf);
buf
}

pub fn decode_scalar<S: Suite>(buf: &[u8]) -> ScalarField<S> {
S::scalar_decode(buf)
}

// Upcoming Arkworks features.
pub(crate) mod ark_next {
use ark_ec::{