Skip to content

Commit

Permalink
Merge pull request #1240 from o1-labs/feature/ecdsa-new
Browse files Browse the repository at this point in the history
ECDSA gadget
  • Loading branch information
mitschabaude committed Dec 6, 2023
2 parents 4efb9e5 + 507a04b commit 74d1b1b
Show file tree
Hide file tree
Showing 29 changed files with 1,144 additions and 197 deletions.
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- **Foreign field arithmetic** exposed through the `createForeignField()` class factory https://github.com/o1-labs/snarkyjs/pull/985
- **ECDSA signature verification**: new provable method `Gadgets.Ecdsa.verify()` and helpers on `Gadgets.Ecdsa.Signature` https://github.com/o1-labs/o1js/pull/1240
- For an example, see `./src/examples/zkprogram/ecdsa`
- `Crypto` namespace which exposes elliptic curve and finite field arithmetic on bigints, as well as example curve parameters https://github.com/o1-labs/o1js/pull/1240
- `Gadgets.ForeignField.assertMul()` for efficiently constraining products of sums in non-native arithmetic https://github.com/o1-labs/o1js/pull/1262
- `Unconstrained` for safely maintaining unconstrained values in provable code https://github.com/o1-labs/o1js/pull/1262
- `Gadgets.rangeCheck8()` to assert that a value fits in 8 bits https://github.com/o1-labs/o1js/pull/1288
Expand All @@ -35,12 +38,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions.
- `this.x.getAndAssertEquals()` is now `this.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1263
- `this.x.assertEquals(x)` is now `this.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1263
- `this.x.assertNothing()` is now `this.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1263
- `this.account.x.getAndAssertEquals(x)` is now `this.account.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1265
- `this.account.x.assertBetween()` is now `this.account.x.requireBetween()` https://github.com/o1-labs/o1js/pull/1265
- `this.account.x.assertEquals(x)` is now `this.account.x.requireEquals(x)` https://github.com/o1-labs/o1js/pull/1265
- `this.account.x.assertNothing()` is now `this.account.x.requireNothing()` https://github.com/o1-labs/o1js/pull/1265
- `this.currentSlot.assertBetween(x,y)` is now `this.currentSlot.requireBetween(x,y)` https://github.com/o1-labs/o1js/pull/1265
- `this.network.x.getAndAssertEquals()` is now `this.network.x.getAndRequireEquals()` https://github.com/o1-labs/o1js/pull/1265
- `Provable.constraintSystem()` and `{ZkProgram,SmartContract}.analyzeMethods()` return a `print()` method for pretty-printing the constraint system https://github.com/o1-labs/o1js/pull/1240

## [0.14.2](https://github.com/o1-labs/o1js/compare/26363465d...1ad7333e9e)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"build:test": "npx tsc -p tsconfig.test.json && cp src/snarky.d.ts dist/node/snarky.d.ts",
"build:node": "npm run build",
"build:web": "rimraf ./dist/web && node src/build/buildWeb.js",
"build:examples": "rimraf ./dist/examples && npx tsc -p tsconfig.examples.json || exit 0",
"build:examples": "npm run build && rimraf ./dist/examples && npx tsc -p tsconfig.examples.json",
"build:docs": "npx typedoc --tsconfig ./tsconfig.web.json",
"prepublish:web": "NODE_ENV=production node src/build/buildWeb.js",
"prepublish:node": "npm run build && NODE_ENV=production node src/build/buildNode.js",
Expand Down
2 changes: 1 addition & 1 deletion src/bindings
6 changes: 5 additions & 1 deletion src/build/copy-to-dist.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import { copyFromTo } from './utils.js';

await copyFromTo(
['src/snarky.d.ts', 'src/bindings/compiled/_node_bindings'],
[
'src/snarky.d.ts',
'src/bindings/compiled/_node_bindings',
'src/bindings/compiled/node_bindings/plonk_wasm.d.cts',
],
'src/',
'dist/node/'
);
2 changes: 1 addition & 1 deletion src/examples/api_exploration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ console.assert(!signature.verify(pubKey, msg1).toBoolean());
*/

/* You can initialize elements as literals as follows: */
let g0 = new Group(-1, 2);
let g0 = Group.from(-1, 2);
let g1 = new Group({ x: -2, y: 2 });

/* There is also a predefined generator. */
Expand Down
4 changes: 2 additions & 2 deletions src/examples/constraint_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Field, Poseidon, Provable } from 'o1js';

let hash = Poseidon.hash([Field(1), Field(-1)]);

let { rows, digest, gates, publicInputSize } = Provable.constraintSystem(() => {
let { rows, digest, publicInputSize, print } = Provable.constraintSystem(() => {
let x = Provable.witness(Field, () => Field(1));
let y = Provable.witness(Field, () => Field(-1));
x.add(y).assertEquals(Field(0));
let z = Poseidon.hash([x, y]);
z.assertEquals(hash);
});

console.log(JSON.stringify(gates));
print();
console.log({ rows, digest, publicInputSize });
2 changes: 1 addition & 1 deletion src/examples/zkapps/local_events_zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ let events = await zkapp.fetchEvents(UInt32.from(0));
console.log(events);
console.log('---- emitted events: ----');
// fetches all events from zkapp starting block height 0 and ending at block height 10
events = await zkapp.fetchEvents(UInt32.from(0), UInt64.from(10));
events = await zkapp.fetchEvents(UInt32.from(0), UInt32.from(10));
console.log(events);
console.log('---- emitted events: ----');
// fetches all events
Expand Down
2 changes: 1 addition & 1 deletion src/examples/zkapps/sudoku/sudoku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
isReady,
Poseidon,
Struct,
Circuit,
Provable,
} from 'o1js';

export { Sudoku, SudokuZkApp };
Expand Down
4 changes: 2 additions & 2 deletions src/examples/zkapps/voting/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export class Member extends CircuitValue {
return this;
}

static empty() {
return new Member(PublicKey.empty(), UInt64.zero);
static empty<T extends new (...args: any) => any>(): InstanceType<T> {
return new Member(PublicKey.empty(), UInt64.zero) as any;
}

static from(publicKey: PublicKey, balance: UInt64) {
Expand Down
8 changes: 4 additions & 4 deletions src/examples/zkprogram/program-with-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ console.log('program digest', MyProgram.digest());

console.log('compiling MyProgram...');
let { verificationKey } = await MyProgram.compile();
console.log('verification key', verificationKey.slice(0, 10) + '..');
console.log('verification key', verificationKey.data.slice(0, 10) + '..');

console.log('proving base case...');
let proof = await MyProgram.baseCase(Field(0));
Expand All @@ -52,7 +52,7 @@ proof = testJsonRoundtrip(MyProof, proof);
proof satisfies Proof<Field, void>;

console.log('verify...');
let ok = await verify(proof.toJSON(), verificationKey);
let ok = await verify(proof.toJSON(), verificationKey.data);
console.log('ok?', ok);

console.log('verify alternative...');
Expand All @@ -64,7 +64,7 @@ proof = await MyProgram.inductiveCase(Field(1), proof);
proof = testJsonRoundtrip(MyProof, proof);

console.log('verify...');
ok = await verify(proof, verificationKey);
ok = await verify(proof, verificationKey.data);
console.log('ok?', ok);

console.log('verify alternative...');
Expand All @@ -76,7 +76,7 @@ proof = await MyProgram.inductiveCase(Field(2), proof);
proof = testJsonRoundtrip(MyProof, proof);

console.log('verify...');
ok = await verify(proof.toJSON(), verificationKey);
ok = await verify(proof.toJSON(), verificationKey.data);

console.log('ok?', ok && proof.publicInput.toString() === '2');

Expand Down
8 changes: 4 additions & 4 deletions src/examples/zkprogram/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ console.log('program digest', MyProgram.digest());

console.log('compiling MyProgram...');
let { verificationKey } = await MyProgram.compile();
console.log('verification key', verificationKey.slice(0, 10) + '..');
console.log('verification key', verificationKey.data.slice(0, 10) + '..');

console.log('proving base case...');
let proof = await MyProgram.baseCase();
Expand All @@ -53,7 +53,7 @@ proof = testJsonRoundtrip(MyProof, proof);
proof satisfies Proof<undefined, Field>;

console.log('verify...');
let ok = await verify(proof.toJSON(), verificationKey);
let ok = await verify(proof.toJSON(), verificationKey.data);
console.log('ok?', ok);

console.log('verify alternative...');
Expand All @@ -65,7 +65,7 @@ proof = await MyProgram.inductiveCase(proof);
proof = testJsonRoundtrip(MyProof, proof);

console.log('verify...');
ok = await verify(proof, verificationKey);
ok = await verify(proof, verificationKey.data);
console.log('ok?', ok);

console.log('verify alternative...');
Expand All @@ -77,7 +77,7 @@ proof = await MyProgram.inductiveCase(proof);
proof = testJsonRoundtrip(MyProof, proof);

console.log('verify...');
ok = await verify(proof.toJSON(), verificationKey);
ok = await verify(proof.toJSON(), verificationKey.data);

console.log('ok?', ok && proof.publicOutput.toString() === '2');

Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export {
method,
declareMethods,
Account,
VerificationKey,
Reducer,
} from './lib/zkapp.js';
export { state, State, declareState } from './lib/state.js';
Expand All @@ -51,6 +50,7 @@ export {
Empty,
Undefined,
Void,
VerificationKey,
} from './lib/proof_system.js';
export { Cache, CacheHeader } from './lib/proof-system/cache.js';

Expand Down Expand Up @@ -87,6 +87,8 @@ export { Nullifier } from './lib/nullifier.js';
import { ExperimentalZkProgram, ZkProgram } from './lib/proof_system.js';
export { ZkProgram };

export { Crypto } from './lib/crypto.js';

// experimental APIs
import { Callback } from './lib/zkapp.js';
import { createChildAccountUpdate } from './lib/account_update.js';
Expand Down
3 changes: 3 additions & 0 deletions src/lib/account_update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,9 @@ class AccountUpdate implements Types.AccountUpdate {
return [{ lazyAuthorization, children, parent, id, label }, aux];
}
static toInput = Types.AccountUpdate.toInput;
static empty() {
return AccountUpdate.dummy();
}
static check = Types.AccountUpdate.check;
static fromFields(fields: Field[], [other, aux]: any[]): AccountUpdate {
let accountUpdate = Types.AccountUpdate.fromFields(fields, aux);
Expand Down
31 changes: 31 additions & 0 deletions src/lib/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { CurveParams as CurveParams_ } from '../bindings/crypto/elliptic-curve-examples.js';
import {
CurveAffine,
createCurveAffine,
} from '../bindings/crypto/elliptic_curve.js';

// crypto namespace
const Crypto = {
/**
* Create elliptic curve arithmetic methods.
*/
createCurve(params: Crypto.CurveParams): Crypto.Curve {
return createCurveAffine(params);
},
/**
* Parameters defining an elliptic curve in short Weierstraß form
* y^2 = x^3 + ax + b
*/
CurveParams: CurveParams_,
};

namespace Crypto {
/**
* Parameters defining an elliptic curve in short Weierstraß form
* y^2 = x^3 + ax + b
*/
export type CurveParams = CurveParams_;

export type Curve = CurveAffine;
}
export { Crypto };
4 changes: 2 additions & 2 deletions src/lib/foreign-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class ForeignField {
// TODO: this is not very efficient, but the only way to abstract away the complicated
// range check assumptions and also not introduce a global context of pending range checks.
// we plan to get rid of bounds checks anyway, then this is just a multi-range check
Gadgets.ForeignField.assertAlmostFieldElements([this.value], this.modulus, {
Gadgets.ForeignField.assertAlmostReduced([this.value], this.modulus, {
skipMrc: true,
});
return this.Constructor.AlmostReduced.unsafeFrom(this);
Expand All @@ -172,7 +172,7 @@ class ForeignField {
static assertAlmostReduced<T extends Tuple<ForeignField>>(
...xs: T
): TupleMap<T, AlmostForeignField> {
Gadgets.ForeignField.assertAlmostFieldElements(
Gadgets.ForeignField.assertAlmostReduced(
xs.map((x) => x.value),
this.modulus,
{ skipMrc: true }
Expand Down
45 changes: 44 additions & 1 deletion src/lib/gadgets/basic.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,58 @@
/**
* Basic gadgets that only use generic gates
*/
import { Fp } from '../../bindings/crypto/finite_field.js';
import type { Field, VarField } from '../field.js';
import { existsOne, toVar } from './common.js';
import { Gates } from '../gates.js';
import { TupleN } from '../util/types.js';

export { assertOneOf };
export { arrayGet, assertOneOf };

// TODO: create constant versions of these and expose on Gadgets

/**
* Get value from array in O(n) rows.
*
* Assumes that index is in [0, n), returns an unconstrained result otherwise.
*
* Note: This saves 0.5*n constraints compared to equals() + switch()
*/
function arrayGet(array: Field[], index: Field) {
let i = toVar(index);

// witness result
let a = existsOne(() => array[Number(i.toBigInt())].toBigInt());

// we prove a === array[j] + z[j]*(i - j) for some z[j], for all j.
// setting j = i, this implies a === array[i]
// thanks to our assumption that the index i is within bounds, we know that j = i for some j
let n = array.length;
for (let j = 0; j < n; j++) {
let zj = existsOne(() => {
let zj = Fp.div(
Fp.sub(a.toBigInt(), array[j].toBigInt()),
Fp.sub(i.toBigInt(), Fp.fromNumber(j))
);
return zj ?? 0n;
});
// prove that z[j]*(i - j) === a - array[j]
// TODO abstract this logic into a general-purpose assertMul() gadget,
// which is able to use the constant coefficient
// (snarky's assert_r1cs somehow leads to much more constraints than this)
if (array[j].isConstant()) {
// zj*i + (-j)*zj + 0*i + array[j] === a
assertBilinear(zj, i, [1n, -BigInt(j), 0n, array[j].toBigInt()], a);
} else {
let aMinusAj = toVar(a.sub(array[j]));
// zj*i + (-j)*zj + 0*i + 0 === (a - array[j])
assertBilinear(zj, i, [1n, -BigInt(j), 0n, 0n], aMinusAj);
}
}

return a;
}

/**
* Assert that a value equals one of a finite list of constants:
* `(x - c1)*(x - c2)*...*(x - cn) === 0`
Expand Down

0 comments on commit 74d1b1b

Please sign in to comment.