Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

feat(acvm): expose black box solvers #372

Closed
wants to merge 11 commits into from
162 changes: 63 additions & 99 deletions acvm/src/pwg/blackbox/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ use acir::{
native_types::{Witness, WitnessMap},
FieldElement,
};
use blake2::digest::generic_array::GenericArray;
use k256::elliptic_curve::sec1::FromEncodedPoint;
use k256::elliptic_curve::PrimeField;

use k256::{ecdsa::Signature, Scalar};
use k256::{
elliptic_curve::{
sec1::{Coordinates, ToEncodedPoint},
IsHigh,
},
AffinePoint, EncodedPoint, ProjectivePoint, PublicKey,
};

use crate::{pwg::witness_to_value, pwg::OpcodeResolution, OpcodeResolutionError};

Expand Down Expand Up @@ -53,113 +65,64 @@ pub(super) fn secp256k1_prehashed(
)
})?;

let result =
ecdsa_secp256k1::verify_prehashed(&hashed_message, &pub_key_x, &pub_key_y, &signature)
.is_ok();
let is_valid =
verify_secp256k1_ecdsa_signature(&hashed_message, &pub_key_x, &pub_key_y, &signature);

initial_witness.insert(output, FieldElement::from(result));
initial_witness.insert(output, FieldElement::from(is_valid));
Ok(OpcodeResolution::Solved)
}

mod ecdsa_secp256k1 {
use k256::elliptic_curve::sec1::FromEncodedPoint;
use k256::elliptic_curve::PrimeField;
use sha2::digest::generic_array::GenericArray;

use k256::{ecdsa::Signature, Scalar};
use k256::{
elliptic_curve::{
sec1::{Coordinates, ToEncodedPoint},
IsHigh,
},
AffinePoint, EncodedPoint, ProjectivePoint, PublicKey,
};
// This method is used to generate test vectors
// in noir. TODO: check that it is indeed used
#[allow(dead_code)]
fn generate_proof_data() {
use k256::ecdsa::{signature::Signer, SigningKey};

use sha2::{Digest, Sha256};

// Signing
let signing_key = SigningKey::from_bytes(&[2u8; 32]).unwrap();
let message =
b"ECDSA proves knowledge of a secret number in the context of a single message";

let digest = Sha256::digest(message);

let signature: Signature = signing_key.sign(message);
let signature_bytes: [u8; 64] = signature.as_ref().try_into().unwrap();
assert!(Signature::try_from(signature_bytes.as_slice()).unwrap() == signature);

// Verification
use k256::ecdsa::{signature::Verifier, VerifyingKey};

let verify_key = VerifyingKey::from(&signing_key);

if let Coordinates::Uncompressed { x, y } = verify_key.to_encoded_point(false).coordinates()
{
let x: [u8; 32] = (*x).into();
let y: [u8; 32] = (*y).into();

verify_prehashed(&digest, &x, &y, &signature_bytes).unwrap();
} else {
unreachable!();
}

assert!(verify_key.verify(message, &signature).is_ok());
/// Verify an ECDSA signature over the secp256k1 elliptic curve, given the hashed message
pub fn verify_secp256k1_ecdsa_signature(
hashed_msg: &[u8],
public_key_x_bytes: &[u8; 32],
public_key_y_bytes: &[u8; 32],
signature: &[u8; 64],
) -> bool {
// Convert the inputs into k256 data structures

let signature = Signature::try_from(signature.as_slice()).unwrap();

let point = EncodedPoint::from_affine_coordinates(
public_key_x_bytes.into(),
public_key_y_bytes.into(),
true,
);
let pubkey = PublicKey::from_encoded_point(&point).unwrap();

let z = Scalar::from_repr(*GenericArray::from_slice(hashed_msg)).unwrap();

// Finished converting bytes into data structures

let r = signature.r();
let s = signature.s();

// Ensure signature is "low S" normalized ala BIP 0062
if s.is_high().into() {
return false;
}

/// Verify an ECDSA signature, given the hashed message
pub(super) fn verify_prehashed(
hashed_msg: &[u8],
public_key_x_bytes: &[u8; 32],
public_key_y_bytes: &[u8; 32],
signature: &[u8; 64],
) -> Result<(), ()> {
// Convert the inputs into k256 data structures

let signature = Signature::try_from(signature.as_slice()).unwrap();

let point = EncodedPoint::from_affine_coordinates(
public_key_x_bytes.into(),
public_key_y_bytes.into(),
true,
);
let pubkey = PublicKey::from_encoded_point(&point).unwrap();

let z = Scalar::from_repr(*GenericArray::from_slice(hashed_msg)).unwrap();

// Finished converting bytes into data structures

let r = signature.r();
let s = signature.s();

// Ensure signature is "low S" normalized ala BIP 0062
if s.is_high().into() {
return Err(());
}

let s_inv = s.invert().unwrap();
let u1 = z * s_inv;
let u2 = *r * s_inv;

#[allow(non_snake_case)]
let R: AffinePoint = ((ProjectivePoint::GENERATOR * u1)
+ (ProjectivePoint::from(*pubkey.as_affine()) * u2))
.to_affine();

if let Coordinates::Uncompressed { x, y: _ } = R.to_encoded_point(false).coordinates() {
if Scalar::from_repr(*x).unwrap().eq(&r) {
return Ok(());
}
}
Err(())
let s_inv = s.invert().unwrap();
let u1 = z * s_inv;
let u2 = *r * s_inv;

#[allow(non_snake_case)]
let R: AffinePoint = ((ProjectivePoint::GENERATOR * u1)
+ (ProjectivePoint::from(*pubkey.as_affine()) * u2))
.to_affine();

match R.to_encoded_point(false).coordinates() {
Coordinates::Uncompressed { x, y: _ } => Scalar::from_repr(*x).unwrap().eq(&r),
_ => unreachable!("Point is uncompressed"),
}
}

#[cfg(test)]
mod test {
use super::verify_secp256k1_ecdsa_signature;

#[test]
fn smoke() {
fn verifies_valid_signature_with_low_s_value() {
// 0x3a73f4123a5cd2121f21cd7e8d358835476949d035d9c2da6806b4633ac8c1e2,
let hashed_message: [u8; 32] = [
0x3a, 0x73, 0xf4, 0x12, 0x3a, 0x5c, 0xd2, 0x12, 0x1f, 0x21, 0xcd, 0x7e, 0x8d, 0x35,
Expand Down Expand Up @@ -190,7 +153,8 @@ mod ecdsa_secp256k1 {
0x01, 0x61, 0xe4, 0x9a, 0x71, 0x5f, 0xcd, 0x55,
];

let valid = verify_prehashed(&hashed_message, &pub_key_x, &pub_key_y, &signature).is_ok();
let valid =
verify_secp256k1_ecdsa_signature(&hashed_message, &pub_key_x, &pub_key_y, &signature);

assert!(valid)
}
Expand Down
6 changes: 3 additions & 3 deletions acvm/src/pwg/blackbox/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ use crate::pwg::{insert_value, witness_to_value};
use crate::{pwg::OpcodeResolution, OpcodeResolutionError};

/// Returns the sha256 hash of the provided `data`.
pub(crate) fn sha256(data: &[u8]) -> [u8; 32] {
pub fn sha256(data: &[u8]) -> [u8; 32] {
generic_hash_256::<Sha256>(data)
}

/// Returns the blake2s hash of the provided `data`.
pub(crate) fn blake2s256(data: &[u8]) -> [u8; 32] {
pub fn blake2s256(data: &[u8]) -> [u8; 32] {
generic_hash_256::<Blake2s256>(data)
}

/// Returns the keccak256 hash of the provided `data`.
pub(crate) fn keccak256(data: &[u8]) -> [u8; 32] {
pub fn keccak256(data: &[u8]) -> [u8; 32] {
generic_hash_256::<Keccak256>(data)
}

Expand Down
6 changes: 4 additions & 2 deletions acvm/src/pwg/blackbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ mod logic;
mod range;

use ecdsa::secp256k1_prehashed;
// Hash functions should eventually be exposed for external consumers.
use hash::{blake2s256, keccak256, sha256};

use hash::{hash_to_field_128_security, solve_generic_256_hash_opcode};
use logic::{and, xor};
use range::solve_range_opcode;

pub use ecdsa::verify_secp256k1_ecdsa_signature;
pub use hash::{blake2s256, keccak256, sha256};

/// Check if all of the inputs to the function have assignments
///
/// Returns the first missing assignment if any are missing
Expand Down
2 changes: 1 addition & 1 deletion acvm/src/pwg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod brillig;
// Directives
mod directives;
// black box functions
mod blackbox;
pub mod blackbox;
mod block;
mod oracle;

Expand Down