Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add native rust implementation of schnorr signature verification #5053

Merged
merged 10 commits into from
May 21, 2024
794 changes: 71 additions & 723 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions acvm-repo/acvm_js/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ tracing-web.workspace = true

const-str = "0.5.5"

# This is an unused dependency, we are adding it
# so that we can enable the js feature in getrandom.
getrandom = { workspace = true, features = ["js"] }

[build-dependencies]
build-data.workspace = true
pkg-config = "0.3"
Expand Down
3 changes: 3 additions & 0 deletions acvm-repo/acvm_js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#![warn(clippy::semicolon_if_nothing_returned)]
#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))]

// See Cargo.toml for explanation.
use getrandom as _;

mod black_box_solvers;
mod build_info;
mod compression;
Expand Down
15 changes: 0 additions & 15 deletions acvm-repo/bn254_blackbox_solver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ repository.workspace = true
[dependencies]
acir.workspace = true
acvm_blackbox_solver.workspace = true
thiserror.workspace = true
cfg-if = "1.0.0"
hex.workspace = true
lazy_static = "1.4"

Expand All @@ -26,19 +24,6 @@ ark-ec = { version = "^0.4.0", default-features = false }
ark-ff = { version = "^0.4.0", default-features = false }
num-bigint.workspace = true

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasmer = { version = "4.2.6", default-features = false, features = [
"js-default",
] }

getrandom = { workspace = true, features = ["js"] }
wasm-bindgen-futures.workspace = true
js-sys.workspace = true

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
getrandom.workspace = true
wasmer = "4.2.6"

[dev-dependencies]
ark-std = { version = "^0.4.0", default-features = false }
criterion = "0.5.0"
Expand Down
40 changes: 12 additions & 28 deletions acvm-repo/bn254_blackbox_solver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,29 @@
#![warn(clippy::semicolon_if_nothing_returned)]
#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))]

use acir::{BlackBoxFunc, FieldElement};
use acir::FieldElement;
use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError};

mod embedded_curve_ops;
mod generator;
mod pedersen;
mod poseidon2;
mod wasm;
mod schnorr;

use ark_ec::AffineRepr;
pub use embedded_curve_ops::{embedded_curve_add, multi_scalar_mul};
pub use poseidon2::poseidon2_permutation;
use wasm::Barretenberg;

use self::wasm::SchnorrSig;

pub struct Bn254BlackBoxSolver {
blackbox_vendor: Barretenberg,
}
pub struct Bn254BlackBoxSolver;

impl Bn254BlackBoxSolver {
pub async fn initialize() -> Bn254BlackBoxSolver {
// We fallback to the sync initialization of barretenberg on non-wasm targets.
// This ensures that wasm packages consuming this still build on the default target (useful for linting, etc.)
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
let blackbox_vendor = Barretenberg::initialize().await;
Bn254BlackBoxSolver { blackbox_vendor }
} else {
Bn254BlackBoxSolver::new()
}
}
Bn254BlackBoxSolver
}

#[cfg(not(target_arch = "wasm32"))]
pub fn new() -> Bn254BlackBoxSolver {
let blackbox_vendor = Barretenberg::new();
Bn254BlackBoxSolver { blackbox_vendor }
Bn254BlackBoxSolver
}
}

Expand All @@ -58,16 +43,15 @@ impl BlackBoxFunctionSolver for Bn254BlackBoxSolver {
signature: &[u8; 64],
message: &[u8],
) -> Result<bool, BlackBoxResolutionError> {
let pub_key_bytes: Vec<u8> =
public_key_x.to_be_bytes().iter().copied().chain(public_key_y.to_be_bytes()).collect();

let pub_key: [u8; 64] = pub_key_bytes.try_into().unwrap();
let sig_s: [u8; 32] = signature[0..32].try_into().unwrap();
let sig_e: [u8; 32] = signature[32..64].try_into().unwrap();

self.blackbox_vendor.verify_signature(pub_key, sig_s, sig_e, message).map_err(|err| {
BlackBoxResolutionError::Failed(BlackBoxFunc::SchnorrVerify, err.to_string())
})
Ok(schnorr::verify_signature(
public_key_x.into_repr(),
public_key_y.into_repr(),
sig_s,
sig_e,
message,
))
}

fn pedersen_commitment(
Expand Down
146 changes: 146 additions & 0 deletions acvm-repo/bn254_blackbox_solver/src/schnorr/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use acvm_blackbox_solver::blake2s;
use ark_ec::{
short_weierstrass::{Affine, SWCurveConfig},
AffineRepr, CurveConfig, CurveGroup,
};
use ark_ff::{BigInteger, PrimeField, Zero};
use grumpkin::{Fq, GrumpkinParameters};

pub(crate) fn verify_signature(
pub_key_x: Fq,
pub_key_y: Fq,
sig_s_bytes: [u8; 32],
sig_e_bytes: [u8; 32],
message: &[u8],
) -> bool {
let pub_key = Affine::<GrumpkinParameters>::new_unchecked(pub_key_x, pub_key_y);

if !pub_key.is_on_curve()
|| !pub_key.is_in_correct_subgroup_assuming_on_curve()
|| pub_key.is_zero()
{
return false;
}

let sig_s =
<GrumpkinParameters as CurveConfig>::ScalarField::from_be_bytes_mod_order(&sig_s_bytes);
let sig_e =
<GrumpkinParameters as CurveConfig>::ScalarField::from_be_bytes_mod_order(&sig_e_bytes);

if sig_s.is_zero() || sig_e.is_zero() {
return false;
}

// R = g^{sig.s} • pub^{sig.e}
let r = GrumpkinParameters::GENERATOR * sig_s + pub_key * sig_e;
if r.is_zero() {
// this result implies k == 0, which would be catastrophic for the prover.
// it is a cheap check that ensures this doesn't happen.
return false;
}

// compare the _hashes_ rather than field elements modulo r
// e = H(pedersen(r, pk.x, pk.y), m), where r = R.x
let target_e_bytes = schnorr_generate_challenge(message, pub_key_x, pub_key_y, r.into_affine());

sig_e_bytes == target_e_bytes
}

fn schnorr_generate_challenge(
message: &[u8],
pub_key_x: Fq,
pub_key_y: Fq,
r: Affine<GrumpkinParameters>,
) -> [u8; 32] {
// create challenge message pedersen_commitment(R.x, pubkey)

let r_x = *r.x().expect("r has been checked to be non-zero");
let pedersen_hash = crate::pedersen::hash::hash_with_index(&[r_x, pub_key_x, pub_key_y], 0);

let mut hash_input: Vec<u8> = pedersen_hash.into_bigint().to_bytes_be();
hash_input.extend(message);

blake2s(&hash_input).unwrap()
}

#[cfg(test)]
mod schnorr_tests {
use acir::FieldElement;

use super::verify_signature;

#[test]
fn verifies_valid_signature() {
let pub_key_x: grumpkin::Fq = FieldElement::from_hex(
"0x04b260954662e97f00cab9adb773a259097f7a274b83b113532bce27fa3fb96a",
)
.unwrap()
.into_repr();
let pub_key_y: grumpkin::Fq = FieldElement::from_hex(
"0x2fd51571db6c08666b0edfbfbc57d432068bccd0110a39b166ab243da0037197",
)
.unwrap()
.into_repr();
let sig_s_bytes: [u8; 32] = [
1, 13, 119, 112, 212, 39, 233, 41, 84, 235, 255, 93, 245, 172, 186, 83, 157, 253, 76,
77, 33, 128, 178, 15, 214, 67, 105, 107, 177, 234, 77, 48,
];
let sig_e_bytes: [u8; 32] = [
27, 237, 155, 84, 39, 84, 247, 27, 22, 8, 176, 230, 24, 115, 145, 220, 254, 122, 135,
179, 171, 4, 214, 202, 64, 199, 19, 84, 239, 138, 124, 12,
];
let message: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

assert!(verify_signature(pub_key_x, pub_key_y, sig_s_bytes, sig_e_bytes, message));
}

#[test]
fn rejects_zero_e() {
let pub_key_x: grumpkin::Fq = FieldElement::from_hex(
"0x04b260954662e97f00cab9adb773a259097f7a274b83b113532bce27fa3fb96a",
)
.unwrap()
.into_repr();
let pub_key_y: grumpkin::Fq = FieldElement::from_hex(
"0x2fd51571db6c08666b0edfbfbc57d432068bccd0110a39b166ab243da0037197",
)
.unwrap()
.into_repr();
let sig_s_bytes: [u8; 32] = [
1, 13, 119, 112, 212, 39, 233, 41, 84, 235, 255, 93, 245, 172, 186, 83, 157, 253, 76,
77, 33, 128, 178, 15, 214, 67, 105, 107, 177, 234, 77, 48,
];
let sig_e_bytes: [u8; 32] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
];
let message: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

assert!(!verify_signature(pub_key_x, pub_key_y, sig_s_bytes, sig_e_bytes, message));
}

#[test]
fn rejects_zero_s() {
let pub_key_x: grumpkin::Fq = FieldElement::from_hex(
"0x04b260954662e97f00cab9adb773a259097f7a274b83b113532bce27fa3fb96a",
)
.unwrap()
.into_repr();
let pub_key_y: grumpkin::Fq = FieldElement::from_hex(
"0x2fd51571db6c08666b0edfbfbc57d432068bccd0110a39b166ab243da0037197",
)
.unwrap()
.into_repr();
let sig_s_bytes: [u8; 32] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
];
let sig_e_bytes: [u8; 32] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
];
let message: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

assert!(!verify_signature(pub_key_x, pub_key_y, sig_s_bytes, sig_e_bytes, message));
}
}
Binary file not shown.
Loading
Loading