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
This commit is contained in:
Sergey Kaunov
2024-02-15 23:18:30 +03:00
committed by GitHub
parent 71cea99030
commit e5febe3cf3
10 changed files with 218 additions and 159 deletions

View File

@@ -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",
] }

3
rust-arkworks/README.MD Normal file
View File

@@ -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.

View File

@@ -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,
}

View File

@@ -1,10 +1,26 @@
use crate::error::HashToCurveError;
/// This crate provides the PLUME signature scheme.
///
/// See <https://blog.aayushg.com/nullifier> 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<P: SWModelParameters>(point: &GroupAffine<P>) -> Vec<u8> {
hex::decode(point.to_encoded_point(true))
.expect(EXPECT_MSG_DECODE)
@@ -72,6 +92,8 @@ fn compute_c_v2<P: SWModelParameters>(
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<P: SWModelParameters>(
ark_serialize_derive::CanonicalDeserialize,
)]
pub struct Parameters<P: SWModelParameters> {
/// The generator point for the SW model parameters.
pub g_point: GroupAffine<P>,
}
/// A struct containing the PLUME signature data
#[derive(
Copy,
Clone,
@@ -89,28 +113,37 @@ pub struct Parameters<P: SWModelParameters> {
ark_serialize_derive::CanonicalDeserialize,
)]
pub struct PlumeSignature<P: SWModelParameters> {
/// The hash-to-curve output multiplied by the random `r`.
pub hashed_to_curve_r: GroupAffine<P>,
/// The randomness `r` represented as the curve point.
pub r_point: GroupAffine<P>,
pub s: P::ScalarField,
pub c: P::ScalarField,
/// The nullifier.
pub nullifier: GroupAffine<P>,
}
// 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<P: SWModelParameters> = GroupAffine<P>;
type SecretKeyMaterial<P: SWModelParameters> = 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<P: SWModelParameters> = GroupAffine<P>;
/// The scalar field element representing the secret key.
pub type SecretKeyMaterial<P: SWModelParameters> = P::ScalarField;
impl<P: SWModelParameters> PlumeSignature<P> {
/// Generate the public key and a private key.
fn keygen(pp: &Parameters<P>, rng: &mut impl Rng) -> (PublicKey<P>, SecretKeyMaterial<P>) {
/// # HAZMAT
/// No measures yet taken for the [`SecretKeyMaterial`] protection
pub fn keygen(pp: &Parameters<P>, rng: &mut impl Rng) -> (PublicKey<P>, SecretKeyMaterial<P>) {
let secret_key = SecretKeyMaterial::<P>::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<P>,
keypair: (&PublicKey<P>, &SecretKeyMaterial<P>),
@@ -174,6 +207,16 @@ impl<P: SWModelParameters> PlumeSignature<P> {
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<P>,

View File

@@ -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}"
}
]
}

View File

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

1
rust-k256/README.MD Normal file
View File

@@ -0,0 +1 @@
https://github.com/plume-sig/zk-nullifier-sig/blob/main/README.md

View File

@@ -1,101 +1,81 @@
// #![feature(generic_const_expr)]
// #![allow(incomplete_features)]
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,
}; // requires 'getrandom' feature
use std::panic;
// TODO #86
pub use k256::ProjectivePoint;
//! A library for generating (coming [soon](https://github.com/plume-sig/zk-nullifier-sig/issues/84)) 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(),
// ...
// };
// # }
// ```
const L: usize = 48;
const COUNT: usize = 2;
const OUT: usize = L * COUNT;
use k256::{
elliptic_curve::ops::ReduceNonZero,
elliptic_curve::{bigint::ArrayEncoding, group::ff::PrimeField},
FieldBytes, U256,
}; // requires 'getrandom' feature
// 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;
mod utils;
// not published due to use of `Projective...`; these utils can be found in other crates
use utils::*;
/// 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
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn c_sha256_vec_signal(values: Vec<&ProjectivePoint>) -> Output<Sha256> {
let preimage_vec = values
.into_iter()
.map(encode_pt)
.collect::<Vec<_>>()
.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);
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<ProjectivePoint, k256::elliptic_curve::Error> {
Secp256k1::hash_from_bytes::<ExpandMsgXmd<Sha256>>(
&[[m, &encode_pt(pk)].concat().as_slice()],
//b"CURVE_XMD:SHA-256_SSWU_RO_",
&[DST],
)
}
/* 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<PlumeSignatureV1Fields<'a>>,
}
/// 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<Sha256>` in the #API
let c = panic::catch_unwind(|| Output::<Sha256>::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<u8> {
point.to_encoded_point(true).to_bytes().to_vec()
fn c_sha256_vec_signal(values: Vec<&ProjectivePoint>) -> Output<Sha256> {
let preimage_vec = values
.into_iter()
.map(encode_pt)
.collect::<Vec<_>>()
.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() {

25
rust-k256/src/utils.rs Normal file
View File

@@ -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<ProjectivePoint, k256::elliptic_curve::Error> {
Secp256k1::hash_from_bytes::<ExpandMsgXmd<Sha256>>(
&[[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<u8> {
point.to_encoded_point(true).to_bytes().to_vec()
}

View File

@@ -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::<ExpandMsgXmd<Sha256>>(
&[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::<ExpandMsgXmd<Sha256>>(
&[[
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();