o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
222 lines (192 loc) • 6.66 kB
text/typescript
import { createCurveAffine } from '../../../bindings/crypto/elliptic-curve.js';
import {
Ecdsa,
EllipticCurve,
Point,
initialAggregator,
verifyEcdsaConstant,
} from '../gadgets/elliptic-curve.js';
import { Field3 } from '../gadgets/foreign-field.js';
import { CurveParams } from '../../../bindings/crypto/elliptic-curve-examples.js';
import { Provable } from '../provable.js';
import { ZkProgram } from '../../proof-system/zkprogram.js';
import { assert } from '../gadgets/common.js';
import { foreignField, uniformForeignField } from './test-utils.js';
import {
First,
Second,
bool,
equivalentProvable,
fromRandom,
map,
oneOf,
record,
} from '../../testing/equivalent.js';
import { Bool } from '../bool.js';
import { Random } from '../../testing/random.js';
// quick tests
const Secp256k1 = createCurveAffine(CurveParams.Secp256k1);
const Pallas = createCurveAffine(CurveParams.Pallas);
const Vesta = createCurveAffine(CurveParams.Vesta);
let curves = [Secp256k1, Pallas, Vesta];
for (let Curve of curves) {
// prepare test inputs
let field = foreignField(Curve.Field);
let scalar = foreignField(Curve.Scalar);
let privateKey = uniformForeignField(Curve.Scalar);
// correct signature shape, but independently random components, which will never form a valid signature
let badSignature = record({
signature: record({ r: scalar, s: scalar }),
msg: scalar,
publicKey: record({ x: field, y: field }),
});
let signatureInputs = record({ privateKey, msg: scalar });
let signature = map(
{ from: signatureInputs, to: badSignature },
({ privateKey, msg }) => {
let publicKey = Curve.scale(Curve.one, privateKey);
let signature = Ecdsa.sign(Curve, msg, privateKey);
return { signature, msg, publicKey };
}
);
// with 30% prob, test the version without GLV even if the curve supports it
let noGlv = fromRandom(Random.map(Random.fraction(), (f) => f < 0.3));
// provable method we want to test
const verify = (sig: Second<typeof signature>, noGlv: boolean) => {
// invalid public key can lead to either a failing constraint, or verify() returning false
EllipticCurve.assertOnCurve(sig.publicKey, Curve);
// additional checks which are inconsistent between constant and variable verification
let { s, r } = sig.signature;
if (Field3.isConstant(s)) {
assert(Field3.toBigint(s) !== 0n, 'invalid signature (s=0)');
}
if (Field3.isConstant(r)) {
assert(Field3.toBigint(r) !== 0n, 'invalid signature (r=0)');
}
let hasGlv = Curve.hasEndomorphism;
if (noGlv) Curve.hasEndomorphism = false; // hack to force non-GLV version
try {
return Ecdsa.verifyV2(Curve, sig.signature, sig.msg, sig.publicKey);
} finally {
Curve.hasEndomorphism = hasGlv;
}
};
// input validation equivalent to the one implicit in verify()
const checkInputs = ({
signature: { r, s },
publicKey,
}: First<typeof signature>) => {
assert(r !== 0n && s !== 0n, 'invalid signature');
let pk = Curve.fromNonzero(publicKey);
assert(Curve.isOnCurve(pk), 'invalid public key');
return true;
};
// positive test
equivalentProvable({ from: [signature, noGlv], to: bool, verbose: true })(
() => true,
verify,
`${Curve.name}: verifies`
);
// negative test
equivalentProvable({ from: [badSignature, noGlv], to: bool, verbose: true })(
(s) => checkInputs(s) && false,
verify,
`${Curve.name}: fails`
);
// test against constant implementation, with both invalid and valid signatures
equivalentProvable({
from: [oneOf(signature, badSignature), noGlv],
to: bool,
verbose: true,
})(
({ signature, publicKey, msg }) => {
checkInputs({ signature, publicKey, msg });
return verifyEcdsaConstant(Curve, signature, msg, publicKey);
},
verify,
`${Curve.name}: verify`
);
}
// full end-to-end test with proving
let publicKey = Point.from({
x: 49781623198970027997721070672560275063607048368575198229673025608762959476014n,
y: 44999051047832679156664607491606359183507784636787036192076848057884504239143n,
});
let signature = Ecdsa.Signature.fromHex(
'0x82de9950cc5aac0dca7210cb4b77320ac9e844717d39b1781e9d941d920a12061da497b3c134f50b2fce514d66e20c5e43f9615f097395a5527041d14860a52f1b'
);
let msgHash =
Field3.from(
0x3e91cd8bd233b3df4e4762b329e2922381da770df1b31276ec77d0557be7fcefn
);
const ia = initialAggregator(Secp256k1);
const config = { G: { windowSize: 4 }, P: { windowSize: 4 }, ia };
let deprecatedVerify = ZkProgram({
name: 'ecdsa',
publicOutput: Bool,
methods: {
ecdsa: {
privateInputs: [],
async method() {
let signature_ = Provable.witness(
Ecdsa.Signature.provable,
() => signature
);
let msgHash_ = Provable.witness(Field3.provable, () => msgHash);
let publicKey_ = Provable.witness(Point.provable, () => publicKey);
return Ecdsa.verify(
Secp256k1,
signature_,
msgHash_,
publicKey_,
config
);
},
},
},
});
let program = ZkProgram({
name: 'ecdsa',
publicOutput: Bool,
methods: {
ecdsa: {
privateInputs: [],
async method() {
let signature_ = Provable.witness(
Ecdsa.Signature.provable,
() => signature
);
let msgHash_ = Provable.witness(Field3.provable, () => msgHash);
let publicKey_ = Provable.witness(Point.provable, () => publicKey);
return Ecdsa.verifyV2(
Secp256k1,
signature_,
msgHash_,
publicKey_,
config
);
},
},
},
});
console.time('ecdsa verify (constant)');
program.rawMethods.ecdsa();
console.timeEnd('ecdsa verify (constant)');
console.time('ecdsa verify (witness gen / check)');
await Provable.runAndCheck(program.rawMethods.ecdsa);
console.timeEnd('ecdsa verify (witness gen / check)');
console.time('ecdsa verify (build constraint system)');
let cs = (await program.analyzeMethods()).ecdsa;
console.timeEnd('ecdsa verify (build constraint system)');
console.log(cs.summary());
console.time('ecdsa verify (compile)');
await program.compile();
await deprecatedVerify.compile();
console.timeEnd('ecdsa verify (compile)');
console.time('ecdsa verify (prove)');
let proof = await program.ecdsa();
let proof2 = await deprecatedVerify.ecdsa();
console.timeEnd('ecdsa verify (prove)');
assert(await program.verify(proof), 'proof verifies');
assert(await deprecatedVerify.verify(proof2), 'deprecated proof verifies');
proof.publicOutput.assertTrue('signature verifies');