diff --git a/rust-k256/Cargo.toml b/rust-k256/Cargo.toml index 98ae3a4..8e89385 100644 --- a/rust-k256/Cargo.toml +++ b/rust-k256/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] rand_core = "0.6.3" -hex-literal = "0.3.4" hash2field = "0.4.0" num-bigint = "0.4.3" num-integer = "0.1.45" @@ -15,3 +14,4 @@ k256 = {version = "0.13.2", features = ["arithmetic", "hash2curve", "expose-fiel [dev-dependencies] hex = "0.4.3" +hex-literal = "0.3.4" \ No newline at end of file diff --git a/rust-k256/src/lib.rs b/rust-k256/src/lib.rs index ecb8e6d..baaea72 100644 --- a/rust-k256/src/lib.rs +++ b/rust-k256/src/lib.rs @@ -7,14 +7,16 @@ use k256::{ elliptic_curve::sec1::ToEncodedPoint, elliptic_curve::{bigint::ArrayEncoding, group::ff::PrimeField}, sha2::{digest::Output, Digest, Sha256}, - FieldBytes, ProjectivePoint, Scalar, Secp256k1, U256, + FieldBytes, Scalar, Secp256k1, U256, }; // requires 'getrandom' feature use std::panic; +// TODO #86 +pub use k256::ProjectivePoint; const L: usize = 48; const COUNT: usize = 2; const OUT: usize = L * COUNT; -const DST: &[u8] = b"QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_"; // Hash to curve algorithm +pub const DST: &[u8] = b"QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_"; // Hash to curve algorithm fn print_type_of(_: &T) { println!("{}", std::any::type_name::()); @@ -59,7 +61,10 @@ fn sha256hash6signals( } // Hashes two values to the curve -fn hash_to_curve(m: &[u8], pk: &ProjectivePoint) -> Result { +fn hash_to_curve( + m: &[u8], + pk: &ProjectivePoint, +) -> Result { Secp256k1::hash_from_bytes::>( &[[m, &encode_pt(pk)].concat().as_slice()], //b"CURVE_XMD:SHA-256_SSWU_RO_", @@ -71,6 +76,7 @@ fn hash_to_curve(m: &[u8], pk: &ProjectivePoint) -> Result { pub message: &'a [u8], pub pk: &'a ProjectivePoint, @@ -79,6 +85,7 @@ pub struct PlumeSignature<'a> { pub s: &'a Scalar, pub v1: Option>, } +#[derive(Debug)] pub struct PlumeSignatureV1Fields<'a> { pub r_point: &'a ProjectivePoint, pub hashed_to_curve_r: &'a ProjectivePoint, @@ -90,8 +97,10 @@ impl PlumeSignature<'_> { // c = hash2(g, g^sk, hash[m, g^sk], hash[m, pk]^sk, gr, hash[m, pk]^r) pub fn verify_signals(&self) -> bool { // don't forget to check `c` is `Output` in the #API - let c = panic::catch_unwind(|| {Output::::from_slice(self.c)}); - if c.is_err() {return false;} + let c = panic::catch_unwind(|| Output::::from_slice(self.c)); + if c.is_err() { + return false; + } let c = c.unwrap(); // TODO should we allow `c` input greater than BaseField::MODULUS? @@ -160,292 +169,27 @@ fn byte_array_to_scalar(bytes: &[u8]) -> Scalar { #[cfg(test)] mod tests { use super::*; + use hex_literal::hex; - use helpers::{gen_test_scalar_sk, hash_to_secp, test_gen_signals, PlumeVersion}; - mod helpers { - use super::*; - use hex_literal::hex; - - #[derive(Debug)] - pub enum PlumeVersion { - V1, - V2, - } - - // Generates a deterministic secret key for deterministic testing. Should be replaced by random oracle in production deployments. - pub fn gen_test_scalar_sk() -> Scalar { - Scalar::from_repr( - hex!("519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464").into(), - ) - .unwrap() - } - - // Generates a deterministic r for deterministic testing. Should be replaced by random oracle in production deployments. - fn gen_test_scalar_r() -> Scalar { - Scalar::from_repr( - hex!("93b9323b629f251b8f3fc2dd11f4672c5544e8230d493eceea98a90bda789808").into(), - ) - .unwrap() - } - - // Calls the hash to curve function for secp256k1, and returns the result as a ProjectivePoint - pub fn hash_to_secp(s: &[u8]) -> ProjectivePoint { - let pt: ProjectivePoint = Secp256k1::hash_from_bytes::>( - &[s], - //b"CURVE_XMD:SHA-256_SSWU_RO_" - &[DST], - ) - .unwrap(); - pt - } - - // These generate test signals as if it were passed from a secure enclave to wallet. Note that leaking these signals would leak pk, but not sk. - // Outputs these 6 signals, in this order - // g^sk (private) - // hash[m, pk]^sk public nullifier - // c = hash2(g, pk, hash[m, pk], hash[m, pk]^sk, gr, hash[m, pk]^r) (public or private) - // r + sk * c (public or private) - // g^r (private, optional) - // hash[m, pk]^r (private, optional) - pub fn test_gen_signals( - m: &[u8], - version: PlumeVersion, - ) -> ( - ProjectivePoint, - ProjectivePoint, - Output, - Scalar, - Option, - Option, - ) { - // The base point or generator of the curve. - let g = ProjectivePoint::GENERATOR; - - // The signer's secret key. It is only accessed within the secure enclave. - let sk = gen_test_scalar_sk(); - - // A random value r. It is only accessed within the secure enclave. - let r = gen_test_scalar_r(); - - // The user's public key: g^sk. - let pk = &g * &sk; - - // The generator exponentiated by r: g^r. - let g_r = &g * &r; - - // hash[m, pk] - let hash_m_pk = hash_to_curve(m, &pk).unwrap(); - - println!( - "h.x: {:?}", - hex::encode(hash_m_pk.to_affine().to_encoded_point(false).x().unwrap()) - ); - println!( - "h.y: {:?}", - hex::encode(hash_m_pk.to_affine().to_encoded_point(false).y().unwrap()) - ); - - // hash[m, pk]^r - let hash_m_pk_pow_r = &hash_m_pk * &r; - println!( - "hash_m_pk_pow_r.x: {:?}", - hex::encode( - hash_m_pk_pow_r - .to_affine() - .to_encoded_point(false) - .x() - .unwrap() - ) - ); - println!( - "hash_m_pk_pow_r.y: {:?}", - hex::encode( - hash_m_pk_pow_r - .to_affine() - .to_encoded_point(false) - .y() - .unwrap() - ) - ); - - // The public nullifier: hash[m, pk]^sk. - let nullifier = &hash_m_pk * &sk; - - // The Fiat-Shamir type step. - let c = match version { - PlumeVersion::V1 => c_sha256_vec_signal(vec![ - &g, - &pk, - &hash_m_pk, - &nullifier, - &g_r, - &hash_m_pk_pow_r, - ]), - PlumeVersion::V2 => c_sha256_vec_signal(vec![&nullifier, &g_r, &hash_m_pk_pow_r]), - }; - dbg!(&c, version); - - let c_scalar = &Scalar::reduce_nonzero(U256::from_be_byte_array(c.to_owned())); - // This value is part of the discrete log equivalence (DLEQ) proof. - let r_sk_c = r + sk * c_scalar; - - // Return the signature. - (pk, nullifier, c, r_sk_c, Some(g_r), Some(hash_m_pk_pow_r)) - } - } - + // Test encode_pt() #[test] - fn plume_v1_test() { - let g = ProjectivePoint::GENERATOR; - - let m = b"An example app message string"; - - // Fixed key nullifier, secret key, and random value for testing - // Normally a secure enclave would generate these values, and output to a wallet implementation - let (pk, nullifier, c, r_sk_c, g_r, hash_m_pk_pow_r) = - test_gen_signals(m, PlumeVersion::V1); - - // The signer's secret key. It is only accessed within the secure enclave. - let sk = gen_test_scalar_sk(); - - // The user's public key: g^sk. - let pk = &g * &sk; - - // Verify the signals, normally this would happen in ZK with only the nullifier public, which would have a zk verifier instead - // The wallet should probably run this prior to snarkify-ing as a sanity check - // m and nullifier should be public, so we can verify that they are correct - let verified = PlumeSignature { - message: m, - pk: &pk, - nullifier: &nullifier, - c: &c, - s: &r_sk_c, - v1: Some(PlumeSignatureV1Fields { - r_point: &g_r.unwrap(), - hashed_to_curve_r: &hash_m_pk_pow_r.unwrap(), - }), - } - .verify_signals(); - println!("Verified: {}", verified); - - // Print nullifier - println!( - "nullifier.x: {:?}", - hex::encode(nullifier.to_affine().to_encoded_point(false).x().unwrap()) - ); - println!( - "nullifier.y: {:?}", - hex::encode(nullifier.to_affine().to_encoded_point(false).y().unwrap()) - ); - - // Print c - println!("c: {:?}", hex::encode(&c)); - - // Print r_sk_c - println!("r_sk_c: {:?}", hex::encode(r_sk_c.to_bytes())); - - // Print g_r - println!( - "g_r.x: {:?}", - hex::encode( - g_r.unwrap() - .to_affine() - .to_encoded_point(false) - .x() - .unwrap() - ) - ); - println!( - "g_r.y: {:?}", - hex::encode( - g_r.unwrap() - .to_affine() - .to_encoded_point(false) - .y() - .unwrap() - ) - ); - - // Print hash_m_pk_pow_r - println!( - "hash_m_pk_pow_r.x: {:?}", - hex::encode( - hash_m_pk_pow_r - .unwrap() - .to_affine() - .to_encoded_point(false) - .x() - .unwrap() - ) - ); - println!( - "hash_m_pk_pow_r.y: {:?}", - hex::encode( - hash_m_pk_pow_r - .unwrap() - .to_affine() - .to_encoded_point(false) - .y() - .unwrap() - ) - ); - - // Test encode_pt() - let g_as_bytes = encode_pt(&g); + fn test_encode_pt() { + let g_as_bytes = encode_pt(&ProjectivePoint::GENERATOR); assert_eq!( hex::encode(g_as_bytes), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" ); + } - // Test byte_array_to_scalar() - let scalar = byte_array_to_scalar(&c); // TODO this `fn` looks suspicious as in reproducing const time ops + // Test byte_array_to_scalar() + #[test] + fn test_byte_array_to_scalar() { + let scalar = byte_array_to_scalar(&hex!( + "c6a7fc2c926ddbaf20731a479fb6566f2daa5514baae5223fe3b32edbce83254" + )); // TODO this `fn` looks suspicious as in reproducing const time ops assert_eq!( hex::encode(scalar.to_bytes()), "c6a7fc2c926ddbaf20731a479fb6566f2daa5514baae5223fe3b32edbce83254" ); - - // Test the hash-to-curve algorithm - let h = hash_to_secp(b"abc"); - assert_eq!( - hex::encode(h.to_affine().to_encoded_point(false).x().unwrap()), - "3377e01eab42db296b512293120c6cee72b6ecf9f9205760bd9ff11fb3cb2c4b" - ); - assert_eq!( - hex::encode(h.to_affine().to_encoded_point(false).y().unwrap()), - "7f95890f33efebd1044d382a01b1bee0900fb6116f94688d487c6c7b9c8371f6" - ); - assert!(verified); - } - - #[test] - fn plume_v2_test() { - let g = ProjectivePoint::GENERATOR; - - let m = b"An example app message string"; - - // Fixed key nullifier, secret key, and random value for testing - // Normally a secure enclave would generate these values, and output to a wallet implementation - let (pk, nullifier, c, r_sk_c, g_r, hash_m_pk_pow_r) = - test_gen_signals(m, PlumeVersion::V2); - - // The signer's secret key. It is only accessed within the secu`re enclave. - let sk = gen_test_scalar_sk(); - - // The user's public key: g^sk. - let pk = &g * &sk; - - // Verify the signals, normally this would happen in ZK with only the nullifier public, which would have a zk verifier instead - // The wallet should probably run this prior to snarkify-ing as a sanity check - // m and nullifier should be public, so we can verify that they are correct - let verified = PlumeSignature { - message: m, - pk: &pk, - nullifier: &nullifier, - c: &c, - s: &r_sk_c, - v1: None, - } - .verify_signals(); - assert!(verified) } } diff --git a/rust-k256/tests/verification.rs b/rust-k256/tests/verification.rs new file mode 100644 index 0000000..8fb6e67 --- /dev/null +++ b/rust-k256/tests/verification.rs @@ -0,0 +1,304 @@ +//! The suite consists of two tests; one for each type of signature. One of them also do printings of the values, +//! which can be useful to you when comparing different implementations. +//! Their setup is shared, `mod helpers` contains barely not refactored code, which is still instrumental to the tests. + +use helpers::{gen_test_scalar_sk, test_gen_signals, PlumeVersion}; +use k256::elliptic_curve::sec1::ToEncodedPoint; +use zk_nullifier::{PlumeSignature, PlumeSignatureV1Fields, ProjectivePoint}; + +const G: ProjectivePoint = ProjectivePoint::GENERATOR; +const M: &[u8; 29] = b"An example app message string"; +const C_V1: &[u8] = + &hex_literal::hex!("c6a7fc2c926ddbaf20731a479fb6566f2daa5514baae5223fe3b32edbce83254"); + +// `test_gen_signals` provides fixed key nullifier, secret key, and the random value for testing +// Normally a secure enclave would generate these values, and output to a wallet implementation + +// `gen_test_scalar_sk()` provides the signer's secret key. It is only accessed within the secure enclave. + +// The user's public key goes to the `pk` field as $g^sk$. + +// Both tests finish with the signals verification, normally this would happen in ZK with only the nullifier public, which would have a zk verifier instead +// The wallet should probably run this prior to snarkify-ing as a sanity check +// `M` and nullifier should be public, so we can verify that they are correct + +#[test] +fn plume_v1_test() { + let test_data = test_gen_signals(M, PlumeVersion::V1); + let r_point = &test_data.4.unwrap(); + let hashed_to_curve_r = &test_data.5.unwrap(); + + let sig = PlumeSignature { + message: M, + pk: &(G * gen_test_scalar_sk()), + nullifier: &test_data.1, + c: C_V1, + s: &test_data.3, + v1: Some(PlumeSignatureV1Fields { + r_point, + hashed_to_curve_r, + }), + }; + let verified = sig.verify_signals(); + println!("Verified: {}", verified); + + // Print nullifier + println!( + "nullifier.x: {:?}", + hex::encode( + sig.nullifier + .to_affine() + .to_encoded_point(false) + .x() + .unwrap() + ) + ); + println!( + "nullifier.y: {:?}", + hex::encode( + sig.nullifier + .to_affine() + .to_encoded_point(false) + .y() + .unwrap() + ) + ); + // Print c + println!("c: {:?}", hex::encode(C_V1)); + // Print r_sk_c + println!("r_sk_c: {:?}", hex::encode(sig.s.to_bytes())); + // Print g_r + println!( + "g_r.x: {:?}", + hex::encode(r_point.to_affine().to_encoded_point(false).x().unwrap()) + ); + println!( + "g_r.y: {:?}", + hex::encode(r_point.to_affine().to_encoded_point(false).y().unwrap()) + ); + // Print hash_m_pk_pow_r + println!( + "hash_m_pk_pow_r.x: {:?}", + hex::encode( + hashed_to_curve_r + .to_affine() + .to_encoded_point(false) + .x() + .unwrap() + ) + ); + println!( + "hash_m_pk_pow_r.y: {:?}", + hex::encode( + hashed_to_curve_r + .to_affine() + .to_encoded_point(false) + .y() + .unwrap() + ) + ); + + assert!(verified); +} + +#[test] +fn plume_v2_test() { + let test_data = test_gen_signals(M, PlumeVersion::V2); + assert!(PlumeSignature { + message: M, + pk: &(G * gen_test_scalar_sk()), + nullifier: &test_data.1, + c: &test_data.2, + s: &test_data.3, + v1: None + } + .verify_signals()); +} + +mod helpers { + /* Feels like this one could/should be replaced with static/constant values. Preserved for historical reasons. + For the same reasons calls for internal `fn` are commented and replaced by "one-liners" adapted from current implementation. */ + use super::*; + use hex_literal::hex; + use k256::{ + elliptic_curve::{ + bigint::ArrayEncoding, + hash2curve::{ExpandMsgXmd, GroupDigest}, + ops::ReduceNonZero, + PrimeField, + }, + sha2::{digest::Output, Digest, Sha256}, + Scalar, Secp256k1, U256, + }; + + #[derive(Debug)] + pub enum PlumeVersion { + V1, + V2, + } + + // Generates a deterministic secret key for deterministic testing. Should be replaced by random oracle in production deployments. + pub fn gen_test_scalar_sk() -> Scalar { + Scalar::from_repr( + hex!("519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464").into(), + ) + .unwrap() + } + + // Generates a deterministic r for deterministic testing. Should be replaced by random oracle in production deployments. + fn gen_test_scalar_r() -> Scalar { + Scalar::from_repr( + hex!("93b9323b629f251b8f3fc2dd11f4672c5544e8230d493eceea98a90bda789808").into(), + ) + .unwrap() + } + + // Calls the hash to curve function for secp256k1, and returns the result as a ProjectivePoint + pub fn hash_to_secp(s: &[u8]) -> ProjectivePoint { + let pt: ProjectivePoint = Secp256k1::hash_from_bytes::>( + &[s], + //b"CURVE_XMD:SHA-256_SSWU_RO_" + &[zk_nullifier::DST], + ) + .unwrap(); + pt + } + + // These generate test signals as if it were passed from a secure enclave to wallet. Note that leaking these signals would leak pk, but not sk. + // Outputs these 6 signals, in this order + // g^sk (private) + // hash[m, pk]^sk public nullifier + // c = hash2(g, pk, hash[m, pk], hash[m, pk]^sk, gr, hash[m, pk]^r) (public or private) + // r + sk * c (public or private) + // g^r (private, optional) + // hash[m, pk]^r (private, optional) + pub fn test_gen_signals( + m: &[u8], + version: PlumeVersion, + ) -> ( + ProjectivePoint, + ProjectivePoint, + Output, + Scalar, + Option, + Option, + ) { + // The base point or generator of the curve. + let g = ProjectivePoint::GENERATOR; + + // The signer's secret key. It is only accessed within the secure enclave. + let sk = gen_test_scalar_sk(); + + // A random value r. It is only accessed within the secure enclave. + let r = gen_test_scalar_r(); + + // The user's public key: g^sk. + let pk = &g * &sk; + + // The generator exponentiated by r: g^r. + let g_r = &g * &r; + + // hash[m, pk] + let hash_m_pk = + // zk_nullifier::hash_to_curve(m, &pk) + Secp256k1::hash_from_bytes::>( + &[[ + m, + // &encode_pt(pk) + &pk.to_encoded_point(true).to_bytes().to_vec() + ].concat().as_slice()], + //b"CURVE_XMD:SHA-256_SSWU_RO_", + &[zk_nullifier::DST], + ) + .unwrap(); + + println!( + "h.x: {:?}", + hex::encode(hash_m_pk.to_affine().to_encoded_point(false).x().unwrap()) + ); + println!( + "h.y: {:?}", + hex::encode(hash_m_pk.to_affine().to_encoded_point(false).y().unwrap()) + ); + + // hash[m, pk]^r + let hash_m_pk_pow_r = &hash_m_pk * &r; + println!( + "hash_m_pk_pow_r.x: {:?}", + hex::encode( + hash_m_pk_pow_r + .to_affine() + .to_encoded_point(false) + .x() + .unwrap() + ) + ); + println!( + "hash_m_pk_pow_r.y: {:?}", + hex::encode( + hash_m_pk_pow_r + .to_affine() + .to_encoded_point(false) + .y() + .unwrap() + ) + ); + + // The public nullifier: hash[m, pk]^sk. + let nullifier = &hash_m_pk * &sk; + + // The Fiat-Shamir type step. + let c = match version { + PlumeVersion::V1 => Sha256::digest( + vec![&g, &pk, &hash_m_pk, &nullifier, &g_r, &hash_m_pk_pow_r] + .into_iter() + .map(|x| x.to_encoded_point(true).to_bytes().to_vec()) + .collect::>() + .concat() + .as_slice(), + ), + PlumeVersion::V2 => { + dbg!("entering `Sha256::digest` for `V2`"); + let result = Sha256::digest( + vec![&nullifier, &g_r, &hash_m_pk_pow_r] + .into_iter() + .map(|x| x.to_encoded_point(true).to_bytes().to_vec()) + .collect::>() + .concat() + .as_slice(), + ); + dbg!("finished `Sha256::digest` for `V2`"); + result + } + }; + dbg!(&c, version); + + let c_scalar = &Scalar::reduce_nonzero(U256::from_be_byte_array(c.to_owned())); + // This value is part of the discrete log equivalence (DLEQ) proof. + let r_sk_c = r + sk * c_scalar; + + // Return the signature. + (pk, nullifier, c, r_sk_c, Some(g_r), Some(hash_m_pk_pow_r)) + } + + /* Yes, testing the tests isn't a conventional things. + This should be straightened if `helpers` will be refactored. */ + #[cfg(test)] + mod tests { + use super::*; + use k256::elliptic_curve::sec1::ToEncodedPoint; + // Test the hash-to-curve algorithm + #[test] + fn test_hash_to_curve() { + let h = hash_to_secp(b"abc"); + assert_eq!( + hex::encode(h.to_affine().to_encoded_point(false).x().unwrap()), + "3377e01eab42db296b512293120c6cee72b6ecf9f9205760bd9ff11fb3cb2c4b" + ); + assert_eq!( + hex::encode(h.to_affine().to_encoded_point(false).y().unwrap()), + "7f95890f33efebd1044d382a01b1bee0900fb6116f94688d487c6c7b9c8371f6" + ); + } + } +}