From e5febe3cf3e4fcf62f7eb3a40c7b7337e8795d5b Mon Sep 17 00:00:00 2001 From: Sergey Kaunov Date: Thu, 15 Feb 2024 23:18:30 +0300 Subject: [PATCH] Resolve half of #67 (Part 1/2) (#93) * current progress * link to relevant issue * crate name edit * Flat the docs entities * meta information * Update the crate name in `tests` * current progress --- rust-arkworks/Cargo.toml | 20 ++-- rust-arkworks/README.MD | 3 + rust-arkworks/src/error.rs | 7 +- rust-arkworks/src/lib.rs | 61 +++++++++-- rust-k256/.vscode/launch.json | 41 ------- rust-k256/Cargo.toml | 7 +- rust-k256/README.MD | 1 + rust-k256/src/lib.rs | 188 +++++++++++++++++--------------- rust-k256/src/utils.rs | 25 +++++ rust-k256/tests/verification.rs | 14 +-- 10 files changed, 213 insertions(+), 154 deletions(-) create mode 100644 rust-arkworks/README.MD delete mode 100644 rust-k256/.vscode/launch.json create mode 100644 rust-k256/README.MD create mode 100644 rust-k256/src/utils.rs diff --git a/rust-arkworks/Cargo.toml b/rust-arkworks/Cargo.toml index 77c8da0..1dc7a90 100644 --- a/rust-arkworks/Cargo.toml +++ b/rust-arkworks/Cargo.toml @@ -1,18 +1,22 @@ [package] -name = "sig" -version = "0.1.0" +name = "plume_arkworks" +version = "0.0.1" edition = "2021" +license = "MIT" +description = "Implementation of PLUME: nullifier friendly signature scheme on ECDSA; using the `arkworks-rs` libraries" +repository = "https://github.com/plume-sig/zk-nullifier-sig/" +categories = ["cryptography", "cryptography::cryptocurrencies"] +keywords = ["nullifier", "zero-knowledge", "ECDSA", "PLUME"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ark-ec = "~0.3.0" -ark-ff = "0.3.0" -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" } +ark-ff = "~0.3.0" +ark-std = "~0.3.0" +ark-serialize = "~0.3.0" +ark-serialize-derive = "~0.3.0" +secp256k1 = { git = "https://github.com/geometryresearch/ark-secp256k1.git", version = "0.1.0" } rand_core = { version = "0.6", default-features = false, features = [ "getrandom", ] } diff --git a/rust-arkworks/README.MD b/rust-arkworks/README.MD new file mode 100644 index 0000000..51d111c --- /dev/null +++ b/rust-arkworks/README.MD @@ -0,0 +1,3 @@ +https://github.com/plume-sig/zk-nullifier-sig/blob/main/README.md +# HAZMAT +Please note that until `v0.1.0` this is very much a preview crate which lets you have some preliminary feel of the structure and the reference implementation approach. \ No newline at end of file diff --git a/rust-arkworks/src/error.rs b/rust-arkworks/src/error.rs index ac7546f..151cad8 100644 --- a/rust-arkworks/src/error.rs +++ b/rust-arkworks/src/error.rs @@ -3,7 +3,7 @@ // use thiserror::Error; -/// This is an error that could occur when running a cryptograhic primitive +// /// This is an error that could occur when running a cryptograhic primitive // #[derive(Error, Debug, PartialEq)] // pub enum CryptoError { // #[error("Cannot hash to curve")] @@ -14,13 +14,18 @@ // } // Let's outline what errors will be in `~0.4.0` +/// It's an interim `enum` between legacy definition of the errors and prospective which will be relying on [`ark_ec::hashing::HashToCurveError`]. #[derive(Debug, Clone)] pub enum HashToCurveError { + /// Mimics the `ark_ec::hashing::HashToCurveError` enum UnsupportedCurveError(String), + /// Mimics the `ark_ec::hashing::HashToCurveError` enum MapToCurveError(String), /* let's add two more items to absorb everything in `crate::hash_to_curve` which is subject to deprecation */ + /// Absorbs any legacy error in [`mod@crate::hash_to_curve`]. They will be deprecated with upgrade to `~0.4.0`. Legacy, + /// A special case for a reference function. It will be moved to <./examples> with the upgrade to `~0.4.0`. ReferenceTryAndIncrement, } diff --git a/rust-arkworks/src/lib.rs b/rust-arkworks/src/lib.rs index 380c5a7..2497515 100644 --- a/rust-arkworks/src/lib.rs +++ b/rust-arkworks/src/lib.rs @@ -1,10 +1,26 @@ -use crate::error::HashToCurveError; +/// This crate provides the PLUME signature scheme. +/// +/// See for more information. +/// +/// Find RustCrypto crate as `plume_rustcrypto`. + +pub use crate::error::HashToCurveError; use crate::hash_to_curve::hash_to_curve; -use ark_ec::short_weierstrass_jacobian::GroupAffine; -use ark_ec::{models::SWModelParameters, AffineCurve, ProjectiveCurve}; + +/// Re-exports the `GroupAffine` and `SWModelParameters` types from the `ark_ec` crate. +/// +/// `GroupAffine` represents an affine point on a short Weierstrass elliptic curve. +/// `SWModelParameters` contains the parameters defining a short Weierstrass curve. +pub use ark_ec::{models::SWModelParameters, short_weierstrass_jacobian::GroupAffine}; +/// Re-exports the `Rng` trait from the `rand` crate in `ark_std`. +/// +/// `Rng` provides methods for generating random values. +pub use ark_std::rand::Rng; + +use ark_ec::{AffineCurve, ProjectiveCurve}; use ark_ff::PrimeField; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; -use ark_std::{rand::Rng, UniformRand}; +use ark_std::UniformRand; use secp256k1::sec1::Sec1EncodePoint; use sha2::digest::Output; use sha2::{Digest, Sha256}; @@ -14,11 +30,15 @@ mod hash_to_curve; const EXPECT_MSG_DECODE: &str = "the value decoded have been generated by a function which is improbable to output a malformed hexstring (still a place for refactoring)"; +/// An `enum` representing the variant of the PLUME protocol. pub enum PlumeVersion { V1, V2, } +/// Converts an affine point on the curve to the byte representation. +/// +/// Serializes the affine point to its SEC1 encoding and returns the raw bytes. pub fn affine_to_bytes(point: &GroupAffine

) -> Vec { hex::decode(point.to_encoded_point(true)) .expect(EXPECT_MSG_DECODE) @@ -72,6 +92,8 @@ fn compute_c_v2( Sha256::digest(c_preimage_vec.as_slice()) } +/// A struct containing parameters for the SW model, including the generator point `g_point`. +/// This struct implements traits for (de)serialization. #[derive( Copy, Clone, @@ -79,9 +101,11 @@ fn compute_c_v2( ark_serialize_derive::CanonicalDeserialize, )] pub struct Parameters { + /// The generator point for the SW model parameters. pub g_point: GroupAffine

, } +/// A struct containing the PLUME signature data #[derive( Copy, Clone, @@ -89,28 +113,37 @@ pub struct Parameters { ark_serialize_derive::CanonicalDeserialize, )] pub struct PlumeSignature { + /// The hash-to-curve output multiplied by the random `r`. pub hashed_to_curve_r: GroupAffine

, + /// The randomness `r` represented as the curve point. pub r_point: GroupAffine

, pub s: P::ScalarField, pub c: P::ScalarField, + /// The nullifier. pub nullifier: GroupAffine

, } // These aliases should be gone in #88 . If they won't TODO pay attention to the warning about `trait` boundaries being not checked for aliases // also not enforcing trait bounds can impact PublicKey -- it's better to find appropriate upstream type -type Message<'a> = &'a [u8]; -type PublicKey = GroupAffine

; -type SecretKeyMaterial = P::ScalarField; + +/// A type alias for a byte slice reference, used for representing the message. +pub type Message<'a> = &'a [u8]; +/// The public key. +pub type PublicKey = GroupAffine

; +/// The scalar field element representing the secret key. +pub type SecretKeyMaterial = P::ScalarField; impl PlumeSignature

{ /// Generate the public key and a private key. - fn keygen(pp: &Parameters

, rng: &mut impl Rng) -> (PublicKey

, SecretKeyMaterial

) { + /// # HAZMAT + /// No measures yet taken for the [`SecretKeyMaterial`] protection + pub fn keygen(pp: &Parameters

, rng: &mut impl Rng) -> (PublicKey

, SecretKeyMaterial

) { let secret_key = SecretKeyMaterial::

::rand(rng); let public_key = pp.g_point.mul(secret_key).into(); (public_key, secret_key) } - /// Sign a message using a specified r value + /// Sign a message using the specified `r` value fn sign_with_r( pp: &Parameters

, keypair: (&PublicKey

, &SecretKeyMaterial

), @@ -174,6 +207,16 @@ impl PlumeSignature

{ Self::sign_with_r(pp, keypair, message, r_scalar, version) } + /// Verifies a PLUME signature. + /// Returns `true` if the signature is valid, `false` otherwise. + /// + /// Computes the curve points and scalars needed for verification from the + /// signature parameters. Then performs the verification steps: + /// - Confirm g^s * pk^-c = g^r + /// - Confirm h^s * nul^-c = z + /// - Confirm c = c' + /// + /// Rejects if any check fails. fn verify_non_zk( self, pp: &Parameters

, diff --git a/rust-k256/.vscode/launch.json b/rust-k256/.vscode/launch.json deleted file mode 100644 index 78ac8a9..0000000 --- a/rust-k256/.vscode/launch.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'zk-nullifier'", - "cargo": { - "args": ["build", "--bin=zk-nullifier", "--package=zk-nullifier"], - "filter": { - "name": "zk-nullifier", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'zk-nullifier'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=zk-nullifier", - "--package=zk-nullifier" - ], - "filter": { - "name": "zk-nullifier", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} diff --git a/rust-k256/Cargo.toml b/rust-k256/Cargo.toml index 8e89385..8d25ce6 100644 --- a/rust-k256/Cargo.toml +++ b/rust-k256/Cargo.toml @@ -1,7 +1,12 @@ [package] -name = "zk-nullifier" +name = "plume_rustcrypto" version = "0.1.0" edition = "2021" +license = "MIT" +description = "Implementation of PLUME: nullifier friendly signature scheme on ECDSA; using the k256 library" +repository = "https://github.com/plume-sig/zk-nullifier-sig/" +categories = ["cryptography", "cryptography::cryptocurrencies"] +keywords = ["nullifier", "zero-knowledge", "ECDSA", "PLUME"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rust-k256/README.MD b/rust-k256/README.MD new file mode 100644 index 0000000..ff91c89 --- /dev/null +++ b/rust-k256/README.MD @@ -0,0 +1 @@ +https://github.com/plume-sig/zk-nullifier-sig/blob/main/README.md \ No newline at end of file diff --git a/rust-k256/src/lib.rs b/rust-k256/src/lib.rs index baaea72..8e78c2a 100644 --- a/rust-k256/src/lib.rs +++ b/rust-k256/src/lib.rs @@ -1,101 +1,81 @@ // #![feature(generic_const_expr)] // #![allow(incomplete_features)] +//! A library for generating (coming [soon](https://github.com/plume-sig/zk-nullifier-sig/issues/84)) and verifying PLUME signatures. +//! +//! See for more information. +//! +// Find `arkworks-rs` crate as `plume_arkworks`. +// +// # Examples +// For V2 just set `v1` to `None` +// ```rust +// # fn main() { +// let sig_good = PlumeSignature<'a>{ +// message: &b"An example app message string", +// pk: ProjectivePoint::GENERATOR * Scalar::from_repr(hex!("519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464").into()).unwrap(), +// ... +// }; +// # } +// ``` + use k256::{ - elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest}, elliptic_curve::ops::ReduceNonZero, - elliptic_curve::sec1::ToEncodedPoint, elliptic_curve::{bigint::ArrayEncoding, group::ff::PrimeField}, - sha2::{digest::Output, Digest, Sha256}, - FieldBytes, Scalar, Secp256k1, U256, + FieldBytes, U256, }; // requires 'getrandom' feature -use std::panic; -// TODO #86 +// TODO pub use k256::ProjectivePoint; +/// Re-exports the [`Scalar`] type, [`Sha256`] hash function, and [`Output`] type +/// from the [`k256`] crate's [`sha2`] module. This allows them to be used +/// from the current module. +pub use k256::{ + sha2::{digest::Output, Digest, Sha256}, + Scalar, +}; +use std::panic; -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 - -fn print_type_of(_: &T) { - println!("{}", std::any::type_name::()); -} - -fn c_sha256_vec_signal(values: Vec<&ProjectivePoint>) -> Output { - let preimage_vec = values - .into_iter() - .map(encode_pt) - .collect::>() - .concat(); - let mut sha256_hasher = Sha256::new(); - sha256_hasher.update(preimage_vec.as_slice()); - sha256_hasher.finalize() -} - -fn sha256hash6signals( - 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); - 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(); - - //println!("c_preimage_vec: {:?}", c_preimage_vec); +mod utils; +// not published due to use of `Projective...`; these utils can be found in other crates +use utils::*; - let mut sha256_hasher = Sha256::new(); - sha256_hasher.update(c_preimage_vec.as_slice()); - let sha512_hasher_result = sha256_hasher.finalize(); //512 bit hash - - let c_bytes = FieldBytes::from_iter(sha512_hasher_result.iter().copied()); - Scalar::from_repr(c_bytes).unwrap() -} - -// Hashes two values to the curve -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_", - &[DST], - ) -} +/// The domain separation tag used for hashing to the `secp256k1` curve +pub const DST: &[u8] = b"QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_"; // Hash to curve algorithm -/* currently seems to right place for this `struct` declaration; -should be moved (to the beginning of the file?) during refactoring for proper order of the items */ -/* while no consistent #API is present here it's completely `pub`; -when API will be designed it should include this `struct` (and it also probably will hold values instead of references) */ -#[derive(Debug)] +/// Struct holding signature data for a PLUME signature. +/// +/// `v1` field differintiate whether V1 or V2 protocol will be used. pub struct PlumeSignature<'a> { + /// The message that was signed. pub message: &'a [u8], + /// The public key used to verify the signature. pub pk: &'a ProjectivePoint, + /// The nullifier. pub nullifier: &'a ProjectivePoint, + /// Part of the signature data. pub c: &'a [u8], + /// Part of the signature data, a scalar value. pub s: &'a Scalar, + /// Optional signature data for variant 1 signatures. pub v1: Option>, } +/// Nested struct holding additional signature data used in variant 1 of the protocol. #[derive(Debug)] pub struct PlumeSignatureV1Fields<'a> { + /// Part of the signature data, a curve point. pub r_point: &'a ProjectivePoint, + /// Part of the signature data, a curve point. pub hashed_to_curve_r: &'a ProjectivePoint, } impl PlumeSignature<'_> { - // 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(&self) -> bool { + /// Verifies a PLUME signature. + /// Returns `true` if the signature is valid. + pub fn verify(&self) -> bool { + // 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) + // 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() { @@ -148,22 +128,43 @@ impl PlumeSignature<'_> { } } -/// Encodes the point by compressing it to 33 bytes -fn encode_pt(point: &ProjectivePoint) -> Vec { - point.to_encoded_point(true).to_bytes().to_vec() +fn c_sha256_vec_signal(values: Vec<&ProjectivePoint>) -> Output { + let preimage_vec = values + .into_iter() + .map(encode_pt) + .collect::>() + .concat(); + let mut sha256_hasher = Sha256::new(); + sha256_hasher.update(preimage_vec.as_slice()); + sha256_hasher.finalize() } -/// 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 +// Withhold removing this before implementing `sign` +fn sha256hash6signals( + 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); + 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(); + + //println!("c_preimage_vec: {:?}", c_preimage_vec); + + let mut sha256_hasher = Sha256::new(); + sha256_hasher.update(c_preimage_vec.as_slice()); + let sha512_hasher_result = sha256_hasher.finalize(); //512 bit hash + + let c_bytes = FieldBytes::from_iter(sha512_hasher_result.iter().copied()); + Scalar::from_repr(c_bytes).unwrap() } #[cfg(test)] @@ -181,6 +182,19 @@ mod tests { ); } + /// 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 + } + // Test byte_array_to_scalar() #[test] fn test_byte_array_to_scalar() { diff --git a/rust-k256/src/utils.rs b/rust-k256/src/utils.rs new file mode 100644 index 0000000..86501e6 --- /dev/null +++ b/rust-k256/src/utils.rs @@ -0,0 +1,25 @@ +use super::*; +use k256::{ + elliptic_curve::{ + hash2curve::{ExpandMsgXmd, GroupDigest}, + sec1::ToEncodedPoint, + }, + ProjectivePoint, Secp256k1, +}; // requires 'getrandom' feature + +// Hashes two values to the curve +pub(crate) 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_", + &[DST], + ) +} + +/// Encodes the point by compressing it to 33 bytes +pub(crate) fn encode_pt(point: &ProjectivePoint) -> Vec { + point.to_encoded_point(true).to_bytes().to_vec() +} diff --git a/rust-k256/tests/verification.rs b/rust-k256/tests/verification.rs index 8fb6e67..6b7d1e0 100644 --- a/rust-k256/tests/verification.rs +++ b/rust-k256/tests/verification.rs @@ -4,7 +4,7 @@ use helpers::{gen_test_scalar_sk, test_gen_signals, PlumeVersion}; use k256::elliptic_curve::sec1::ToEncodedPoint; -use zk_nullifier::{PlumeSignature, PlumeSignatureV1Fields, ProjectivePoint}; +use plume_rustcrypto::{PlumeSignature, PlumeSignatureV1Fields, ProjectivePoint}; const G: ProjectivePoint = ProjectivePoint::GENERATOR; const M: &[u8; 29] = b"An example app message string"; @@ -39,7 +39,7 @@ fn plume_v1_test() { hashed_to_curve_r, }), }; - let verified = sig.verify_signals(); + let verified = sig.verify(); println!("Verified: {}", verified); // Print nullifier @@ -112,7 +112,7 @@ fn plume_v2_test() { s: &test_data.3, v1: None } - .verify_signals()); + .verify()); } mod helpers { @@ -158,7 +158,7 @@ mod helpers { let pt: ProjectivePoint = Secp256k1::hash_from_bytes::>( &[s], //b"CURVE_XMD:SHA-256_SSWU_RO_" - &[zk_nullifier::DST], + &[plume_rustcrypto::DST], ) .unwrap(); pt @@ -199,16 +199,16 @@ mod helpers { let g_r = &g * &r; // hash[m, pk] - let hash_m_pk = + let hash_m_pk = // zk_nullifier::hash_to_curve(m, &pk) Secp256k1::hash_from_bytes::>( &[[ - m, + 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], + &[plume_rustcrypto::DST], ) .unwrap();