Skip to content

Commit

Permalink
Merge pull request #68 from plume-sig/61
Browse files Browse the repository at this point in the history
61
  • Loading branch information
Divide-By-0 authored Nov 3, 2023
2 parents f5dbd6a + 1cfa817 commit cac9b4c
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 115 deletions.
3 changes: 1 addition & 2 deletions rust-k256/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ edition = "2021"

[dependencies]
crate = "0.0.2"
digest = "0.10.3"
ring = "0.16.20"
# ring = "0.16.20"
rand_core = "0.6.3"
hex-literal = "0.3.4"
hash2field = "0.4.0"
Expand Down
232 changes: 119 additions & 113 deletions rust-k256/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
#![allow(dead_code)]
#![allow(unused_variables)]
// #![feature(generic_const_expr)]
// #![allow(incomplete_features)]

use digest::Output;
use elliptic_curve::bigint::ArrayEncoding;
use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest};
use elliptic_curve::ops::Reduce;
use elliptic_curve::sec1::ToEncodedPoint;
use k256::U256;
use k256::{
// ecdsa::{signature::Signer, Signature, SigningKey},
elliptic_curve::group::ff::PrimeField,
sha2::{Digest, Sha256},
sha2::{digest::Output, Digest, Sha256},
FieldBytes,
ProjectivePoint,
Scalar,
Secp256k1,
U256,
}; // 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
const DEFAULT_VERSION: PlumeVersion = PlumeVersion::V1;

#[derive(Debug, PartialEq)]
pub enum Error {
Expand All @@ -34,10 +30,10 @@ fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}

fn c_sha256_vec_signal(values: &[ProjectivePoint]) -> Output<Sha256> {
fn c_sha256_vec_signal(values: Vec<&ProjectivePoint>) -> Output<Sha256> {
let preimage_vec = values
.iter()
.map(|value| encode_pt(*value).unwrap())
.into_iter()
.map(|value| encode_pt(value).unwrap())
.collect::<Vec<_>>()
.concat();
let mut sha256_hasher = Sha256::new();
Expand All @@ -53,12 +49,12 @@ fn sha256hash6signals(
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).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();

Expand All @@ -75,95 +71,92 @@ fn sha256hash6signals(
// Hashes two values to the curve
fn hash_to_curve(m: &[u8], pk: &ProjectivePoint) -> ProjectivePoint {
let pt: ProjectivePoint = Secp256k1::hash_from_bytes::<ExpandMsgXmd<Sha256>>(
&[[m, &encode_pt(*pk).unwrap()].concat().as_slice()],
&[[m, &encode_pt(pk).unwrap()].concat().as_slice()],
//b"CURVE_XMD:SHA-256_SSWU_RO_",
DST,
)
.unwrap();
pt
}

enum PlumeVersion {
V1,
V2,
/* 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) */
pub struct PlumeSignature<'a> {
pub message: &'a [u8],
pub pk: &'a ProjectivePoint,
pub nullifier: &'a ProjectivePoint,
pub c: &'a [u8],
pub s: &'a Scalar,
pub v1: Option<PlumeSignatureV1Fields<'a>>,
}

// 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(
message: &[u8],
pk: &ProjectivePoint,
nullifier: &ProjectivePoint,
c: &Output<Sha256>,
s: &Scalar,
r_point_optional: &Option<ProjectivePoint>,
hashed_to_curve_optional: &Option<ProjectivePoint>,
version: PlumeVersion,
) -> bool {
let mut verified: bool = true; // looks like antipattern to @skaunov; also see #22

// The base point or generator of the curve.
let g = &ProjectivePoint::GENERATOR;

// hash[m, pk]
let hashed_to_curve_computed = &hash_to_curve(message, pk);

// Check whether g^r equals g^s * pk^{-c}
let r_point_computed: ProjectivePoint;
// TODO should we use non-zero `Scalar`?
let c_scalar = &Scalar::from_uint_reduced(U256::from_be_byte_array(*c));
match *r_point_optional {
Some(_g_r_value) => {
if (g * s - pk * c_scalar) != _g_r_value {
verified = false;
}
pub struct PlumeSignatureV1Fields<'a> {
pub r_point: &'a ProjectivePoint,
pub hashed_to_curve_r: &'a ProjectivePoint,
}
impl PlumeSignature<'_> {
/// WARNING: panics when `self.c` isn't an `Output::<Sha256>`.
/// So catch it if it's a possible case for you.
// 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 {
// don't forget to check `c` is `Output<Sha256>` in the #API
let c = Output::<Sha256>::from_slice(self.c);
// TODO should we allow `c` input greater than BaseField::MODULUS?
let c_scalar = &Scalar::from_uint_reduced(U256::from_be_byte_array(c.to_owned()));
/* @skaunov would be glad to discuss with @Divide-By-0 excessive of the following check.
Though I should notice that it at least doesn't breaking anything. */
if c_scalar.is_zero().into() {
return false;
}
None => println!("g^r not provided, check skipped"),
}
r_point_computed = g * s - pk * c_scalar;

// Check whether h^r equals h^{r + sk * c} * nullifier^{-c}
let hashed_to_curve_r_computed: ProjectivePoint;
match *hashed_to_curve_optional {
Some(_hash_m_pk_pow_r_value) => {
if (hashed_to_curve_computed * s - nullifier * c_scalar) != _hash_m_pk_pow_r_value {
verified = false;

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_r = &hashed_to_curve * self.s - self.nullifier * &c_scalar;

// Check if the given hash matches
let result = |components: Vec<&ProjectivePoint>| -> bool {
if &c_sha256_vec_signal(components) == c {
true
} else {
false
}
}
None => println!("hash_m_pk_pow_r not provided, check skipped"),
}
hashed_to_curve_r_computed = hashed_to_curve_computed * s - nullifier * c_scalar;

// Check if the given hash matches
match version {
PlumeVersion::V1 => {
if c_sha256_vec_signal(&[
*g,
*pk,
*hashed_to_curve_computed,
*nullifier,
r_point_computed,
hashed_to_curve_r_computed,
]) != *c
{
verified = false;
};

if let Some(PlumeSignatureV1Fields {
r_point: sig_r_point,
hashed_to_curve_r: sig_hashed_to_curve_r,
}) = self.v1
{
// Check whether g^r equals g^s * pk^{-c}
if &r_point != sig_r_point {
return false;
}
}
PlumeVersion::V2 => {
if c_sha256_vec_signal(&[*nullifier, r_point_computed, hashed_to_curve_r_computed])
!= *c
{
verified = false;

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

result(vec![
&ProjectivePoint::GENERATOR,
self.pk,
&hashed_to_curve,
self.nullifier,
&r_point,
&hashed_to_curve_r,
])
} else {
result(vec![self.nullifier, &r_point, &hashed_to_curve_r])
}
}
verified
}

/// Encodes the point by compressing it to 33 bytes
fn encode_pt(point: ProjectivePoint) -> Result<Vec<u8>, Error> {
fn encode_pt(point: &ProjectivePoint) -> Result<Vec<u8>, Error> {
let encoded = point.to_encoded_point(true);
Ok(encoded.to_bytes().to_vec())
}
Expand All @@ -185,11 +178,17 @@ fn byte_array_to_scalar(bytes: &[u8]) -> Scalar {
mod tests {
use super::*;

use helpers::{gen_test_scalar_sk, hash_to_secp, test_gen_signals};
use helpers::{gen_test_scalar_sk, hash_to_secp, test_gen_signals, PlumeVersion};
mod helpers {
use super::*;
use hex_literal::hex;

#[derive(Debug)]
pub enum PlumeVersion {
V1,
V2,
}

// Generates a deterministic secret key for deterministic testing. Should be replaced by random oracle in production deployments.
pub fn gen_test_scalar_sk() -> Scalar {
Scalar::from_repr(
Expand Down Expand Up @@ -291,11 +290,17 @@ mod tests {

// The Fiat-Shamir type step.
let c = match version {
PlumeVersion::V1 => {
c_sha256_vec_signal(&[g, pk, hash_m_pk, nullifier, g_r, hash_m_pk_pow_r])
}
PlumeVersion::V2 => c_sha256_vec_signal(&[nullifier, g_r, hash_m_pk_pow_r]),
PlumeVersion::V1 => c_sha256_vec_signal(vec![
&g,
&pk,
&hash_m_pk,
&nullifier,
&g_r,
&hash_m_pk_pow_r,
]),
PlumeVersion::V2 => c_sha256_vec_signal(vec![&nullifier, &g_r, &hash_m_pk_pow_r]),
};
dbg!(&c, version);

let c_scalar = &Scalar::from_uint_reduced(U256::from_be_byte_array(c.clone()));
// This value is part of the discrete log equivalence (DLEQ) proof.
Expand Down Expand Up @@ -326,16 +331,18 @@ mod tests {
// 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,
PlumeVersion::V1,
);
let verified = PlumeSignature {
message: m,
pk: &pk,
nullifier: &nullifier,
c: &c,
s: &r_sk_c,
v1: Some(PlumeSignatureV1Fields {
r_point: &g_r.unwrap(),
hashed_to_curve_r: &hash_m_pk_pow_r.unwrap(),
}),
}
.verify_signals();
println!("Verified: {}", verified);

// Print nullifier
Expand Down Expand Up @@ -401,7 +408,7 @@ mod tests {
);

// Test encode_pt()
let g_as_bytes = encode_pt(g).unwrap();
let g_as_bytes = encode_pt(&g).unwrap();
assert_eq!(
hex::encode(g_as_bytes),
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
Expand Down Expand Up @@ -447,16 +454,15 @@ mod tests {
// 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,
PlumeVersion::V2,
);
let verified = PlumeSignature {
message: m,
pk: &pk,
nullifier: &nullifier,
c: &c,
s: &r_sk_c,
v1: None,
}
.verify_signals();
assert!(verified)
}
}

0 comments on commit cac9b4c

Please sign in to comment.