diff --git a/CHANGELOG.md b/CHANGELOG.md index b04d1ea09e..9529faa796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed - Mitigate security hazard of deploying token contracts https://github.com/o1-labs/o1js/issues/1439 +- Make `Circuit` handle types with a `.provable` property (like those used in ECDSA) https://github.com/o1-labs/o1js/pull/1471 + - To support offchain, non-Pickles proofs of ECDSA signatures ## [0.16.1](https://github.com/o1-labs/o1js/compare/834a44002...3b5f7c7) diff --git a/src/examples/circuit/ecdsa.ts b/src/examples/circuit/ecdsa.ts new file mode 100644 index 0000000000..8dfe02627e --- /dev/null +++ b/src/examples/circuit/ecdsa.ts @@ -0,0 +1,45 @@ +import { + Circuit, + circuitMain, + public_, + Crypto, + createEcdsa, + createForeignCurve, + Bytes, + assert, +} from 'o1js'; + +export { Secp256k1, Ecdsa, Bytes32, Reserves }; + +class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} +class Ecdsa extends createEcdsa(Secp256k1) {} +class Bytes32 extends Bytes(32) {} + +class Reserves extends Circuit { + @circuitMain + static main( + @public_ message: Bytes32, + signature: Ecdsa, + publicKey: Secp256k1 + ) { + assert(signature.verify(message, publicKey)); + } +} + +console.time('generateKeypair'); +let kp = await Reserves.generateKeypair(); +console.timeEnd('generateKeypair'); + +let message = Bytes32.random(); +let privateKey = Secp256k1.Scalar.random(); +let publicKey = Secp256k1.generator.scale(privateKey); +let signature = Ecdsa.sign(message.toBytes(), privateKey.toBigInt()); + +console.time('prove'); +let proof = await Reserves.prove([signature, publicKey], [message], kp); +console.timeEnd('prove'); + +console.time('verify'); +let isValid = await Reserves.verify([message], kp.verificationKey(), proof); +assert(isValid, 'verifies'); +console.timeEnd('verify'); diff --git a/src/lib/circuit.ts b/src/lib/circuit.ts index dd697cc967..5942a1f009 100644 --- a/src/lib/circuit.ts +++ b/src/lib/circuit.ts @@ -266,20 +266,27 @@ function circuitMain( }; } +type ProvableInputPure = ProvablePure | { provable: ProvablePure }; + // TODO support auxiliary data -function provableFromTuple(typs: ProvablePure[]): ProvablePure { +function provableFromTuple( + inputTypes: ProvableInputPure[] +): ProvablePure { + let types = inputTypes.map((t) => ('provable' in t ? t.provable : t)); return { sizeInFields: () => { - return typs.reduce((acc, typ) => acc + typ.sizeInFields(), 0); + return types.reduce((acc, type) => acc + type.sizeInFields(), 0); }, toFields: (t: Array) => { - if (t.length !== typs.length) { - throw new Error(`typOfArray: Expected ${typs.length}, got ${t.length}`); + if (t.length !== types.length) { + throw new Error( + `typOfArray: Expected ${types.length}, got ${t.length}` + ); } let res = []; for (let i = 0; i < t.length; ++i) { - res.push(...typs[i].toFields(t[i])); + res.push(...types[i].toFields(t[i])); } return res; }, @@ -291,7 +298,7 @@ function provableFromTuple(typs: ProvablePure[]): ProvablePure { fromFields: (xs: Array) => { let offset = 0; let res: Array = []; - typs.forEach((typ) => { + types.forEach((typ) => { const n = typ.sizeInFields(); res.push(typ.fromFields(xs.slice(offset, offset + n))); offset += n; @@ -300,7 +307,7 @@ function provableFromTuple(typs: ProvablePure[]): ProvablePure { }, check(xs: Array) { - typs.forEach((typ, i) => (typ as any).check(xs[i])); + types.forEach((typ, i) => (typ as any).check(xs[i])); }, }; }