Skip to content

Commit

Permalink
Merge pull request #1885 from zksecurity/feature/secp256r1
Browse files Browse the repository at this point in the history
Support Secp256r1
  • Loading branch information
Trivo25 authored Oct 30, 2024
2 parents 90743e1 + d28034b commit e1bac02
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 14 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/build-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '20'

- name: Cache dependencies and build
uses: actions/cache@v4
Expand Down Expand Up @@ -89,7 +89,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '20'

- name: Restore cache
uses: actions/cache@v4
Expand Down Expand Up @@ -133,7 +133,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '20'

- name: Restore cache
uses: actions/cache@v4
Expand Down Expand Up @@ -212,7 +212,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '20'

- name: Restore npm cache
uses: actions/cache@v4
Expand Down Expand Up @@ -264,7 +264,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '20'

- name: Build o1js
run: |
Expand Down Expand Up @@ -293,7 +293,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '20'

- name: Build mina-signer
run: |
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased](https://github.com/o1-labs/o1js/compare/b04520d...HEAD)

### Added

- Support secp256r1 in elliptic curve and ECDSA gadgets https://github.com/o1-labs/o1js/pull/1885

### Fixed

- Witness generation error in `Gadgets.arrayGet()` when accessing out-of-bounds indices https://github.com/o1-labs/o1js/pull/1886
Expand Down
9 changes: 4 additions & 5 deletions src/lib/provable/gadgets/elliptic-curve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { provable } from '../types/provable-derivers.js';
import { assertPositiveInteger } from '../../../bindings/crypto/non-negative.js';
import { arrayGet, assertNotVectorEquals } from './basic.js';
import { sliceField3 } from './bit-slices.js';
import { Hashed } from '../packed.js';
import { exists } from '../core/exists.js';
import { ProvableType } from '../types/provable-intf.js';

Expand Down Expand Up @@ -54,7 +53,7 @@ namespace Ecdsa {
export type signature = { r: bigint; s: bigint };
}

function add(p1: Point, p2: Point, Curve: { modulus: bigint }) {
function add(p1: Point, p2: Point, Curve: { modulus: bigint; a: bigint }) {
let { x: x1, y: y1 } = p1;
let { x: x2, y: y2 } = p2;
let f = Curve.modulus;
Expand All @@ -63,7 +62,7 @@ function add(p1: Point, p2: Point, Curve: { modulus: bigint }) {

// constant case
if (Point.isConstant(p1) && Point.isConstant(p2)) {
let p3 = affineAdd(Point.toBigint(p1), Point.toBigint(p2), f);
let p3 = affineAdd(Point.toBigint(p1), Point.toBigint(p2), f, Curve.a);
return Point.from(p3);
}

Expand Down Expand Up @@ -120,7 +119,7 @@ function double(p1: Point, Curve: { modulus: bigint; a: bigint }) {

// constant case
if (Point.isConstant(p1)) {
let p3 = affineDouble(Point.toBigint(p1), f);
let p3 = affineDouble(Point.toBigint(p1), f, Curve.a);
return Point.from(p3);
}

Expand All @@ -129,7 +128,7 @@ function double(p1: Point, Curve: { modulus: bigint; a: bigint }) {
let [x1_, y1_] = Field3.toBigints(x1, y1);
let denom = inverse(mod(2n * y1_, f), f) ?? 0n;

let m = mod(3n * mod(x1_ ** 2n, f) * denom, f);
let m = mod((3n * mod(x1_ ** 2n, f) + Curve.a) * denom, f);
let x3 = mod(m * m - 2n * x1_, f);
let y3 = mod(m * (x1_ - x3) - y1_, f);

Expand Down
93 changes: 92 additions & 1 deletion src/lib/provable/test/ecdsa.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,57 @@ import {
} from '../../testing/equivalent.js';
import { Bool } from '../bool.js';
import { Random } from '../../testing/random.js';
import { bytesToBigInt } from '../../../bindings/crypto/bigint-helpers.js';
import { expect } from 'expect';

// quick tests
const Secp256k1 = createCurveAffine(CurveParams.Secp256k1);
const Secp256r1 = createCurveAffine(CurveParams.Secp256r1);
const Pallas = createCurveAffine(CurveParams.Pallas);
const Vesta = createCurveAffine(CurveParams.Vesta);
let curves = [Secp256k1, Pallas, Vesta];

// secp256r1 test against web crypto API
{
// generate a key pair
let { privateKey, publicKey } = await crypto.subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['sign']
);

// extract public and private key to bigints
let pkBuffer = await crypto.subtle.exportKey('raw', publicKey);
let x = bytesToBigIntBE(pkBuffer.slice(1, 33));
let y = bytesToBigIntBE(pkBuffer.slice(33));
let pk = { x, y };

var skJwk = await crypto.subtle.exportKey('jwk', privateKey);
let sk = bytesToBigIntBE(fromBase64Url(skJwk.d!));

// sanity check: we extracted keys correctly
expect(Secp256r1.from(pk)).toEqual(Secp256r1.scale(Secp256r1.one, sk));

// sign a message using web crypto
let message = new TextEncoder().encode('hello world');

let sigBuffer = await crypto.subtle.sign(
{ name: 'ECDSA', hash: 'SHA-256' },
privateKey,
message
);
let r = bytesToBigIntBE(sigBuffer.slice(0, 32));
let s = bytesToBigIntBE(sigBuffer.slice(32));
let signature = Ecdsa.Signature.from({ r, s });

// check that we can verify the signature on the message hash
let msgHash = await crypto.subtle.digest('SHA-256', message);
let m = Field3.from(Secp256r1.Scalar.mod(bytesToBigIntBE(msgHash)));

let ok = Ecdsa.verify(Secp256r1, signature, m, Point.from(pk));
assert(ok, 'web crypto signature verifies');
}

let curves = [Secp256k1, Secp256r1, Pallas, Vesta];

for (let Curve of curves) {
// prepare test inputs
Expand Down Expand Up @@ -189,3 +234,49 @@ console.timeEnd('ecdsa verify (prove)');

assert(await program.verify(proof), 'proof verifies');
proof.publicOutput.assertTrue('signature verifies');

// check constraints w/o endomorphism

let programNoEndo = ZkProgram({
name: 'ecdsa-secp256r1',
publicOutput: Bool,
methods: {
ecdsa: {
privateInputs: [],
async method() {
let signature_ = Provable.witness(Ecdsa.Signature, () => signature);
let msgHash_ = Provable.witness(Field3, () => msgHash);
let publicKey_ = Provable.witness(Point, () => publicKey);

return {
publicOutput: Ecdsa.verify(
Secp256r1,
signature_,
msgHash_,
publicKey_,
config
),
};
},
},
},
});

console.time('ecdsa verify, no endomorphism (build constraint system)');
let csNoEndo = (await programNoEndo.analyzeMethods()).ecdsa;
console.timeEnd('ecdsa verify, no endomorphism (build constraint system)');

console.log(csNoEndo.summary());

// helpers

function bytesToBigIntBE(bytes: ArrayBuffer | Uint8Array | number[]) {
return bytesToBigInt([...new Uint8Array(bytes)].reverse());
}

function fromBase64Url(b64url: string): Uint8Array {
let b64 =
b64url.replace(/-/g, '+').replace(/_/g, '/') +
'===='.slice(0, (4 - (b64url.length % 4)) % 4);
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
}
3 changes: 2 additions & 1 deletion src/lib/provable/test/elliptic-curve.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import { foreignField, throwError } from './test-utils.js';

// provable equivalence tests
const Secp256k1 = createCurveAffine(CurveParams.Secp256k1);
const Secp256r1 = createCurveAffine(CurveParams.Secp256r1);
const Pallas = createCurveAffine(CurveParams.Pallas);
const Vesta = createCurveAffine(CurveParams.Vesta);
let curves = [Secp256k1, Pallas, Vesta];
let curves = [Secp256k1, Secp256r1, Pallas, Vesta];

for (let Curve of curves) {
// prepare test inputs
Expand Down

0 comments on commit e1bac02

Please sign in to comment.