Skip to content

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

noir-ecdsa

Optimized Noir library that evaluates ECDSA (Elliptic Curve Digital Signature Algorithm) signatures for multiple elliptic curves.

This library supports verification of ECDSA signatures on various elliptic curves including NIST standard curves (secp192r1, secp224r1, secp256r1, secp384r1, secp521r1) and Brainpool curves (brainpoolP192r1/t1, brainpoolP224r1/t1, brainpoolP256r1/t1, brainpoolP384r1/t1, brainpoolP512r1/t1).

This library uses a slightly modified version of noir-bignum as a dependency for big number arithmetic operations as well as a slightly modified version of noir-bigcurve for curve operations.

Supported Curves

NIST Curves

  • secp192r1 (P-192): 192-bit prime field curve
  • secp224r1 (P-224): 224-bit prime field curve
  • secp256r1 (P-256): 256-bit prime field curve
  • secp384r1 (P-384): 384-bit prime field curve
  • secp521r1 (P-521): 521-bit prime field curve

Brainpool Curves

  • brainpoolP192r1: 192-bit Brainpool curve
  • brainpoolP192t1: 192-bit Brainpool curve (twisted)
  • brainpoolP224r1: 224-bit Brainpool curve
  • brainpoolP224t1: 224-bit Brainpool curve (twisted)
  • brainpoolP256r1: 256-bit Brainpool curve
  • brainpoolP256t1: 256-bit Brainpool curve (twisted)
  • brainpoolP384r1: 384-bit Brainpool curve
  • brainpoolP384t1: 384-bit Brainpool curve (twisted)
  • brainpoolP512r1: 512-bit Brainpool curve
  • brainpoolP512t1: 512-bit Brainpool curve (twisted)

Installation

In your Nargo.toml file, add the version of this library you would like to install under dependency:

[dependencies]
noir_ecdsa = { tag = "v0.5.0", git = "https://github.com/zkpassport/noir-ecdsa" }
bigcurve = {tag = "v0.14.0-2", git = "https://github.com/zkpassport/noir_bigcurve"}

Usage

Basic Usage

The library provides both a generic verify_ecdsa function and curve-specific convenience functions. Here are examples for each supported curve:

secp256r1 (P-256)

use bigcurve::curves::secp256r1::{Secp256r1_Fq, Secp256r1_Fr};
use noir_ecdsa::ecdsa::verify_secp256r1_ecdsa;


// Your message hash (typically SHA-256)
let message_hash: [u8; 32] = [/* your 32-byte hash */];

// Public key coordinates (on the secp256r1 curve)
let public_key_x: Secp256r1_Fq = /* your public key x coordinate */;
let public_key_y: Secp256r1_Fq = /* your public key y coordinate */;
// Both must be valid field elements: either call `validate_in_field()` on them,
// or obtain them from `from_be_bytes` / `from_le_bytes`, which validate for you.

// ECDSA signature (r, s)
let signature_r: Secp256r1_Fr = /* signature r component */;
let signature_s: Secp256r1_Fr = /* signature s component */;
// Both must be valid field elements: either call `validate_in_field()` on them,
// or obtain them from `from_be_bytes` / `from_le_bytes`, which validate for you.

// Verify the signature
let is_valid = verify_secp256r1_ecdsa(
    public_key_x,
    public_key_y,
    message_hash,
    (signature_r, signature_s)
);

assert(is_valid);

secp384r1 (P-384)

use bigcurve::curves::secp384r1::{Secp384r1_Fq, Secp384r1_Fr};
use noir_ecdsa::ecdsa::verify_secp384r1_ecdsa;

let message_hash: [u8; 32] = [/* your hash */];
let public_key_x: Secp384r1_Fq = /* public key x */;
let public_key_y: Secp384r1_Fq = /* public key y */;
let signature_r: Secp384r1_Fr = /* signature r */;
let signature_s: Secp384r1_Fr = /* signature s */;

let is_valid = verify_secp384r1_ecdsa(
    public_key_x,
    public_key_y,
    message_hash,
    (signature_r, signature_s)
);

secp521r1 (P-521)

use bigcurve::curves::secp521r1::{Secp521r1_Fq, Secp521r1_Fr};
use noir_ecdsa::ecdsa::verify_secp521r1_ecdsa;

let message_hash: [u8; 32] = [/* your hash */];
let public_key_x: Secp521r1_Fq = /* public key x */;
let public_key_y: Secp521r1_Fq = /* public key y */;
let signature_r: Secp521r1_Fr = /* signature r */;
let signature_s: Secp521r1_Fr = /* signature s */;

let is_valid = verify_secp521r1_ecdsa(
    public_key_x,
    public_key_y,
    message_hash,
    (signature_r, signature_s)
);

Brainpool Curves

use bigcurve::curves::brainpoolP256r1::{BrainpoolP256r1_Fq, BrainpoolP256r1_Fr};
use noir_ecdsa::ecdsa::{
    verify_brainpoolp192r1_ecdsa,
    verify_brainpoolp192t1_ecdsa,
    verify_brainpoolp224r1_ecdsa,
    verify_brainpoolp224t1_ecdsa,
    verify_brainpoolp256r1_ecdsa,
    verify_brainpoolp256t1_ecdsa,
    verify_brainpoolp384r1_ecdsa,
    verify_brainpoolp384t1_ecdsa,
    verify_brainpoolp512r1_ecdsa,
    verify_brainpoolp512t1_ecdsa
};

// Example with brainpoolP256r1
let message_hash: [u8; 32] = [/* your hash */];
let public_key_x: BrainpoolP256r1_Fq = /* public key x */;
let public_key_y: BrainpoolP256r1_Fq = /* public key y */;
let signature_r: BrainpoolP256r1_Fr = /* signature r */;
let signature_s: BrainpoolP256r1_Fr = /* signature s */;

let is_valid = verify_brainpoolp256r1_ecdsa(
    public_key_x,
    public_key_y,
    message_hash,
    (signature_r, signature_s)
);

Generic Usage

For advanced use cases, you can use the generic verify_ecdsa function:

use noir_ecdsa::ecdsa::verify_ecdsa;
use bigcurve::curves::secp256r1::{Secp256r1, Secp256r1_Fq, Secp256r1_Fr};

let is_valid = verify_ecdsa::<32, 65, Secp256r1_Fq, Secp256r1_Fr, Secp256r1>(
    public_key_x,
    public_key_y,
    message_hash,
    (signature_r, signature_s)
);

Variable Hash Sizes

All verification functions support variable hash sizes using generics:

// For a SHA-384 hash (48 bytes)
let sha384_hash: [u8; 48] = [/* your 48-byte hash */];
// Note: you should not need to specify the hash size as it is inferred from the array size
let is_valid = verify_secp384r1_ecdsa::<48>(
    public_key_x,
    public_key_y,
    sha384_hash,
    (signature_r, signature_s)
);

// For a SHA-512 hash (64 bytes)
// Note: you should not need to specify the hash size as it is inferred from the array size
let sha512_hash: [u8; 64] = [/* your 64-byte hash */];
let is_valid = verify_secp521r1_ecdsa::<64>(
    public_key_x,
    public_key_y,
    sha512_hash,
    (signature_r, signature_s)
);

Security Features

Signature Malleability Protection

This library automatically protects against signature malleability attacks by enforcing canonical signature form. For any valid signature (r, s), there could potentially be another valid signature (r, n - s) where n is the curve order. To prevent this, the library checks that s ≤ n/2, ensuring only one canonical form is accepted.

This assumes the signature you pass has a s value less than n/2, otherwise you will get this error: Signature s value is not in canonical form. So make sure to preprocess your ECDSA signatures before passing them to the library to ensure this. You can check how we do this in TypeScript here.

Input Assumptions

  • r and s are expected to be valid field elements. Either call validate_in_field on them, or use the safe from_be_bytes / from_le_bytes versions when importing them from bytes.
  • The s <= n/2 malleability check is a requirement on the caller. You must pass the low-s form of the signature, otherwise verification fails (see "Signature Malleability Protection" above).
  • public_key_x and public_key_y must be valid field elements. As with r and s, either call validate_in_field on them, or use the safe from_be_bytes / from_le_bytes versions when importing them from bytes, so an out-of-range limb encoding can't slip through.
  • The public key must lie on the curve. The library enforces this with validate_on_curve(); do not remove it. Skipping it allows an invalid-curve attack, where a prover supplies an off-curve point and forges a passing verification.
  • The public key must NOT be a free private witness. This is a soundness requirement, not a nicety. validate_on_curve() only checks that the point is a valid curve point, not that it is the expected signer's key. If public_key_x / public_key_y are supplied as private, prover-controlled inputs, verification is unsound: an attacker can generate their own keypair, sign any message with it, and prove a "valid signature" for arbitrary data. Always bind the public key to something the verifier trusts, e.g. expose it as a public input, hash it into the public inputs, or check it against a trusted key registry.
  • The final R.x == r comparison casts R.x limb by limb from the base field into the scalar field. This works for the supported curves. The rule is bitlen(Fq) <= bitlen(Fr): if the base field modulus p has more bits than the scalar field modulus n, R.x could overflow the scalar field's range. Check the bit lengths, do not assume - cofactor > 1 is a red flag (n << p), but even a cofactor-1 curve can have bitlen(Fq) > bitlen(Fr) when p sits just above a power of two. (The reverse, n wider than p, is fine.)

Examples

See the test functions in src/tests.nr for complete working examples of each curve. Each test demonstrates the following:

  1. Computing a SHA-256 hash of a message
  2. Setting up public key coordinates and signature values
  3. Verifying the signature
  4. Asserting the result

To run the tests:

nargo test

Algorithm Details

The ECDSA verification algorithm implemented follows the standard process:

  1. Input processing: Split the signature into its r and s components
  2. Non-zero check: Ensure r != 0 (mod n), which also rejects the non-canonical r == n
  3. Canonical form check: Ensure s ≤ n/2 to prevent malleability
  4. Hash processing: Pad the message hash to the appropriate size and convert it to a field element
  5. Inverse computation: Calculate w = s^(-1) mod n
  6. Scalar computation: Calculate u1 = e*w mod n and u2 = r*w mod n
  7. Public key validation: Convert the public key to a point on the curve and ensure it is on the curve
  8. Point computation: Calculate R = u1*G + u2*Q using multi-scalar multiplication (MSM)
  9. Verification: Check if R.x ≡ r (mod n), the final check to ensure the signature is valid

Where:

  • e is the message hash as a field element
  • G is the curve generator point
  • Q is the public key point
  • n is the curve order

Contributing

We welcome contributions! Please feel free to submit issues and pull requests.

License

This project is licensed under the Apache 2.0 License - see the LICENSE file for details.

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages