From 7c038a404ec2c743712b1fd49e51902d7c0eef3e Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sat, 8 Oct 2022 23:35:10 +0200 Subject: [PATCH 1/4] add wasm bindings --- rust-arkworks/Cargo.toml | 3 + rust-arkworks/src/hash_to_curve.rs | 33 ++-- rust-arkworks/src/lib.rs | 82 ++++---- rust-arkworks/src/tests.rs | 135 +++++++------ rust-arkworks/src/wasm.rs | 49 +++++ rust-k256/Cargo.lock | 76 ++++++-- rust-k256/Cargo.toml | 12 ++ rust-k256/src/lib.rs | 10 + rust-k256/src/main.rs | 297 ----------------------------- rust-k256/src/nullifier.rs | 129 +++++++++++++ rust-k256/src/serialize.rs | 35 ++++ rust-k256/src/tests.rs | 215 +++++++++++++++++++++ rust-k256/src/wasm.rs | 50 +++++ rust-k256/wasm-pack.sh | 3 + 14 files changed, 694 insertions(+), 435 deletions(-) create mode 100644 rust-arkworks/src/wasm.rs create mode 100644 rust-k256/src/lib.rs delete mode 100644 rust-k256/src/main.rs create mode 100644 rust-k256/src/nullifier.rs create mode 100644 rust-k256/src/serialize.rs create mode 100644 rust-k256/src/tests.rs create mode 100644 rust-k256/src/wasm.rs create mode 100755 rust-k256/wasm-pack.sh diff --git a/rust-arkworks/Cargo.toml b/rust-arkworks/Cargo.toml index d3e2cb4..815c1ea 100644 --- a/rust-arkworks/Cargo.toml +++ b/rust-arkworks/Cargo.toml @@ -21,6 +21,9 @@ elliptic-curve = { version = "0.12.2", features = ["arithmetic"]} k256 = {version = "0.11.3", features = ["arithmetic", "hash2curve", "expose-field", "sha2"] } generic-array = { version = "0.14", default-features = false } hex = "0.4.3" +wasm-bindgen = "0.2.83" +js-sys = "0.3.60" +console_error_panic_hook = "0.1.7" [patch.crates-io] ark-ec = { git = "https://github.com/FindoraNetwork/ark-algebra" } diff --git a/rust-arkworks/src/hash_to_curve.rs b/rust-arkworks/src/hash_to_curve.rs index bbe8133..6b542c6 100644 --- a/rust-arkworks/src/hash_to_curve.rs +++ b/rust-arkworks/src/hash_to_curve.rs @@ -1,23 +1,19 @@ use crate::error::CryptoError; +use ark_ec::short_weierstrass_jacobian::GroupAffine; use ark_ec::{AffineCurve, ProjectiveCurve}; -use tiny_keccak::{Hasher, Shake, Xof}; +use ark_ff::FromBytes; use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest}; -use k256::{AffinePoint}; -use k256::sha2::Sha256; use elliptic_curve::sec1::ToEncodedPoint; -use ark_ec::short_weierstrass_jacobian::GroupAffine; +use k256::sha2::Sha256; +use k256::AffinePoint; use k256::{ProjectivePoint, Secp256k1}; -use ark_ff::FromBytes; use secp256k1::Sec1EncodePoint; +use tiny_keccak::{Hasher, Shake, Xof}; -pub fn hash_to_curve< - Fp: ark_ff::PrimeField, - P: ark_ec::SWModelParameters, ->( +pub fn hash_to_curve( msg: &[u8], pk: &GroupAffine

, ) -> GroupAffine

{ - let pk_encoded = pk.to_encoded_point(true); let b = hex::decode(pk_encoded).unwrap(); let x = [msg, b.as_slice()]; @@ -26,8 +22,9 @@ pub fn hash_to_curve< let pt: ProjectivePoint = Secp256k1::hash_from_bytes::>( &[x], - b"QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_" - ).unwrap(); + b"QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_", + ) + .unwrap(); let pt_affine = pt.to_affine(); @@ -36,7 +33,7 @@ pub fn hash_to_curve< pub fn k256_affine_to_arkworks_secp256k1_affine< Fp: ark_ff::PrimeField, - P: ark_ec::SWModelParameters + P: ark_ec::SWModelParameters, >( k_pt: AffinePoint, ) -> GroupAffine

{ @@ -50,7 +47,10 @@ pub fn k256_affine_to_arkworks_secp256k1_affine< // pad x bytes let mut k_pt_x_bytes_vec = vec![0u8; num_field_bytes]; for (i, _) in k_pt_x_bytes.clone().iter().enumerate() { - let _ = std::mem::replace(&mut k_pt_x_bytes_vec[i], k_pt_x_bytes[k_pt_x_bytes.len() - 1 - i]); + let _ = std::mem::replace( + &mut k_pt_x_bytes_vec[i], + k_pt_x_bytes[k_pt_x_bytes.len() - 1 - i], + ); } let reader = std::io::BufReader::new(k_pt_x_bytes_vec.as_slice()); let g_x = P::BaseField::read(reader).unwrap(); @@ -61,7 +61,10 @@ pub fn k256_affine_to_arkworks_secp256k1_affine< // pad y bytes let mut k_pt_y_bytes_vec = vec![0u8; num_field_bytes]; for (i, _) in k_pt_y_bytes.clone().iter().enumerate() { - let _ = std::mem::replace(&mut k_pt_y_bytes_vec[i], k_pt_y_bytes[k_pt_y_bytes.len() - 1 - i]); + let _ = std::mem::replace( + &mut k_pt_y_bytes_vec[i], + k_pt_y_bytes[k_pt_y_bytes.len() - 1 - i], + ); } let reader = std::io::BufReader::new(k_pt_y_bytes_vec.as_slice()); diff --git a/rust-arkworks/src/lib.rs b/rust-arkworks/src/lib.rs index f974701..20b7609 100644 --- a/rust-arkworks/src/lib.rs +++ b/rust-arkworks/src/lib.rs @@ -1,33 +1,35 @@ mod error; mod hash_to_curve; +mod wasm; #[cfg(test)] mod tests; pub mod sig { use crate::error::CryptoError; use crate::hash_to_curve; - use ark_ec::{AffineCurve, ProjectiveCurve, models::SWModelParameters}; use ark_ec::short_weierstrass_jacobian::GroupAffine; + use ark_ec::{models::SWModelParameters, AffineCurve, ProjectiveCurve}; use ark_ff::{PrimeField, ToBytes}; - use ark_std::{ - marker::PhantomData, - UniformRand, - rand::Rng, + use ark_serialize::{ + CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write, }; - use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError, Read, Write}; - use sha2::{Sha512, Digest}; + use ark_std::{marker::PhantomData, rand::Rng, UniformRand}; use secp256k1::sec1::Sec1EncodePoint; - - pub struct DeterministicNullifierSignatureScheme<'a, C: ProjectiveCurve, Fq: ark_ff::PrimeField, P: ark_ec::SWModelParameters> { + use sha2::{Digest, Sha512}; + + pub struct DeterministicNullifierSignatureScheme< + 'a, + C: ProjectiveCurve, + Fq: ark_ff::PrimeField, + P: ark_ec::SWModelParameters, + > { _group: PhantomData, _field: PhantomData, _parameters: PhantomData

, _message_lifetime: PhantomData<&'a ()>, } - pub fn affine_to_bytes( - point: &GroupAffine

- ) -> Vec:: { + pub fn affine_to_bytes(point: &GroupAffine

) -> Vec { let encoded = point.to_encoded_point(true); let b = hex::decode(encoded).unwrap(); b.to_vec() @@ -59,14 +61,7 @@ pub mod sig { let g_r_bytes = affine_to_bytes::

(g_r); let z_bytes = affine_to_bytes::

(z); - let c_preimage_vec = [ - g_bytes, - pk_bytes, - h_bytes, - nul_bytes, - g_r_bytes, - z_bytes, - ].concat(); + let c_preimage_vec = [g_bytes, pk_bytes, h_bytes, nul_bytes, g_r_bytes, z_bytes].concat(); let mut sha512_hasher = Sha512::new(); sha512_hasher.update(c_preimage_vec.as_slice()); @@ -122,12 +117,22 @@ pub mod sig { ) -> Result; } - #[derive(Copy, Clone, ark_serialize_derive::CanonicalSerialize, ark_serialize_derive::CanonicalDeserialize)] + #[derive( + Copy, + Clone, + ark_serialize_derive::CanonicalSerialize, + ark_serialize_derive::CanonicalDeserialize, + )] pub struct Parameters { pub g: GroupAffine

, } - #[derive(Copy, Clone, ark_serialize_derive::CanonicalSerialize, ark_serialize_derive::CanonicalDeserialize)] + #[derive( + Copy, + Clone, + ark_serialize_derive::CanonicalSerialize, + ark_serialize_derive::CanonicalDeserialize, + )] pub struct Signature { pub z: GroupAffine

, pub g_r: GroupAffine

, @@ -136,7 +141,9 @@ pub mod sig { pub nul: GroupAffine

, } - impl<'a, C: ProjectiveCurve, Fq: PrimeField, P: SWModelParameters> VerifiableUnpredictableFunction for DeterministicNullifierSignatureScheme<'a, C, Fq, P> { + impl<'a, C: ProjectiveCurve, Fq: PrimeField, P: SWModelParameters> + VerifiableUnpredictableFunction for DeterministicNullifierSignatureScheme<'a, C, Fq, P> + { type Message = &'a [u8]; type Parameters = Parameters

; type PublicKey = GroupAffine

; @@ -151,7 +158,7 @@ pub mod sig { let public_key = pp.g.mul(secret_key).into(); Ok((public_key, secret_key)) } - + fn sign_with_r( pp: &Self::Parameters, keypair: (&Self::PublicKey, &Self::SecretKey), @@ -163,7 +170,7 @@ pub mod sig { // Compute h = htc([m, pk]) let h = compute_h::(&keypair.0, &message).unwrap(); - + // Compute z = h^r let z = h.mul(r).into_affine(); @@ -171,14 +178,7 @@ pub mod sig { let nul = h.mul(*keypair.1).into_affine(); // Compute c = sha512([g, pk, h, nul, g^r, z]) - let c_scalar: P::ScalarField = compute_c::

( - &g, - keypair.0, - &h, - &nul, - &g_r, - &z, - ); + let c_scalar: P::ScalarField = compute_c::

(&g, keypair.0, &h, &nul, &g_r, &z); // Compute s = r + sk ⋅ c let sk_c = keypair.1.into_repr().into() * c_scalar.into_repr().into(); @@ -191,7 +191,7 @@ pub mod sig { s: s_scalar, g_r, c: c_scalar, - nul + nul, }; Ok(signature) } @@ -218,14 +218,8 @@ pub mod sig { let h = compute_h::(pk, message).unwrap(); // Compute c' = sha512([g, pk, h, nul, g^r, z]) - let c_scalar: P::ScalarField = compute_c::

( - &pp.g, - pk, - &h, - &sig.nul, - &sig.g_r, - &sig.z, - ); + let c_scalar: P::ScalarField = + compute_c::

(&pp.g, pk, &h, &sig.nul, &sig.g_r, &sig.z); // Reject if g^s ⋅ pk^{-c} != g^r let g_s = pp.g.mul(sig.s); @@ -242,14 +236,14 @@ pub mod sig { let h_s_nul_c = h_s - nul_c; if sig.z != h_s_nul_c { - return Ok(false) + return Ok(false); } // Reject if c != c' if c_scalar != sig.c { return Ok(false); } - + Ok(true) } } diff --git a/rust-arkworks/src/tests.rs b/rust-arkworks/src/tests.rs index e23f550..0749939 100644 --- a/rust-arkworks/src/tests.rs +++ b/rust-arkworks/src/tests.rs @@ -1,19 +1,16 @@ -use secp256k1::curves::Affine; -use secp256k1::curves::Secp256k1Parameters; -use secp256k1::fields::Fq; -use crate::sig::VerifiableUnpredictableFunction; -use crate::hash_to_curve::{ - hash_to_curve, - k256_affine_to_arkworks_secp256k1_affine, -}; -use ark_std::rand; +use crate::hash_to_curve::{hash_to_curve, k256_affine_to_arkworks_secp256k1_affine}; use crate::sig::DeterministicNullifierSignatureScheme; -use ark_ec::{AffineCurve, ProjectiveCurve}; +use crate::sig::VerifiableUnpredictableFunction; use ark_ec::models::short_weierstrass_jacobian::GroupAffine; -use ark_ff::bytes::{ToBytes, FromBytes}; +use ark_ec::{AffineCurve, ProjectiveCurve}; use ark_ff::biginteger; -use rand::{prelude::ThreadRng, thread_rng}; +use ark_ff::bytes::{FromBytes, ToBytes}; +use ark_std::rand; use k256::{ProjectivePoint, Scalar}; +use rand::{prelude::ThreadRng, thread_rng}; +use secp256k1::curves::Affine; +use secp256k1::curves::Secp256k1Parameters; +use secp256k1::fields::Fq; type Parameters = crate::sig::Parameters; @@ -24,7 +21,8 @@ fn test_template() -> (ThreadRng, Affine) { (rng, g) } -type Scheme<'a> = DeterministicNullifierSignatureScheme::<'a, secp256k1::Projective, Fq, Secp256k1Parameters>; +type Scheme<'a> = + DeterministicNullifierSignatureScheme<'a, secp256k1::Projective, Fq, Secp256k1Parameters>; #[test] pub fn test_k256_affine_to_arkworks_secp256k1_affine() { @@ -40,7 +38,7 @@ pub fn test_k256_affine_to_arkworks_secp256k1_affine() { // Convert k256_pt to an arkworks point let converted_pt = k256_affine_to_arkworks_secp256k1_affine::< secp256k1::fields::Fq, - Secp256k1Parameters + Secp256k1Parameters, >(k256_pt.to_affine()); // The points should match @@ -48,9 +46,7 @@ pub fn test_k256_affine_to_arkworks_secp256k1_affine() { } } -fn hex_to_fr( - hex: &str, -) -> secp256k1::fields::Fr { +fn hex_to_fr(hex: &str) -> secp256k1::fields::Fr { let num_field_bytes = 320; let mut sk_bytes_vec = vec![0u8; num_field_bytes]; let mut sk_bytes = hex::decode(hex).unwrap(); @@ -64,9 +60,7 @@ fn hex_to_fr( secp256k1::fields::Fr::read(sk_bytes_vec.as_slice()).unwrap() } -fn coord_to_hex( - coord: biginteger::BigInteger320 -) -> String { +fn coord_to_hex(coord: biginteger::BigInteger320) -> String { let mut coord_bytes = vec![]; let _ = coord.write(&mut coord_bytes); coord_bytes.reverse(); @@ -75,11 +69,11 @@ fn coord_to_hex( } fn hardcoded_sk() -> String { - "519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464".to_string() + "519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464".to_string() } fn hardcoded_r() -> String { - "93b9323b629f251b8f3fc2dd11f4672c5544e8230d493eceea98a90bda789808".to_string() + "93b9323b629f251b8f3fc2dd11f4672c5544e8230d493eceea98a90bda789808".to_string() } pub fn hardcoded_msg() -> String { @@ -89,7 +83,7 @@ pub fn hardcoded_msg() -> String { #[test] pub fn test_keygen() { let (mut rng, g) = test_template(); - let pp = Parameters{ g }; + let pp = Parameters { g }; let (pk, sk) = Scheme::keygen(&pp, &mut rng).unwrap(); @@ -100,31 +94,21 @@ pub fn test_keygen() { #[test] pub fn test_sign_and_verify() { let (mut rng, g) = test_template(); - let pp = Parameters{ g }; + let pp = Parameters { g }; let message = b"Message"; let keypair = Scheme::keygen(&pp, &mut rng).unwrap(); - let sig = Scheme::sign( - &pp, - &mut rng, - (&keypair.0, &keypair.1), - message - ).unwrap(); - - let is_valid = Scheme::verify_non_zk( - &pp, - &keypair.0, - &sig, - message, - ); + let sig = Scheme::sign(&pp, &mut rng, (&keypair.0, &keypair.1), message).unwrap(); + + let is_valid = Scheme::verify_non_zk(&pp, &keypair.0, &sig, message); assert!(is_valid.unwrap()); } -pub fn compute_h() -> GroupAffine:: { +pub fn compute_h() -> GroupAffine { let msg = hardcoded_msg(); let message = msg.as_bytes(); - + let sk = hex_to_fr(&hardcoded_sk()); let (_, g) = test_template(); let pk_projective = g.mul(sk); @@ -142,9 +126,15 @@ pub fn test_against_zk_nullifier_sig_pk() { let (_, g) = test_template(); let pk_projective = g.mul(sk); let pk = GroupAffine::::from(pk_projective); - - assert_eq!(coord_to_hex(pk.x.into()), "00000000000000000cec028ee08d09e02672a68310814354f9eabfff0de6dacc1cd3a774496076ae"); - assert_eq!(coord_to_hex(pk.y.into()), "0000000000000000eff471fba0409897b6a48e8801ad12f95d0009b753cf8f51c128bf6b0bd27fbd"); + + assert_eq!( + coord_to_hex(pk.x.into()), + "00000000000000000cec028ee08d09e02672a68310814354f9eabfff0de6dacc1cd3a774496076ae" + ); + assert_eq!( + coord_to_hex(pk.y.into()), + "0000000000000000eff471fba0409897b6a48e8801ad12f95d0009b753cf8f51c128bf6b0bd27fbd" + ); } #[test] @@ -154,8 +144,14 @@ pub fn test_against_zk_nullifier_sig_g_r() { let (_, g) = test_template(); let g_r_projective = g.mul(r); let g_r = GroupAffine::::from(g_r_projective); - assert_eq!(coord_to_hex(g_r.x.into()), "00000000000000009d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804"); - assert_eq!(coord_to_hex(g_r.y.into()), "0000000000000000ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1"); + assert_eq!( + coord_to_hex(g_r.x.into()), + "00000000000000009d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804" + ); + assert_eq!( + coord_to_hex(g_r.y.into()), + "0000000000000000ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1" + ); } //TODO: add test vectors for hash_to_curve @@ -163,8 +159,14 @@ pub fn test_against_zk_nullifier_sig_g_r() { pub fn test_against_zk_nullifier_sig_h() { let h = compute_h(); - assert_eq!(coord_to_hex(h.x.into()), "0000000000000000bcac2d0e12679f23c218889395abcdc01f2affbc49c54d1136a2190db0800b65"); - assert_eq!(coord_to_hex(h.y.into()), "00000000000000003bcfb339c974c0e757d348081f90a123b0a91a53e32b3752145d87f0cd70966e"); + assert_eq!( + coord_to_hex(h.x.into()), + "0000000000000000bcac2d0e12679f23c218889395abcdc01f2affbc49c54d1136a2190db0800b65" + ); + assert_eq!( + coord_to_hex(h.y.into()), + "00000000000000003bcfb339c974c0e757d348081f90a123b0a91a53e32b3752145d87f0cd70966e" + ); } #[test] @@ -175,8 +177,14 @@ pub fn test_against_zk_nullifier_sig_h_r() { let r = secp256k1::fields::Fr::from(hex_to_fr(&hardcoded_r())); let h_r_projective = h.mul(r); let h_r = GroupAffine::::from(h_r_projective); - assert_eq!(coord_to_hex(h_r.x.into()), "00000000000000006d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c"); - assert_eq!(coord_to_hex(h_r.y.into()), "0000000000000000586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed"); + assert_eq!( + coord_to_hex(h_r.x.into()), + "00000000000000006d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c" + ); + assert_eq!( + coord_to_hex(h_r.y.into()), + "0000000000000000586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed" + ); } #[test] @@ -187,8 +195,14 @@ pub fn test_against_zk_nullifier_sig_h_sk() { // Test h^r using the hardcoded sk let h_sk_projective = h.mul(sk); let h_sk = GroupAffine::::from(h_sk_projective); - assert_eq!(coord_to_hex(h_sk.x.into()), "000000000000000057bc3ed28172ef8adde4b9e0c2cce745fcc5a66473a45c1e626f1d0c67e55830"); - assert_eq!(coord_to_hex(h_sk.y.into()), "00000000000000006a2f41488d58f33ae46edd2188e111609f9f3ae67ea38fa891d6087fe59ecb73"); + assert_eq!( + coord_to_hex(h_sk.x.into()), + "000000000000000057bc3ed28172ef8adde4b9e0c2cce745fcc5a66473a45c1e626f1d0c67e55830" + ); + assert_eq!( + coord_to_hex(h_sk.y.into()), + "00000000000000006a2f41488d58f33ae46edd2188e111609f9f3ae67ea38fa891d6087fe59ecb73" + ); } #[test] @@ -198,18 +212,19 @@ pub fn test_against_zk_nullifier_sig_c_and_s() { let message = message.as_bytes(); let sk = hex_to_fr(&hardcoded_sk()); let (_, g) = test_template(); - let pp = Parameters{ g }; + let pp = Parameters { g }; let pk_projective = g.mul(sk); let pk = GroupAffine::::from(pk_projective); let keypair = (pk, sk); - let sig = Scheme::sign_with_r( - &pp, - (&keypair.0, &keypair.1), - message, - r - ).unwrap(); - - assert_eq!(coord_to_hex(sig.c.into()), "00000000000000007da1ad3f63c6180beefd0d6a8e3c87620b54f1b1d2c8287d104da9e53b6b5524"); - assert_eq!(coord_to_hex(sig.s.into()), "0000000000000000638330fea277e97ad407b32c9dc4d522454f5483abd903e6710a59d14f6fbdf2"); + let sig = Scheme::sign_with_r(&pp, (&keypair.0, &keypair.1), message, r).unwrap(); + + assert_eq!( + coord_to_hex(sig.c.into()), + "00000000000000007da1ad3f63c6180beefd0d6a8e3c87620b54f1b1d2c8287d104da9e53b6b5524" + ); + assert_eq!( + coord_to_hex(sig.s.into()), + "0000000000000000638330fea277e97ad407b32c9dc4d522454f5483abd903e6710a59d14f6fbdf2" + ); } diff --git a/rust-arkworks/src/wasm.rs b/rust-arkworks/src/wasm.rs new file mode 100644 index 0000000..257c79f --- /dev/null +++ b/rust-arkworks/src/wasm.rs @@ -0,0 +1,49 @@ +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; +use js_sys::Error; + + +fn compute_h(message: String, sk_hex: String) -> GroupAffine:: { + let sk = hex_to_fr(&sk_hex); + let g = Affine::prime_subgroup_generator(); + let pk_projective = g.mul(sk); + let pk = GroupAffine::::from(pk_projective); + + let h = hash_to_curve::(message.to_bytes(), &pk); + h +} + +fn hex_to_fr( + hex: &str, +) -> secp256k1::fields::Fr { + let num_field_bytes = 320; + let mut sk_bytes_vec = vec![0u8; num_field_bytes]; + let mut sk_bytes = hex::decode(hex).unwrap(); + + sk_bytes.reverse(); + + for (i, _) in sk_bytes.clone().iter().enumerate() { + let _ = std::mem::replace(&mut sk_bytes_vec[i], sk_bytes[i]); + } + + secp256k1::fields::Fr::read(sk_bytes_vec.as_slice()).unwrap() +} + +fn coord_to_hex(coord: biginteger::BigInteger320) -> String { + let mut coord_bytes = vec![]; + let _ = coord.write(&mut coord_bytes); + coord_bytes.reverse(); + + String::from(hex::encode(coord_bytes)) +} + + +#[wasm_bindgen] +pub fn make_nullifier(sk_hex: String, message: String) -> String { + let h = compute_h(&message); + let sk = hex_to_fr(&sk_hex); + + let h_sk_projective = h.mul(sk); + let h_sk = GroupAffine::::from(h_sk_projective); + let h_sk_hex = coord_to_hex(h_sk.x.into()); + h_sk_hex +} \ No newline at end of file diff --git a/rust-k256/Cargo.lock b/rust-k256/Cargo.lock index af6ad00..c7a9dff 100644 --- a/rust-k256/Cargo.lock +++ b/rust-k256/Cargo.lock @@ -47,6 +47,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.7.1" @@ -233,8 +243,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -293,9 +305,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -312,12 +324,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.126" @@ -379,6 +385,12 @@ dependencies = [ "spki", ] +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro2" version = "1.0.40" @@ -397,6 +409,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.3" @@ -532,9 +565,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -542,13 +575,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -557,9 +590,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -567,9 +600,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -580,9 +613,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" @@ -626,15 +659,20 @@ checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" name = "zk-nullifier" version = "0.1.0" dependencies = [ + "console_error_panic_hook", "crate", "digest 0.10.3", "elliptic-curve 0.12.2", + "getrandom", "hash2field", "hex", "hex-literal", + "js-sys", "k256", "num-bigint", "num-integer", + "rand", "rand_core", "ring", + "wasm-bindgen", ] diff --git a/rust-k256/Cargo.toml b/rust-k256/Cargo.toml index 28b0d8d..5efc2d3 100644 --- a/rust-k256/Cargo.toml +++ b/rust-k256/Cargo.toml @@ -5,6 +5,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "zk_nullifier" +path = "src/lib.rs" +crate-type = ["cdylib", "rlib"] + [dependencies] crate = "0.0.2" digest = "0.10.3" @@ -17,3 +22,10 @@ num-integer = "0.1.45" k256 = {version = "0.11.3", features = ["arithmetic", "hash2curve", "expose-field", "sha2"]} elliptic-curve = { version = "0.12.2", features = ["arithmetic"]} hex = "0.4.3" + +# TODO: Move those to 'wasm' feature +wasm-bindgen = "0.2.83" +js-sys = "0.3.60" +console_error_panic_hook = "0.1.7" +getrandom = { version = "0.2", features = ["js"] } +rand = "0.8.5" diff --git a/rust-k256/src/lib.rs b/rust-k256/src/lib.rs new file mode 100644 index 0000000..06f5282 --- /dev/null +++ b/rust-k256/src/lib.rs @@ -0,0 +1,10 @@ +#![allow(dead_code)] +#![allow(unused_variables)] +// #![feature(generic_const_expr)] +// #![allow(incomplete_features)] + +mod nullifier; +mod serialize; +#[cfg(test)] +mod tests; +mod wasm; diff --git a/rust-k256/src/main.rs b/rust-k256/src/main.rs deleted file mode 100644 index 556e65d..0000000 --- a/rust-k256/src/main.rs +++ /dev/null @@ -1,297 +0,0 @@ -#![allow(dead_code)] -#![allow(unused_variables)] -// #![feature(generic_const_expr)] -// #![allow(incomplete_features)] - -use elliptic_curve::sec1::ToEncodedPoint; -use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest}; -use hex_literal::hex; -use k256::{ - // ecdsa::{signature::Signer, Signature, SigningKey}, - elliptic_curve::group::ff::PrimeField, - sha2::{Digest, Sha256, Sha512}, - FieldBytes, - ProjectivePoint, - Scalar, - Secp256k1, -}; // requires 'getrandom' feature - -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 - -#[derive(Debug, PartialEq)] -pub enum Error { - IsPointAtInfinityError, -} - - -fn print_type_of(_: &T) { - println!("{}", std::any::type_name::()); -} - -// Generates a deterministic secret key for us temporarily. Can be replaced by random oracle anytime. -fn gen_test_scalar_x() -> Scalar { - Scalar::from_repr( - hex!("519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464").into(), - ) - .unwrap() -} - -// Generates a deterministic r for us temporarily. Can be replaced by random oracle anytime. -fn gen_test_scalar_r() -> Scalar { - Scalar::from_repr( - hex!("93b9323b629f251b8f3fc2dd11f4672c5544e8230d493eceea98a90bda789808").into(), - ) - .unwrap() -} - -// 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) -fn test_gen_signals( - m: &[u8], -) -> ( - ProjectivePoint, - ProjectivePoint, - Scalar, - 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_x(); - - // 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_m_pk_to_secp(m, &pk); - - 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 = sha512hash6signals(&g, &pk, &hash_m_pk, &nullifier, &g_r, &hash_m_pk_pow_r); - - // This value is part of the discrete log equivalence (DLEQ) proof. - let r_sk_c = r + sk * c; - - // Return the signature. - (pk, nullifier, c, r_sk_c, Some(g_r), Some(hash_m_pk_pow_r)) -} - -fn sha512hash6signals( - g: &ProjectivePoint, - pk: &ProjectivePoint, - hash_m_pk: &ProjectivePoint, - nullifier: &ProjectivePoint, - g_r: &ProjectivePoint, - hash_m_pk_pow_r: &ProjectivePoint, -) -> Scalar { - let g_bytes = encode_pt(*g).unwrap(); - let pk_bytes = encode_pt(*pk).unwrap(); - let h_bytes = encode_pt(*hash_m_pk).unwrap(); - let nul_bytes = encode_pt(*nullifier).unwrap(); - let g_r_bytes = encode_pt(*g_r).unwrap(); - let z_bytes = encode_pt(*hash_m_pk_pow_r).unwrap(); - - let c_preimage_vec = [ - g_bytes, - pk_bytes, - h_bytes, - nul_bytes, - g_r_bytes, - z_bytes, - ].concat(); - - //println!("c_preimage_vec: {:?}", c_preimage_vec); - - let mut sha512_hasher = Sha512::new(); - sha512_hasher.update(c_preimage_vec.as_slice()); - let sha512_hasher_result = sha512_hasher.finalize(); //512 bit hash - - let c_bytes = FieldBytes::from_iter(sha512_hasher_result.iter().copied()); - let c_scalar = Scalar::from_repr(c_bytes).unwrap(); - c_scalar -} - -// Calls the hash to curve function for secp256k1, and returns the result as a ProjectivePoint -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 -} - -// Hashes two values to the curve -fn hash_m_pk_to_secp(m: &[u8], pk: &ProjectivePoint) -> ProjectivePoint { - let pt: ProjectivePoint = Secp256k1::hash_from_bytes::>( - &[[m, &encode_pt(*pk).unwrap()].concat().as_slice()], - //b"CURVE_XMD:SHA-256_SSWU_RO_", - DST - ) - .unwrap(); - pt -} - -// Verifier check in SNARK: -// g^[r + sk * c] / (g^sk)^c = g^r -// hash[m, gsk]^[r + sk * c] / (hash[m, pk]^sk)^c = hash[m, pk]^r -// c = hash2(g, g^sk, hash[m, g^sk], hash[m, pk]^sk, gr, hash[m, pk]^r) -fn verify_signals( - m: &[u8], - pk: &ProjectivePoint, - nullifier: &ProjectivePoint, - c: &Scalar, - r_sk_c: &Scalar, - g_r_option: &Option, - hash_m_pk_pow_r_option: &Option, -) -> bool { - let mut verified: bool = true; - - // The base point or generator of the curve. - let g = &ProjectivePoint::GENERATOR; - - // hash[m, pk] - let hash_m_pk = &hash_m_pk_to_secp(m, pk); - - // Check whether g^r equals g^s * pk^{-c} - let g_r: ProjectivePoint; - match *g_r_option { - Some(_g_r_value) => { - if (g * r_sk_c - pk * c) != _g_r_value { - verified = false; - } - } - None => println!("g^r not provided, check skipped"), - } - g_r = g * r_sk_c - pk * c; - - // Check whether h^r equals h^{r + sk * c} * nullifier^{-c} - let hash_m_pk_pow_r: ProjectivePoint; - match *hash_m_pk_pow_r_option { - Some(_hash_m_pk_pow_r_value) => { - if (hash_m_pk * r_sk_c - nullifier * c) != _hash_m_pk_pow_r_value { - verified = false; - } - } - None => println!("hash_m_pk_pow_r not provided, check skipped"), - } - hash_m_pk_pow_r = hash_m_pk * r_sk_c - nullifier * c; - - // Check if the given hash matches - if (sha512hash6signals(g, pk, hash_m_pk, nullifier, &g_r, &hash_m_pk_pow_r)) != *c { - verified = false; - } - verified -} - -// NOTE: MAKE SURE TO HAVE RUST-ANALYZER ENABLED IN VSCODE EXTENSIONS TO FILL IN INFERRED TYPES -fn main() -> Result<(), ()> { - 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); - - // The signer's secret key. It is only accessed within the secure enclave. - let sk = gen_test_scalar_x(); - - // 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 = verify_signals(m, &pk, &nullifier, &c, &r_sk_c, &g_r, &hash_m_pk_pow_r); - 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.to_bytes())); - - // 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).unwrap(); - assert_eq!(hex::encode(g_as_bytes), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"); - - // Test byte_array_to_scalar() - let bytes_to_convert = c.to_bytes(); - let scalar = byte_array_to_scalar(&bytes_to_convert); - assert_eq!(hex::encode(scalar.to_bytes()), "7da1ad3f63c6180beefd0d6a8e3c87620b54f1b1d2c8287d104da9e53b6b5524"); - - // 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"); - - - Ok(()) -} - -/// Format a ProjectivePoint to 64 bytes - the concatenation of the x and y values. We use 64 -/// bytes instead of SEC1 encoding as our arkworks secp256k1 implementation doesn't support SEC1 -/// encoding yet. -fn encode_pt( - point: ProjectivePoint -) -> Result, Error> { - let encoded = point.to_encoded_point(true); - Ok(encoded.to_bytes().to_vec()) -} - -/// Convert a 32-byte array to a scalar -fn byte_array_to_scalar( - bytes: &[u8], -) -> Scalar { - // From https://docs.rs/ark-ff/0.3.0/src/ark_ff/fields/mod.rs.html#371-393 - assert!(bytes.len() == 32); - let mut res = Scalar::from(0u64); - let window_size = Scalar::from(256u64); - for byte in bytes.iter() { - res *= window_size; - res += Scalar::from(*byte as u64); - } - res -} diff --git a/rust-k256/src/nullifier.rs b/rust-k256/src/nullifier.rs new file mode 100644 index 0000000..47f5e02 --- /dev/null +++ b/rust-k256/src/nullifier.rs @@ -0,0 +1,129 @@ +#![allow(dead_code)] +#![allow(unused_variables)] +// #![feature(generic_const_expr)] +// #![allow(incomplete_features)] + +use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest}; +use k256::{ + // ecdsa::{signature::Signer, Signature, SigningKey}, + elliptic_curve::group::ff::PrimeField, + sha2::{Digest, Sha256, Sha512}, + FieldBytes, + ProjectivePoint, + Scalar, + Secp256k1, +}; // requires 'getrandom' feature + +use crate::serialize::encode_pt; + +const L: usize = 48; +const COUNT: usize = 2; +const OUT: usize = L * COUNT; +pub const DST: &[u8] = b"QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_"; // Hash to curve algorithm + +#[derive(Debug, PartialEq)] +pub enum Error { + IsPointAtInfinityError, +} + +pub fn sha512hash6signals( + g: &ProjectivePoint, + pk: &ProjectivePoint, + hash_m_pk: &ProjectivePoint, + nullifier: &ProjectivePoint, + g_r: &ProjectivePoint, + hash_m_pk_pow_r: &ProjectivePoint, +) -> Scalar { + let g_bytes = encode_pt(*g).unwrap(); + let pk_bytes = encode_pt(*pk).unwrap(); + let h_bytes = encode_pt(*hash_m_pk).unwrap(); + let nul_bytes = encode_pt(*nullifier).unwrap(); + let g_r_bytes = encode_pt(*g_r).unwrap(); + let z_bytes = encode_pt(*hash_m_pk_pow_r).unwrap(); + + let c_preimage_vec = [g_bytes, pk_bytes, h_bytes, nul_bytes, g_r_bytes, z_bytes].concat(); + + //println!("c_preimage_vec: {:?}", c_preimage_vec); + + let mut sha512_hasher = Sha512::new(); + sha512_hasher.update(c_preimage_vec.as_slice()); + let sha512_hasher_result = sha512_hasher.finalize(); //512 bit hash + + let c_bytes = FieldBytes::from_iter(sha512_hasher_result.iter().copied()); + let c_scalar = Scalar::from_repr(c_bytes).unwrap(); + c_scalar +} + +// 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 +} + +// Hashes two values to the curve +pub fn hash_m_pk_to_secp(m: &[u8], pk: &ProjectivePoint) -> ProjectivePoint { + let pt: ProjectivePoint = Secp256k1::hash_from_bytes::>( + &[[m, &encode_pt(*pk).unwrap()].concat().as_slice()], + //b"CURVE_XMD:SHA-256_SSWU_RO_", + DST, + ) + .unwrap(); + pt +} + +// Verifier check in SNARK: +// g^[r + sk * c] / (g^sk)^c = g^r +// hash[m, gsk]^[r + sk * c] / (hash[m, pk]^sk)^c = hash[m, pk]^r +// c = hash2(g, g^sk, hash[m, g^sk], hash[m, pk]^sk, gr, hash[m, pk]^r) +pub fn verify_signals( + m: &[u8], + pk: &ProjectivePoint, + nullifier: &ProjectivePoint, + c: &Scalar, + r_sk_c: &Scalar, + g_r_option: &Option, + hash_m_pk_pow_r_option: &Option, +) -> bool { + let mut verified: bool = true; + + // The base point or generator of the curve. + let g = &ProjectivePoint::GENERATOR; + + // hash[m, pk] + let hash_m_pk = &hash_m_pk_to_secp(m, pk); + + // Check whether g^r equals g^s * pk^{-c} + let g_r: ProjectivePoint; + match *g_r_option { + Some(_g_r_value) => { + if (g * r_sk_c - pk * c) != _g_r_value { + verified = false; + } + } + None => println!("g^r not provided, check skipped"), + } + g_r = g * r_sk_c - pk * c; + + // Check whether h^r equals h^{r + sk * c} * nullifier^{-c} + let hash_m_pk_pow_r: ProjectivePoint; + match *hash_m_pk_pow_r_option { + Some(_hash_m_pk_pow_r_value) => { + if (hash_m_pk * r_sk_c - nullifier * c) != _hash_m_pk_pow_r_value { + verified = false; + } + } + None => println!("hash_m_pk_pow_r not provided, check skipped"), + } + hash_m_pk_pow_r = hash_m_pk * r_sk_c - nullifier * c; + + // Check if the given hash matches + if (sha512hash6signals(g, pk, hash_m_pk, nullifier, &g_r, &hash_m_pk_pow_r)) != *c { + verified = false; + } + verified +} diff --git a/rust-k256/src/serialize.rs b/rust-k256/src/serialize.rs new file mode 100644 index 0000000..6034fa6 --- /dev/null +++ b/rust-k256/src/serialize.rs @@ -0,0 +1,35 @@ +use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest}; +use elliptic_curve::sec1::ToEncodedPoint; +use hex_literal::hex; +use k256::{ + // ecdsa::{signature::Signer, Signature, SigningKey}, + elliptic_curve::group::ff::PrimeField, + sha2::{Digest, Sha256, Sha512}, + FieldBytes, + ProjectivePoint, + Scalar, + Secp256k1, +}; + +use crate::nullifier::Error; + +// Format a ProjectivePoint to 64 bytes - the concatenation of the x and y values. We use 64 +/// bytes instead of SEC1 encoding as our arkworks secp256k1 implementation doesn't support SEC1 +/// encoding yet. +pub fn encode_pt(point: ProjectivePoint) -> Result, Error> { + let encoded = point.to_encoded_point(true); + Ok(encoded.to_bytes().to_vec()) +} + +/// Convert a 32-byte array to a scalar +pub fn byte_array_to_scalar(bytes: &[u8]) -> Scalar { + // From https://docs.rs/ark-ff/0.3.0/src/ark_ff/fields/mod.rs.html#371-393 + assert!(bytes.len() == 32); + let mut res = Scalar::from(0u64); + let window_size = Scalar::from(256u64); + for byte in bytes.iter() { + res *= window_size; + res += Scalar::from(*byte as u64); + } + res +} diff --git a/rust-k256/src/tests.rs b/rust-k256/src/tests.rs new file mode 100644 index 0000000..c8589e0 --- /dev/null +++ b/rust-k256/src/tests.rs @@ -0,0 +1,215 @@ +use elliptic_curve::sec1::ToEncodedPoint; +use hex_literal::hex; +use k256::{elliptic_curve::group::ff::PrimeField, ProjectivePoint, Scalar}; // requires 'getrandom' feature + +use crate::nullifier::*; +use crate::serialize::*; + +// Generates a deterministic secret key for us temporarily. Can be replaced by random oracle anytime. +fn gen_test_scalar_x() -> Scalar { + Scalar::from_repr( + hex!("519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464").into(), + ) + .unwrap() +} + +// Generates a deterministic r for us temporarily. Can be replaced by random oracle anytime. +fn gen_test_scalar_r() -> Scalar { + Scalar::from_repr( + hex!("93b9323b629f251b8f3fc2dd11f4672c5544e8230d493eceea98a90bda789808").into(), + ) + .unwrap() +} + +// 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) +fn test_gen_signals( + m: &[u8], +) -> ( + ProjectivePoint, + ProjectivePoint, + Scalar, + 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_x(); + + // 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_m_pk_to_secp(m, &pk); + + 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 = sha512hash6signals(&g, &pk, &hash_m_pk, &nullifier, &g_r, &hash_m_pk_pow_r); + + // This value is part of the discrete log equivalence (DLEQ) proof. + let r_sk_c = r + sk * c; + + // Return the signature. + (pk, nullifier, c, r_sk_c, Some(g_r), Some(hash_m_pk_pow_r)) +} + +// NOTE: MAKE SURE TO HAVE RUST-ANALYZER ENABLED IN VSCODE EXTENSIONS TO FILL IN INFERRED TYPES +#[test] +fn main() { + 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); + + // The signer's secret key. It is only accessed within the secure enclave. + let sk = gen_test_scalar_x(); + + // 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 = verify_signals(m, &pk, &nullifier, &c, &r_sk_c, &g_r, &hash_m_pk_pow_r); + 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.to_bytes())); + + // 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).unwrap(); + assert_eq!( + hex::encode(g_as_bytes), + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ); + + // Test byte_array_to_scalar() + let bytes_to_convert = c.to_bytes(); + let scalar = byte_array_to_scalar(&bytes_to_convert); + assert_eq!( + hex::encode(scalar.to_bytes()), + "7da1ad3f63c6180beefd0d6a8e3c87620b54f1b1d2c8287d104da9e53b6b5524" + ); + + // 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" + ); +} diff --git a/rust-k256/src/wasm.rs b/rust-k256/src/wasm.rs new file mode 100644 index 0000000..0c5421b --- /dev/null +++ b/rust-k256/src/wasm.rs @@ -0,0 +1,50 @@ +use k256::{elliptic_curve::group::ff::PrimeField, ProjectivePoint, Scalar}; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use wasm_bindgen::prelude::{wasm_bindgen}; // requires 'getrandom' feature + +use crate::nullifier::{hash_m_pk_to_secp}; +use crate::serialize::{byte_array_to_scalar, encode_pt}; + +#[wasm_bindgen] +pub fn make_nullifier(sk_hex: String, message: String, rng_seed: &[u8]) -> String { + console_error_panic_hook::set_once(); + + let sk = hex::decode(sk_hex).unwrap(); + let sk = byte_array_to_scalar(&sk); + + // The base point or generator of the curve. + let g = ProjectivePoint::GENERATOR; + + let rng_seed: [u8; 32] = rng_seed.try_into().unwrap(); + let rng = &mut StdRng::from_seed(rng_seed); + let r_bytes = rng.gen::<[u8; 32]>(); + // The signer's secret key. It is only accessed within the secure enclave. + let r = Scalar::from_repr(r_bytes.into()).unwrap(); + + // 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_m_pk_to_secp(message.as_bytes(), &pk); + + // hash[m, pk]^r + let hash_m_pk_pow_r = &hash_m_pk * &r; + + // The public nullifier: hash[m, pk]^sk. + let nullifier = &hash_m_pk * &sk; + + // // The Fiat-Shamir type step. + // let c = sha512hash6signals(&g, &pk, &hash_m_pk, &nullifier, &g_r, &hash_m_pk_pow_r); + + // // This value is part of the discrete log equivalence (DLEQ) proof. + // let r_sk_c = r + sk * c; + + // // Return the signature. + // (pk, nullifier, c, r_sk_c, g_r, hash_m_pk_pow_r) + + let nullifier = encode_pt(nullifier).unwrap(); + hex::encode(nullifier) +} \ No newline at end of file diff --git a/rust-k256/wasm-pack.sh b/rust-k256/wasm-pack.sh new file mode 100755 index 0000000..6c5a026 --- /dev/null +++ b/rust-k256/wasm-pack.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +wasm-pack build --target web --release \ No newline at end of file From 455551b15af37702e634de077042c943981af94e Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sun, 9 Oct 2022 01:31:43 +0200 Subject: [PATCH 2/4] make signature --- rust-k256/src/wasm.rs | 76 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/rust-k256/src/wasm.rs b/rust-k256/src/wasm.rs index 0c5421b..a73918e 100644 --- a/rust-k256/src/wasm.rs +++ b/rust-k256/src/wasm.rs @@ -1,8 +1,8 @@ use k256::{elliptic_curve::group::ff::PrimeField, ProjectivePoint, Scalar}; use rand::{rngs::StdRng, Rng, SeedableRng}; -use wasm_bindgen::prelude::{wasm_bindgen}; // requires 'getrandom' feature +use wasm_bindgen::prelude::wasm_bindgen; // requires 'getrandom' feature -use crate::nullifier::{hash_m_pk_to_secp}; +use crate::nullifier::{hash_m_pk_to_secp, sha512hash6signals}; use crate::serialize::{byte_array_to_scalar, encode_pt}; #[wasm_bindgen] @@ -36,15 +36,71 @@ pub fn make_nullifier(sk_hex: String, message: String, rng_seed: &[u8]) -> Strin // The public nullifier: hash[m, pk]^sk. let nullifier = &hash_m_pk * &sk; - // // The Fiat-Shamir type step. - // let c = sha512hash6signals(&g, &pk, &hash_m_pk, &nullifier, &g_r, &hash_m_pk_pow_r); + let nullifier = encode_pt(nullifier).unwrap(); + hex::encode(nullifier) +} + +#[wasm_bindgen] +pub struct Signature { + pk: Vec, + nullifier: Vec, + c: Vec, + r_sk_c: Vec, + g_r: Vec, + hash_m_pk_pow_r: Vec, +} + +#[wasm_bindgen] +pub fn make_signature(sk_hex: String, message: String, rng_seed: &[u8]) -> Signature { + console_error_panic_hook::set_once(); + + let sk = hex::decode(sk_hex).unwrap(); + let sk = byte_array_to_scalar(&sk); + + // The base point or generator of the curve. + let g = ProjectivePoint::GENERATOR; + + let rng_seed: [u8; 32] = rng_seed.try_into().unwrap(); + let rng = &mut StdRng::from_seed(rng_seed); + let r_bytes = rng.gen::<[u8; 32]>(); + // The signer's secret key. It is only accessed within the secure enclave. + let r = Scalar::from_repr(r_bytes.into()).unwrap(); + + // 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_m_pk_to_secp(message.as_bytes(), &pk); - // // This value is part of the discrete log equivalence (DLEQ) proof. - // let r_sk_c = r + sk * c; + // hash[m, pk]^r + let hash_m_pk_pow_r = &hash_m_pk * &r; - // // Return the signature. - // (pk, nullifier, c, r_sk_c, g_r, hash_m_pk_pow_r) + // The public nullifier: hash[m, pk]^sk. + let nullifier = &hash_m_pk * &sk; + // The Fiat-Shamir type step. + let c = sha512hash6signals(&g, &pk, &hash_m_pk, &nullifier, &g_r, &hash_m_pk_pow_r); + + // This value is part of the discrete log equivalence (DLEQ) proof. + let r_sk_c = r + sk * c; + + let pk = encode_pt(pk).unwrap(); let nullifier = encode_pt(nullifier).unwrap(); - hex::encode(nullifier) -} \ No newline at end of file + let c = c.to_bytes().to_vec(); + let r_sk_c = r_sk_c.to_bytes().to_vec(); + let g_r = encode_pt(g_r).unwrap(); + let hash_m_pk_pow_r = encode_pt(hash_m_pk_pow_r).unwrap(); + + // Return the signature. + Signature { + pk, + nullifier, + c, + r_sk_c, + g_r, + hash_m_pk_pow_r, + } +} From fb31b5a8920f6dc5a483f516a5d5eefa654569f9 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sun, 20 Nov 2022 18:59:47 +0100 Subject: [PATCH 3/4] redo wasm bindings for rust-k256 --- README.md | 9 +- rust-arkworks/Cargo.toml | 3 +- rust-arkworks/src/error.rs | 2 +- rust-arkworks/src/hash_to_curve.rs | 2 +- rust-arkworks/src/lib.rs | 15 +- rust-arkworks/src/tests.rs | 11 +- rust-arkworks/src/wasm.rs | 35 +-- rust-k256/Cargo.lock | 114 +++++++-- rust-k256/Cargo.toml | 11 +- .../{wasm-pack.sh => scripts/build-wasm.sh} | 0 rust-k256/src/lib.rs | 1 + rust-k256/src/nullifier.rs | 84 +++++-- rust-k256/src/serialize.rs | 13 +- rust-k256/src/tests.rs | 12 +- rust-k256/src/utils.rs | 10 + rust-k256/src/wasm.rs | 219 +++++++++++------- 16 files changed, 379 insertions(+), 162 deletions(-) rename rust-k256/{wasm-pack.sh => scripts/build-wasm.sh} (100%) create mode 100644 rust-k256/src/utils.rs diff --git a/README.md b/README.md index 35b739b..c18133d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ - `rust-k256`: Rust, using the k256 library - `rust-arkworks`: Rust, using arkworks +## WASM bindings + +Currently, WASM bindings are only available for the `rust-k256` implementation. + ## TODO - zk verifier circuits (WIP Circom here: https://github.com/geometryresearch/secp256k1_hash_to_curve/tree/main/circuits) @@ -16,14 +20,17 @@ ## Resources ### Paper + https://aayushg.com/thesis.pdf ### Slides + https://docs.google.com/presentation/d/1mKtOI4XgKrWBEPpKFAYkRjxZsBomwhy6Cc2Ia87hAnY/edit#slide=id.g13e97fbcd2c_0_76 ### Unpublished Blog Post + https://docs.google.com/document/d/1Q9nUNGaeiKoZYAiN9ndh4iE9e-_ql-Rf7MESTo7UB8s/edit ### Spec -https://hackmd.io/uZQbMHrVSbOHvoI_HrJJlw +https://hackmd.io/uZQbMHrVSbOHvoI_HrJJlw diff --git a/rust-arkworks/Cargo.toml b/rust-arkworks/Cargo.toml index 815c1ea..0120619 100644 --- a/rust-arkworks/Cargo.toml +++ b/rust-arkworks/Cargo.toml @@ -12,7 +12,7 @@ ark-std = "0.3.0" ark-serialize = "0.3.0" ark-serialize-derive = "0.3.0" thiserror = "1.0.30" -secp256k1 = { git = "https://github.com/geometryresearch/ark-secp256k1.git" } +secp256k1 = { git = "https://github.com/geometryresearch/ark-secp256k1" } rand_core = {version = "0.6", default-features=false, features = ["getrandom"] } rand = "0.8.4" tiny-keccak = { version = "2.0.2", features = [ "shake" ] } @@ -24,6 +24,7 @@ hex = "0.4.3" wasm-bindgen = "0.2.83" js-sys = "0.3.60" console_error_panic_hook = "0.1.7" +serde = "1.0.147" [patch.crates-io] ark-ec = { git = "https://github.com/FindoraNetwork/ark-algebra" } diff --git a/rust-arkworks/src/error.rs b/rust-arkworks/src/error.rs index 85a7fbe..7b796ce 100644 --- a/rust-arkworks/src/error.rs +++ b/rust-arkworks/src/error.rs @@ -1,7 +1,7 @@ use thiserror::Error; /// This is an error that could occur when running a cryptograhic primitive -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] pub enum CryptoError { #[error("Cannot hash to curve")] CannotHashToCurve, diff --git a/rust-arkworks/src/hash_to_curve.rs b/rust-arkworks/src/hash_to_curve.rs index 6b542c6..0d681d3 100644 --- a/rust-arkworks/src/hash_to_curve.rs +++ b/rust-arkworks/src/hash_to_curve.rs @@ -17,7 +17,7 @@ pub fn hash_to_curve( let pk_encoded = pk.to_encoded_point(true); let b = hex::decode(pk_encoded).unwrap(); let x = [msg, b.as_slice()]; - let x = x.concat().clone(); + let x = x.concat(); let x = x.as_slice(); let pt: ProjectivePoint = Secp256k1::hash_from_bytes::>( diff --git a/rust-arkworks/src/lib.rs b/rust-arkworks/src/lib.rs index 20b7609..3e4ccd6 100644 --- a/rust-arkworks/src/lib.rs +++ b/rust-arkworks/src/lib.rs @@ -1,8 +1,8 @@ mod error; mod hash_to_curve; -mod wasm; #[cfg(test)] mod tests; +mod wasm; pub mod sig { use crate::error::CryptoError; @@ -35,9 +35,9 @@ pub mod sig { b.to_vec() } - fn compute_h<'a, C: ProjectiveCurve, Fq: PrimeField, P: SWModelParameters>( + fn compute_h( pk: &GroupAffine

, - message: &'a [u8], + message: &[u8], ) -> Result, CryptoError> { //let pk_affine_bytes_vec = affine_to_bytes::

(pk); //let m_pk = [message, pk_affine_bytes_vec.as_slice()].concat(); @@ -75,9 +75,8 @@ pub mod sig { // Convert digest bytes to a scalar let c = first_32.as_slice(); - let c_be = P::ScalarField::from_be_bytes_mod_order(c); - P::ScalarField::from(c_be) + P::ScalarField::from_be_bytes_mod_order(c) } pub trait VerifiableUnpredictableFunction { @@ -154,7 +153,7 @@ pub mod sig { pp: &Self::Parameters, rng: &mut R, ) -> Result<(Self::PublicKey, Self::SecretKey), CryptoError> { - let secret_key = Self::SecretKey::rand(rng).into(); + let secret_key = Self::SecretKey::rand(rng); let public_key = pp.g.mul(secret_key).into(); Ok((public_key, secret_key)) } @@ -169,7 +168,7 @@ pub mod sig { let g_r = g.mul(r).into_affine(); // Compute h = htc([m, pk]) - let h = compute_h::(&keypair.0, &message).unwrap(); + let h = compute_h::(keypair.0, message).unwrap(); // Compute z = h^r let z = h.mul(r).into_affine(); @@ -203,7 +202,7 @@ pub mod sig { message: Self::Message, ) -> Result { // Pick a random r from Fp - let r: P::ScalarField = Self::SecretKey::rand(rng).into(); + let r: P::ScalarField = Self::SecretKey::rand(rng); Self::sign_with_r(pp, keypair, message, r) } diff --git a/rust-arkworks/src/tests.rs b/rust-arkworks/src/tests.rs index 0749939..462a838 100644 --- a/rust-arkworks/src/tests.rs +++ b/rust-arkworks/src/tests.rs @@ -65,7 +65,7 @@ fn coord_to_hex(coord: biginteger::BigInteger320) -> String { let _ = coord.write(&mut coord_bytes); coord_bytes.reverse(); - String::from(hex::encode(coord_bytes)) + hex::encode(coord_bytes) } fn hardcoded_sk() -> String { @@ -114,8 +114,7 @@ pub fn compute_h() -> GroupAffine { let pk_projective = g.mul(sk); let pk = GroupAffine::::from(pk_projective); - let h = hash_to_curve::(message, &pk); - h + hash_to_curve::(message, &pk) } #[test] @@ -140,7 +139,7 @@ pub fn test_against_zk_nullifier_sig_pk() { #[test] pub fn test_against_zk_nullifier_sig_g_r() { // Test g^r using the hardcoded r - let r = secp256k1::fields::Fr::from(hex_to_fr(&hardcoded_r())); + let r = hex_to_fr(&hardcoded_r()); let (_, g) = test_template(); let g_r_projective = g.mul(r); let g_r = GroupAffine::::from(g_r_projective); @@ -174,7 +173,7 @@ pub fn test_against_zk_nullifier_sig_h_r() { let h = compute_h(); // Test h^r using the hardcoded r - let r = secp256k1::fields::Fr::from(hex_to_fr(&hardcoded_r())); + let r = hex_to_fr(&hardcoded_r()); let h_r_projective = h.mul(r); let h_r = GroupAffine::::from(h_r_projective); assert_eq!( @@ -207,7 +206,7 @@ pub fn test_against_zk_nullifier_sig_h_sk() { #[test] pub fn test_against_zk_nullifier_sig_c_and_s() { - let r = secp256k1::fields::Fr::from(hex_to_fr(&hardcoded_r())); + let r = hex_to_fr(&hardcoded_r()); let message = hardcoded_msg(); let message = message.as_bytes(); let sk = hex_to_fr(&hardcoded_sk()); diff --git a/rust-arkworks/src/wasm.rs b/rust-arkworks/src/wasm.rs index 257c79f..d6bcd1b 100644 --- a/rust-arkworks/src/wasm.rs +++ b/rust-arkworks/src/wasm.rs @@ -1,20 +1,22 @@ -use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; -use js_sys::Error; +use ark_ec::{short_weierstrass_jacobian::GroupAffine, AffineCurve}; +use ark_ff::{biginteger, ToBytes}; +use secp256k1::{Affine, Secp256k1Parameters}; +use wasm_bindgen::prelude::wasm_bindgen; -fn compute_h(message: String, sk_hex: String) -> GroupAffine:: { - let sk = hex_to_fr(&sk_hex); +use crate::hash_to_curve::hash_to_curve; + +fn compute_h(message: &str, sk_hex: &str) -> GroupAffine { + let sk = hex_to_fr(sk_hex); let g = Affine::prime_subgroup_generator(); let pk_projective = g.mul(sk); let pk = GroupAffine::::from(pk_projective); - let h = hash_to_curve::(message.to_bytes(), &pk); + let h = hash_to_curve::(message.as_bytes(), &pk); h } -fn hex_to_fr( - hex: &str, -) -> secp256k1::fields::Fr { +fn hex_to_fr(hex: &str) -> secp256k1::fields::Fr { let num_field_bytes = 320; let mut sk_bytes_vec = vec![0u8; num_field_bytes]; let mut sk_bytes = hex::decode(hex).unwrap(); @@ -25,7 +27,7 @@ fn hex_to_fr( let _ = std::mem::replace(&mut sk_bytes_vec[i], sk_bytes[i]); } - secp256k1::fields::Fr::read(sk_bytes_vec.as_slice()).unwrap() + ::read(sk_bytes_vec.as_slice()).unwrap() } fn coord_to_hex(coord: biginteger::BigInteger320) -> String { @@ -33,17 +35,16 @@ fn coord_to_hex(coord: biginteger::BigInteger320) -> String { let _ = coord.write(&mut coord_bytes); coord_bytes.reverse(); - String::from(hex::encode(coord_bytes)) + hex::encode(coord_bytes) } - #[wasm_bindgen] -pub fn make_nullifier(sk_hex: String, message: String) -> String { - let h = compute_h(&message); - let sk = hex_to_fr(&sk_hex); +pub fn make_nullifier(sk_hex: &str, message: &str) -> String { + let h = compute_h(message, sk_hex); + let sk = hex_to_fr(sk_hex); let h_sk_projective = h.mul(sk); let h_sk = GroupAffine::::from(h_sk_projective); - let h_sk_hex = coord_to_hex(h_sk.x.into()); - h_sk_hex -} \ No newline at end of file + + coord_to_hex(h_sk.x.into()) +} diff --git a/rust-k256/Cargo.lock b/rust-k256/Cargo.lock index c7a9dff..33d8ab3 100644 --- a/rust-k256/Cargo.lock +++ b/rust-k256/Cargo.lock @@ -20,6 +20,15 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "block-buffer" version = "0.10.2" @@ -159,13 +168,14 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.14.3" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd46e0c364655e5baf2f5e99b603e7a09905da9966d7928d7470af393b28670" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der 0.6.0", - "elliptic-curve 0.12.2", + "elliptic-curve 0.12.3", "rfc6979", + "serdect", "signature", ] @@ -188,9 +198,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c47abd0a791d2ac0c7aa1118715f85b83689e4522c4e3a244e159d4fc9848a8d" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint 0.4.8", @@ -202,6 +212,7 @@ dependencies = [ "pkcs8", "rand_core", "sec1", + "serdect", "subtle", "zeroize", ] @@ -314,13 +325,14 @@ dependencies = [ [[package]] name = "k256" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8a5a96d92d849c4499d99461da81c9cdc1467418a8ed2aaeb407e8d85940ed" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", "ecdsa", - "elliptic-curve 0.12.2", + "elliptic-curve 0.12.3", + "serdect", "sha2", ] @@ -432,9 +444,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -465,6 +477,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "sec1" version = "0.3.0" @@ -475,10 +493,41 @@ dependencies = [ "der 0.6.0", "generic-array", "pkcs8", + "serdect", "subtle", "zeroize", ] +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serdect" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038fce1bf4d74b9b30ea7dcd59df75ba8ec669a5dcb3cc64fbfcef7334ced32c" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha2" version = "0.10.2" @@ -492,9 +541,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.5.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ "digest 0.10.3", "rand_core", @@ -588,6 +637,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -617,6 +678,30 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d2fff962180c3fadf677438054b1db62bee4aa32af26a45388af07d1287e1d" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683da3dfc016f704c9f82cf401520c4f1cb3ee440f7f52b3d6ac29506a49ca7" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "web-sys" version = "0.3.58" @@ -659,10 +744,11 @@ checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" name = "zk-nullifier" version = "0.1.0" dependencies = [ + "bincode", "console_error_panic_hook", "crate", "digest 0.10.3", - "elliptic-curve 0.12.2", + "elliptic-curve 0.12.3", "getrandom", "hash2field", "hex", @@ -674,5 +760,7 @@ dependencies = [ "rand", "rand_core", "ring", + "serde", "wasm-bindgen", + "wasm-bindgen-test", ] diff --git a/rust-k256/Cargo.toml b/rust-k256/Cargo.toml index 5efc2d3..1444364 100644 --- a/rust-k256/Cargo.toml +++ b/rust-k256/Cargo.toml @@ -19,13 +19,20 @@ hex-literal = "0.3.4" hash2field = "0.4.0" num-bigint = "0.4.3" num-integer = "0.1.45" -k256 = {version = "0.11.3", features = ["arithmetic", "hash2curve", "expose-field", "sha2"]} -elliptic-curve = { version = "0.12.2", features = ["arithmetic"]} +k256 = {version = "0.11.3", features = ["arithmetic", "hash2curve", "expose-field", "sha2", "serde"]} +elliptic-curve = { version = "0.12.3", features = ["arithmetic", "serde"]} hex = "0.4.3" +# TODO: Move to 'serde' feature +bincode = "1.3.3" +serde = { version = "1.0", features = ["derive"] } + # TODO: Move those to 'wasm' feature wasm-bindgen = "0.2.83" js-sys = "0.3.60" console_error_panic_hook = "0.1.7" getrandom = { version = "0.2", features = ["js"] } rand = "0.8.5" + +[dev-dependencies] +wasm-bindgen-test = "0.3.28" \ No newline at end of file diff --git a/rust-k256/wasm-pack.sh b/rust-k256/scripts/build-wasm.sh similarity index 100% rename from rust-k256/wasm-pack.sh rename to rust-k256/scripts/build-wasm.sh diff --git a/rust-k256/src/lib.rs b/rust-k256/src/lib.rs index 06f5282..3e5c6f7 100644 --- a/rust-k256/src/lib.rs +++ b/rust-k256/src/lib.rs @@ -7,4 +7,5 @@ mod nullifier; mod serialize; #[cfg(test)] mod tests; +mod utils; mod wasm; diff --git a/rust-k256/src/nullifier.rs b/rust-k256/src/nullifier.rs index 47f5e02..df14d05 100644 --- a/rust-k256/src/nullifier.rs +++ b/rust-k256/src/nullifier.rs @@ -12,7 +12,8 @@ use k256::{ ProjectivePoint, Scalar, Secp256k1, -}; // requires 'getrandom' feature +}; +use rand::{rngs::StdRng, Rng}; // requires 'getrandom' feature use crate::serialize::encode_pt; @@ -21,7 +22,7 @@ const COUNT: usize = 2; const OUT: usize = L * COUNT; pub const DST: &[u8] = b"QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_"; // Hash to curve algorithm -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Error { IsPointAtInfinityError, } @@ -34,12 +35,12 @@ pub fn sha512hash6signals( g_r: &ProjectivePoint, hash_m_pk_pow_r: &ProjectivePoint, ) -> Scalar { - let g_bytes = encode_pt(*g).unwrap(); - let pk_bytes = encode_pt(*pk).unwrap(); - let h_bytes = encode_pt(*hash_m_pk).unwrap(); - let nul_bytes = encode_pt(*nullifier).unwrap(); - let g_r_bytes = encode_pt(*g_r).unwrap(); - let z_bytes = encode_pt(*hash_m_pk_pow_r).unwrap(); + let g_bytes = encode_pt(*g); + let pk_bytes = encode_pt(*pk); + let h_bytes = encode_pt(*hash_m_pk); + let nul_bytes = encode_pt(*nullifier); + let g_r_bytes = encode_pt(*g_r); + let z_bytes = encode_pt(*hash_m_pk_pow_r); let c_preimage_vec = [g_bytes, pk_bytes, h_bytes, nul_bytes, g_r_bytes, z_bytes].concat(); @@ -50,8 +51,8 @@ pub fn sha512hash6signals( let sha512_hasher_result = sha512_hasher.finalize(); //512 bit hash let c_bytes = FieldBytes::from_iter(sha512_hasher_result.iter().copied()); - let c_scalar = Scalar::from_repr(c_bytes).unwrap(); - c_scalar + + Scalar::from_repr(c_bytes).unwrap() } // Calls the hash to curve function for secp256k1, and returns the result as a ProjectivePoint @@ -68,7 +69,7 @@ pub fn hash_to_secp(s: &[u8]) -> ProjectivePoint { // Hashes two values to the curve pub fn hash_m_pk_to_secp(m: &[u8], pk: &ProjectivePoint) -> ProjectivePoint { let pt: ProjectivePoint = Secp256k1::hash_from_bytes::>( - &[[m, &encode_pt(*pk).unwrap()].concat().as_slice()], + &[[m, &encode_pt(*pk)].concat().as_slice()], //b"CURVE_XMD:SHA-256_SSWU_RO_", DST, ) @@ -98,7 +99,7 @@ pub fn verify_signals( let hash_m_pk = &hash_m_pk_to_secp(m, pk); // Check whether g^r equals g^s * pk^{-c} - let g_r: ProjectivePoint; + match *g_r_option { Some(_g_r_value) => { if (g * r_sk_c - pk * c) != _g_r_value { @@ -107,10 +108,10 @@ pub fn verify_signals( } None => println!("g^r not provided, check skipped"), } - g_r = g * r_sk_c - pk * c; + let g_r: ProjectivePoint = g * r_sk_c - pk * c; // Check whether h^r equals h^{r + sk * c} * nullifier^{-c} - let hash_m_pk_pow_r: ProjectivePoint; + match *hash_m_pk_pow_r_option { Some(_hash_m_pk_pow_r_value) => { if (hash_m_pk * r_sk_c - nullifier * c) != _hash_m_pk_pow_r_value { @@ -119,7 +120,7 @@ pub fn verify_signals( } None => println!("hash_m_pk_pow_r not provided, check skipped"), } - hash_m_pk_pow_r = hash_m_pk * r_sk_c - nullifier * c; + let hash_m_pk_pow_r: ProjectivePoint = hash_m_pk * r_sk_c - nullifier * c; // Check if the given hash matches if (sha512hash6signals(g, pk, hash_m_pk, nullifier, &g_r, &hash_m_pk_pow_r)) != *c { @@ -127,3 +128,56 @@ pub fn verify_signals( } verified } + +// Not using serde::{Serialize, Deserialize} because it doesn't work with ProjectivePoint +// See this comment about serialization: https://github.com/axelarnetwork/tofn/issues/197#issuecomment-1090715311 +#[derive(Debug, Clone)] +pub struct NullifierSignature { + pub pk: ProjectivePoint, + pub nullifier: ProjectivePoint, + pub c: Scalar, + pub r_sk_c: Scalar, + pub g_r: ProjectivePoint, + pub hash_m_pk_pow_r: ProjectivePoint, +} + +impl NullifierSignature { + pub fn new(sk: &Scalar, message: &[u8], rng: &mut StdRng) -> Self { + // The base point or generator of the curve. + let g = ProjectivePoint::GENERATOR; + + let r_bytes = rng.gen::<[u8; 32]>(); + // The signer's secret key. It is only accessed within the secure enclave. + let r = Scalar::from_repr(r_bytes.into()).unwrap(); + + // 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_m_pk_to_secp(message, &pk); + + // hash[m, pk]^r + let hash_m_pk_pow_r = hash_m_pk * r; + + // The public nullifier: hash[m, pk]^sk. + let nullifier = hash_m_pk * sk; + + // The Fiat-Shamir type step. + let c = sha512hash6signals(&g, &pk, &hash_m_pk, &nullifier, &g_r, &hash_m_pk_pow_r); + + // This value is part of the discrete log equivalence (DLEQ) proof. + let r_sk_c = r + sk * &c; + + Self { + pk, + nullifier, + c, + r_sk_c, + g_r, + hash_m_pk_pow_r, + } + } +} diff --git a/rust-k256/src/serialize.rs b/rust-k256/src/serialize.rs index 6034fa6..e71a818 100644 --- a/rust-k256/src/serialize.rs +++ b/rust-k256/src/serialize.rs @@ -1,24 +1,17 @@ -use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest}; use elliptic_curve::sec1::ToEncodedPoint; -use hex_literal::hex; + use k256::{ // ecdsa::{signature::Signer, Signature, SigningKey}, - elliptic_curve::group::ff::PrimeField, - sha2::{Digest, Sha256, Sha512}, - FieldBytes, ProjectivePoint, Scalar, - Secp256k1, }; -use crate::nullifier::Error; - // Format a ProjectivePoint to 64 bytes - the concatenation of the x and y values. We use 64 /// bytes instead of SEC1 encoding as our arkworks secp256k1 implementation doesn't support SEC1 /// encoding yet. -pub fn encode_pt(point: ProjectivePoint) -> Result, Error> { +pub fn encode_pt(point: ProjectivePoint) -> Vec { let encoded = point.to_encoded_point(true); - Ok(encoded.to_bytes().to_vec()) + encoded.to_bytes().to_vec() } /// Convert a 32-byte array to a scalar diff --git a/rust-k256/src/tests.rs b/rust-k256/src/tests.rs index c8589e0..c716564 100644 --- a/rust-k256/src/tests.rs +++ b/rust-k256/src/tests.rs @@ -49,10 +49,10 @@ fn test_gen_signals( let r = gen_test_scalar_r(); // The user's public key: g^sk. - let pk = &g * &sk; + let pk = g * sk; // The generator exponentiated by r: g^r. - let g_r = &g * &r; + let g_r = g * r; // hash[m, pk] let hash_m_pk = hash_m_pk_to_secp(m, &pk); @@ -67,7 +67,7 @@ fn test_gen_signals( ); // hash[m, pk]^r - let hash_m_pk_pow_r = &hash_m_pk * &r; + let hash_m_pk_pow_r = hash_m_pk * r; println!( "hash_m_pk_pow_r.x: {:?}", hex::encode( @@ -90,7 +90,7 @@ fn test_gen_signals( ); // The public nullifier: hash[m, pk]^sk. - let nullifier = &hash_m_pk * &sk; + let nullifier = hash_m_pk * sk; // The Fiat-Shamir type step. let c = sha512hash6signals(&g, &pk, &hash_m_pk, &nullifier, &g_r, &hash_m_pk_pow_r); @@ -117,7 +117,7 @@ fn main() { let sk = gen_test_scalar_x(); // The user's public key: g^sk. - let pk = &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 @@ -188,7 +188,7 @@ fn main() { ); // Test encode_pt() - let g_as_bytes = encode_pt(g).unwrap(); + let g_as_bytes = encode_pt(g); assert_eq!( hex::encode(g_as_bytes), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" diff --git a/rust-k256/src/utils.rs b/rust-k256/src/utils.rs new file mode 100644 index 0000000..b1d7929 --- /dev/null +++ b/rust-k256/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} diff --git a/rust-k256/src/wasm.rs b/rust-k256/src/wasm.rs index a73918e..8e798c5 100644 --- a/rust-k256/src/wasm.rs +++ b/rust-k256/src/wasm.rs @@ -1,106 +1,163 @@ -use k256::{elliptic_curve::group::ff::PrimeField, ProjectivePoint, Scalar}; -use rand::{rngs::StdRng, Rng, SeedableRng}; -use wasm_bindgen::prelude::wasm_bindgen; // requires 'getrandom' feature - -use crate::nullifier::{hash_m_pk_to_secp, sha512hash6signals}; -use crate::serialize::{byte_array_to_scalar, encode_pt}; - +use digest::generic_array::GenericArray; +use elliptic_curve::group::GroupEncoding; +use k256::ProjectivePoint; +use rand::{rngs::StdRng, SeedableRng}; +use rand_core::OsRng; +use wasm_bindgen::prelude::wasm_bindgen; +// requires 'getrandom' feature +use wasm_bindgen::JsValue; + +use crate::{ + serialize::{byte_array_to_scalar, encode_pt}, + utils::set_panic_hook, +}; + +// TODO: Probaly have to use a "serde_as" annotation here to serialize the ProjectivePoint as a byte array +// https://github.com/RustCrypto/elliptic-curves/issues/393 #[wasm_bindgen] -pub fn make_nullifier(sk_hex: String, message: String, rng_seed: &[u8]) -> String { - console_error_panic_hook::set_once(); +#[derive(Debug, Clone)] +pub struct NullifierSignature(pub(crate) crate::nullifier::NullifierSignature); - let sk = hex::decode(sk_hex).unwrap(); - let sk = byte_array_to_scalar(&sk); +// const fields are not yet supported in wasm-bindgen, so placing them outside of the struct +const PPOINT_LEN: usize = 33; +const SCALAR_LEN: usize = 32; - // The base point or generator of the curve. - let g = ProjectivePoint::GENERATOR; +#[wasm_bindgen] +impl NullifierSignature { + #[wasm_bindgen(getter, js_name = "publicKey")] + pub fn public_key(&self) -> Vec { + encode_pt(self.0.pk) + } - let rng_seed: [u8; 32] = rng_seed.try_into().unwrap(); - let rng = &mut StdRng::from_seed(rng_seed); - let r_bytes = rng.gen::<[u8; 32]>(); - // The signer's secret key. It is only accessed within the secure enclave. - let r = Scalar::from_repr(r_bytes.into()).unwrap(); + #[wasm_bindgen(getter, js_name = "nullifier")] + pub fn nullifier(&self) -> Vec { + encode_pt(self.0.nullifier) + } - // The user's public key: g^sk. - let pk = &g * &sk; + #[wasm_bindgen(getter, js_name = "c")] + pub fn c(&self) -> Vec { + self.0.c.to_bytes().to_vec() + } - // The generator exponentiated by r: g^r. - let g_r = &g * &r; + #[wasm_bindgen(getter, js_name = "r_sk_c")] + pub fn r_sk_c(&self) -> Vec { + self.0.r_sk_c.to_bytes().to_vec() + } - // hash[m, pk] - let hash_m_pk = hash_m_pk_to_secp(message.as_bytes(), &pk); + #[wasm_bindgen(getter, js_name = "g_r")] + pub fn g_r(&self) -> Vec { + encode_pt(self.0.g_r) + } - // hash[m, pk]^r - let hash_m_pk_pow_r = &hash_m_pk * &r; + #[wasm_bindgen(getter, js_name = "hash_m_pk_pow_r")] + pub fn hash_m_pk_pow_r(&self) -> Vec { + encode_pt(self.0.hash_m_pk_pow_r) + } - // The public nullifier: hash[m, pk]^sk. - let nullifier = &hash_m_pk * &sk; + #[wasm_bindgen(js_name = "toBytes")] + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend_from_slice(self.public_key().as_slice()); + bytes.extend_from_slice(self.nullifier().as_slice()); + bytes.extend_from_slice(self.c().as_slice()); + bytes.extend_from_slice(self.r_sk_c().as_slice()); + bytes.extend_from_slice(self.g_r().as_slice()); + bytes.extend_from_slice(self.hash_m_pk_pow_r().as_slice()); + bytes + } - let nullifier = encode_pt(nullifier).unwrap(); - hex::encode(nullifier) -} + #[wasm_bindgen(js_name = "serializedLength")] + pub fn serialized_len() -> usize { + PPOINT_LEN * 4 + SCALAR_LEN * 2 + } -#[wasm_bindgen] -pub struct Signature { - pk: Vec, - nullifier: Vec, - c: Vec, - r_sk_c: Vec, - g_r: Vec, - hash_m_pk_pow_r: Vec, + #[wasm_bindgen(js_name = "fromBytes")] + pub fn from_bytes(bytes: &[u8]) -> Result { + set_panic_hook(); + + if bytes.len() != Self::serialized_len() { + panic!("Invalid length for NullifierSignature"); + } + + let pk_last_idx = PPOINT_LEN; + let pk = + ProjectivePoint::from_bytes(GenericArray::from_slice(&bytes[0..pk_last_idx])).unwrap(); + + let nullifier_last_idx = pk_last_idx + PPOINT_LEN; + let nullifier = ProjectivePoint::from_bytes(GenericArray::from_slice( + &bytes[pk_last_idx..nullifier_last_idx], + )) + .unwrap(); + + let c_last_idx = nullifier_last_idx + SCALAR_LEN; + let c = byte_array_to_scalar(&bytes[nullifier_last_idx..c_last_idx]); + + let r_sk_c_last_idx = c_last_idx + SCALAR_LEN; + let r_sk_c = byte_array_to_scalar(&bytes[c_last_idx..r_sk_c_last_idx]); + + let g_r_last_idx = r_sk_c_last_idx + PPOINT_LEN; + let g_r = ProjectivePoint::from_bytes(GenericArray::from_slice( + &bytes[r_sk_c_last_idx..g_r_last_idx], + )) + .unwrap(); + + let hash_m_pk_pow_r = + ProjectivePoint::from_bytes(GenericArray::from_slice(&bytes[g_r_last_idx..])).unwrap(); + + Ok(NullifierSignature(crate::nullifier::NullifierSignature { + pk, + nullifier, + c, + r_sk_c, + g_r, + hash_m_pk_pow_r, + })) + } } #[wasm_bindgen] -pub fn make_signature(sk_hex: String, message: String, rng_seed: &[u8]) -> Signature { - console_error_panic_hook::set_once(); - - let sk = hex::decode(sk_hex).unwrap(); - let sk = byte_array_to_scalar(&sk); +impl NullifierSignature { + #[wasm_bindgen] + pub fn new(sk: &[u8], message: &str, rng_seed: Option>) -> Self { + set_panic_hook(); - // The base point or generator of the curve. - let g = ProjectivePoint::GENERATOR; + if sk.len() != 32 { + panic!("Invalid secret key length"); + } - let rng_seed: [u8; 32] = rng_seed.try_into().unwrap(); - let rng = &mut StdRng::from_seed(rng_seed); - let r_bytes = rng.gen::<[u8; 32]>(); - // The signer's secret key. It is only accessed within the secure enclave. - let r = Scalar::from_repr(r_bytes.into()).unwrap(); + let mut rng = match rng_seed { + Some(seed) => { + let seed: [u8; 32] = seed.as_ref().try_into().unwrap(); - // The user's public key: g^sk. - let pk = &g * &sk; + StdRng::from_seed(seed) + } + None => StdRng::from_rng(OsRng).unwrap(), + }; + let sk = byte_array_to_scalar(sk); - // The generator exponentiated by r: g^r. - let g_r = &g * &r; + let nullifier_signature = + crate::nullifier::NullifierSignature::new(&sk, message.as_bytes(), &mut rng); - // hash[m, pk] - let hash_m_pk = hash_m_pk_to_secp(message.as_bytes(), &pk); - - // hash[m, pk]^r - let hash_m_pk_pow_r = &hash_m_pk * &r; - - // The public nullifier: hash[m, pk]^sk. - let nullifier = &hash_m_pk * &sk; + Self(nullifier_signature) + } +} - // The Fiat-Shamir type step. - let c = sha512hash6signals(&g, &pk, &hash_m_pk, &nullifier, &g_r, &hash_m_pk_pow_r); +#[cfg(test)] +mod wasm_test { + use elliptic_curve::Field; + use wasm_bindgen_test::*; - // This value is part of the discrete log equivalence (DLEQ) proof. - let r_sk_c = r + sk * c; + #[test] + #[wasm_bindgen_test] + fn serializes_and_deserializes_nullifier() { + let rng = rand::thread_rng(); + let sk = k256::Scalar::random(rng).to_bytes().to_vec(); + let message = "Hello, world!".to_string(); - let pk = encode_pt(pk).unwrap(); - let nullifier = encode_pt(nullifier).unwrap(); - let c = c.to_bytes().to_vec(); - let r_sk_c = r_sk_c.to_bytes().to_vec(); - let g_r = encode_pt(g_r).unwrap(); - let hash_m_pk_pow_r = encode_pt(hash_m_pk_pow_r).unwrap(); + let sig = super::NullifierSignature::new(&sk, &message, None); + let bytes = sig.to_bytes(); + let sig2 = super::NullifierSignature::from_bytes(&bytes).unwrap(); - // Return the signature. - Signature { - pk, - nullifier, - c, - r_sk_c, - g_r, - hash_m_pk_pow_r, + assert_eq!(sig.to_bytes(), sig2.to_bytes()); } } From d61f6ea75cd63fe8a173ed234be6dcfa8c639dcb Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sun, 20 Nov 2022 19:43:49 +0100 Subject: [PATCH 4/4] add gh actions --- .github/workflows/ci.yml | 93 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 +++ 2 files changed, 99 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 Cargo.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..460d539 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: Build & Test + +on: + pull_request: + paths-ignore: + - README.md + push: + branches: + - main + paths-ignore: + - README.md + tags: + - v* + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + override: true + profile: minimal + - run: cargo clippy --all --all-features -- -D warnings + + rustfmt: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v1 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + profile: minimal + override: true + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: x86_64-unknown-linux-gnu + rust: 1.57 # MSRV + - target: x86_64-unknown-linux-gnu + rust: stable + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + - run: ${{ matrix.deps }} + - run: cargo check --all-features + - run: cargo test --release --all-features + + wasm-test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.57 # MSRV + - stable + target: + - wasm32-unknown-unknown + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + - run: cargo install wasm-pack + - run: cd rust-k256 && wasm-pack test --node diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b2bbde4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +members = [ + # "rust-arkworks", # Doesn't work yet + "rust-k256", +] \ No newline at end of file