Skip to content

Commit

Permalink
Resolve #84 (#98)
Browse files Browse the repository at this point in the history
* current progress

* current progress

* current progress

* current progress

* current progress

* semantically finished

* `fmt`

* that's for another issue and is more complex
  • Loading branch information
skaunov authored Feb 27, 2024
1 parent 0c19d2a commit 57d5b39
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 120 deletions.
6 changes: 3 additions & 3 deletions rust-arkworks/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ pub fn test_k256_affine_to_arkworks_secp256k1_affine() {
}

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 num_field_bits = 320;
let mut sk_bytes_vec = vec![0u8; num_field_bits];
let mut sk_bytes = hex::decode(hex).unwrap();

sk_bytes.reverse();
Expand Down Expand Up @@ -245,7 +245,7 @@ pub fn test_against_zk_nullifier_sig_c_and_s() {
let sig =
PlumeSignature::sign_with_r(&pp, (&keypair.0, &keypair.1), message, r, PlumeVersion::V2)
.unwrap();

assert_eq!(
coord_to_hex(sig.c.into()),
"00000000000000003dbfb717705010d4f44a70720c95e74b475bd3a783ab0b9e8a6b3b363434eb96"
Expand Down
13 changes: 7 additions & 6 deletions rust-k256/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "plume_rustcrypto"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "MIT"
description = "Implementation of PLUME: nullifier friendly signature scheme on ECDSA; using the k256 library"
Expand All @@ -11,11 +11,12 @@ keywords = ["nullifier", "zero-knowledge", "ECDSA", "PLUME"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand_core = "0.6.3"
hash2field = "0.4.0"
num-bigint = "0.4.3"
num-integer = "0.1.45"
k256 = {version = "0.13.2", features = ["arithmetic", "hash2curve", "expose-field", "sha2"]}
rand_core = "~0.6.3"
# hash2field = "0.4.0"
num-bigint = "~0.4.3"
num-integer = "~0.1.45"
k256 = {version = "~0.13.3", features = ["arithmetic", "hash2curve", "expose-field", "sha2"]}
signature = "^2.2.0"

[dev-dependencies]
hex = "0.4.3"
Expand Down
177 changes: 84 additions & 93 deletions rust-k256/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,73 +1,84 @@
// #![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.
//! A library for generating and verifying PLUME signatures.
//!
//! See <https://blog.aayushg.com/nullifier> 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::ops::ReduceNonZero,
elliptic_curve::{bigint::ArrayEncoding, group::ff::PrimeField},
FieldBytes, U256,
}; // requires 'getrandom' feature
//! # Examples
//! If you want more control or to be more generic on traits `use` [`PlumeSigner`] from [`randomizedsigner`]
//! ```rust
//! use plume_rustcrypto::{PlumeSignature, SecretKey};
//! use rand_core::OsRng;
//! # fn main() {
//! # let sk = SecretKey::random(&mut OsRng);
//! #
//! let sig_v1 = PlumeSignature::sign_v1(
//! &sk, b"ZK nullifier signature", &mut OsRng
//! );
//! assert!(sig_v1.verify());
//!
//! let sig_v2 = PlumeSignature::sign_v2(
//! &sk, b"ZK nullifier signature", &mut OsRng
//! );
//! assert!(sig_v2.verify());
//! # }
//! ```

use k256::elliptic_curve::bigint::ArrayEncoding;
use k256::elliptic_curve::ops::Reduce;
use k256::sha2::{digest::Output, Digest, Sha256}; // requires 'getrandom' feature
use k256::Scalar;
use k256::U256;
use signature::RandomizedSigner;
// 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;
/// Re-exports the `NonZeroScalar` and `SecretKey` types from the `k256` crate.
/// These are used for generating secret keys and non-zero scalars for signing.
pub use k256::{NonZeroScalar, SecretKey};
/// Re-exports the [`CryptoRngCore`] trait from the [`rand_core`] crate.
/// This allows it to be used from the current module.
pub use rand_core::CryptoRngCore;

mod utils;
// not published due to use of `Projective...`; these utils can be found in other crates
use utils::*;

/// Provides the [`RandomizedSigner`] trait implementation over [`PlumeSignature`].
pub mod randomizedsigner;
use randomizedsigner::PlumeSigner;

/// 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

/// Struct holding signature data for a PLUME signature.
///
/// `v1` field differintiate whether V1 or V2 protocol will be used.
pub struct PlumeSignature<'a> {
/// `v1specific` field differintiate whether V1 or V2 protocol will be used.
pub struct PlumeSignature {
/// The message that was signed.
pub message: &'a [u8],
pub message: Vec<u8>,
/// The public key used to verify the signature.
pub pk: &'a ProjectivePoint,
pub pk: ProjectivePoint,
/// The nullifier.
pub nullifier: &'a ProjectivePoint,
/// Part of the signature data.
pub c: &'a [u8],
pub nullifier: ProjectivePoint,
/// Part of the signature data. SHA-256 interpreted as a scalar.
pub c: NonZeroScalar,
/// Part of the signature data, a scalar value.
pub s: &'a Scalar,
pub s: NonZeroScalar,
/// Optional signature data for variant 1 signatures.
pub v1: Option<PlumeSignatureV1Fields<'a>>,
pub v1specific: Option<PlumeSignatureV1Fields>,
}
/// Nested struct holding additional signature data used in variant 1 of the protocol.
#[derive(Debug)]
pub struct PlumeSignatureV1Fields<'a> {
pub struct PlumeSignatureV1Fields {
/// Part of the signature data, a curve point.
pub r_point: &'a ProjectivePoint,
pub r_point: ProjectivePoint,
/// Part of the signature data, a curve point.
pub hashed_to_curve_r: &'a ProjectivePoint,
pub hashed_to_curve_r: ProjectivePoint,
}
impl PlumeSignature<'_> {
impl PlumeSignature {
/// Verifies a PLUME signature.
/// Returns `true` if the signature is valid.
pub fn verify(&self) -> bool {
Expand All @@ -76,56 +87,64 @@ impl PlumeSignature<'_> {
// 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<Sha256>` in the #API
let c = panic::catch_unwind(|| Output::<Sha256>::from_slice(self.c));
if c.is_err() {
return false;
}
let c = c.unwrap();
let c_scalar = *self.c;

// TODO should we allow `c` input greater than BaseField::MODULUS?
// TODO `reduce_nonzero` doesn't seems to be correct here. `NonZeroScalar` should be appropriate.
let c_scalar = &Scalar::reduce_nonzero(U256::from_be_byte_array(c.to_owned()));
let r_point = (ProjectivePoint::GENERATOR * *self.s) - (self.pk * (c_scalar));

let r_point = ProjectivePoint::GENERATOR * self.s - self.pk * c_scalar;

let hashed_to_curve = hash_to_curve(self.message, self.pk);
let hashed_to_curve = hash_to_curve(&self.message, &self.pk);
if hashed_to_curve.is_err() {
return false;
}
let hashed_to_curve = hashed_to_curve.unwrap();

let hashed_to_curve_r = hashed_to_curve * self.s - self.nullifier * c_scalar;
let hashed_to_curve_r = hashed_to_curve * *self.s - self.nullifier * (c_scalar);

if let Some(PlumeSignatureV1Fields {
r_point: sig_r_point,
hashed_to_curve_r: sig_hashed_to_curve_r,
}) = self.v1
}) = self.v1specific
{
// Check whether g^r equals g^s * pk^{-c}
if &r_point != sig_r_point {
if r_point != sig_r_point {
return false;
}

// Check whether h^r equals h^{r + sk * c} * nullifier^{-c}
if &hashed_to_curve_r != sig_hashed_to_curve_r {
if hashed_to_curve_r != sig_hashed_to_curve_r {
return false;
}

// Check if the given hash matches
c == &c_sha256_vec_signal(vec![
&ProjectivePoint::GENERATOR,
self.pk,
&hashed_to_curve,
self.nullifier,
&r_point,
&hashed_to_curve_r,
])
c_scalar
== Scalar::reduce(U256::from_be_byte_array(c_sha256_vec_signal(vec![
&ProjectivePoint::GENERATOR,
&self.pk,
&hashed_to_curve,
&self.nullifier,
&r_point,
&hashed_to_curve_r,
])))
} else {
// Check if the given hash matches
c == &c_sha256_vec_signal(vec![self.nullifier, &r_point, &hashed_to_curve_r])
c_scalar
== Scalar::reduce(U256::from_be_byte_array(c_sha256_vec_signal(vec![
&self.nullifier,
&r_point,
&hashed_to_curve_r,
])))
}
}

/// Yields the signature with `None` for `v1specific`. Same as using [`RandomizedSigner`] with [`PlumeSigner`];
/// use it when you don't want to `use` PlumeSigner and the trait in your code.
pub fn sign_v1(secret_key: &SecretKey, msg: &[u8], rng: &mut impl CryptoRngCore) -> Self {
PlumeSigner::new(secret_key, true).sign_with_rng(rng, msg)
}
/// Yields the signature with `Some` for `v1specific`. Same as using [`RandomizedSigner`] with [`PlumeSigner`];
/// use it when you don't want to `use` PlumeSigner and the trait in your code.
pub fn sign_v2(secret_key: &SecretKey, msg: &[u8], rng: &mut impl CryptoRngCore) -> Self {
PlumeSigner::new(secret_key, false).sign_with_rng(rng, msg)
}
}

fn c_sha256_vec_signal(values: Vec<&ProjectivePoint>) -> Output<Sha256> {
Expand All @@ -139,34 +158,6 @@ fn c_sha256_vec_signal(values: Vec<&ProjectivePoint>) -> Output<Sha256> {
sha256_hasher.finalize()
}

// 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)]
mod tests {
use super::*;
Expand Down Expand Up @@ -200,7 +191,7 @@ mod tests {
fn test_byte_array_to_scalar() {
let scalar = byte_array_to_scalar(&hex!(
"c6a7fc2c926ddbaf20731a479fb6566f2daa5514baae5223fe3b32edbce83254"
)); // TODO this `fn` looks suspicious as in reproducing const time ops
));
assert_eq!(
hex::encode(scalar.to_bytes()),
"c6a7fc2c926ddbaf20731a479fb6566f2daa5514baae5223fe3b32edbce83254"
Expand Down
Loading

0 comments on commit 57d5b39

Please sign in to comment.