mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
Merge branch 'dev' of github.com:zk-passport/proof-of-passport into dev
This commit is contained in:
@@ -35,7 +35,7 @@ template ProvePassportNotInOfac(nLevels) {
|
||||
for (var i = 0; i < 9; i++) {
|
||||
poseidon_hasher.inputs[i] <== mrz[49 + i];
|
||||
}
|
||||
signal smtleaf_hash <== Poseidon(3)([poseidon_hasher.out, 1,1]); // REMOVE
|
||||
signal smtleaf_hash <== Poseidon(3)([poseidon_hasher.out, 1,1]);
|
||||
// If computedRoot != smt_root; the below assertion fails as path and siblings do not compute to root and proof is not generated.
|
||||
// If computedRoot == smt_root; path and siblings are true but the proof could be membership or non-membership and then furthur checks are made.
|
||||
signal computedRoot <== BinaryMerkleRoot(256)(closest_leaf, smt_size, smt_path, smt_siblings);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
pragma circom 2.1.5;
|
||||
|
||||
include "circomlib/circuits/poseidon.circom";
|
||||
include "@zk-email/circuits/utils/bytes.circom";
|
||||
include "./verifier/passport_verifier_ecdsaWithSHA1Encryption.circom";
|
||||
include "binary-merkle-root.circom";
|
||||
include "../utils/splitSignalsToWords.circom";
|
||||
|
||||
template Register_ecdsaWithSHA1Encryption(n, k, max_datahashes_bytes, nLevels, signatureAlgorithm) {
|
||||
signal input secret;
|
||||
signal input mrz[93];
|
||||
signal input dg1_hash_offset;
|
||||
signal input econtent[max_datahashes_bytes];
|
||||
signal input datahashes_padded_length;
|
||||
signal input signed_attributes[92];
|
||||
|
||||
signal input signature_r[k]; // ECDSA signature component r
|
||||
signal input signature_s[k]; // ECDSA signature component s
|
||||
signal input dsc_modulus[2][k]; // Public Key (split into Qx and Qy)
|
||||
|
||||
signal input dsc_secret;
|
||||
signal input attestation_id;
|
||||
|
||||
// Hash DSC modulus and signature components
|
||||
// Poseidon(dsc_modulus[0][0], dsc_modulus[0][1], ..., dsc_modulus[0][5])
|
||||
component poseidon_hasher_dsc_modules_x = Poseidon(6);
|
||||
component poseidon_hasher_dsc_modules_y = Poseidon(6);
|
||||
|
||||
// Poseidon(signature_r[0], signature_r[1], ..., signature_r[5])
|
||||
component poseidon_hasher_signature_r = Poseidon(6);
|
||||
component poseidon_hasher_signature_s = Poseidon(6);
|
||||
|
||||
for (var i = 0; i < k; i++) {
|
||||
poseidon_hasher_dsc_modules_x.inputs[i] <== dsc_modulus[0][i];
|
||||
poseidon_hasher_dsc_modules_y.inputs[i] <== dsc_modulus[1][i];
|
||||
poseidon_hasher_signature_r.inputs[i] <== signature_r[i];
|
||||
poseidon_hasher_signature_s.inputs[i] <== signature_s[i];
|
||||
}
|
||||
|
||||
component dsc_commitment_hasher = Poseidon(3);
|
||||
component nullifier_hasher = Poseidon(3);
|
||||
component leaf_hasher = Poseidon(3);
|
||||
|
||||
dsc_commitment_hasher.inputs[0] <== dsc_secret;
|
||||
dsc_commitment_hasher.inputs[1] <== poseidon_hasher_dsc_modules_x.out;
|
||||
dsc_commitment_hasher.inputs[2] <== poseidon_hasher_dsc_modules_y.out;
|
||||
|
||||
nullifier_hasher.inputs[0] <== secret;
|
||||
nullifier_hasher.inputs[1] <== poseidon_hasher_signature_r.out;
|
||||
nullifier_hasher.inputs[2] <== poseidon_hasher_signature_s.out;
|
||||
|
||||
leaf_hasher.inputs[0] <== signatureAlgorithm;
|
||||
leaf_hasher.inputs[1] <== poseidon_hasher_dsc_modules_x.out;
|
||||
leaf_hasher.inputs[2] <== poseidon_hasher_dsc_modules_y.out;
|
||||
|
||||
signal output blinded_dsc_commitment <== dsc_commitment_hasher.out;
|
||||
signal output nullifier <== nullifier_hasher.out;
|
||||
|
||||
// Verify passport validity
|
||||
component PV = PassportVerifier_ecdsaWithSHA1Encryption(n, k, max_datahashes_bytes);
|
||||
PV.mrz <== mrz;
|
||||
PV.dg1_hash_offset <== dg1_hash_offset;
|
||||
PV.dataHashes <== econtent;
|
||||
PV.datahashes_padded_length <== datahashes_padded_length;
|
||||
PV.eContentBytes <== signed_attributes;
|
||||
PV.dsc_modulus <== dsc_modulus;
|
||||
PV.signature_r <== signature_r;
|
||||
PV.signature_s <== signature_s;
|
||||
|
||||
// signature is valid
|
||||
PV.result === 1;
|
||||
|
||||
// Generate the commitment
|
||||
component poseidon_hasher = Poseidon(6);
|
||||
poseidon_hasher.inputs[0] <== secret;
|
||||
poseidon_hasher.inputs[1] <== attestation_id;
|
||||
poseidon_hasher.inputs[2] <== leaf_hasher.out;
|
||||
|
||||
signal mrz_packed[3] <== PackBytes(93)(mrz);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
poseidon_hasher.inputs[i + 3] <== mrz_packed[i];
|
||||
}
|
||||
signal output commitment <== poseidon_hasher.out;
|
||||
}
|
||||
|
||||
component main { public [ attestation_id ] } = Register_ecdsaWithSHA1Encryption(43, 6, 320, 16, 7);
|
||||
@@ -0,0 +1,86 @@
|
||||
pragma circom 2.1.5;
|
||||
|
||||
include "circomlib/circuits/poseidon.circom";
|
||||
include "@zk-email/circuits/utils/bytes.circom";
|
||||
include "./verifier/passport_verifier_ecdsaWithSHA256Encryption.circom";
|
||||
include "binary-merkle-root.circom";
|
||||
include "../utils/splitSignalsToWords.circom";
|
||||
|
||||
template Register_ecdsaWithSHA256Encryption(n, k, max_datahashes_bytes, nLevels, signatureAlgorithm) {
|
||||
signal input secret;
|
||||
signal input mrz[93];
|
||||
signal input dg1_hash_offset;
|
||||
signal input econtent[max_datahashes_bytes];
|
||||
signal input datahashes_padded_length;
|
||||
signal input signed_attributes[104];
|
||||
|
||||
signal input signature_r[k]; // ECDSA signature component r
|
||||
signal input signature_s[k]; // ECDSA signature component s
|
||||
signal input dsc_modulus[2][k]; // Public Key (split into Qx and Qy)
|
||||
|
||||
signal input dsc_secret;
|
||||
signal input attestation_id;
|
||||
|
||||
// Hash DSC modulus and signature components
|
||||
// Poseidon(dsc_modulus[0][0], dsc_modulus[0][1], ..., dsc_modulus[0][5])
|
||||
component poseidon_hasher_dsc_modules_x = Poseidon(6);
|
||||
component poseidon_hasher_dsc_modules_y = Poseidon(6);
|
||||
|
||||
// Poseidon(signature_r[0], signature_r[1], ..., signature_r[5])
|
||||
component poseidon_hasher_signature_r = Poseidon(6);
|
||||
component poseidon_hasher_signature_s = Poseidon(6);
|
||||
|
||||
for (var i = 0; i < k; i++) {
|
||||
poseidon_hasher_dsc_modules_x.inputs[i] <== dsc_modulus[0][i];
|
||||
poseidon_hasher_dsc_modules_y.inputs[i] <== dsc_modulus[1][i];
|
||||
poseidon_hasher_signature_r.inputs[i] <== signature_r[i];
|
||||
poseidon_hasher_signature_s.inputs[i] <== signature_s[i];
|
||||
}
|
||||
|
||||
component dsc_commitment_hasher = Poseidon(3);
|
||||
component nullifier_hasher = Poseidon(3);
|
||||
component leaf_hasher = Poseidon(3);
|
||||
|
||||
dsc_commitment_hasher.inputs[0] <== dsc_secret;
|
||||
dsc_commitment_hasher.inputs[1] <== poseidon_hasher_dsc_modules_x.out;
|
||||
dsc_commitment_hasher.inputs[2] <== poseidon_hasher_dsc_modules_y.out;
|
||||
|
||||
nullifier_hasher.inputs[0] <== secret;
|
||||
nullifier_hasher.inputs[1] <== poseidon_hasher_signature_r.out;
|
||||
nullifier_hasher.inputs[2] <== poseidon_hasher_signature_s.out;
|
||||
|
||||
leaf_hasher.inputs[0] <== signatureAlgorithm;
|
||||
leaf_hasher.inputs[1] <== poseidon_hasher_dsc_modules_x.out;
|
||||
leaf_hasher.inputs[2] <== poseidon_hasher_dsc_modules_y.out;
|
||||
|
||||
signal output blinded_dsc_commitment <== dsc_commitment_hasher.out;
|
||||
signal output nullifier <== nullifier_hasher.out;
|
||||
|
||||
// Verify passport validity
|
||||
component PV = PassportVerifier_ecdsaWithSHA256Encryption(n, k, max_datahashes_bytes);
|
||||
PV.mrz <== mrz;
|
||||
PV.dg1_hash_offset <== dg1_hash_offset;
|
||||
PV.dataHashes <== econtent;
|
||||
PV.datahashes_padded_length <== datahashes_padded_length;
|
||||
PV.eContentBytes <== signed_attributes;
|
||||
PV.dsc_modulus <== dsc_modulus;
|
||||
PV.signature_r <== signature_r;
|
||||
PV.signature_s <== signature_s;
|
||||
|
||||
// signature is valid
|
||||
PV.result === 1;
|
||||
|
||||
// Generate the commitment
|
||||
component poseidon_hasher = Poseidon(6);
|
||||
poseidon_hasher.inputs[0] <== secret;
|
||||
poseidon_hasher.inputs[1] <== attestation_id;
|
||||
poseidon_hasher.inputs[2] <== leaf_hasher.out;
|
||||
|
||||
signal mrz_packed[3] <== PackBytes(93)(mrz);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
poseidon_hasher.inputs[i + 3] <== mrz_packed[i];
|
||||
}
|
||||
signal output commitment <== poseidon_hasher.out;
|
||||
}
|
||||
|
||||
component main { public [ attestation_id ] } = Register_ecdsaWithSHA256Encryption(43, 6, 320, 16, 8);
|
||||
@@ -0,0 +1,102 @@
|
||||
pragma circom 2.1.5;
|
||||
|
||||
include "@zk-email/circuits/utils/bytes.circom";
|
||||
include "../../utils/Sha1BytesStatic.circom";
|
||||
include "../../utils/Sha1Bytes.circom";
|
||||
include "../../utils/rsaPkcs1.circom";
|
||||
include "dmpierre/sha1-circom/circuits/sha1.circom";
|
||||
include "../../utils/circom-ecdsa/ecdsa.circom";
|
||||
|
||||
template PassportVerifier_ecdsaWithSHA1Encryption(n, k, max_datahashes_bytes) {
|
||||
var hashLen = 20;
|
||||
var eContentBytesLength = 72 + hashLen; // 92
|
||||
|
||||
signal input mrz[93]; // formatted mrz (5 + 88) chars
|
||||
signal input dg1_hash_offset;
|
||||
signal input dataHashes[max_datahashes_bytes];
|
||||
|
||||
signal input datahashes_padded_length;
|
||||
signal input eContentBytes[eContentBytesLength];
|
||||
|
||||
signal input dsc_modulus[2][k]; // Public Key (split into Qx and Qy)
|
||||
|
||||
signal input signature_r[k]; // ECDSA signature component r
|
||||
signal input signature_s[k]; // ECDSA signature component s
|
||||
|
||||
// compute sha1 of formatted mrz
|
||||
signal mrzSha[160] <== Sha1BytesStatic(93)(mrz);
|
||||
|
||||
// mrzSha_bytes: list of 32 Bits2Num
|
||||
component mrzSha_bytes[hashLen];
|
||||
|
||||
// cast the 160 bits from mrzSha into a list of 20 bytes
|
||||
for (var i = 0; i < hashLen; i++) {
|
||||
mrzSha_bytes[i] = Bits2Num(8);
|
||||
|
||||
for (var j = 0; j < 8; j++) {
|
||||
mrzSha_bytes[i].in[7 - j] <== mrzSha[i * 8 + j];
|
||||
}
|
||||
}
|
||||
|
||||
// assert mrz_hash equals the one extracted from dataHashes input (bytes dg1_hash_offset to dg1_hash_offset + hashLen)
|
||||
signal dg1Hash[hashLen] <== SelectSubArray(max_datahashes_bytes, hashLen)(dataHashes, dg1_hash_offset, hashLen);
|
||||
for(var i = 0; i < hashLen; i++) {
|
||||
dg1Hash[i] === mrzSha_bytes[i].out;
|
||||
}
|
||||
|
||||
// hash dataHashes dynamically
|
||||
signal dataHashesSha[160] <== Sha1Bytes(max_datahashes_bytes)(dataHashes, datahashes_padded_length);
|
||||
|
||||
// get output of dataHashes into bytes to check against eContent
|
||||
component dataHashesSha_bytes[hashLen];
|
||||
for (var i = 0; i < hashLen; i++) {
|
||||
dataHashesSha_bytes[i] = Bits2Num(8);
|
||||
for (var j = 0; j < 8; j++) {
|
||||
dataHashesSha_bytes[i].in[7 - j] <== dataHashesSha[i * 8 + j];
|
||||
}
|
||||
}
|
||||
|
||||
// assert dataHashesSha is in eContentBytes in range bytes 72 to 92
|
||||
for(var i = 0; i < hashLen; i++) {
|
||||
eContentBytes[eContentBytesLength - hashLen + i] === dataHashesSha_bytes[i].out;
|
||||
}
|
||||
|
||||
// hash eContentBytes
|
||||
signal eContentSha[160] <== Sha1BytesStatic(eContentBytesLength)(eContentBytes);
|
||||
|
||||
// get output of eContentBytes sha1 into k chunks of n bits each
|
||||
var msg_len = (160 + n) \ n;
|
||||
|
||||
//eContentHash: list of length 160/n +1 of components of n bits
|
||||
component eContentHash[msg_len];
|
||||
for (var i = 0; i < msg_len; i++) {
|
||||
eContentHash[i] = Bits2Num(n);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 160; i++) {
|
||||
eContentHash[i \ n].in[i % n] <== eContentSha[159 - i];
|
||||
}
|
||||
|
||||
for (var i = 160; i < n * msg_len; i++) {
|
||||
eContentHash[i \ n].in[i % n] <== 0;
|
||||
}
|
||||
|
||||
// 43 * 6 = 258;
|
||||
signal msgHash[6];
|
||||
for(var i = 0; i < 4; i++) {
|
||||
msgHash[i] <== eContentHash[i].out;
|
||||
}
|
||||
msgHash[4] <== 0;
|
||||
msgHash[5] <== 0;
|
||||
|
||||
// verify eContentHash signature
|
||||
component ecdsa_verify = ECDSAVerifyNoPubkeyCheck(n,k);
|
||||
|
||||
ecdsa_verify.r <== signature_r;
|
||||
ecdsa_verify.s <== signature_s;
|
||||
ecdsa_verify.msghash <== msgHash;
|
||||
ecdsa_verify.pubkey <== dsc_modulus;
|
||||
|
||||
signal output result <== ecdsa_verify.result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
pragma circom 2.1.5;
|
||||
|
||||
include "@zk-email/circuits/utils/bytes.circom";
|
||||
include "../../utils/circom-ecdsa/ecdsa.circom";
|
||||
include "../../utils/Sha256BytesStatic.circom";
|
||||
include "@zk-email/circuits/lib/sha.circom";
|
||||
|
||||
template PassportVerifier_ecdsaWithSHA256Encryption(n, k, max_datahashes_bytes) {
|
||||
var hashLen = 32;
|
||||
var eContentBytesLength = 72 + hashLen; // 104
|
||||
|
||||
signal input mrz[93]; // formatted mrz (5 + 88) chars
|
||||
signal input dg1_hash_offset;
|
||||
signal input dataHashes[max_datahashes_bytes];
|
||||
|
||||
signal input datahashes_padded_length;
|
||||
signal input eContentBytes[eContentBytesLength];
|
||||
|
||||
signal input dsc_modulus[2][k]; // Public Key (split into Qx and Qy)
|
||||
|
||||
signal input signature_r[k]; // ECDSA signature component r
|
||||
signal input signature_s[k]; // ECDSA signature component s
|
||||
|
||||
// compute sha256 of formatted mrz
|
||||
signal mrzSha[256] <== Sha256BytesStatic(93)(mrz);
|
||||
|
||||
// mrzSha_bytes: list of 32 Bits2Num
|
||||
component mrzSha_bytes[hashLen];
|
||||
|
||||
// cast the 256 bits from mrzSha into a list of 32 bytes
|
||||
for (var i = 0; i < hashLen; i++) {
|
||||
mrzSha_bytes[i] = Bits2Num(8);
|
||||
|
||||
for (var j = 0; j < 8; j++) {
|
||||
mrzSha_bytes[i].in[7 - j] <== mrzSha[i * 8 + j];
|
||||
}
|
||||
}
|
||||
|
||||
// assert mrz_hash equals the one extracted from dataHashes input (bytes dg1_hash_offset to dg1_hash_offset + hashLen)
|
||||
signal dg1Hash[hashLen] <== SelectSubArray(max_datahashes_bytes, hashLen)(dataHashes, dg1_hash_offset, hashLen);
|
||||
for(var i = 0; i < hashLen; i++) {
|
||||
dg1Hash[i] === mrzSha_bytes[i].out;
|
||||
}
|
||||
|
||||
// hash dataHashes dynamically
|
||||
signal dataHashesSha[256] <== Sha256Bytes(max_datahashes_bytes)(dataHashes, datahashes_padded_length);
|
||||
|
||||
// get output of dataHashes into bytes to check against eContent
|
||||
component dataHashesSha_bytes[hashLen];
|
||||
for (var i = 0; i < hashLen; i++) {
|
||||
dataHashesSha_bytes[i] = Bits2Num(8);
|
||||
for (var j = 0; j < 8; j++) {
|
||||
dataHashesSha_bytes[i].in[7 - j] <== dataHashesSha[i * 8 + j];
|
||||
}
|
||||
}
|
||||
|
||||
// assert dataHashesSha is in eContentBytes in range bytes 72 to 92
|
||||
for(var i = 0; i < hashLen; i++) {
|
||||
eContentBytes[eContentBytesLength - hashLen + i] === dataHashesSha_bytes[i].out;
|
||||
}
|
||||
|
||||
// hash eContentBytes
|
||||
signal eContentSha[256] <== Sha256BytesStatic(104)(eContentBytes);
|
||||
|
||||
// get output of eContentBytes sha256 into k chunks of n bits each
|
||||
var msg_len = (256 + n) \ n;
|
||||
|
||||
//eContentHash: list of length 256/n +1 of components of n bits
|
||||
component eContentHash[msg_len];
|
||||
for (var i = 0; i < msg_len; i++) {
|
||||
eContentHash[i] = Bits2Num(n);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 256; i++) {
|
||||
eContentHash[i \ n].in[i % n] <== eContentSha[255 - i];
|
||||
}
|
||||
|
||||
for (var i = 256; i < n * msg_len; i++) {
|
||||
eContentHash[i \ n].in[i % n] <== 0;
|
||||
}
|
||||
|
||||
|
||||
// 43 * 6 = 258;
|
||||
signal msgHash[6];
|
||||
for(var i = 0; i < msg_len; i++) {
|
||||
msgHash[i] <== eContentHash[i].out;
|
||||
}
|
||||
|
||||
// verify eContentHash signature
|
||||
component ecdsa_verify = ECDSAVerifyNoPubkeyCheck(n,k);
|
||||
|
||||
ecdsa_verify.r <== signature_r;
|
||||
ecdsa_verify.s <== signature_s;
|
||||
ecdsa_verify.msghash <== msgHash;
|
||||
ecdsa_verify.pubkey <== dsc_modulus;
|
||||
|
||||
signal output result <== ecdsa_verify.result;
|
||||
}
|
||||
|
||||
990
circuits/circuits/utils/circom-ecdsa/bigInt.circom
Normal file
990
circuits/circuits/utils/circom-ecdsa/bigInt.circom
Normal file
@@ -0,0 +1,990 @@
|
||||
pragma circom 2.0.3;
|
||||
|
||||
include "../../../node_modules/circomlib/circuits/comparators.circom";
|
||||
include "../../../node_modules/circomlib/circuits/bitify.circom";
|
||||
include "../../../node_modules/circomlib/circuits/gates.circom";
|
||||
|
||||
include "bigInt_func.circom";
|
||||
|
||||
|
||||
// addition mod 2**n with carry bit
|
||||
template ModSum(n) {
|
||||
assert(n <= 252);
|
||||
signal input a;
|
||||
signal input b;
|
||||
signal output sum;
|
||||
signal output carry;
|
||||
|
||||
component n2b = Num2Bits(n + 1);
|
||||
n2b.in <== a + b;
|
||||
carry <== n2b.out[n];
|
||||
sum <== a + b - carry * (1 << n);
|
||||
}
|
||||
|
||||
// check if k-register variables a, b are equal everywhere
|
||||
template BigIsEqual(k) {
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal output out;
|
||||
|
||||
component isEquals[k];
|
||||
var total = k;
|
||||
for (var i = 0; i < k; i ++) {
|
||||
isEquals[i] = IsEqual();
|
||||
isEquals[i].in[0] <== a[i];
|
||||
isEquals[i].in[1] <== b[i];
|
||||
total -= isEquals[i].out;
|
||||
}
|
||||
component checkZero = IsZero();
|
||||
checkZero.in <== total;
|
||||
out <== checkZero.out;
|
||||
}
|
||||
|
||||
// check if k-register variable a is equal to zero
|
||||
template BigIsZero(k) {
|
||||
signal input in[k];
|
||||
signal output out;
|
||||
|
||||
component isZeros[k];
|
||||
var total = k;
|
||||
for (var i = 0; i < k; i ++) {
|
||||
isZeros[i] = IsZero();
|
||||
isZeros[i].in <== in[i];
|
||||
total -= isZeros[i].out;
|
||||
}
|
||||
component checkZero = IsZero();
|
||||
checkZero.in <== total;
|
||||
out <== checkZero.out;
|
||||
}
|
||||
|
||||
|
||||
// a - b
|
||||
template ModSub(n) {
|
||||
assert(n <= 252);
|
||||
signal input a;
|
||||
signal input b;
|
||||
signal output out;
|
||||
signal output borrow;
|
||||
component lt = LessThan(n);
|
||||
lt.in[0] <== a;
|
||||
lt.in[1] <== b;
|
||||
borrow <== lt.out;
|
||||
out <== borrow * (1 << n) + a - b;
|
||||
}
|
||||
|
||||
// a - b - c
|
||||
// assume a - b - c + 2**n >= 0
|
||||
template ModSubThree(n) {
|
||||
assert(n + 2 <= 253);
|
||||
signal input a;
|
||||
signal input b;
|
||||
signal input c;
|
||||
assert(a - b - c + (1 << n) >= 0);
|
||||
signal output out;
|
||||
signal output borrow;
|
||||
signal b_plus_c;
|
||||
b_plus_c <== b + c;
|
||||
component lt = LessThan(n + 1);
|
||||
lt.in[0] <== a;
|
||||
lt.in[1] <== b_plus_c;
|
||||
borrow <== lt.out;
|
||||
out <== borrow * (1 << n) + a - b_plus_c;
|
||||
}
|
||||
|
||||
template ModSumThree(n) {
|
||||
assert(n + 2 <= 253);
|
||||
signal input a;
|
||||
signal input b;
|
||||
signal input c;
|
||||
signal output sum;
|
||||
signal output carry;
|
||||
|
||||
component n2b = Num2Bits(n + 2);
|
||||
n2b.in <== a + b + c;
|
||||
carry <== n2b.out[n] + 2 * n2b.out[n + 1];
|
||||
sum <== a + b + c - carry * (1 << n);
|
||||
}
|
||||
|
||||
template ModSumFour(n) {
|
||||
assert(n + 2 <= 253);
|
||||
signal input a;
|
||||
signal input b;
|
||||
signal input c;
|
||||
signal input d;
|
||||
signal output sum;
|
||||
signal output carry;
|
||||
|
||||
component n2b = Num2Bits(n + 2);
|
||||
n2b.in <== a + b + c + d;
|
||||
carry <== n2b.out[n] + 2 * n2b.out[n + 1];
|
||||
sum <== a + b + c + d - carry * (1 << n);
|
||||
}
|
||||
|
||||
// product mod 2**n with carry
|
||||
template ModProd(n) {
|
||||
assert(n <= 126);
|
||||
signal input a;
|
||||
signal input b;
|
||||
signal output prod;
|
||||
signal output carry;
|
||||
|
||||
component n2b = Num2Bits(2 * n);
|
||||
n2b.in <== a * b;
|
||||
|
||||
component b2n1 = Bits2Num(n);
|
||||
component b2n2 = Bits2Num(n);
|
||||
var i;
|
||||
for (i = 0; i < n; i++) {
|
||||
b2n1.in[i] <== n2b.out[i];
|
||||
b2n2.in[i] <== n2b.out[i + n];
|
||||
}
|
||||
prod <== b2n1.out;
|
||||
carry <== b2n2.out;
|
||||
}
|
||||
|
||||
// split a n + m bit input into two outputs
|
||||
template Split(n, m) {
|
||||
assert(n <= 126);
|
||||
signal input in;
|
||||
signal output small;
|
||||
signal output big;
|
||||
|
||||
small <-- in % (1 << n);
|
||||
big <-- in \ (1 << n);
|
||||
|
||||
component n2b_small = Num2Bits(n);
|
||||
n2b_small.in <== small;
|
||||
component n2b_big = Num2Bits(m);
|
||||
n2b_big.in <== big;
|
||||
|
||||
in === small + big * (1 << n);
|
||||
}
|
||||
|
||||
// split a n + m + k bit input into three outputs
|
||||
template SplitThree(n, m, k) {
|
||||
assert(n <= 126);
|
||||
signal input in;
|
||||
signal output small;
|
||||
signal output medium;
|
||||
signal output big;
|
||||
|
||||
small <-- in % (1 << n);
|
||||
medium <-- (in \ (1 << n)) % (1 << m);
|
||||
big <-- in \ (1 << n + m);
|
||||
|
||||
component n2b_small = Num2Bits(n);
|
||||
n2b_small.in <== small;
|
||||
component n2b_medium = Num2Bits(m);
|
||||
n2b_medium.in <== medium;
|
||||
component n2b_big = Num2Bits(k);
|
||||
n2b_big.in <== big;
|
||||
|
||||
in === small + medium * (1 << n) + big * (1 << n + m);
|
||||
}
|
||||
|
||||
// a[i], b[i] in 0... 2**n-1
|
||||
// represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k)
|
||||
template BigAdd(n, k) {
|
||||
assert(n <= 252);
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal output out[k + 1];
|
||||
|
||||
component unit0 = ModSum(n);
|
||||
unit0.a <== a[0];
|
||||
unit0.b <== b[0];
|
||||
out[0] <== unit0.sum;
|
||||
|
||||
component unit[k - 1];
|
||||
for (var i = 1; i < k; i++) {
|
||||
unit[i - 1] = ModSumThree(n);
|
||||
unit[i - 1].a <== a[i];
|
||||
unit[i - 1].b <== b[i];
|
||||
if (i == 1) {
|
||||
unit[i - 1].c <== unit0.carry;
|
||||
} else {
|
||||
unit[i - 1].c <== unit[i - 2].carry;
|
||||
}
|
||||
out[i] <== unit[i - 1].sum;
|
||||
}
|
||||
out[k] <== unit[k - 2].carry;
|
||||
}
|
||||
|
||||
/*
|
||||
Polynomial Multiplication
|
||||
Inputs:
|
||||
- a = a[0] + a[1] * X + ... + a[k-1] * X^{k-1}
|
||||
- b = b[0] + b[1] * X + ... + b[k-1] * X^{k-1}
|
||||
Output:
|
||||
- out = out[0] + out[1] * X + ... + out[2 * k - 2] * X^{2*k - 2}
|
||||
- out = a * b as polynomials in X
|
||||
Notes:
|
||||
- Optimization due to xJsnark:
|
||||
-- witness is calculated by normal polynomial multiplication
|
||||
-- out is contrained by evaluating out(X) === a(X) * b(X) at X = 0, ..., 2*k - 2
|
||||
- If a[i], b[j] have absolute value < B, then out[i] has absolute value < k * B^2
|
||||
m_out is the expected max number of bits in the output registers
|
||||
*/
|
||||
template BigMultShortLong(n, k, m_out) {
|
||||
assert(n <= 126);
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal output out[2 * k - 1];
|
||||
|
||||
var prod_val[2 * k - 1];
|
||||
for (var i = 0; i < 2 * k - 1; i++) {
|
||||
prod_val[i] = 0;
|
||||
if (i < k) {
|
||||
for (var a_idx = 0; a_idx <= i; a_idx++) {
|
||||
prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx];
|
||||
}
|
||||
} else {
|
||||
for (var a_idx = i - k + 1; a_idx < k; a_idx++) {
|
||||
prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx];
|
||||
}
|
||||
}
|
||||
out[i] <-- prod_val[i];
|
||||
}
|
||||
|
||||
var k2 = 2 * k - 1;
|
||||
var pow[k2][k2]; // we cache the exponent values because it makes a big difference in witness generation time
|
||||
for(var i = 0; i<k2; i++)for(var j=0; j<k2; j++)
|
||||
pow[i][j] = i ** j;
|
||||
|
||||
var a_poly[2 * k - 1];
|
||||
var b_poly[2 * k - 1];
|
||||
var out_poly[2 * k - 1];
|
||||
for (var i = 0; i < 2 * k - 1; i++) {
|
||||
out_poly[i] = 0;
|
||||
a_poly[i] = 0;
|
||||
b_poly[i] = 0;
|
||||
for (var j = 0; j < 2 * k - 1; j++) {
|
||||
out_poly[i] = out_poly[i] + out[j] * pow[i][j];
|
||||
}
|
||||
for (var j = 0; j < k; j++) {
|
||||
a_poly[i] = a_poly[i] + a[j] * pow[i][j];
|
||||
b_poly[i] = b_poly[i] + b[j] * pow[i][j];
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < 2 * k - 1; i++) {
|
||||
out_poly[i] === a_poly[i] * b_poly[i];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
same as BigMultShortLong except a has degree ka - 1, b has degree kb - 1
|
||||
- If a[i], b[j] have absolute value < B, then out[i] has absolute value < min(ka, kb) * B^2
|
||||
*/
|
||||
template BigMultShortLongUnequal(n, ka, kb, m_out) {
|
||||
assert(n <= 126);
|
||||
signal input a[ka];
|
||||
signal input b[kb];
|
||||
signal output out[ka + kb - 1];
|
||||
|
||||
var prod_val[ka + kb - 1];
|
||||
for (var i = 0; i < ka + kb - 1; i++) {
|
||||
prod_val[i] = 0;
|
||||
}
|
||||
for (var i = 0; i < ka; i++) {
|
||||
for (var j = 0; j < kb; j++) {
|
||||
prod_val[i + j] = prod_val[i + j] + a[i] * b[j];
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < ka + kb - 1; i++) {
|
||||
out[i] <-- prod_val[i];
|
||||
}
|
||||
|
||||
var k2 = ka + kb - 1;
|
||||
var pow[k2][k2];
|
||||
for(var i = 0; i<k2; i++)for(var j=0; j<k2; j++)
|
||||
pow[i][j] = i ** j;
|
||||
|
||||
var a_poly[ka + kb - 1];
|
||||
var b_poly[ka + kb - 1];
|
||||
var out_poly[ka + kb - 1];
|
||||
for (var i = 0; i < ka + kb - 1; i++) {
|
||||
out_poly[i] = 0;
|
||||
a_poly[i] = 0;
|
||||
b_poly[i] = 0;
|
||||
for (var j = 0; j < ka + kb - 1; j++) {
|
||||
out_poly[i] = out_poly[i] + out[j] * pow[i][j];
|
||||
}
|
||||
for (var j = 0; j < ka; j++) {
|
||||
a_poly[i] = a_poly[i] + a[j] * pow[i][j];
|
||||
}
|
||||
for (var j = 0; j < kb; j++) {
|
||||
b_poly[i] = b_poly[i] + b[j] * pow[i][j];
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < ka + kb - 1; i++) {
|
||||
out_poly[i] === a_poly[i] * b_poly[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// in[i] contains longs
|
||||
// out[i] contains shorts
|
||||
template LongToShortNoEndCarry(n, k) {
|
||||
assert(n <= 126);
|
||||
signal input in[k];
|
||||
signal output out[k+1];
|
||||
|
||||
var split[k][3];
|
||||
for (var i = 0; i < k; i++) {
|
||||
split[i] = SplitThreeFn(in[i], n, n, n);
|
||||
}
|
||||
|
||||
var carry[k];
|
||||
carry[0] = 0;
|
||||
out[0] <-- split[0][0];
|
||||
if (k > 1) {
|
||||
var sumAndCarry[2] = SplitFn(split[0][1] + split[1][0], n, n);
|
||||
out[1] <-- sumAndCarry[0];
|
||||
carry[1] = sumAndCarry[1];
|
||||
}
|
||||
if (k > 2) {
|
||||
for (var i = 2; i < k; i++) {
|
||||
var sumAndCarry[2] = SplitFn(split[i][0] + split[i-1][1] + split[i-2][2] + carry[i-1], n, n);
|
||||
out[i] <-- sumAndCarry[0];
|
||||
carry[i] = sumAndCarry[1];
|
||||
}
|
||||
out[k] <-- split[k-1][1] + split[k-2][2] + carry[k-1];
|
||||
}
|
||||
|
||||
component outRangeChecks[k+1];
|
||||
for (var i = 0; i < k+1; i++) {
|
||||
outRangeChecks[i] = Num2Bits(n);
|
||||
outRangeChecks[i].in <== out[i];
|
||||
}
|
||||
|
||||
signal runningCarry[k];
|
||||
component runningCarryRangeChecks[k];
|
||||
runningCarry[0] <-- (in[0] - out[0]) / (1 << n);
|
||||
runningCarryRangeChecks[0] = Num2Bits(n + log_ceil_ecdsa(k));
|
||||
runningCarryRangeChecks[0].in <== runningCarry[0];
|
||||
runningCarry[0] * (1 << n) === in[0] - out[0];
|
||||
for (var i = 1; i < k; i++) {
|
||||
runningCarry[i] <-- (in[i] - out[i] + runningCarry[i-1]) / (1 << n);
|
||||
runningCarryRangeChecks[i] = Num2Bits(n + log_ceil_ecdsa(k));
|
||||
runningCarryRangeChecks[i].in <== runningCarry[i];
|
||||
runningCarry[i] * (1 << n) === in[i] - out[i] + runningCarry[i-1];
|
||||
}
|
||||
runningCarry[k-1] === out[k];
|
||||
}
|
||||
|
||||
template BigMult(n, k) {
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal output out[2 * k];
|
||||
|
||||
var LOGK = log_ceil_ecdsa(k);
|
||||
component mult = BigMultShortLong(n, k, 2*n + LOGK);
|
||||
for (var i = 0; i < k; i++) {
|
||||
mult.a[i] <== a[i];
|
||||
mult.b[i] <== b[i];
|
||||
}
|
||||
|
||||
// no carry is possible in the highest order register
|
||||
component longshort = LongToShortNoEndCarry(n, 2 * k - 1);
|
||||
for (var i = 0; i < 2 * k - 1; i++) {
|
||||
longshort.in[i] <== mult.out[i];
|
||||
}
|
||||
for (var i = 0; i < 2 * k; i++) {
|
||||
out[i] <== longshort.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Inputs:
|
||||
- BigInts a, b
|
||||
Output:
|
||||
- out = (a < b) ? 1 : 0
|
||||
*/
|
||||
template BigLessThanEcdsa(n, k){
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal output out;
|
||||
|
||||
component lt[k];
|
||||
component eq[k];
|
||||
for (var i = 0; i < k; i++) {
|
||||
lt[i] = LessThan(n);
|
||||
lt[i].in[0] <== a[i];
|
||||
lt[i].in[1] <== b[i];
|
||||
eq[i] = IsEqual();
|
||||
eq[i].in[0] <== a[i];
|
||||
eq[i].in[1] <== b[i];
|
||||
}
|
||||
|
||||
// ors[i] holds (lt[k - 1] || (eq[k - 1] && lt[k - 2]) .. || (eq[k - 1] && .. && lt[i]))
|
||||
// ands[i] holds (eq[k - 1] && .. && lt[i])
|
||||
// eq_ands[i] holds (eq[k - 1] && .. && eq[i])
|
||||
component ors[k - 1];
|
||||
component ands[k - 1];
|
||||
component eq_ands[k - 1];
|
||||
for (var i = k - 2; i >= 0; i--) {
|
||||
ands[i] = AND();
|
||||
eq_ands[i] = AND();
|
||||
ors[i] = OR();
|
||||
|
||||
if (i == k - 2) {
|
||||
ands[i].a <== eq[k - 1].out;
|
||||
ands[i].b <== lt[k - 2].out;
|
||||
eq_ands[i].a <== eq[k - 1].out;
|
||||
eq_ands[i].b <== eq[k - 2].out;
|
||||
ors[i].a <== lt[k - 1].out;
|
||||
ors[i].b <== ands[i].out;
|
||||
} else {
|
||||
ands[i].a <== eq_ands[i + 1].out;
|
||||
ands[i].b <== lt[i].out;
|
||||
eq_ands[i].a <== eq_ands[i + 1].out;
|
||||
eq_ands[i].b <== eq[i].out;
|
||||
ors[i].a <== ors[i + 1].out;
|
||||
ors[i].b <== ands[i].out;
|
||||
}
|
||||
}
|
||||
out <== ors[0].out;
|
||||
}
|
||||
|
||||
// leading register of b should be non-zero
|
||||
template BigMod(n, k) {
|
||||
assert(n <= 126);
|
||||
signal input a[2 * k];
|
||||
signal input b[k];
|
||||
|
||||
signal output div[k + 1];
|
||||
signal output mod[k];
|
||||
|
||||
var longdiv[2][50] = long_div_ecdsa(n, k, a, b);
|
||||
for (var i = 0; i < k; i++) {
|
||||
div[i] <-- longdiv[0][i];
|
||||
mod[i] <-- longdiv[1][i];
|
||||
}
|
||||
div[k] <-- longdiv[0][k];
|
||||
component div_range_checks[k + 1];
|
||||
for (var i = 0; i <= k; i++) {
|
||||
div_range_checks[i] = Num2Bits(n);
|
||||
div_range_checks[i].in <== div[i];
|
||||
}
|
||||
component mod_range_checks[k];
|
||||
for (var i = 0; i < k; i++) {
|
||||
mod_range_checks[i] = Num2Bits(n);
|
||||
mod_range_checks[i].in <== mod[i];
|
||||
}
|
||||
|
||||
component mul = BigMult(n, k + 1);
|
||||
for (var i = 0; i < k; i++) {
|
||||
mul.a[i] <== div[i];
|
||||
mul.b[i] <== b[i];
|
||||
}
|
||||
mul.a[k] <== div[k];
|
||||
mul.b[k] <== 0;
|
||||
|
||||
for (var i = 0; i < 2 * k + 2; i++) {
|
||||
//log(mul.out[i]);
|
||||
}
|
||||
|
||||
component add = BigAdd(n, 2 * k + 2);
|
||||
for (var i = 0; i < 2 * k; i++) {
|
||||
add.a[i] <== mul.out[i];
|
||||
if (i < k) {
|
||||
add.b[i] <== mod[i];
|
||||
} else {
|
||||
add.b[i] <== 0;
|
||||
}
|
||||
}
|
||||
add.a[2 * k] <== mul.out[2 * k];
|
||||
add.a[2 * k + 1] <== mul.out[2 * k + 1];
|
||||
add.b[2 * k] <== 0;
|
||||
add.b[2 * k + 1] <== 0;
|
||||
|
||||
for (var i = 0; i < 2 * k + 2; i++) {
|
||||
//log(add.out[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 2 * k; i++) {
|
||||
add.out[i] === a[i];
|
||||
}
|
||||
add.out[2 * k] === 0;
|
||||
add.out[2 * k + 1] === 0;
|
||||
|
||||
component lt = BigLessThanEcdsa(n, k);
|
||||
for (var i = 0; i < k; i++) {
|
||||
lt.a[i] <== mod[i];
|
||||
lt.b[i] <== b[i];
|
||||
}
|
||||
lt.out === 1;
|
||||
}
|
||||
|
||||
// copied from BigMod to allow a to have m registers and use long_div2
|
||||
template BigMod2(n, k, m) {
|
||||
assert(n <= 126);
|
||||
signal input a[m];
|
||||
signal input b[k];
|
||||
|
||||
signal output div[m - k + 1];
|
||||
signal output mod[k];
|
||||
|
||||
var longdiv[2][50] = long_div2(n, k, m-k, a, b);
|
||||
for (var i = 0; i < k; i++) {
|
||||
mod[i] <-- longdiv[1][i];
|
||||
}
|
||||
for (var i = 0; i <= m-k; i++) {
|
||||
div[i] <-- longdiv[0][i];
|
||||
}
|
||||
component div_range_checks[m - k + 1];
|
||||
for (var i = 0; i <= m-k; i++) {
|
||||
div_range_checks[i] = Num2Bits(n);
|
||||
div_range_checks[i].in <== div[i];
|
||||
}
|
||||
component mod_range_checks[k];
|
||||
for (var i = 0; i < k; i++) {
|
||||
mod_range_checks[i] = Num2Bits(n);
|
||||
mod_range_checks[i].in <== mod[i];
|
||||
}
|
||||
|
||||
component mul = BigMult(n, m-k + 1);
|
||||
// this might need to be optimized since b has less registers than div
|
||||
for (var i = 0; i < k; i++) {
|
||||
mul.a[i] <== div[i];
|
||||
mul.b[i] <== b[i];
|
||||
}
|
||||
for (var i = k; i <= m-k; i++) {
|
||||
mul.a[i] <== div[i];
|
||||
mul.b[i] <== 0;
|
||||
}
|
||||
|
||||
// mul shouldn't have more registers than a
|
||||
for (var i = m; i < 2*(m-k)+2; i++) {
|
||||
mul.out[i] === 0;
|
||||
}
|
||||
|
||||
component add = BigAdd(n, m);
|
||||
for (var i = 0; i < m; i++) {
|
||||
add.a[i] <== mul.out[i];
|
||||
if (i < k) {
|
||||
add.b[i] <== mod[i];
|
||||
} else {
|
||||
add.b[i] <== 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < m; i++) {
|
||||
add.out[i] === a[i];
|
||||
}
|
||||
add.out[m] === 0;
|
||||
|
||||
component lt = BigLessThanEcdsa(n, k);
|
||||
for (var i = 0; i < k; i++) {
|
||||
lt.a[i] <== mod[i];
|
||||
lt.b[i] <== b[i];
|
||||
}
|
||||
lt.out === 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// a[i], b[i] in 0... 2**n-1
|
||||
// represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k)
|
||||
// calculates (a+b)%p, where 0<= a,b < p
|
||||
template BigAddModP(n, k){
|
||||
assert(n <= 252);
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal input p[k];
|
||||
signal output out[k];
|
||||
|
||||
component add = BigAdd(n,k);
|
||||
for (var i = 0; i < k; i++) {
|
||||
add.a[i] <== a[i];
|
||||
add.b[i] <== b[i];
|
||||
}
|
||||
component lt = BigLessThanEcdsa(n, k+1);
|
||||
for (var i = 0; i < k; i++) {
|
||||
lt.a[i] <== add.out[i];
|
||||
lt.b[i] <== p[i];
|
||||
}
|
||||
lt.a[k] <== add.out[k];
|
||||
lt.b[k] <== 0;
|
||||
|
||||
component sub = BigSub(n,k+1);
|
||||
for (var i = 0; i < k; i++) {
|
||||
sub.a[i] <== add.out[i];
|
||||
sub.b[i] <== (1-lt.out) * p[i];
|
||||
}
|
||||
sub.a[k] <== add.out[k];
|
||||
sub.b[k] <== 0;
|
||||
|
||||
sub.out[k] === 0;
|
||||
for (var i = 0; i < k; i++) {
|
||||
out[i] <== sub.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Inputs:
|
||||
- BigInts a, b
|
||||
- Assume a >= b
|
||||
Output:
|
||||
- BigInt out = a - b
|
||||
- underflow = how much is borrowed at the highest digit of subtraction, only nonzero if a < b
|
||||
*/
|
||||
template BigSub(n, k) {
|
||||
assert(n <= 252);
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal output out[k];
|
||||
signal output underflow;
|
||||
|
||||
component unit0 = ModSub(n);
|
||||
unit0.a <== a[0];
|
||||
unit0.b <== b[0];
|
||||
out[0] <== unit0.out;
|
||||
|
||||
component unit[k - 1];
|
||||
for (var i = 1; i < k; i++) {
|
||||
unit[i - 1] = ModSubThree(n);
|
||||
unit[i - 1].a <== a[i];
|
||||
unit[i - 1].b <== b[i];
|
||||
if (i == 1) {
|
||||
unit[i - 1].c <== unit0.borrow;
|
||||
} else {
|
||||
unit[i - 1].c <== unit[i - 2].borrow;
|
||||
}
|
||||
out[i] <== unit[i - 1].out;
|
||||
}
|
||||
underflow <== unit[k - 2].borrow;
|
||||
}
|
||||
|
||||
// calculates (a - b) % p, where a, b < p
|
||||
// note: does not assume a >= b
|
||||
template BigSubModP(n, k){
|
||||
assert(n <= 252);
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal input p[k];
|
||||
signal output out[k];
|
||||
component sub = BigSub(n, k);
|
||||
for (var i = 0; i < k; i++){
|
||||
sub.a[i] <== a[i];
|
||||
sub.b[i] <== b[i];
|
||||
}
|
||||
signal flag;
|
||||
flag <== sub.underflow;
|
||||
component add = BigAdd(n, k);
|
||||
for (var i = 0; i < k; i++){
|
||||
add.a[i] <== sub.out[i];
|
||||
add.b[i] <== p[i];
|
||||
}
|
||||
signal tmp[k];
|
||||
for (var i = 0; i < k; i++){
|
||||
tmp[i] <== (1 - flag) * sub.out[i];
|
||||
out[i] <== tmp[i] + flag * add.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Note: deprecated
|
||||
template BigMultModP(n, k) {
|
||||
assert(n <= 252);
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal input p[k];
|
||||
signal output out[k];
|
||||
|
||||
component big_mult = BigMult(n, k);
|
||||
for (var i = 0; i < k; i++) {
|
||||
big_mult.a[i] <== a[i];
|
||||
big_mult.b[i] <== b[i];
|
||||
}
|
||||
component big_mod = BigMod(n, k);
|
||||
for (var i = 0; i < 2 * k; i++) {
|
||||
big_mod.a[i] <== big_mult.out[i];
|
||||
}
|
||||
for (var i = 0; i < k; i++) {
|
||||
big_mod.b[i] <== p[i];
|
||||
}
|
||||
for (var i = 0; i < k; i++) {
|
||||
out[i] <== big_mod.mod[i];
|
||||
}
|
||||
}
|
||||
|
||||
template BigModInv(n, k) {
|
||||
assert(n <= 252);
|
||||
signal input in[k];
|
||||
signal input p[k];
|
||||
signal output out[k];
|
||||
|
||||
// length k
|
||||
var inv[50] = mod_inv(n, k, in, p);
|
||||
for (var i = 0; i < k; i++) {
|
||||
out[i] <-- inv[i];
|
||||
}
|
||||
component range_checks[k];
|
||||
for (var i = 0; i < k; i++) {
|
||||
range_checks[i] = Num2Bits(n);
|
||||
range_checks[i].in <== out[i];
|
||||
}
|
||||
|
||||
component mult = BigMult(n, k);
|
||||
for (var i = 0; i < k; i++) {
|
||||
mult.a[i] <== in[i];
|
||||
mult.b[i] <== out[i];
|
||||
}
|
||||
component mod = BigMod(n, k);
|
||||
for (var i = 0; i < 2 * k; i++) {
|
||||
mod.a[i] <== mult.out[i];
|
||||
}
|
||||
for (var i = 0; i < k; i++) {
|
||||
mod.b[i] <== p[i];
|
||||
}
|
||||
mod.mod[0] === 1;
|
||||
for (var i = 1; i < k; i++) {
|
||||
mod.mod[i] === 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Taken from circom-ecdsa
|
||||
Input:
|
||||
- in = in[0] + in[1] * X + ... + in[k-1] * X^{k-1} as signed overflow representation
|
||||
- Assume each in[i] is in range (-2^{m-1}, 2^{m-1})
|
||||
Implements:
|
||||
- constrain that in[] evaluated at X = 2^n as a big integer equals zero
|
||||
*/
|
||||
template CheckCarryToZeroEcdsa(n, m, k) {
|
||||
assert(k >= 2);
|
||||
|
||||
var EPSILON = 1; // see below for why 1 is ok
|
||||
|
||||
signal input in[k];
|
||||
|
||||
signal carry[k];
|
||||
component carryRangeChecks[k];
|
||||
for (var i = 0; i < k-1; i++){
|
||||
carryRangeChecks[i] = Num2Bits(m + EPSILON - n);
|
||||
if( i == 0 ){
|
||||
carry[i] <-- in[i] / (1<<n);
|
||||
in[i] === carry[i] * (1<<n);
|
||||
}
|
||||
else{
|
||||
carry[i] <-- (in[i]+carry[i-1]) / (1<<n);
|
||||
in[i] + carry[i-1] === carry[i] * (1<<n);
|
||||
}
|
||||
// checking carry is in the range of -2^(m-n-1+eps), 2^(m-n-1+eps)
|
||||
carryRangeChecks[i].in <== carry[i] + ( 1<< (m + EPSILON - n - 1));
|
||||
// carry[i] is bounded by 2^{m-1} * (2^{-n} + 2^{-2n} + ... ) = 2^{m-n-1} * ( 1/ (1-2^{-n})) < 2^{m-n} by geometric series
|
||||
}
|
||||
|
||||
in[k-1] + carry[k-2] === 0;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Let X = 2^n
|
||||
Input:
|
||||
- in is length k + m array in signed overflow representation
|
||||
- in = in[0] + in[1] * X + ... + in[k+m-1] * X^{k+m-1}
|
||||
- Assume each in[i] is a signed integer such that abs(in[i] * 2^n) < 2^252
|
||||
- p is prime in BigInt format passed as parameter
|
||||
Output:
|
||||
- out = out[0] + out[1] * X + ... + out[k-1] * X^{k-1} is BigInt congruent to in (mod p)
|
||||
Implementation:
|
||||
- For i >= k, we precompute X^i = r[i] mod p, where r[i] represented as k registers with r[i][j] in [0, 2^n)
|
||||
- in[i] * X^i is replaced by sum_j in[i] * r[i][j] * X^j
|
||||
Notes:
|
||||
- If each in[i] has absolute value <B, then out[i] has absolute value < (m+1) * 2^n * B
|
||||
m_out is the expected max number of bits in the output registers
|
||||
*/
|
||||
template PrimeReduce(n, k, m, p, m_out){
|
||||
signal input in[m+k];
|
||||
signal output out[k];
|
||||
|
||||
var two[k];
|
||||
var e[k];
|
||||
for(var i=1; i<k; i++){
|
||||
two[i]=0;
|
||||
e[i]=0;
|
||||
}
|
||||
two[0] = 2;
|
||||
|
||||
|
||||
e[0] = n;
|
||||
var pow2n[50] = mod_exp(n, k, two, p, e);
|
||||
e[0] = k;
|
||||
assert(k < (1<<n) );
|
||||
var pow2nk[50] = mod_exp(n, k, pow2n, p, e);
|
||||
|
||||
var r[m][50];
|
||||
for(var i=0; i<m; i++){
|
||||
// r[i] = 2^{n(k+i)} mod p
|
||||
if(i==0){
|
||||
r[i] = pow2nk;
|
||||
}else{
|
||||
r[i] = prod_mod(n, k, r[i-1], pow2n, p);
|
||||
}
|
||||
}
|
||||
var out_sum[k];
|
||||
for(var i=0; i<k; i++)
|
||||
out_sum[i] = in[i];
|
||||
for(var i=0; i<m; i++)
|
||||
for(var j=0; j<k; j++)
|
||||
out_sum[j] += in[i+k] * r[i][j]; // linear constraint
|
||||
for(var i=0; i<k; i++)
|
||||
out[i] <== out_sum[i];
|
||||
/*component range_checks[k];
|
||||
for (var i = 0; i < k; i++) {
|
||||
range_checks[i] = Num2Bits(m_out+1);
|
||||
range_checks[i].in <== out[i] + (1 << m_out);
|
||||
}*/
|
||||
}
|
||||
|
||||
/*
|
||||
Polynomial multiplication in 2 variables
|
||||
Input:
|
||||
- a = sum_{i=0}^{l-1} sum_{j=0}^{k-1} a[i][j] * w^i * X^j
|
||||
- b = sum_{i=0}^{l-1} sum_{j=0}^{k-1} b[i][j] * w^i * X^j
|
||||
Output:
|
||||
- out = sum_{i=0}^{2*l-2} sum_{j=0}^{2*k-1} out[i][j] * w^i * X^j
|
||||
- out = a * b as product of polynomials in two variables w, X
|
||||
Notes:
|
||||
- Uses same xJsnark optimization as BigMultShortLong
|
||||
- If a[i][j], b[i][j] have absolute value < B, then out[i][j] has absolute value < l * k * B^2
|
||||
Use case: one variable will end up being 2^n; the other will be the field extension generator
|
||||
*/
|
||||
template BigMultShortLong2D(n, k, l) {
|
||||
signal input a[l][k];
|
||||
signal input b[l][k];
|
||||
signal output out[2*l-1][2*k-1];
|
||||
|
||||
var prod_val[2*l-1][2*k-1];
|
||||
for (var i = 0; i < 2*l-1; i++) {
|
||||
for (var j = 0; j < 2*k-1; j++) {
|
||||
prod_val[i][j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i1 = 0; i1 < l; i1 ++) {
|
||||
for (var i2 = 0; i2 < l; i2 ++) {
|
||||
for (var j1 = 0; j1 < k; j1 ++) {
|
||||
for (var j2 = 0; j2 < k; j2 ++) {
|
||||
var i = i1 + i2;
|
||||
var j = j1 + j2;
|
||||
prod_val[i][j] += a[i1][j1] * b[i2][j2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < 2*l-1; i++) {
|
||||
for (var j = 0; j < 2*k-1; j++) {
|
||||
out[i][j] <-- prod_val[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
var k2 = (2*k-1 > 2*l-1) ? 2*k-1 : 2*l-1;
|
||||
var pow[k2][k2];
|
||||
for(var i = 0; i<k2; i++)for(var j=0; j<k2; j++)
|
||||
pow[i][j] = i ** j;
|
||||
|
||||
var a_poly[2*l-1][2*k-1];
|
||||
var b_poly[2*l-1][2*k-1];
|
||||
var out_poly[2*l-1][2*k-1];
|
||||
for (var i = 0; i < 2*l-1; i++) {
|
||||
for (var j = 0; j < 2*k-1; j++) {
|
||||
a_poly[i][j] = 0;
|
||||
b_poly[i][j] = 0;
|
||||
out_poly[i][j] = 0;
|
||||
for (var deg1 = 0; deg1 < l; deg1 ++) {
|
||||
for (var deg2 = 0; deg2 < k; deg2 ++) {
|
||||
a_poly[i][j] = a_poly[i][j] + a[deg1][deg2] * pow[i][deg1] * pow[j][deg2]; // (i ** deg1) * (j ** deg2);
|
||||
b_poly[i][j] = b_poly[i][j] + b[deg1][deg2] * pow[i][deg1] * pow[j][deg2]; // (i ** deg1) * (j ** deg2);
|
||||
}
|
||||
}
|
||||
for (var deg1 = 0; deg1 < 2*l-1; deg1 ++) {
|
||||
for (var deg2 = 0; deg2 < 2*k-1; deg2 ++) {
|
||||
out_poly[i][j] = out_poly[i][j] + out[deg1][deg2] * pow[i][deg1] * pow[j][deg2];// (i ** deg1) * (j ** deg2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < 2*l-1; i++) {
|
||||
for (var j = 0; j < 2*k-1; j++) {
|
||||
out_poly[i][j] === a_poly[i][j] * b_poly[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Same as BigMultShortLong2D except a has degrees la - 1, ka - 1 and b has degrees lb - 1, kb - 1
|
||||
Notes:
|
||||
- If a[i][j], b[i][j] have absolute value < B, then out[i][j] has absolute value < min(la, lb) * min(ka, kb) * B^2
|
||||
*/
|
||||
template BigMultShortLong2DUnequal(n, ka, kb, la, lb) {
|
||||
signal input a[la][ka];
|
||||
signal input b[lb][kb];
|
||||
signal output out[la + lb -1][ka + kb -1];
|
||||
|
||||
var prod_val[la + lb -1][ka + kb -1];
|
||||
for (var i = 0; i < la + lb -1; i++) {
|
||||
for (var j = 0; j < ka + kb -1; j++) {
|
||||
prod_val[i][j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i1 = 0; i1 < la; i1 ++) {
|
||||
for (var i2 = 0; i2 < lb; i2 ++) {
|
||||
for (var j1 = 0; j1 < ka; j1 ++) {
|
||||
for (var j2 = 0; j2 < kb; j2 ++) {
|
||||
var i = i1 + i2;
|
||||
var j = j1 + j2;
|
||||
prod_val[i][j] += a[i1][j1] * b[i2][j2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < la + lb -1; i++) {
|
||||
for (var j = 0; j < ka + kb -1; j++) {
|
||||
out[i][j] <-- prod_val[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
var k2 = (ka + kb -1 > la + lb -1) ? ka + kb - 1 : la + lb -1;
|
||||
var pow[k2][k2];
|
||||
for(var i = 0; i<k2; i++)for(var j=0; j<k2; j++)
|
||||
pow[i][j] = i ** j;
|
||||
|
||||
var a_poly[la + lb - 1][ka + kb -1];
|
||||
var b_poly[la + lb - 1][ka + kb -1];
|
||||
var out_poly[la + lb - 1][ka + kb -1];
|
||||
for (var i = 0; i < la + lb - 1; i++) {
|
||||
for (var j = 0; j < ka + kb - 1; j++) {
|
||||
a_poly[i][j] = 0;
|
||||
b_poly[i][j] = 0;
|
||||
out_poly[i][j] = 0;
|
||||
for (var deg1 = 0; deg1 < la + lb - 1; deg1 ++) {
|
||||
if (deg1 < la) {
|
||||
for (var deg2 = 0; deg2 < ka; deg2 ++) {
|
||||
a_poly[i][j] = a_poly[i][j] + a[deg1][deg2] * pow[i][deg1] * pow[j][deg2]; //(i ** deg1) * (j ** deg2);
|
||||
}
|
||||
}
|
||||
if (deg1 < lb) {
|
||||
for (var deg2 = 0; deg2 < kb; deg2 ++) {
|
||||
b_poly[i][j] = b_poly[i][j] + b[deg1][deg2] * pow[i][deg1] * pow[j][deg2]; // (i ** deg1) * (j ** deg2);
|
||||
}
|
||||
}
|
||||
for (var deg2 = 0; deg2 < ka + kb -1; deg2 ++) {
|
||||
out_poly[i][j] = out_poly[i][j] + out[deg1][deg2] * pow[i][deg1] * pow[j][deg2]; // (i ** deg1) * (j ** deg2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < la + lb - 1; i++) {
|
||||
for (var j = 0; j < ka + kb - 1; j++) {
|
||||
out_poly[i][j] === a_poly[i][j] * b_poly[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
530
circuits/circuits/utils/circom-ecdsa/bigInt_func.circom
Normal file
530
circuits/circuits/utils/circom-ecdsa/bigInt_func.circom
Normal file
@@ -0,0 +1,530 @@
|
||||
pragma circom 2.0.3;
|
||||
|
||||
function min(a, b) {
|
||||
if(a < b)
|
||||
return a;
|
||||
return b;
|
||||
}
|
||||
|
||||
function max(a, b) {
|
||||
if(a > b)
|
||||
return a;
|
||||
return b;
|
||||
}
|
||||
|
||||
function div_ceil_ecdsa(m, n) {
|
||||
var ret = 0;
|
||||
if (m % n == 0) {
|
||||
ret = m \ n;
|
||||
} else {
|
||||
ret = m \ n + 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function log_ceil_ecdsa(n) {
|
||||
var n_temp = n;
|
||||
for (var i = 0; i < 254; i++) {
|
||||
if (n_temp == 0) {
|
||||
return i;
|
||||
}
|
||||
n_temp = n_temp \ 2;
|
||||
}
|
||||
return 254;
|
||||
}
|
||||
|
||||
function SplitFn(in, n, m) {
|
||||
return [in % (1 << n), (in \ (1 << n)) % (1 << m)];
|
||||
}
|
||||
|
||||
function SplitThreeFn(in, n, m, k) {
|
||||
return [in % (1 << n), (in \ (1 << n)) % (1 << m), (in \ (1 << n + m)) % (1 << k)];
|
||||
}
|
||||
|
||||
// 1 if true, 0 if false
|
||||
function long_gt_ecdsa(n, k, a, b) {
|
||||
for (var i = k - 1; i >= 0; i--) {
|
||||
if (a[i] > b[i]) {
|
||||
return 1;
|
||||
}
|
||||
if (a[i] < b[i]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function long_is_zero(k, a){
|
||||
for(var idx=0; idx<k; idx++){
|
||||
if(a[idx] != 0)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// n bits per register
|
||||
// a has k registers
|
||||
// b has k registers
|
||||
// output has k+1 registers
|
||||
function long_add(n, k, a, b){
|
||||
var carry = 0;
|
||||
var sum[50];
|
||||
for(var i=0; i<k; i++){
|
||||
var sumAndCarry[2] = SplitFn(a[i] + b[i] + carry, n, n);
|
||||
sum[i] = sumAndCarry[0];
|
||||
carry = sumAndCarry[1];
|
||||
}
|
||||
sum[k] = carry;
|
||||
return sum;
|
||||
}
|
||||
|
||||
// n bits per register
|
||||
// a has k registers
|
||||
// b has k registers
|
||||
// c has k registers
|
||||
// d has k registers
|
||||
// output has k+1 registers
|
||||
function long_add4(n, k, a, b, c, d){
|
||||
var carry = 0;
|
||||
var sum[50];
|
||||
for(var i=0; i < k; i++){
|
||||
var sumAndCarry[2] = SplitFn(a[i] + b[i] + c[i] + d[i] + carry, n, n);
|
||||
sum[i] = sumAndCarry[0];
|
||||
carry = sumAndCarry[1];
|
||||
}
|
||||
sum[k] = carry;
|
||||
return sum;
|
||||
}
|
||||
|
||||
// n bits per register
|
||||
// a has k1 registers
|
||||
// b has k2 registers
|
||||
// assume k1 > k2
|
||||
// output has k1+1 registers
|
||||
function long_add_unequal(n, k1, k2, a, b){
|
||||
var carry = 0;
|
||||
var sum[50];
|
||||
for(var i=0; i<k1; i++){
|
||||
if (i < k2) {
|
||||
var sumAndCarry[2] = SplitFn(a[i] + b[i] + carry, n, n);
|
||||
sum[i] = sumAndCarry[0];
|
||||
carry = sumAndCarry[1];
|
||||
} else {
|
||||
var sumAndCarry[2] = SplitFn(a[i] + carry, n, n);
|
||||
sum[i] = sumAndCarry[0];
|
||||
carry = sumAndCarry[1];
|
||||
}
|
||||
}
|
||||
sum[k1] = carry;
|
||||
return sum;
|
||||
}
|
||||
|
||||
// n bits per register
|
||||
// a has k registers
|
||||
// b has k registers
|
||||
// a >= b
|
||||
function long_sub_ecdsa(n, k, a, b) {
|
||||
var diff[50];
|
||||
var borrow[50];
|
||||
for (var i = 0; i < k; i++) {
|
||||
if (i == 0) {
|
||||
if (a[i] >= b[i]) {
|
||||
diff[i] = a[i] - b[i];
|
||||
borrow[i] = 0;
|
||||
} else {
|
||||
diff[i] = a[i] - b[i] + (1 << n);
|
||||
borrow[i] = 1;
|
||||
}
|
||||
} else {
|
||||
if (a[i] >= b[i] + borrow[i - 1]) {
|
||||
diff[i] = a[i] - b[i] - borrow[i - 1];
|
||||
borrow[i] = 0;
|
||||
} else {
|
||||
diff[i] = (1 << n) + a[i] - b[i] - borrow[i - 1];
|
||||
borrow[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
// a is a n-bit scalar
|
||||
// b has k registers
|
||||
function long_scalar_mult_ecdsa(n, k, a, b) {
|
||||
var out[50];
|
||||
for (var i = 0; i < 50; i++) {
|
||||
out[i] = 0;
|
||||
}
|
||||
for (var i = 0; i < k; i++) {
|
||||
var temp = out[i] + (a * b[i]);
|
||||
out[i] = temp % (1 << n);
|
||||
out[i + 1] = out[i + 1] + temp \ (1 << n);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
// n bits per register
|
||||
// a has k + m registers
|
||||
// b has k registers
|
||||
// out[0] has length m + 1 -- quotient
|
||||
// out[1] has length k -- remainder
|
||||
// implements algorithm of https://people.eecs.berkeley.edu/~fateman/282/F%20Wright%20notes/week4.pdf
|
||||
// b[k-1] must be nonzero!
|
||||
function long_div2(n, k, m, a, b){
|
||||
var out[2][50];
|
||||
// assume k+m < 50
|
||||
var remainder[50];
|
||||
for (var i = 0; i < m + k; i++) {
|
||||
remainder[i] = a[i];
|
||||
}
|
||||
|
||||
var dividend[50];
|
||||
for (var i = m; i >= 0; i--) {
|
||||
if (i == m) {
|
||||
dividend[k] = 0;
|
||||
for (var j = k - 1; j >= 0; j--) {
|
||||
dividend[j] = remainder[j + m];
|
||||
}
|
||||
} else {
|
||||
for (var j = k; j >= 0; j--) {
|
||||
dividend[j] = remainder[j + i];
|
||||
}
|
||||
}
|
||||
out[0][i] = short_div_ecdsa(n, k, dividend, b);
|
||||
var mult_shift[50] = long_scalar_mult_ecdsa(n, k, out[0][i], b);
|
||||
var subtrahend[50];
|
||||
for (var j = 0; j < m + k; j++) {
|
||||
subtrahend[j] = 0;
|
||||
}
|
||||
for (var j = 0; j <= k; j++) {
|
||||
if (i + j < m + k) {
|
||||
subtrahend[i + j] = mult_shift[j];
|
||||
}
|
||||
}
|
||||
remainder = long_sub_ecdsa(n, m + k, remainder, subtrahend);
|
||||
}
|
||||
for (var i = 0; i < k; i++) {
|
||||
out[1][i] = remainder[i];
|
||||
}
|
||||
out[1][k] = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
function long_div_ecdsa(n, k, a, b) {
|
||||
return long_div2(n, k, k, a, b);
|
||||
}
|
||||
|
||||
// n bits per register
|
||||
// a has k + 1 registers
|
||||
// b has k registers
|
||||
// assumes leading digit of b is at least 2^(n - 1)
|
||||
// 0 <= a < (2**n) * b
|
||||
function short_div_norm_ecdsa(n, k, a, b) {
|
||||
var qhat = (a[k] * (1 << n) + a[k - 1]) \ b[k - 1];
|
||||
if (qhat > (1 << n) - 1) {
|
||||
qhat = (1 << n) - 1;
|
||||
}
|
||||
|
||||
var mult[50] = long_scalar_mult_ecdsa(n, k, qhat, b);
|
||||
if (long_gt_ecdsa(n, k + 1, mult, a) == 1) {
|
||||
mult = long_sub_ecdsa(n, k + 1, mult, b);
|
||||
if (long_gt_ecdsa(n, k + 1, mult, a) == 1) {
|
||||
return qhat - 2;
|
||||
} else {
|
||||
return qhat - 1;
|
||||
}
|
||||
} else {
|
||||
return qhat;
|
||||
}
|
||||
}
|
||||
|
||||
// n bits per register
|
||||
// a has k + 1 registers
|
||||
// b has k registers
|
||||
// assumes leading digit of b is non-zero
|
||||
// 0 <= a < b * 2^n
|
||||
function short_div_ecdsa(n, k, a, b) {
|
||||
var scale = (1 << n) \ (1 + b[k - 1]);
|
||||
// k + 2 registers now
|
||||
var norm_a[50] = long_scalar_mult_ecdsa(n, k + 1, scale, a);
|
||||
// k + 1 registers now
|
||||
var norm_b[50] = long_scalar_mult_ecdsa(n, k, scale, b);
|
||||
|
||||
var ret;
|
||||
if (norm_b[k] != 0) {
|
||||
ret = short_div_norm_ecdsa(n, k + 1, norm_a, norm_b);
|
||||
} else {
|
||||
ret = short_div_norm_ecdsa(n, k, norm_a, norm_b);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// a = a0 + a1 * X + ... + a[k-1] * X^{k-1} with X = 2^n
|
||||
// a_i can be "negative" assume a_i in (-2^251, 2^251)
|
||||
// output is the value of a with a_i all of the same sign
|
||||
// out[50] = 0 if positive, 1 if negative
|
||||
function signed_long_to_short(n, k, a){
|
||||
var out[51];
|
||||
var MAXL = 50;
|
||||
var temp[51];
|
||||
|
||||
// is a positive?
|
||||
for(var i=0; i<k; i++) temp[i] = a[i];
|
||||
for(var i=k; i<=MAXL; i++) temp[i] = 0;
|
||||
|
||||
var X = (1<<n);
|
||||
for(var i=0; i<MAXL; i++){
|
||||
if(temp[i] >= 0){ // circom automatically takes care of signs in comparator
|
||||
out[i] = temp[i] % X;
|
||||
temp[i+1] += temp[i] \ X;
|
||||
}else{
|
||||
var borrow = (-temp[i] + X - 1 ) \ X;
|
||||
out[i] = temp[i] + borrow * X;
|
||||
temp[i+1] -= borrow;
|
||||
}
|
||||
}
|
||||
if(temp[MAXL] >= 0){
|
||||
assert(temp[MAXL]==0); // otherwise not enough registers!
|
||||
out[MAXL] = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
// must be negative then, reset
|
||||
for(var i=0; i<k; i++) temp[i] = a[i];
|
||||
for(var i=k; i<=MAXL; i++) temp[i] = 0;
|
||||
|
||||
for(var i=0; i<MAXL; i++){
|
||||
if(temp[i] < 0){
|
||||
var carry = (-temp[i]) \ X;
|
||||
out[i] = temp[i] + carry * X;
|
||||
temp[i+1] -= carry;
|
||||
}else{
|
||||
var borrow = (temp[i] + X - 1 ) \ X;
|
||||
out[i] = temp[i] - borrow * X;
|
||||
temp[i+1] += borrow;
|
||||
}
|
||||
}
|
||||
assert( temp[MAXL] == 0 );
|
||||
out[MAXL] = 1;
|
||||
return out;
|
||||
}
|
||||
|
||||
// n bits per register
|
||||
// a and b both have k registers
|
||||
// out[0] has length 2 * k
|
||||
// adapted from BigMulShortLong and LongToShortNoEndCarry witness computation
|
||||
function prod(n, k, a, b) {
|
||||
// first compute the intermediate values. taken from BigMulShortLong
|
||||
var prod_val[50]; // length is 2 * k - 1
|
||||
for (var i = 0; i < 2 * k - 1; i++) {
|
||||
prod_val[i] = 0;
|
||||
if (i < k) {
|
||||
for (var a_idx = 0; a_idx <= i; a_idx++) {
|
||||
prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx];
|
||||
}
|
||||
} else {
|
||||
for (var a_idx = i - k + 1; a_idx < k; a_idx++) {
|
||||
prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now do a bunch of carrying to make sure registers not overflowed. taken from LongToShortNoEndCarry
|
||||
var out[50]; // length is 2 * k
|
||||
|
||||
var split[50][3]; // first dimension has length 2 * k - 1
|
||||
for (var i = 0; i < 2 * k - 1; i++) {
|
||||
split[i] = SplitThreeFn(prod_val[i], n, n, n);
|
||||
}
|
||||
|
||||
var carry[50]; // length is 2 * k - 1
|
||||
carry[0] = 0;
|
||||
out[0] = split[0][0];
|
||||
if (2 * k - 1 > 1) {
|
||||
var sumAndCarry[2] = SplitFn(split[0][1] + split[1][0], n, n);
|
||||
out[1] = sumAndCarry[0];
|
||||
carry[1] = sumAndCarry[1];
|
||||
}
|
||||
if (2 * k - 1 > 2) {
|
||||
for (var i = 2; i < 2 * k - 1; i++) {
|
||||
var sumAndCarry[2] = SplitFn(split[i][0] + split[i-1][1] + split[i-2][2] + carry[i-1], n, n);
|
||||
out[i] = sumAndCarry[0];
|
||||
carry[i] = sumAndCarry[1];
|
||||
}
|
||||
out[2 * k - 1] = split[2*k-2][1] + split[2*k-3][2] + carry[2*k-2];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
// n bits per register
|
||||
// a and b both have l x k registers
|
||||
// out has length 2l - 1 x 2k
|
||||
// adapted from BigMultShortLong2D and LongToShortNoEndCarry2 witness computation
|
||||
function prod2D(n, k, l, a, b) {
|
||||
// first compute the intermediate values. taken from BigMulShortLong
|
||||
var prod_val[20][50]; // length is 2l - 1 by 2k - 1
|
||||
for (var i = 0; i < 2 * k - 1; i++) {
|
||||
for (var j = 0; j < 2 * l - 1; j ++) {
|
||||
prod_val[j][i] = 0;
|
||||
}
|
||||
}
|
||||
for (var i1 = 0; i1 < k; i1 ++) {
|
||||
for (var i2 = 0; i2 < k; i2 ++) {
|
||||
for (var j1 = 0; j1 < l; j1 ++) {
|
||||
for (var j2 = 0; j2 < l; j2 ++) {
|
||||
prod_val[j1+j2][i1+i2] = prod_val[j1+j2][i1+i2] + a[j1][i1] * b[j2][i2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now do a bunch of carrying to make sure registers not overflowed. taken from LongToShortNoEndCarry2
|
||||
var out[20][50]; // length is 2 * l by 2 * k
|
||||
|
||||
var split[20][50][3]; // second dimension has length 2 * k - 1
|
||||
for (var j = 0; j < 2 * l - 1; j ++) {
|
||||
for (var i = 0; i < 2 * k - 1; i++) {
|
||||
split[j][i] = SplitThreeFn(prod_val[j][i], n, n, n);
|
||||
}
|
||||
}
|
||||
|
||||
var carry[20][50]; // length is 2l-1 x 2k
|
||||
var sumAndCarry[20][2];
|
||||
for ( var j = 0; j < 2 * l - 1; j ++) {
|
||||
carry[j][0] = 0;
|
||||
out[j][0] = split[j][0][0];
|
||||
if (2 * k - 1 > 1) {
|
||||
sumAndCarry[j] = SplitFn(split[j][0][1] + split[j][1][0], n, n);
|
||||
out[j][1] = sumAndCarry[j][0];
|
||||
carry[j][1] = sumAndCarry[j][1];
|
||||
}
|
||||
if (2 * k - 1 > 2) {
|
||||
for (var i = 2; i < 2 * k - 1; i++) {
|
||||
sumAndCarry[j] = SplitFn(split[j][i][0] + split[j][i-1][1] + split[j][i-2][2] + carry[j][i-1], n, n);
|
||||
out[j][i] = sumAndCarry[j][0];
|
||||
carry[j][i] = sumAndCarry[j][1];
|
||||
}
|
||||
out[j][2 * k - 1] = split[j][2*k-2][1] + split[j][2*k-3][2] + carry[j][2*k-2];
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Put all modular arithmetic, aka F_p field stuff, at the end
|
||||
|
||||
function long_add_mod(n, k, a, b, p) {
|
||||
var sum[50] = long_add(n,k,a,b);
|
||||
var temp[2][50] = long_div2(n,k,1,sum,p);
|
||||
return temp[1];
|
||||
}
|
||||
|
||||
function long_sub_mod(n, k, a, b, p) {
|
||||
if(long_gt_ecdsa(n, k, b, a) == 1){
|
||||
return long_add(n, k, a, long_sub_ecdsa(n,k,p,b));
|
||||
}else{
|
||||
return long_sub_ecdsa(n, k, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
function prod_mod(n, k, a, b, p) {
|
||||
var prod[50] = prod(n,k,a,b);
|
||||
var temp[2][50] = long_div_ecdsa(n,k,prod,p);
|
||||
return temp[1];
|
||||
}
|
||||
|
||||
|
||||
// n bits per register
|
||||
// a has k registers
|
||||
// p has k registers
|
||||
// e has k registers
|
||||
// k * n <= 500
|
||||
// p is a prime
|
||||
// computes a^e mod p
|
||||
function mod_exp(n, k, a, p, e) {
|
||||
var eBits[500]; // length is k * n
|
||||
var bitlength;
|
||||
for (var i = 0; i < k; i++) {
|
||||
for (var j = 0; j < n; j++) {
|
||||
eBits[j + n * i] = (e[i] >> j) & 1;
|
||||
if(eBits[j + n * i] == 1)
|
||||
bitlength = j + n * i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
var out[50]; // length is k
|
||||
for (var i = 0; i < 50; i++) {
|
||||
out[i] = 0;
|
||||
}
|
||||
out[0] = 1;
|
||||
|
||||
// repeated squaring
|
||||
for (var i = bitlength-1; i >= 0; i--) {
|
||||
// multiply by a if bit is 0
|
||||
if (eBits[i] == 1) {
|
||||
var temp[50]; // length 2 * k
|
||||
temp = prod(n, k, out, a);
|
||||
var temp2[2][50];
|
||||
temp2 = long_div_ecdsa(n, k, temp, p);
|
||||
out = temp2[1];
|
||||
}
|
||||
|
||||
// square, unless we're at the end
|
||||
if (i > 0) {
|
||||
var temp[50]; // length 2 * k
|
||||
temp = prod(n, k, out, out);
|
||||
var temp2[2][50];
|
||||
temp2 = long_div_ecdsa(n, k, temp, p);
|
||||
out = temp2[1];
|
||||
}
|
||||
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// n bits per register
|
||||
// a has k registers
|
||||
// p has k registers
|
||||
// k * n <= 500
|
||||
// p is a prime
|
||||
// if a == 0 mod p, returns 0
|
||||
// else computes inv = a^(p-2) mod p
|
||||
function mod_inv(n, k, a, p) {
|
||||
var isZero = 1;
|
||||
for (var i = 0; i < k; i++) {
|
||||
if (a[i] != 0) {
|
||||
isZero = 0;
|
||||
}
|
||||
}
|
||||
if (isZero == 1) {
|
||||
var ret[50];
|
||||
for (var i = 0; i < k; i++) {
|
||||
ret[i] = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
var pCopy[50];
|
||||
for (var i = 0; i < 50; i++) {
|
||||
if (i < k) {
|
||||
pCopy[i] = p[i];
|
||||
} else {
|
||||
pCopy[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var two[50];
|
||||
for (var i = 0; i < 50; i++) {
|
||||
two[i] = 0;
|
||||
}
|
||||
two[0] = 2;
|
||||
|
||||
var pMinusTwo[50];
|
||||
pMinusTwo = long_sub_ecdsa(n, k, pCopy, two); // length k
|
||||
var out[50];
|
||||
out = mod_exp(n, k, a, pCopy, pMinusTwo);
|
||||
return out;
|
||||
}
|
||||
|
||||
524
circuits/circuits/utils/circom-ecdsa/curve.circom
Normal file
524
circuits/circuits/utils/circom-ecdsa/curve.circom
Normal file
@@ -0,0 +1,524 @@
|
||||
pragma circom 2.0.3;
|
||||
|
||||
include "../../../node_modules/circomlib/circuits/bitify.circom";
|
||||
include "fp2.circom";
|
||||
include "bigInt.circom";
|
||||
include "bigInt_func.circom";
|
||||
|
||||
// in[i] = (x_i, y_i)
|
||||
// Implements constraint: (y_1 + y_3) * (x_2 - x_1) - (y_2 - y_1)*(x_1 - x_3) = 0 mod p
|
||||
// used to show (x1, y1), (x2, y2), (x3, -y3) are co-linear
|
||||
template PointOnLine(n, k, p) {
|
||||
signal input in[3][2][k];
|
||||
|
||||
var LOGK = log_ceil_ecdsa(k);
|
||||
var LOGK2 = log_ceil_ecdsa(3*k*k);
|
||||
assert(3*n + LOGK2 < 251);
|
||||
|
||||
// AKA check point on line
|
||||
component left = BigMultShortLong(n, k, 2*n + LOGK + 1); // 2k-1 registers abs val < 2k*2^{2n}
|
||||
for(var i = 0; i < k; i++){
|
||||
left.a[i] <== in[0][1][i] + in[2][1][i];
|
||||
left.b[i] <== in[1][0][i] - in[0][0][i];
|
||||
}
|
||||
|
||||
component right = BigMultShortLong(n, k, 2*n + LOGK); // 2k-1 registers abs val < k*2^{2n}
|
||||
for(var i = 0; i < k; i++){
|
||||
right.a[i] <== in[1][1][i] - in[0][1][i];
|
||||
right.b[i] <== in[0][0][i] - in[2][0][i];
|
||||
}
|
||||
|
||||
component diff_red;
|
||||
diff_red = PrimeReduce(n, k, k-1, p, 3*n + LOGK2);
|
||||
for(var i=0; i<2*k-1; i++)
|
||||
diff_red.in[i] <== left.out[i] - right.out[i];
|
||||
|
||||
// diff_red has k registers abs val < 3*k^2*2^{3n}
|
||||
component diff_mod = SignedCheckCarryModToZero(n, k, 3*n + LOGK2, p);
|
||||
for(var i=0; i<k; i++)
|
||||
diff_mod.in[i] <== diff_red.out[i];
|
||||
}
|
||||
|
||||
// in = (x, y)
|
||||
// Implements:
|
||||
// x^3 + ax + b - y^2 = 0 mod p
|
||||
// Assume: a, b in [0, 2^n)
|
||||
template PointOnCurve(n, k, a, b, p){
|
||||
signal input in[2][k];
|
||||
|
||||
var LOGK = log_ceil_ecdsa(k);
|
||||
var LOGK2 = log_ceil_ecdsa( (2*k-1)*(k*k+1) );
|
||||
assert(4*n + LOGK2 < 251);
|
||||
|
||||
// compute x^3, y^2
|
||||
component x_sq = BigMultShortLong(n, k, 2*n + LOGK); // 2k-1 registers in [0, k*2^{2n})
|
||||
component y_sq = BigMultShortLong(n, k, 2*n + LOGK); // 2k-1 registers in [0, k*2^{2n})
|
||||
for(var i=0; i<k; i++){
|
||||
x_sq.a[i] <== in[0][i];
|
||||
x_sq.b[i] <== in[0][i];
|
||||
|
||||
y_sq.a[i] <== in[1][i];
|
||||
y_sq.b[i] <== in[1][i];
|
||||
}
|
||||
component x_cu = BigMultShortLongUnequal(n, 2*k-1, k, 3*n + 2*LOGK); // 3k-2 registers in [0, k^2 * 2^{3n})
|
||||
for(var i=0; i<2*k-1; i++)
|
||||
x_cu.a[i] <== x_sq.out[i];
|
||||
for(var i=0; i<k; i++)
|
||||
x_cu.b[i] <== in[0][i];
|
||||
|
||||
component ax = BigMultShortLong(n, k, 2*n + LOGK); // 2k-1 registers in [0, k*2^{2n})
|
||||
for (var i=0; i<k; i++) {
|
||||
ax.a[i] <== a[i];
|
||||
ax.b[i] <== in[0][i];
|
||||
}
|
||||
|
||||
// x_cu + a x + b has 3k-2 positive registers < k^2 * 2^{3n} + 2^{2n} + 2^n < (k^2 + 1) * 2^{3n}
|
||||
component cu_red = PrimeReduce(n, k, 2*k-2, p, 4*n + 3*LOGK + 1);
|
||||
for(var i=0; i<3*k-2; i++){
|
||||
if (i < k) {
|
||||
cu_red.in[i] <== x_cu.out[i] + ax.out[i] + b[i];
|
||||
} else {
|
||||
if (i < 2*k-1) {
|
||||
cu_red.in[i] <== x_cu.out[i] + ax.out[i];
|
||||
} else {
|
||||
cu_red.in[i] <== x_cu.out[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// cu_red has k registers < (k^2 + 1)*(2k-1)*2^{4n}
|
||||
|
||||
component y_sq_red = PrimeReduce(n, k, k-1, p, 3*n + 2*LOGK + 1);
|
||||
for(var i=0; i<2*k-1; i++)
|
||||
y_sq_red.in[i] <== y_sq.out[i];
|
||||
// y_sq_red has positive registers, so when we subtract from cu_red it doesn't increase absolute value
|
||||
|
||||
component constraint = SignedCheckCarryModToZero(n, k, 4*n + LOGK2, p);
|
||||
for(var i=0; i<k; i++){
|
||||
constraint.in[i] <== cu_red.out[i] - y_sq_red.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// in[0] = (x_1, y_1), in[1] = (x_3, y_3)
|
||||
// Checks that the line between (x_1, y_1) and (x_3, -y_3) is equal to the tangent line to the elliptic curve at the point (x_1, y_1)
|
||||
// Implements:
|
||||
// (y_1 + y_3) = lambda * (x_1 - x_3)
|
||||
// where lambda = (3 x_1^2 + a)/(2 y_1)
|
||||
// Actual constraint is 2y_1 (y_1 + y_3) = (3 x_1^2 + a ) ( x_1 - x_3 )
|
||||
template PointOnTangent(n, k, a, p){
|
||||
signal input in[2][2][k];
|
||||
|
||||
var LOGK = log_ceil_ecdsa(k);
|
||||
var LOGK3 = log_ceil_ecdsa((3*k)*(2*k-1) + 1);
|
||||
assert(4*n + LOGK3 < 251);
|
||||
component x_sq = BigMultShortLong(n, k, 2*n + LOGK); // 2k-1 registers < k*2^{2n})
|
||||
for(var i=0; i<k; i++){
|
||||
x_sq.a[i] <== in[0][0][i];
|
||||
x_sq.b[i] <== in[0][0][i];
|
||||
}
|
||||
component right = BigMultShortLongUnequal(n, 2*k-1, k, 3*n + 2*LOGK + 3); // 3k-2 registers < (3*k+1)*k*2^{3n}
|
||||
for(var i=0; i<2*k-1; i++){
|
||||
if (i < k) {
|
||||
right.a[i] <== 3 * x_sq.out[i] + a[i]; // registers in [0, 3*k*2^{2n} + 2^n = (3k+2^{-n})*2^{2n})
|
||||
} else {
|
||||
right.a[i] <== 3 * x_sq.out[i];
|
||||
}
|
||||
}
|
||||
for(var i=0; i<k; i++){
|
||||
right.b[i] <== in[0][0][i] - in[1][0][i];
|
||||
}
|
||||
|
||||
component left = BigMultShortLong(n, k, 2*n + 2 + LOGK); // 2k-1 registers in [0, 4k * 2^{2n})
|
||||
for(var i=0; i<k; i++){
|
||||
left.a[i] <== 2*in[0][1][i];
|
||||
left.b[i] <== in[0][1][i] + in[1][1][i];
|
||||
}
|
||||
|
||||
// prime reduce right - left
|
||||
component diff_red = PrimeReduce(n, k, 2*k-2, p, 4*n + LOGK3);
|
||||
for(var i=0; i<3*k-2; i++){
|
||||
if(i < 2*k-1)
|
||||
diff_red.in[i] <== right.out[i] - left.out[i];
|
||||
else
|
||||
diff_red.in[i] <== right.out[i];
|
||||
}
|
||||
// inputs of diff_red has registers < (3k+2^{-n})k*2^{3n} + 4k*2^{2n} < (3k^2 + 1)*2^{3n} assuming 5k <= 2^n
|
||||
// diff_red.out has registers < (3k+1)*(2k-1) * 2^{4n}
|
||||
component constraint = SignedCheckCarryModToZero(n, k, 4*n + LOGK3, p);
|
||||
for(var i=0; i<k; i++)
|
||||
constraint.in[i] <== diff_red.out[i];
|
||||
}
|
||||
|
||||
// requires x_1 != x_2
|
||||
// assume p is size k array, the prime that curve lives over
|
||||
//
|
||||
// Implements:
|
||||
// Given a = (x_1, y_1) and b = (x_2, y_2),
|
||||
// assume x_1 != x_2 and a != -b,
|
||||
// Find a + b = (x_3, y_3)
|
||||
// By solving:
|
||||
// x_1 + x_2 + x_3 - lambda^2 = 0 mod p
|
||||
// y_3 = lambda (x_1 - x_3) - y_1 mod p
|
||||
// where lambda = (y_2-y_1)/(x_2-x_1) is the slope of the line between (x_1, y_1) and (x_2, y_2)
|
||||
// these equations are equivalent to:
|
||||
// (x_1 + x_2 + x_3)*(x_2 - x_1)^2 = (y_2 - y_1)^2 mod p
|
||||
// (y_1 + y_3)*(x_2 - x_1) = (y_2 - y_1)*(x_1 - x_3) mod p
|
||||
template EllipticCurveAddUnequal(n, k, p) {
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
|
||||
signal output out[2][k];
|
||||
|
||||
var LOGK = log_ceil_ecdsa(k);
|
||||
var LOGK3 = log_ceil_ecdsa( (3*k*k)*(2*k-1) + 1 );
|
||||
assert(4*n + LOGK3 < 251);
|
||||
|
||||
// precompute lambda and x_3 and then y_3
|
||||
var dy[50] = long_sub_mod(n, k, b[1], a[1], p);
|
||||
var dx[50] = long_sub_mod(n, k, b[0], a[0], p);
|
||||
var dx_inv[50] = mod_inv(n, k, dx, p);
|
||||
var lambda[50] = prod_mod(n, k, dy, dx_inv, p);
|
||||
var lambda_sq[50] = prod_mod(n, k, lambda, lambda, p);
|
||||
// out[0] = x_3 = lamb^2 - a[0] - b[0] % p
|
||||
// out[1] = y_3 = lamb * (a[0] - x_3) - a[1] % p
|
||||
var x3[50] = long_sub_mod(n, k, long_sub_mod(n, k, lambda_sq, a[0], p), b[0], p);
|
||||
var y3[50] = long_sub_mod(n, k, prod_mod(n, k, lambda, long_sub_mod(n, k, a[0], x3, p), p), a[1], p);
|
||||
|
||||
for(var i = 0; i < k; i++){
|
||||
out[0][i] <-- x3[i];
|
||||
out[1][i] <-- y3[i];
|
||||
}
|
||||
|
||||
// constrain x_3 by CUBIC (x_1 + x_2 + x_3) * (x_2 - x_1)^2 - (y_2 - y_1)^2 = 0 mod p
|
||||
|
||||
component dx_sq = BigMultShortLong(n, k, 2*n+LOGK+2); // 2k-1 registers abs val < k*2^{2n}
|
||||
component dy_sq = BigMultShortLong(n, k, 2*n+LOGK+2); // 2k-1 registers < k*2^{2n}
|
||||
for(var i = 0; i < k; i++){
|
||||
dx_sq.a[i] <== b[0][i] - a[0][i];
|
||||
dx_sq.b[i] <== b[0][i] - a[0][i];
|
||||
|
||||
dy_sq.a[i] <== b[1][i] - a[1][i];
|
||||
dy_sq.b[i] <== b[1][i] - a[1][i];
|
||||
}
|
||||
|
||||
// x_1 + x_2 + x_3 has registers in [0, 3*2^n)
|
||||
component cubic = BigMultShortLongUnequal(n, k, 2*k-1, 3*n+4+2*LOGK); // 3k-2 registers < 3 * k^2 * 2^{3n} )
|
||||
for(var i=0; i<k; i++)
|
||||
cubic.a[i] <== a[0][i] + b[0][i] + out[0][i];
|
||||
for(var i=0; i<2*k-1; i++){
|
||||
cubic.b[i] <== dx_sq.out[i];
|
||||
}
|
||||
|
||||
component cubic_red = PrimeReduce(n, k, 2*k-2, p, 4*n + LOGK3);
|
||||
for(var i=0; i<2*k-1; i++)
|
||||
cubic_red.in[i] <== cubic.out[i] - dy_sq.out[i]; // registers abs val < 3k^2*2^{3n} + k*2^{2n} < (3k^2+1)2^{3n}
|
||||
for(var i=2*k-1; i<3*k-2; i++)
|
||||
cubic_red.in[i] <== cubic.out[i];
|
||||
// cubic_red has k registers < (3k^2+1)(2k-1) * 2^{4n}
|
||||
|
||||
component cubic_mod = SignedCheckCarryModToZero(n, k, 4*n + LOGK3, p);
|
||||
for(var i=0; i<k; i++)
|
||||
cubic_mod.in[i] <== cubic_red.out[i];
|
||||
// END OF CONSTRAINING x3
|
||||
|
||||
// constrain y_3 by (y_1 + y_3) * (x_2 - x_1) = (y_2 - y_1)*(x_1 - x_3) mod p
|
||||
component y_constraint = PointOnLine(n, k, p); // 2k-1 registers in [0, k*2^{2n+1})
|
||||
for(var i = 0; i < k; i++)for(var j=0; j<2; j++){
|
||||
y_constraint.in[0][j][i] <== a[j][i];
|
||||
y_constraint.in[1][j][i] <== b[j][i];
|
||||
y_constraint.in[2][j][i] <== out[j][i];
|
||||
}
|
||||
// END OF CONSTRAINING y3
|
||||
|
||||
// check if out[][] has registers in [0, 2^n)
|
||||
component range_check = RangeCheck2D(n, k);
|
||||
for(var j=0; j<2; j++)for(var i=0; i<k; i++)
|
||||
range_check.in[j][i] <== out[j][i];
|
||||
}
|
||||
|
||||
|
||||
// Elliptic curve is E : y**2 = x**3 + ax + b
|
||||
// assuming a < 2^n for now
|
||||
// Note that for BLS12-381, a = 0, b = 4
|
||||
|
||||
// Implements:
|
||||
// computing 2P on elliptic curve E for P = (x_1, y_1)
|
||||
// formula from https://crypto.stanford.edu/pbc/notes/elliptic/explicit.html
|
||||
// x_1 = in[0], y_1 = in[1]
|
||||
// assume y_1 != 0 (otherwise 2P = O)
|
||||
|
||||
// lamb = (3x_1^2 + a) / (2 y_1) % p
|
||||
// x_3 = out[0] = lambda^2 - 2 x_1 % p
|
||||
// y_3 = out[1] = lambda (x_1 - x_3) - y_1 % p
|
||||
|
||||
// We precompute (x_3, y_3) and then constrain by showing that:
|
||||
// * (x_3, y_3) is a valid point on the curve
|
||||
// * (x_3, y_3) is on the tangent line to E at (x_1, y_1)
|
||||
// * x_1 != x_3
|
||||
template EllipticCurveDouble(n, k, a, b, p) {
|
||||
signal input in[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
var long_3[k];
|
||||
long_3[0] = 3;
|
||||
for (var i = 1; i < k; i++) {
|
||||
long_3[i] = 0;
|
||||
}
|
||||
|
||||
// precompute lambda
|
||||
var lamb_num[50] = long_add_mod(n, k, a, prod_mod(n, k, long_3, prod_mod(n, k, in[0], in[0], p), p), p);
|
||||
var lamb_denom[50] = long_add_mod(n, k, in[1], in[1], p);
|
||||
var lamb[50] = prod_mod(n, k, lamb_num, mod_inv(n, k, lamb_denom, p), p);
|
||||
|
||||
// precompute x_3, y_3
|
||||
var x3[50] = long_sub_mod(n, k, prod_mod(n, k, lamb, lamb, p), long_add_mod(n, k, in[0], in[0], p), p);
|
||||
var y3[50] = long_sub_mod(n, k, prod_mod(n, k, lamb, long_sub_mod(n, k, in[0], x3, p), p), in[1], p);
|
||||
|
||||
for(var i=0; i<k; i++){
|
||||
out[0][i] <-- x3[i];
|
||||
out[1][i] <-- y3[i];
|
||||
}
|
||||
// check if out[][] has registers in [0, 2^n)
|
||||
component range_check = RangeCheck2D(n, k);
|
||||
for(var j=0; j<2; j++)for(var i=0; i<k; i++)
|
||||
range_check.in[j][i] <== out[j][i];
|
||||
|
||||
component point_on_tangent = PointOnTangent(n, k, a, p);
|
||||
for(var j=0; j<2; j++)for(var i=0; i<k; i++){
|
||||
point_on_tangent.in[0][j][i] <== in[j][i];
|
||||
point_on_tangent.in[1][j][i] <== out[j][i];
|
||||
}
|
||||
|
||||
component point_on_curve = PointOnCurve(n, k, a, b, p);
|
||||
for(var j=0; j<2; j++)for(var i=0; i<k; i++)
|
||||
point_on_curve.in[j][i] <== out[j][i];
|
||||
|
||||
component x3_eq_x1 = FpIsEqual(n, k, p);
|
||||
for(var i = 0; i < k; i++){
|
||||
x3_eq_x1.in[0][i] <== out[0][i];
|
||||
x3_eq_x1.in[1][i] <== in[0][i];
|
||||
}
|
||||
x3_eq_x1.out === 0;
|
||||
}
|
||||
|
||||
|
||||
// Fp curve y^2 = x^3 + a1*x + b1
|
||||
// Assume curve has no Fp points of order 2, i.e., x^3 + a1*x + b1 has no Fp roots
|
||||
// Fact: ^ this is the case for BLS12-381 and BN254
|
||||
// If isInfinity = 1, replace `out` with `a` so if `a` was on curve, so is output
|
||||
template EllipticCurveAdd(n, k, a1, b1, p){
|
||||
signal input a[2][k];
|
||||
signal input aIsInfinity;
|
||||
signal input b[2][k];
|
||||
signal input bIsInfinity;
|
||||
|
||||
signal output out[2][k];
|
||||
signal output isInfinity;
|
||||
|
||||
component x_equal = FpIsEqual(n, k, p);
|
||||
component y_equal = FpIsEqual(n, k, p);
|
||||
|
||||
for(var idx=0; idx<k; idx++){
|
||||
x_equal.in[0][idx] <== a[0][idx];
|
||||
x_equal.in[1][idx] <== b[0][idx];
|
||||
|
||||
y_equal.in[0][idx] <== a[1][idx];
|
||||
y_equal.in[1][idx] <== b[1][idx];
|
||||
}
|
||||
// if a.x = b.x then a = +-b
|
||||
// if a = b then a + b = 2*a so we need to do point doubling
|
||||
// if a = -a then out is infinity
|
||||
signal add_is_double;
|
||||
add_is_double <== x_equal.out * y_equal.out; // AND gate
|
||||
|
||||
// if a.x = b.x, need to replace b.x by a different number just so AddUnequal doesn't break
|
||||
// I will do this in a dumb way: replace b[0][0] by (b[0][0] == 0)
|
||||
component iz = IsZero();
|
||||
iz.in <== b[0][0];
|
||||
|
||||
component add = EllipticCurveAddUnequal(n, k, p);
|
||||
component doub = EllipticCurveDouble(n, k, a1, b1, p);
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++){
|
||||
add.a[i][idx] <== a[i][idx];
|
||||
if(i==0 && idx==0)
|
||||
add.b[i][idx] <== b[i][idx] + x_equal.out * (iz.out - b[i][idx]);
|
||||
else
|
||||
add.b[i][idx] <== b[i][idx];
|
||||
|
||||
doub.in[i][idx] <== a[i][idx];
|
||||
}
|
||||
|
||||
// out = O iff ( a = O AND b = O ) OR (a != 0 AND b != 0) AND ( x_equal AND NOT y_equal )
|
||||
signal ab0;
|
||||
ab0 <== aIsInfinity * bIsInfinity;
|
||||
signal ab_non0;
|
||||
ab_non0 <== (1- aIsInfinity) * (1 - bIsInfinity);
|
||||
signal anegb;
|
||||
anegb <== x_equal.out - x_equal.out * y_equal.out;
|
||||
signal inverse;
|
||||
inverse <== ab_non0 * anegb;
|
||||
isInfinity <== ab0 + inverse - ab0 * inverse; // OR gate
|
||||
|
||||
signal tmp[3][2][k];
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++){
|
||||
tmp[0][i][idx] <== add.out[i][idx] + add_is_double * (doub.out[i][idx] - add.out[i][idx]);
|
||||
// if a = O, then a + b = b
|
||||
tmp[1][i][idx] <== tmp[0][i][idx] + aIsInfinity * (b[i][idx] - tmp[0][i][idx]);
|
||||
// if b = O, then a + b = a
|
||||
tmp[2][i][idx] <== tmp[1][i][idx] + bIsInfinity * (a[i][idx] - tmp[1][i][idx]);
|
||||
out[i][idx] <== tmp[2][i][idx] + isInfinity * (a[i][idx] - tmp[2][i][idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Curve E : y^2 = x^3 + b
|
||||
// Inputs:
|
||||
// in is 2 x k array where P = (x, y) is a point in E(Fp)
|
||||
// inIsInfinity = 1 if P = O, else = 0
|
||||
// Output:
|
||||
// out = [x]P is 2 x k array representing a point in E(Fp)
|
||||
// isInfinity = 1 if [x]P = O, else = 0
|
||||
// Assume:
|
||||
// x in [0, 2^250)
|
||||
// `in` is point in E even if inIsInfinity = 1 just so nothing goes wrong
|
||||
// E(Fp) has no points of order 2
|
||||
template EllipticCurveScalarMultiply(n, k, b, x, p){
|
||||
signal input in[2][k];
|
||||
signal input inIsInfinity;
|
||||
|
||||
signal output out[2][k];
|
||||
signal output isInfinity;
|
||||
|
||||
var LOGK = log_ceil_ecdsa(k);
|
||||
|
||||
var Bits[250];
|
||||
var BitLength;
|
||||
var SigBits=0;
|
||||
for (var i = 0; i < 250; i++) {
|
||||
Bits[i] = (x >> i) & 1;
|
||||
if(Bits[i] == 1){
|
||||
SigBits++;
|
||||
BitLength = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
signal R[BitLength][2][k];
|
||||
signal R_isO[BitLength];
|
||||
component Pdouble[BitLength];
|
||||
component Padd[SigBits];
|
||||
var curid=0;
|
||||
|
||||
// if in = O then [x]O = O so there's no point to any of this
|
||||
signal P[2][k];
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++)
|
||||
P[j][idx] <== in[j][idx];
|
||||
|
||||
for(var i=BitLength - 1; i>=0; i--){
|
||||
if( i == BitLength - 1 ){
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++)
|
||||
R[i][j][idx] <== P[j][idx];
|
||||
R_isO[i] <== 0;
|
||||
}else{
|
||||
// E(Fp) has no points of order 2, so the only way 2*R[i+1] = O is if R[i+1] = O
|
||||
Pdouble[i] = EllipticCurveDouble(n, k, 0, b, p);
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++)
|
||||
Pdouble[i].in[j][idx] <== R[i+1][j][idx];
|
||||
|
||||
if(Bits[i] == 0){
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++)
|
||||
R[i][j][idx] <== Pdouble[i].out[j][idx];
|
||||
R_isO[i] <== R_isO[i+1];
|
||||
}else{
|
||||
// Padd[curid] = Pdouble[i] + P
|
||||
Padd[curid] = EllipticCurveAdd(n, k, 0, b, p);
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++){
|
||||
Padd[curid].a[j][idx] <== Pdouble[i].out[j][idx];
|
||||
Padd[curid].b[j][idx] <== P[j][idx];
|
||||
}
|
||||
Padd[curid].aIsInfinity <== R_isO[i+1];
|
||||
Padd[curid].bIsInfinity <== 0;
|
||||
|
||||
R_isO[i] <== Padd[curid].isInfinity;
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++)
|
||||
R[i][j][idx] <== Padd[curid].out[j][idx];
|
||||
|
||||
curid++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// output = O if input = O or R[0] = O
|
||||
isInfinity <== inIsInfinity + R_isO[0] - inIsInfinity * R_isO[0];
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++)
|
||||
out[i][idx] <== R[0][i][idx] + isInfinity * (in[i][idx] - R[0][i][idx]);
|
||||
}
|
||||
|
||||
// Curve E : y^2 = x^3 + b
|
||||
// Inputs:
|
||||
// in = P is 2 x k array where P = (x, y) is a point in E(Fp)
|
||||
// Output:
|
||||
// out = [x]P is 2 x k array representing a point in E(Fp)
|
||||
// Assume:
|
||||
// x in [0, 2^250)
|
||||
// E(Fp) has no points of order 2
|
||||
// P has order > x so never hit point at infinity, and can always use add unequal: constraint assertion fails if add unequal fails
|
||||
template EllipticCurveScalarMultiplyUnequal(n, k, b, x, p){
|
||||
signal input in[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
var LOGK = log_ceil_ecdsa(k);
|
||||
|
||||
var Bits[250];
|
||||
var BitLength;
|
||||
var SigBits=0;
|
||||
for (var i = 0; i < 250; i++) {
|
||||
Bits[i] = (x >> i) & 1;
|
||||
if(Bits[i] == 1){
|
||||
SigBits++;
|
||||
BitLength = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
signal R[BitLength][2][k];
|
||||
component Pdouble[BitLength];
|
||||
component Padd[SigBits];
|
||||
component add_exception[SigBits];
|
||||
var curid=0;
|
||||
|
||||
for(var i=BitLength - 1; i>=0; i--){
|
||||
if( i == BitLength - 1 ){
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++)
|
||||
R[i][j][idx] <== in[j][idx];
|
||||
}else{
|
||||
// E(Fp) has no points of order 2, so the only way 2*R[i+1] = O is if R[i+1] = O
|
||||
Pdouble[i] = EllipticCurveDouble(n, k, 0, b, p);
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++)
|
||||
Pdouble[i].in[j][idx] <== R[i+1][j][idx];
|
||||
|
||||
if(Bits[i] == 0){
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++)
|
||||
R[i][j][idx] <== Pdouble[i].out[j][idx];
|
||||
}else{
|
||||
// Constrain that Pdouble[i].x != P.x
|
||||
add_exception[curid] = FpIsEqual(n, k, p);
|
||||
for(var idx=0; idx<k; idx++){
|
||||
add_exception[curid].in[0][idx] <== Pdouble[i].out[0][idx];
|
||||
add_exception[curid].in[1][idx] <== in[0][idx];
|
||||
}
|
||||
add_exception[curid].out === 0;
|
||||
|
||||
// Padd[curid] = Pdouble[i] + P
|
||||
Padd[curid] = EllipticCurveAddUnequal(n, k, p);
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++){
|
||||
Padd[curid].a[j][idx] <== Pdouble[i].out[j][idx];
|
||||
Padd[curid].b[j][idx] <== in[j][idx];
|
||||
}
|
||||
for(var j=0; j<2; j++)for(var idx=0; idx<k; idx++)
|
||||
R[i][j][idx] <== Padd[curid].out[j][idx];
|
||||
|
||||
curid++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++)
|
||||
out[i][idx] <== R[0][i][idx];
|
||||
}
|
||||
|
||||
|
||||
239
circuits/circuits/utils/circom-ecdsa/ecdsa.circom
Normal file
239
circuits/circuits/utils/circom-ecdsa/ecdsa.circom
Normal file
@@ -0,0 +1,239 @@
|
||||
/// This file implements the ECDSA verification algorithm along with public key generation (xG)
|
||||
|
||||
pragma circom 2.1.5;
|
||||
include "../../../node_modules/circomlib/circuits/multiplexer.circom";
|
||||
|
||||
include "p256.circom";
|
||||
include "ecdsa_func.circom";
|
||||
include "p256_func.circom";
|
||||
|
||||
// keys are encoded as (x, y) pairs with each coordinate being
|
||||
// encoded with k registers of n bits each
|
||||
template ECDSAPrivToPub(n, k) {
|
||||
var stride = 8;
|
||||
signal input privkey[k];
|
||||
signal output pubkey[2][k];
|
||||
|
||||
component n2b[k];
|
||||
for (var i = 0; i < k; i++) {
|
||||
n2b[i] = Num2Bits(n);
|
||||
n2b[i].in <== privkey[i];
|
||||
}
|
||||
|
||||
var num_strides = div_ceil_ecdsa(n * k, stride);
|
||||
// power[i][j] contains: [j * (1 << stride * i) * G] for 1 <= j < (1 << stride)
|
||||
var powers[num_strides][2 ** stride][2][k];
|
||||
powers = get_g_pow_stride8_table(n, k);
|
||||
|
||||
// contains a dummy point G * 2 ** 255 to stand in when we are adding 0
|
||||
// this point is sometimes an input into AddUnequal, so it must be guaranteed
|
||||
// to never equal any possible partial sum that we might get
|
||||
var dummyHolder[2][100] = get_dummy_point(n, k);
|
||||
var dummy[2][k];
|
||||
for (var i = 0; i < k; i++) dummy[0][i] = dummyHolder[0][i];
|
||||
for (var i = 0; i < k; i++) dummy[1][i] = dummyHolder[1][i];
|
||||
|
||||
// selector[i] contains a value in [0, ..., 2**i - 1]
|
||||
component selectors[num_strides];
|
||||
for (var i = 0; i < num_strides; i++) {
|
||||
selectors[i] = Bits2Num(stride);
|
||||
for (var j = 0; j < stride; j++) {
|
||||
var bit_idx1 = (i * stride + j) \ n;
|
||||
var bit_idx2 = (i * stride + j) % n;
|
||||
if (bit_idx1 < k) {
|
||||
selectors[i].in[j] <== n2b[bit_idx1].out[bit_idx2];
|
||||
} else {
|
||||
selectors[i].in[j] <== 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// multiplexers[i][l].out will be the coordinates of:
|
||||
// selectors[i].out * (2 ** (i * stride)) * G if selectors[i].out is non-zero
|
||||
// (2 ** 255) * G if selectors[i].out is zero
|
||||
component multiplexers[num_strides][2];
|
||||
// select from k-register outputs using a 2 ** stride bit selector
|
||||
for (var i = 0; i < num_strides; i++) {
|
||||
for (var l = 0; l < 2; l++) {
|
||||
multiplexers[i][l] = Multiplexer(k, (1 << stride));
|
||||
multiplexers[i][l].sel <== selectors[i].out;
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
multiplexers[i][l].inp[0][idx] <== dummy[l][idx];
|
||||
for (var j = 1; j < (1 << stride); j++) {
|
||||
multiplexers[i][l].inp[j][idx] <== powers[i][j][l][idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component iszero[num_strides];
|
||||
for (var i = 0; i < num_strides; i++) {
|
||||
iszero[i] = IsZero();
|
||||
iszero[i].in <== selectors[i].out;
|
||||
}
|
||||
|
||||
// has_prev_nonzero[i] = 1 if at least one of the selections in privkey up to stride i is non-zero
|
||||
component has_prev_nonzero[num_strides];
|
||||
has_prev_nonzero[0] = OR();
|
||||
has_prev_nonzero[0].a <== 0;
|
||||
has_prev_nonzero[0].b <== 1 - iszero[0].out;
|
||||
for (var i = 1; i < num_strides; i++) {
|
||||
has_prev_nonzero[i] = OR();
|
||||
has_prev_nonzero[i].a <== has_prev_nonzero[i - 1].out;
|
||||
has_prev_nonzero[i].b <== 1 - iszero[i].out;
|
||||
}
|
||||
|
||||
signal partial[num_strides][2][k];
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
for (var l = 0; l < 2; l++) {
|
||||
partial[0][l][idx] <== multiplexers[0][l].out[idx];
|
||||
}
|
||||
}
|
||||
|
||||
component adders[num_strides - 1];
|
||||
signal intermed1[num_strides - 1][2][k];
|
||||
signal intermed2[num_strides - 1][2][k];
|
||||
for (var i = 1; i < num_strides; i++) {
|
||||
adders[i - 1] = P256AddUnequal(n, k);
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
for (var l = 0; l < 2; l++) {
|
||||
adders[i - 1].a[l][idx] <== partial[i - 1][l][idx];
|
||||
adders[i - 1].b[l][idx] <== multiplexers[i][l].out[idx];
|
||||
}
|
||||
}
|
||||
|
||||
// partial[i] = has_prev_nonzero[i - 1] * ((1 - iszero[i]) * adders[i - 1].out + iszero[i] * partial[i - 1][0][idx])
|
||||
// + (1 - has_prev_nonzero[i - 1]) * (1 - iszero[i]) * multiplexers[i]
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
for (var l = 0; l < 2; l++) {
|
||||
intermed1[i - 1][l][idx] <== iszero[i].out * (partial[i - 1][l][idx] - adders[i - 1].out[l][idx]) + adders[i - 1].out[l][idx];
|
||||
intermed2[i - 1][l][idx] <== multiplexers[i][l].out[idx] - iszero[i].out * multiplexers[i][l].out[idx];
|
||||
partial[i][l][idx] <== has_prev_nonzero[i - 1].out * (intermed1[i - 1][l][idx] - intermed2[i - 1][l][idx]) + intermed2[i - 1][l][idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < k; i++) {
|
||||
for (var l = 0; l < 2; l++) {
|
||||
pubkey[l][i] <== partial[num_strides - 1][l][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// r, s, msghash, and pubkey have coordinates
|
||||
// encoded with k registers of n bits each
|
||||
// signature is (r, s)
|
||||
// Does not check that pubkey is valid
|
||||
template ECDSAVerifyNoPubkeyCheck(n, k) {
|
||||
assert(k >= 2);
|
||||
assert(k <= 100);
|
||||
|
||||
signal input r[k];
|
||||
signal input s[k];
|
||||
signal input msghash[k];
|
||||
signal input pubkey[2][k];
|
||||
|
||||
signal output result;
|
||||
|
||||
var p[100] = get_p256_prime(n, k);
|
||||
var order[100] = get_p256_order(n, k);
|
||||
|
||||
// compute multiplicative inverse of s mod n
|
||||
var sinv_comp[100] = mod_inv(n, k, s, order);
|
||||
signal sinv[k];
|
||||
component sinv_range_checks[k];
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
sinv[idx] <-- sinv_comp[idx];
|
||||
sinv_range_checks[idx] = Num2Bits(n);
|
||||
sinv_range_checks[idx].in <== sinv[idx];
|
||||
}
|
||||
component sinv_check = BigMultModP(n, k);
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
sinv_check.a[idx] <== sinv[idx];
|
||||
sinv_check.b[idx] <== s[idx];
|
||||
sinv_check.p[idx] <== order[idx];
|
||||
}
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
if (idx > 0) {
|
||||
sinv_check.out[idx] === 0;
|
||||
}
|
||||
if (idx == 0) {
|
||||
sinv_check.out[idx] === 1;
|
||||
}
|
||||
}
|
||||
|
||||
// compute (h * sinv) mod n
|
||||
component g_coeff = BigMultModP(n, k);
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
g_coeff.a[idx] <== sinv[idx];
|
||||
g_coeff.b[idx] <== msghash[idx];
|
||||
g_coeff.p[idx] <== order[idx];
|
||||
}
|
||||
|
||||
// compute (h * sinv) * G
|
||||
component g_mult = ECDSAPrivToPub(n, k);
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
g_mult.privkey[idx] <== g_coeff.out[idx];
|
||||
}
|
||||
|
||||
// compute (r * sinv) mod n
|
||||
component pubkey_coeff = BigMultModP(n, k);
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
pubkey_coeff.a[idx] <== sinv[idx];
|
||||
pubkey_coeff.b[idx] <== r[idx];
|
||||
pubkey_coeff.p[idx] <== order[idx];
|
||||
}
|
||||
|
||||
// compute (r * sinv) * pubkey
|
||||
component pubkey_mult = P256ScalarMult(n, k);
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
pubkey_mult.scalar[idx] <== pubkey_coeff.out[idx];
|
||||
pubkey_mult.point[0][idx] <== pubkey[0][idx];
|
||||
pubkey_mult.point[1][idx] <== pubkey[1][idx];
|
||||
}
|
||||
|
||||
// compute (h * sinv) * G + (r * sinv) * pubkey
|
||||
component sum_res = P256AddUnequal(n, k);
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
sum_res.a[0][idx] <== g_mult.pubkey[0][idx];
|
||||
sum_res.a[1][idx] <== g_mult.pubkey[1][idx];
|
||||
sum_res.b[0][idx] <== pubkey_mult.out[0][idx];
|
||||
sum_res.b[1][idx] <== pubkey_mult.out[1][idx];
|
||||
}
|
||||
|
||||
// compare sum_res.x with r
|
||||
component compare[k];
|
||||
signal num_equal[k - 1];
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
compare[idx] = IsEqual();
|
||||
compare[idx].in[0] <== r[idx];
|
||||
compare[idx].in[1] <== sum_res.out[0][idx];
|
||||
|
||||
if (idx > 0) {
|
||||
if (idx == 1) {
|
||||
num_equal[idx - 1] <== compare[0].out + compare[1].out;
|
||||
} else {
|
||||
num_equal[idx - 1] <== num_equal[idx - 2] + compare[idx].out;
|
||||
}
|
||||
}
|
||||
}
|
||||
component res_comp = IsEqual();
|
||||
res_comp.in[0] <== k;
|
||||
res_comp.in[1] <== num_equal[k - 2];
|
||||
result <== res_comp.out;
|
||||
}
|
||||
|
||||
// TODO: implement ECDSA extended verify
|
||||
// r, s, and msghash have coordinates
|
||||
// encoded with k registers of n bits each
|
||||
// v is a single bit
|
||||
// extended signature is (r, s, v)
|
||||
template ECDSAExtendedVerify(n, k) {
|
||||
signal input r[k];
|
||||
signal input s[k];
|
||||
signal input v;
|
||||
signal input msghash[k];
|
||||
|
||||
signal output result;
|
||||
}
|
||||
|
||||
109833
circuits/circuits/utils/circom-ecdsa/ecdsa_func.circom
Normal file
109833
circuits/circuits/utils/circom-ecdsa/ecdsa_func.circom
Normal file
File diff suppressed because it is too large
Load Diff
292
circuits/circuits/utils/circom-ecdsa/field_elements_func.circom
Normal file
292
circuits/circuits/utils/circom-ecdsa/field_elements_func.circom
Normal file
@@ -0,0 +1,292 @@
|
||||
pragma circom 2.0.3;
|
||||
|
||||
include "bigInt_func.circom";
|
||||
|
||||
function get_fp_sgn0(a){
|
||||
return a[0] % 2;
|
||||
}
|
||||
|
||||
// n bits per register
|
||||
// num has k registers
|
||||
// p has k registers
|
||||
// k * n <= 500
|
||||
// p is a prime
|
||||
// if num == 0 mod p, returns 0
|
||||
// else computes inv = num^{-1} mod p using extended euclidean algorithm
|
||||
// https://brilliant.org/wiki/extended-euclidean-algorithm/
|
||||
function find_Fp_inverse(n, k, num, p) {
|
||||
var amodp[2][50] = long_div2(n, k, 0, num, p);
|
||||
var a[50];
|
||||
var b[50];
|
||||
var x[50];
|
||||
var y[50];
|
||||
var u[50];
|
||||
var v[50];
|
||||
|
||||
var ret[50];
|
||||
|
||||
for(var i=0; i<k; i++){
|
||||
a[i] = amodp[1][i];
|
||||
b[i] = p[i];
|
||||
x[i] = 0;
|
||||
y[i] = 0;
|
||||
u[i] = 0;
|
||||
v[i] = 0;
|
||||
}
|
||||
y[0] = 1;
|
||||
u[0] = 1;
|
||||
// euclidean algorithm takes log_phi( min(a, p) ) iterations, where phi is golden ratio
|
||||
// should be less than 1000 for our cases...
|
||||
for(var l=0; l<1000; l++){
|
||||
var ka = 0;
|
||||
for (var i = 0; i < k; i++) {
|
||||
if (a[i] != 0) {
|
||||
ka = i + 1;
|
||||
}
|
||||
}
|
||||
if (ka == 0) {
|
||||
for (var i = 0; i < k; i++) {
|
||||
ret[i] = x[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
var r[2][50] = long_div2(n, ka, k - ka, b, a);
|
||||
var q[50];
|
||||
for(var i = 0; i < k - ka + 1; i++)
|
||||
q[i] = r[0][i];
|
||||
for(var i = k - ka + 1; i < k; i++)
|
||||
q[i] = 0;
|
||||
|
||||
var newu[50] = long_sub_mod(n, k, x, prod_mod(n, k, u, q, p), p);
|
||||
var newv[50] = long_sub_mod(n, k, y, prod_mod(n, k, v, q, p), p);
|
||||
|
||||
for(var i = 0; i < k; i++){
|
||||
b[i] = a[i];
|
||||
if( i < ka )
|
||||
a[i] = r[1][i];
|
||||
else
|
||||
a[i] = 0;
|
||||
x[i] = u[i];
|
||||
y[i] = v[i];
|
||||
u[i] = newu[i];
|
||||
v[i] = newv[i];
|
||||
}
|
||||
}
|
||||
// should never reach here (loop should always return before now)
|
||||
assert(0 == 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// a[k] registers can overflow
|
||||
// assume actual value of a < 2^{n*(k+m)}
|
||||
// p[k] registers in [0, 2^n)
|
||||
// out[2][k] solving
|
||||
// a = p * out[0] + out[1] with out[1] in [0,p)
|
||||
// out[0] has m registers in range [-2^n, 2^n)
|
||||
// out[1] has k registers in range [0, 2^n)
|
||||
function get_signed_Fp_carry_witness(n, k, m, a, p){
|
||||
var out[2][50];
|
||||
var a_short[51] = signed_long_to_short(n, k, a);
|
||||
|
||||
/* // commenting out to improve speed
|
||||
// let me make sure everything is in <= k+m registers
|
||||
for(var j=k+m; j<50; j++)
|
||||
assert( a_short[j] == 0 );
|
||||
*/
|
||||
|
||||
if(a_short[50] == 0){
|
||||
out = long_div2(n, k, m, a_short, p);
|
||||
}else{
|
||||
var a_pos[50];
|
||||
for(var i=0; i<k+m; i++)
|
||||
a_pos[i] = -a_short[i];
|
||||
|
||||
var X[2][50] = long_div2(n, k, m, a_pos, p);
|
||||
// what if X[1] is 0?
|
||||
var Y_is_zero = 1;
|
||||
for(var i=0; i<k; i++){
|
||||
if(X[1][i] != 0)
|
||||
Y_is_zero = 0;
|
||||
}
|
||||
if( Y_is_zero == 1 ){
|
||||
out[1] = X[1];
|
||||
}else{
|
||||
out[1] = long_sub_ecdsa(n, k, p, X[1]);
|
||||
|
||||
X[0][0]++;
|
||||
if(X[0][0] >= (1<<n)){
|
||||
for(var i=0; i<m-1; i++){
|
||||
var carry = X[0][i] \ (1<<n);
|
||||
X[0][i+1] += carry;
|
||||
X[0][i] -= carry * (1<<n);
|
||||
}
|
||||
assert( X[0][m-1] < (1<<n) );
|
||||
}
|
||||
}
|
||||
for(var i=0; i<m; i++)
|
||||
out[0][i] = -X[0][i];
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
// Implements:
|
||||
// calls get_signed_Fp_carry_witness twice
|
||||
// a[2][k] registers can overflow
|
||||
// assume actual value of each a[i] < (2^n)^{k+m}
|
||||
// p[k] registers in [0, 2^n)
|
||||
// out[2][2][k] solving
|
||||
// a[0] = p * out[0][0] + out[0][1] with out[0][1] in [0,p)
|
||||
// a[1] = p * out[1][0] + out[1][1] with out[1][1] in [0,p)
|
||||
// out[i][0] has m registers in range [-2^n, 2^n)
|
||||
// out[i][1] has k registers in range [0, 2^n)
|
||||
function get_signed_Fp2_carry_witness(n, k, m, a, p){
|
||||
var out[2][2][50];
|
||||
|
||||
for(var i=0; i<2; i++)
|
||||
out[i] = get_signed_Fp_carry_witness(n, k, m, a[i], p);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
function get_fp2_sgn0(k, a){
|
||||
var z = long_is_zero(k, a[0]);
|
||||
var sgn0 = a[0][0] % 2;
|
||||
var sgn1 = a[1][0] % 2;
|
||||
return sgn0 | (z & sgn1);
|
||||
}
|
||||
|
||||
// helper function to precompute the product of two elements a, b in Fp2
|
||||
// a[2][k], b[2][k] all registers in [0, 2^n)
|
||||
// (a0 + a1 u)*(b0 + b1 u) = (a0*b0 - a1*b1) + (a0*b1 + a1*b0)u
|
||||
// this is a direct computation - totally distinct from the combo of Fp2multiplyNoCarry and get_Fp2_carry_witness
|
||||
function find_Fp2_product(n, k, a, b, p){
|
||||
var out[2][50];
|
||||
var ab[2][2][50];
|
||||
for(var i=0; i<2; i++)for(var j=0; j<2; j++){
|
||||
ab[i][j] = prod_mod(n,k,a[i],b[j],p);
|
||||
}
|
||||
out[0] = long_sub_mod(n,k,ab[0][0],ab[1][1],p);
|
||||
out[1] = long_add_mod(n,k,ab[0][1],ab[1][0],p);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// helper function to precompute the sum of two elements a, b in Fp2
|
||||
// a[2][k], b[2][k] all registers in [0, 2^n)
|
||||
// this is a direct computation
|
||||
function find_Fp2_sum(n, k, a, b, p){
|
||||
var out[2][50];
|
||||
out[0] = long_add_mod(n,k,a[0],b[0],p);
|
||||
out[1] = long_add_mod(n,k,a[1],b[1],p);
|
||||
return out;
|
||||
}
|
||||
|
||||
// helper function to precompute the difference of two elements a, b in Fp2
|
||||
// a[2][k], b[2][k] all registers in [0, 2^n)
|
||||
// this is a direct computation
|
||||
function find_Fp2_diff(n, k, a, b, p){
|
||||
var out[2][50];
|
||||
out[0] = long_sub_mod(n,k,a[0],b[0],p);
|
||||
out[1] = long_sub_mod(n,k,a[1],b[1],p);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
// n bits per register
|
||||
// a has 2 x k registers, elt of Fp2
|
||||
// p has k registers
|
||||
// e has 2k registers
|
||||
// k * n <= 400
|
||||
// p is a prime
|
||||
// computes a^e in Fp2
|
||||
function find_Fp2_exp(n, k, a, p, e){
|
||||
var eBits[800]; // length is (2k-1) * n
|
||||
var bitLength;
|
||||
for (var i = 0; i < 2*k; i++) {
|
||||
for (var j = 0; j < n; j++) {
|
||||
eBits[j + n * i] = (e[i] >> j) & 1;
|
||||
if(eBits[j + n * i] == 1)
|
||||
bitLength = j + n * i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
var out[2][50]; // length is k
|
||||
for(var i = 0; i < 50; i++) {
|
||||
out[0][i] = 0;
|
||||
out[1][i] = 0;
|
||||
}
|
||||
out[0][0] = 1;
|
||||
|
||||
// repeated squaring
|
||||
for(var i = bitLength-1; i >= 0; i--) {
|
||||
// multiply by a if bit is 0
|
||||
if (eBits[i] == 1)
|
||||
out = find_Fp2_product(n, k, out, a, p);
|
||||
// square, unless we're at the end
|
||||
if (i > 0)
|
||||
out = find_Fp2_product(n, k, out, out, p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function is_equal_Fp2(n, k, a, b){
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++){
|
||||
if(a[i][idx] != b[i][idx])
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// a[2][k] elt in Fp2
|
||||
// output multiplies by XI0 +u
|
||||
// multiplies register bounds by (XI0 + 1)
|
||||
function signed_Fp2_mult_w6(k, a, XI0){
|
||||
var out[2][50];
|
||||
for(var i=0; i<k; i++){
|
||||
out[0][i] = a[0][i]*XI0 - a[1][i];
|
||||
out[1][i] = a[0][i] + a[1][i]*XI0;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
// a is 2 x k, represents element of Fp^2
|
||||
// out is the inverse of a in Fp^2 (2 x k array of shorts)
|
||||
|
||||
// Src: https://github.com/paulmillr/noble-bls12-381/blob/23823d664b1767fb20c9c19c5800c66993b576a5/math.ts#L444
|
||||
// We wish to find the multiplicative inverse of a nonzero
|
||||
// element a + bu in Fp2. We leverage an identity
|
||||
//
|
||||
// (a + bu)(a - bu) = a² + b²
|
||||
//
|
||||
// which holds because u² = -1. This can be rewritten as
|
||||
//
|
||||
// (a + bu)(a - bu)/(a² + b²) = 1
|
||||
//
|
||||
// because a² + b² = 0 has no nonzero solutions for (a, b).
|
||||
// This gives that (a - bu)/(a² + b²) is the inverse
|
||||
// of (a + bu).
|
||||
function find_Fp2_inverse(n, k, a, p) {
|
||||
var sq0[50] = prod(n, k, a[0], a[0]);
|
||||
var sq1[50] = prod(n, k, a[1], a[1]);
|
||||
var sq_sum[50] = long_add(n, 2*k, sq0, sq1);
|
||||
var sq_sum_div[2][50] = long_div2(n, k, k+1, sq_sum, p);
|
||||
// lambda = 1/(sq_sum)%p
|
||||
var lambda[50] = mod_inv(n, k, sq_sum_div[1], p);
|
||||
var out0[50] = prod(n, k, lambda, a[0]);
|
||||
var out0_div[2][50] = long_div_ecdsa(n, k, out0, p);
|
||||
var out[2][50];
|
||||
out[0] = out0_div[1];
|
||||
|
||||
var out1_pre[50] = long_sub_ecdsa(n, k, p, a[1]);
|
||||
var out1[50] = prod(n, k, lambda, out1_pre);
|
||||
var out1_div[2][50] = long_div_ecdsa(n, k, out1, p);
|
||||
out[1] = out1_div[1];
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
293
circuits/circuits/utils/circom-ecdsa/fp.circom
Normal file
293
circuits/circuits/utils/circom-ecdsa/fp.circom
Normal file
@@ -0,0 +1,293 @@
|
||||
pragma circom 2.0.3;
|
||||
|
||||
include "bigInt.circom";
|
||||
include "bigInt_func.circom";
|
||||
|
||||
// a[i], b[i] in 0... 2**n-1
|
||||
// represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k)
|
||||
// calculates (a+b)%p, where 0<= a,b < p
|
||||
template FpAdd(n, k, p){
|
||||
assert(n <= 252);
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal output out[k];
|
||||
|
||||
component add = BigAdd(n,k);
|
||||
for (var i = 0; i < k; i++) {
|
||||
add.a[i] <== a[i];
|
||||
add.b[i] <== b[i];
|
||||
}
|
||||
component lt = BigLessThanEcdsa(n, k+1);
|
||||
for (var i = 0; i < k; i++) {
|
||||
lt.a[i] <== add.out[i];
|
||||
lt.b[i] <== p[i];
|
||||
}
|
||||
lt.a[k] <== add.out[k];
|
||||
lt.b[k] <== 0;
|
||||
|
||||
component sub = BigSub(n,k+1);
|
||||
for (var i = 0; i < k; i++) {
|
||||
sub.a[i] <== add.out[i];
|
||||
sub.b[i] <== p[i] - lt.out * p[i];
|
||||
}
|
||||
sub.a[k] <== add.out[k];
|
||||
sub.b[k] <== 0;
|
||||
|
||||
sub.out[k] === 0;
|
||||
for (var i = 0; i < k; i++) {
|
||||
out[i] <== sub.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// calculates (a - b) % p, where a, b < p
|
||||
// note: does not assume a >= b
|
||||
template FpSubtract(n, k, p){
|
||||
assert(n <= 252);
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal output out[k];
|
||||
component sub = BigSub(n, k);
|
||||
for (var i = 0; i < k; i++){
|
||||
sub.a[i] <== a[i];
|
||||
sub.b[i] <== b[i];
|
||||
}
|
||||
signal flag;
|
||||
flag <== sub.underflow;
|
||||
component add = BigAdd(n, k);
|
||||
for (var i = 0; i < k; i++){
|
||||
add.a[i] <== sub.out[i];
|
||||
add.b[i] <== p[i];
|
||||
}
|
||||
signal tmp[k];
|
||||
for (var i = 0; i < k; i++){
|
||||
tmp[i] <== (1 - flag) * sub.out[i];
|
||||
out[i] <== tmp[i] + flag * add.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Input: in <= p
|
||||
// Output: -in (mod p) = p - in if in != 0, else 0
|
||||
// Constrains in <= p
|
||||
template FpNegate(n, k, p){
|
||||
signal input in[k];
|
||||
signal output out[k];
|
||||
|
||||
component neg = BigSub(n, k);
|
||||
component is_zero = BigIsZero(k);
|
||||
for(var idx=0; idx<k; idx++){
|
||||
neg.a[idx] <== p[idx];
|
||||
neg.b[idx] <== in[idx];
|
||||
|
||||
is_zero.in[idx] <== in[idx];
|
||||
}
|
||||
neg.underflow === 0; // constrain in <= p
|
||||
for(var idx=0; idx<k; idx++)
|
||||
out[idx] <== (1-is_zero.out)*neg.out[idx];
|
||||
}
|
||||
|
||||
template FpMultiply(n, k, p) {
|
||||
assert(n <= 252);
|
||||
signal input a[k];
|
||||
signal input b[k];
|
||||
signal output out[k];
|
||||
|
||||
var LOGK = log_ceil_ecdsa(k);
|
||||
|
||||
component nocarry = BigMultShortLong(n, k, 2*n + LOGK);
|
||||
for (var i = 0; i < k; i++) {
|
||||
nocarry.a[i] <== a[i];
|
||||
nocarry.b[i] <== b[i];
|
||||
}
|
||||
component red = PrimeReduce(n, k, k-1, p, 3*n + 2*LOGK);
|
||||
for(var i=0; i<2*k-1; i++)
|
||||
red.in[i] <== nocarry.out[i];
|
||||
|
||||
component big_mod = SignedFpCarryModP(n, k, 3*n + 2*LOGK, p);
|
||||
for (var i = 0; i < k; i++)
|
||||
big_mod.in[i] <== red.out[i];
|
||||
|
||||
for (var i = 0; i < k; i++)
|
||||
out[i] <== big_mod.out[i];
|
||||
}
|
||||
|
||||
// constrain in = p * X + Y
|
||||
// in[i] in (-2^overflow, 2^overflow)
|
||||
// assume registers of X have abs value < 2^{overflow - n - log(min(k,m)) - 1}
|
||||
// assume overflow - 1 >= n
|
||||
template CheckCarryModP(n, k, m, overflow, p){
|
||||
signal input in[k];
|
||||
signal input X[m];
|
||||
signal input Y[k];
|
||||
|
||||
assert( overflow < 251 );
|
||||
assert( n <= overflow - 1);
|
||||
component pX;
|
||||
component carry_check;
|
||||
|
||||
pX = BigMultShortLongUnequal(n, k, m, overflow); // p has k registers, X has m registers, so output really has k+m-1 registers
|
||||
// overflow register in (-2^{overflow-1} , 2^{overflow-1})
|
||||
for(var i=0; i<k; i++)
|
||||
pX.a[i] <== p[i];
|
||||
for(var i=0; i<m; i++)
|
||||
pX.b[i] <== X[i];
|
||||
|
||||
// in - p*X - Y has registers in (-2^{overflow+1}, 2^{overflow+1})
|
||||
carry_check = CheckCarryToZeroEcdsa(n, overflow+1, k+m-1 );
|
||||
for(var i=0; i<k; i++){
|
||||
carry_check.in[i] <== in[i] - pX.out[i] - Y[i];
|
||||
}
|
||||
for(var i=k; i<k+m-1; i++)
|
||||
carry_check.in[i] <== -pX.out[i];
|
||||
}
|
||||
|
||||
// solve for in = p * X + out
|
||||
// assume in has registers in (-2^overflow, 2^overflow)
|
||||
// X has registers lying in [-2^n, 2^n)
|
||||
// X has at most Ceil( overflow / n ) registers
|
||||
|
||||
// out has registers in [0, 2^n) but don't constrain out < p
|
||||
template SignedFpCarryModP(n, k, overflow, p){
|
||||
signal input in[k];
|
||||
var m = (overflow + n - 1) \ n;
|
||||
signal output X[m];
|
||||
signal output out[k];
|
||||
|
||||
assert( overflow < 251 );
|
||||
|
||||
var Xvar[2][50] = get_signed_Fp_carry_witness(n, k, m, in, p);
|
||||
component X_range_checks[m];
|
||||
component range_checks[k];
|
||||
// component lt = BigLessThanEcdsa(n, k);
|
||||
|
||||
for(var i=0; i<k; i++){
|
||||
out[i] <-- Xvar[1][i];
|
||||
range_checks[i] = Num2Bits(n);
|
||||
range_checks[i].in <== out[i];
|
||||
//lt.a[i] <== out[i];
|
||||
//lt.b[i] <== p[i];
|
||||
}
|
||||
//lt.out === 1;
|
||||
|
||||
for(var i=0; i<m; i++){
|
||||
X[i] <-- Xvar[0][i];
|
||||
X_range_checks[i] = Num2Bits(n+1);
|
||||
X_range_checks[i].in <== X[i] + (1<<n); // X[i] should be between [-2^n, 2^n)
|
||||
}
|
||||
|
||||
component mod_check = CheckCarryModP(n, k, m, overflow, p);
|
||||
for(var i=0; i<k; i++){
|
||||
mod_check.in[i] <== in[i];
|
||||
mod_check.Y[i] <== out[i];
|
||||
}
|
||||
for(var i=0; i<m; i++){
|
||||
mod_check.X[i] <== X[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Constrain in = 0 mod p by solving for in = p * X
|
||||
// assume in has registers in (-2^overflow, 2^overflow)
|
||||
// X has registers lying in [-2^n, 2^n)
|
||||
// X has at most Ceil( overflow / n ) registers
|
||||
|
||||
// save range check on Y compared to SignedFpCarryModP
|
||||
template SignedCheckCarryModToZero(n, k, overflow, p){
|
||||
signal input in[k];
|
||||
var m = (overflow + n - 1) \ n;
|
||||
signal output X[m];
|
||||
|
||||
assert( overflow < 251 );
|
||||
|
||||
var Xvar[2][50] = get_signed_Fp_carry_witness(n, k, m, in, p);
|
||||
component X_range_checks[m];
|
||||
|
||||
for(var i=0; i<m; i++){
|
||||
X[i] <-- Xvar[0][i];
|
||||
X_range_checks[i] = Num2Bits(n+1);
|
||||
X_range_checks[i].in <== X[i] + (1<<n); // X[i] should be between [-2^n, 2^n)
|
||||
}
|
||||
|
||||
component mod_check = CheckCarryModP(n, k, m, overflow, p);
|
||||
for(var i=0; i<k; i++){
|
||||
mod_check.in[i] <== in[i];
|
||||
mod_check.Y[i] <== 0;
|
||||
}
|
||||
for(var i=0; i<m; i++){
|
||||
mod_check.X[i] <== X[i];
|
||||
}
|
||||
}
|
||||
|
||||
// in has k registers, elt of Fp
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4.1
|
||||
// return in % 2
|
||||
// This requires `in` to be the unique element < p.
|
||||
// NOTE: different from Wahby-Boneh paper https://eprint.iacr.org/2019/403.pdf and python reference code: https://github.com/algorand/bls_sigs_ref/blob/master/python-impl/opt_swu_g2.py
|
||||
template FpSgn0(n, k, p){
|
||||
signal input in[k];
|
||||
signal output out;
|
||||
|
||||
// constrain in < p
|
||||
component lt = BigLessThanEcdsa(n, k);
|
||||
for(var i=0; i<k; i++){
|
||||
lt.a[i] <== in[i];
|
||||
lt.b[i] <== p[i];
|
||||
}
|
||||
lt.out === 1;
|
||||
|
||||
// note we only need in[0] !
|
||||
var r = in[0] % 2;
|
||||
var q = (in[0] - r) / 2;
|
||||
out <-- r;
|
||||
signal div;
|
||||
div <-- q;
|
||||
out * (1 - out) === 0;
|
||||
in[0] === 2 * div + out;
|
||||
}
|
||||
|
||||
template FpIsZero(n, k, p){
|
||||
signal input in[k];
|
||||
signal output out;
|
||||
|
||||
// check that in < p
|
||||
component lt = BigLessThanEcdsa(n, k);
|
||||
component isZero = BigIsZero(k);
|
||||
for(var i = 0; i < k; i++) {
|
||||
lt.a[i] <== in[i];
|
||||
lt.b[i] <== p[i];
|
||||
|
||||
isZero.in[i] <== in[i];
|
||||
}
|
||||
lt.out === 1;
|
||||
out <== isZero.out;
|
||||
}
|
||||
|
||||
template FpIsEqual(n, k, p){
|
||||
signal input in[2][k];
|
||||
signal output out;
|
||||
|
||||
// check in[i] < p
|
||||
component lt[2];
|
||||
for(var i = 0; i < 2; i++){
|
||||
lt[i] = BigLessThanEcdsa(n, k);
|
||||
for(var idx=0; idx<k; idx++){
|
||||
lt[i].a[idx] <== in[i][idx];
|
||||
lt[i].b[idx] <== p[idx];
|
||||
}
|
||||
lt[i].out === 1;
|
||||
}
|
||||
|
||||
component isEqual[k+1];
|
||||
var sum = 0;
|
||||
for(var i = 0; i < k; i++){
|
||||
isEqual[i] = IsEqual();
|
||||
isEqual[i].in[0] <== in[0][i];
|
||||
isEqual[i].in[1] <== in[1][i];
|
||||
sum = sum + isEqual[i].out;
|
||||
}
|
||||
|
||||
isEqual[k] = IsEqual();
|
||||
isEqual[k].in[0] <== sum;
|
||||
isEqual[k].in[1] <== k;
|
||||
out <== isEqual[k].out;
|
||||
}
|
||||
540
circuits/circuits/utils/circom-ecdsa/fp2.circom
Normal file
540
circuits/circuits/utils/circom-ecdsa/fp2.circom
Normal file
@@ -0,0 +1,540 @@
|
||||
pragma circom 2.0.3;
|
||||
|
||||
include "bigInt.circom";
|
||||
include "bigInt_func.circom";
|
||||
include "field_elements_func.circom";
|
||||
include "fp.circom";
|
||||
|
||||
// add two elements in Fp2
|
||||
template Fp2Add(n, k, p) {
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
component adders[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
adders[i] = FpAdd(n, k, p);
|
||||
for (var j = 0; j < k; j++) {
|
||||
adders[i].a[j] <== a[i][j];
|
||||
adders[i].b[j] <== b[i][j];
|
||||
}
|
||||
for (var j = 0; j < k; j ++) {
|
||||
out[i][j] <== adders[i].out[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
p has k registers
|
||||
Inputs:
|
||||
- a[2][ka] allow signed overflow
|
||||
- b[2][kb]
|
||||
Outputs:
|
||||
- out[2][ka+kb-1] such that
|
||||
(a0 + a1 u)*(b0 + b1 u) = out[0] + out[1] u
|
||||
Notes:
|
||||
- if each a[i][j], b[i][j] has abs value < B then out[i][j] has abs val < 2*k*B^2
|
||||
- out[i] has ka+kb-1 registers since that's output of BigMultShortLong
|
||||
m_out is the expected max number of bits in the output registers
|
||||
*/
|
||||
template SignedFp2MultiplyNoCarryUnequal(n, ka, kb, m_out){
|
||||
signal input a[2][ka];
|
||||
signal input b[2][kb];
|
||||
signal output out[2][ka+kb-1];
|
||||
|
||||
component ab[2][2];
|
||||
for(var i=0; i<2; i++)for(var j=0; j<2; j++){
|
||||
ab[i][j] = BigMultShortLongUnequal(n, ka, kb, m_out); // output has ka+kb-1 registers
|
||||
for(var l=0; l<ka; l++)
|
||||
ab[i][j].a[l] <== a[i][l];
|
||||
for(var l=0; l<kb; l++)
|
||||
ab[i][j].b[l] <== b[j][l];
|
||||
}
|
||||
|
||||
for(var j=0; j<ka+kb-1; j++){
|
||||
out[0][j] <== ab[0][0].out[j] - ab[1][1].out[j];
|
||||
out[1][j] <== ab[0][1].out[j] + ab[1][0].out[j];
|
||||
}
|
||||
}
|
||||
|
||||
template SignedFp2MultiplyNoCarry(n, k, m_out){
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
signal output out[2][2*k-1];
|
||||
|
||||
component mult = SignedFp2MultiplyNoCarryUnequal(n, k, k, m_out);
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++){
|
||||
mult.a[i][j] <== a[i][j];
|
||||
mult.b[i][j] <== b[i][j];
|
||||
}
|
||||
for(var i=0; i<2; i++)for(var j=0; j<2*k-1; j++)
|
||||
out[i][j] <== mult.out[i][j];
|
||||
}
|
||||
|
||||
// input is 2 x (k+m) with registers in (-B,B)
|
||||
// in[0] + in[1] u
|
||||
// output is congruent to input (mod p) and represented as 2 x k where registers have abs val < (m+1)*2^n*B
|
||||
// m_out is the expected max number of bits in the output registers
|
||||
template Fp2Compress(n, k, m, p, m_out){
|
||||
signal input in[2][k+m];
|
||||
signal output out[2][k];
|
||||
|
||||
component c[2];
|
||||
for(var i=0; i<2; i++){
|
||||
c[i] = PrimeReduce(n, k, m, p, m_out);
|
||||
for(var j=0; j<k+m; j++)
|
||||
c[i].in[j] <== in[i][j];
|
||||
}
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++)
|
||||
out[i][j] <== c[i].out[j];
|
||||
}
|
||||
// same input as above
|
||||
// outputs:
|
||||
// out[2][k] such that
|
||||
// out[i] has k registers because we use the "prime trick" to compress from 2*k-1 to k registers
|
||||
// if each a[i][j] is in (-B, B) then out[i][j] has abs val < 2k^2 * 2^n*B^2
|
||||
// 2k*B^2 from SignedFp2MultiplyNoCarry
|
||||
// *k*2^n from prime trick
|
||||
// m_in is the expected max number of bits in the input registers (necessary for some intermediate overflow validation)
|
||||
// m_out is the expected max number of bits in the output registers
|
||||
template SignedFp2MultiplyNoCarryCompress(n, k, p, m_in, m_out){
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
var LOGK1 = log_ceil_ecdsa(2*k);
|
||||
component ab = SignedFp2MultiplyNoCarry(n, k, 2*m_in + LOGK1);
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++){
|
||||
ab.a[i][idx] <== a[i][idx];
|
||||
ab.b[i][idx] <== b[i][idx];
|
||||
}
|
||||
|
||||
var LOGK2 = log_ceil_ecdsa(2*k*k);
|
||||
component compress = Fp2Compress(n, k, k-1, p, 2*m_in + n + LOGK2);
|
||||
for(var i=0; i<2; i++)for(var j=0; j<2*k-1; j++)
|
||||
compress.in[i][j] <== ab.out[i][j];
|
||||
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++)
|
||||
out[i][j] <== compress.out[i][j];
|
||||
}
|
||||
|
||||
|
||||
template SignedFp2MultiplyNoCarryCompressThree(n, k, p, m_in, m_out){
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
signal input c[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
var LOGK = log_ceil_ecdsa(k);
|
||||
component ab = SignedFp2MultiplyNoCarry(n, k, 2*m_in + LOGK + 1);
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++){
|
||||
ab.a[i][idx] <== a[i][idx];
|
||||
ab.b[i][idx] <== b[i][idx];
|
||||
}
|
||||
|
||||
component abc = SignedFp2MultiplyNoCarryUnequal(n, 2*k-1, k, 3*m_in + 2*LOGK + 2);
|
||||
for(var i=0; i<2; i++){
|
||||
for(var idx=0; idx<2*k-1; idx++)
|
||||
abc.a[i][idx] <== ab.out[i][idx];
|
||||
for(var idx=0; idx<k; idx++)
|
||||
abc.b[i][idx] <== c[i][idx];
|
||||
}
|
||||
|
||||
component compress = Fp2Compress(n, k, 2*k-2, p, 3*m_in + n + 3*LOGK + 3);
|
||||
for(var i=0; i<2; i++)for(var j=0; j<3*k-2; j++)
|
||||
compress.in[i][j] <== abc.out[i][j];
|
||||
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++)
|
||||
out[i][j] <== compress.out[i][j];
|
||||
}
|
||||
|
||||
// check if in[0], in[1] both have k registers in [0,2^n)
|
||||
// to save constraints, DO NOT CONSTRAIN in[i] < p
|
||||
template RangeCheck2D(n, k){
|
||||
signal input in[2][k];
|
||||
component range_checks[2][k];
|
||||
//component lt[2];
|
||||
|
||||
for(var eps=0; eps<2; eps++){
|
||||
//lt[eps] = BigLessThanEcdsa(n, k);
|
||||
for(var i=0; i<k; i++){
|
||||
range_checks[eps][i] = Num2Bits(n);
|
||||
range_checks[eps][i].in <== in[eps][i];
|
||||
//lt[eps].a[i] <== in[eps][i];
|
||||
//lt[eps].b[i] <== p[i];
|
||||
}
|
||||
//lt[eps].out === 1;
|
||||
}
|
||||
}
|
||||
|
||||
// solve for in = p * X + out
|
||||
// X has registers lying in [-2^n, 2^n)
|
||||
// X has at most Ceil( overflow / n ) registers
|
||||
// assume in has registers in (-2^overflow, 2^overflow)
|
||||
template SignedFp2CarryModP(n, k, overflow, p){
|
||||
signal input in[2][k];
|
||||
var m = (overflow + n - 1) \ n;
|
||||
signal output X[2][m];
|
||||
signal output out[2][k];
|
||||
|
||||
assert( overflow < 251 );
|
||||
|
||||
component carry[2];
|
||||
for(var i=0; i<2; i++){
|
||||
carry[i] = SignedFpCarryModP(n, k, overflow, p);
|
||||
for(var idx=0; idx<k; idx++)
|
||||
carry[i].in[idx] <== in[i][idx];
|
||||
for(var idx=0; idx<m; idx++)
|
||||
X[i][idx] <== carry[i].X[idx];
|
||||
for(var idx=0; idx<k; idx++)
|
||||
out[i][idx] <== carry[i].out[idx];
|
||||
}
|
||||
}
|
||||
|
||||
// input is 2 x (k+m) with registers in (-2^overflow, 2^overflow)
|
||||
// in[0] + in[1] u
|
||||
// calls Fp2Compress and SignedFp2CarryModP
|
||||
template SignedFp2CompressCarry(n, k, m, overflow, p){
|
||||
signal input in[2][k+m];
|
||||
signal output out[2][k];
|
||||
|
||||
var LOGM = log_ceil_ecdsa(m+1);
|
||||
component compress = Fp2Compress(n, k, m, p, overflow + n + LOGM);
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k+m; idx++)
|
||||
compress.in[i][idx] <== in[i][idx];
|
||||
|
||||
component carry = SignedFp2CarryModP(n, k, overflow + n + LOGM, p);
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++)
|
||||
carry.in[i][idx] <== compress.out[i][idx];
|
||||
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++)
|
||||
out[i][idx] <== carry.out[i][idx];
|
||||
}
|
||||
|
||||
// outputs a*b in Fp2
|
||||
// (a0 + a1 u)*(b0 + b1 u) = (a0*b0 - a1*b1) + (a0*b1 + a1*b0)u
|
||||
// out[i] has k registers each in [0, 2^n)
|
||||
// out[i] in [0, p)
|
||||
template Fp2Multiply(n, k, p){
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
var LOGK2 = log_ceil_ecdsa(2*k*k);
|
||||
assert(3*n + LOGK2 < 251);
|
||||
|
||||
component c = SignedFp2MultiplyNoCarryCompress(n, k, p, n, 3*n + LOGK2);
|
||||
for(var i=0; i<k; i++){
|
||||
c.a[0][i] <== a[0][i];
|
||||
c.a[1][i] <== a[1][i];
|
||||
c.b[0][i] <== b[0][i];
|
||||
c.b[1][i] <== b[1][i];
|
||||
}
|
||||
|
||||
component carry_mod = SignedFp2CarryModP(n, k, 3*n + LOGK2, p);
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++)
|
||||
carry_mod.in[i][j] <== c.out[i][j];
|
||||
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++)
|
||||
out[i][j] <== carry_mod.out[i][j];
|
||||
}
|
||||
|
||||
|
||||
template Fp2MultiplyThree(n, k, p){
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
signal input c[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
var LOGK3 = log_ceil_ecdsa(4*k*k*(2*k-1));
|
||||
assert(4*n + LOGK3 < 251);
|
||||
|
||||
component compress = SignedFp2MultiplyNoCarryCompressThree(n, k, p, n, 4*n + LOGK3);
|
||||
for(var i=0; i<2; i++)for(var idx=0; idx<k; idx++){
|
||||
compress.a[i][idx] <== a[i][idx];
|
||||
compress.b[i][idx] <== b[i][idx];
|
||||
compress.c[i][idx] <== c[i][idx];
|
||||
}
|
||||
|
||||
component carry_mod = SignedFp2CarryModP(n, k, 4*n + LOGK3, p);
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++)
|
||||
carry_mod.in[i][j] <== compress.out[i][j];
|
||||
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++)
|
||||
out[i][j] <== carry_mod.out[i][j];
|
||||
}
|
||||
|
||||
|
||||
// input: in[0] + in[1] u
|
||||
// output: (p-in[0]) + (p-in[1]) u
|
||||
// assume 0 <= in < p
|
||||
template Fp2Negate(n, k, p){
|
||||
signal input in[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
component neg[2];
|
||||
for(var j=0; j<2; j++){
|
||||
neg[j] = FpNegate(n, k, p);
|
||||
for(var i=0; i<k; i++)
|
||||
neg[j].in[i] <== in[j][i];
|
||||
for(var i=0; i<k; i++)
|
||||
out[j][i] <== neg[j].out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// input: a0 + a1 u, b0 + b1 u
|
||||
// output: (a0-b0) + (a1-b1)u
|
||||
template Fp2Subtract(n, k, p){
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
component sub0 = FpSubtract(n, k, p);
|
||||
component sub1 = FpSubtract(n, k, p);
|
||||
for(var i=0; i<k; i++){
|
||||
sub0.a[i] <== a[0][i];
|
||||
sub0.b[i] <== b[0][i];
|
||||
sub1.a[i] <== a[1][i];
|
||||
sub1.b[i] <== b[1][i];
|
||||
}
|
||||
for(var i=0; i<k; i++){
|
||||
out[0][i] <== sub0.out[i];
|
||||
out[1][i] <== sub1.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Call find_Fp2_inverse to compute inverse
|
||||
// Then check out * in = 1, out is an array of shorts
|
||||
template Fp2Invert(n, k, p){
|
||||
signal input in[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
var inverse[2][50] = find_Fp2_inverse(n, k, in, p); // 2 x 50, only 2 x k relevant
|
||||
for (var i = 0; i < 2; i ++) {
|
||||
for (var j = 0; j < k; j ++) {
|
||||
out[i][j] <-- inverse[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
//range checks
|
||||
component outRangeChecks[2][k];
|
||||
for(var i=0; i<2; i++) for(var j=0; j<k; j++){
|
||||
outRangeChecks[i][j] = Num2Bits(n);
|
||||
outRangeChecks[i][j].in <== out[i][j];
|
||||
}
|
||||
|
||||
component in_out = Fp2Multiply(n, k, p);
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++){
|
||||
in_out.a[i][j] <== in[i][j];
|
||||
in_out.b[i][j] <== out[i][j];
|
||||
}
|
||||
|
||||
for(var i=0; i<2; i++)for(var j=0; j<k; j++){
|
||||
if(i == 0 && j == 0)
|
||||
in_out.out[i][j] === 1;
|
||||
else
|
||||
in_out.out[i][j] === 0;
|
||||
}
|
||||
}
|
||||
|
||||
// a, b are two elements of Fp2 where we use the 2 x k format
|
||||
// solve for out * b - a = p * X
|
||||
// assume X has at most m registers, lying in [-2^{n+1}, 2^{n+1} ) NOTE n+1 not n DIFFERENT FROM Fp2CarryModP
|
||||
// out has registers in [0, 2^n)
|
||||
template SignedFp2Divide(n, k, overflowa, overflowb, p){
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
var ma = overflowa \ n;
|
||||
var mb = overflowb \ n;
|
||||
// first precompute a, b mod p as shorts
|
||||
var a_mod[2][50];
|
||||
var b_mod[2][50];
|
||||
for(var eps=0; eps<2; eps++){
|
||||
// 2^{overflow} <= 2^{n*ceil(overflow/n)}
|
||||
var temp[2][50] = get_signed_Fp_carry_witness(n, k, ma, a[eps], p);
|
||||
a_mod[eps] = temp[1];
|
||||
temp = get_signed_Fp_carry_witness(n, k, mb, b[eps], p);
|
||||
b_mod[eps] = temp[1];
|
||||
}
|
||||
|
||||
// precompute 1/b
|
||||
var b_inv[2][50] = find_Fp2_inverse(n, k, b_mod, p);
|
||||
// precompute a/b
|
||||
var out_var[2][50] = find_Fp2_product(n, k, a_mod, b_inv, p);
|
||||
|
||||
for(var eps=0; eps<2; eps++)for(var i=0; i<k; i++)
|
||||
out[eps][i] <-- out_var[eps][i];
|
||||
|
||||
component check = RangeCheck2D(n, k);
|
||||
for(var eps=0; eps<2; eps++)for(var i=0; i<k; i++)
|
||||
check.in[eps][i] <== out[eps][i];
|
||||
|
||||
// constraint is out * b = a + p * X
|
||||
// precompute out * b = p * X' + Y' and a = p * X'' + Y''
|
||||
// should have Y' = Y'' so X = X' - X''
|
||||
|
||||
var LOGK2 = log_ceil_ecdsa(2*k*k);
|
||||
// out * b, registers overflow in 2*k*k * 2^{2n + overflowb}
|
||||
component mult = SignedFp2MultiplyNoCarryCompress(n, k, p, max(n, overflowb), 2*n + overflowb + LOGK2);
|
||||
for(var eps=0; eps<2; eps++)for(var i=0; i<k; i++){
|
||||
mult.a[eps][i] <== out[eps][i];
|
||||
mult.b[eps][i] <== b[eps][i];
|
||||
}
|
||||
|
||||
var m = max( mb + k, ma );
|
||||
// get mult = out * b = p*X' + Y'
|
||||
var XY[2][2][50] = get_signed_Fp2_carry_witness(n, k, m, mult.out, p); // total value is < 2^{nk} * 2^{n*k + overflowb - n + 1}
|
||||
// get a = p*X' + Y'
|
||||
var XY1[2][2][50] = get_signed_Fp2_carry_witness(n, k, m, a, p); // same as above, m extra registers enough
|
||||
|
||||
signal X[2][m];
|
||||
component X_range_checks[2][m];
|
||||
for(var eps=0; eps<2; eps++){
|
||||
for(var i=0; i<m; i++){
|
||||
// X'' = X-X'
|
||||
X[eps][i] <-- XY[eps][0][i] - XY1[eps][0][i]; // each XY[eps][0] is in [-2^n, 2^n) so difference is in [-2^{n+1}, 2^{n+1})
|
||||
X_range_checks[eps][i] = Num2Bits(n+2);
|
||||
X_range_checks[eps][i].in <== X[eps][i] + (1<<(n+1)); // X[eps][i] should be between [-2^{n+1}, 2^{n+1})
|
||||
}
|
||||
}
|
||||
|
||||
var overflow = max(2*n + overflowb + LOGK2, overflowa);
|
||||
// finally constrain out * b - a = p * X
|
||||
// out * b - a has overflow in (-2^{overflow+1}, 2^{overflow +1})
|
||||
component mod_check[2];
|
||||
for(var eps=0; eps<2; eps++){
|
||||
mod_check[eps] = CheckCarryModP(n, k, m, overflow + 1, p);
|
||||
for(var i=0; i<k; i++){
|
||||
mod_check[eps].in[i] <== mult.out[eps][i] - a[eps][i];
|
||||
mod_check[eps].Y[i] <== 0;
|
||||
}
|
||||
for(var i=0; i<m; i++)
|
||||
mod_check[eps].X[i] <== X[eps][i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// input: a+b u
|
||||
// output: a-b u
|
||||
// IF p = 3 mod 4 THEN a - b u = (a+b u)^p <-- Frobenius map
|
||||
// aka Fp2FrobeniusMap(n, k)
|
||||
template Fp2Conjugate(n, k, p){
|
||||
signal input in[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
component neg1 = FpNegate(n, k, p);
|
||||
for(var i=0; i<k; i++){
|
||||
neg1.in[i] <== in[1][i];
|
||||
}
|
||||
for(var i=0; i<k; i++){
|
||||
out[0][i] <== in[0][i];
|
||||
out[1][i] <== neg1.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// raises to q^power-th power
|
||||
template Fp2FrobeniusMap(n, k, power, p){
|
||||
signal input in[2][k];
|
||||
signal output out[2][k];
|
||||
|
||||
var pow = power % 2;
|
||||
component neg1 = FpNegate(n,k,p);
|
||||
if(pow == 0){
|
||||
for(var i=0; i<k; i++){
|
||||
out[0][i] <== in[0][i];
|
||||
out[1][i] <== in[1][i];
|
||||
}
|
||||
}else{
|
||||
for(var i=0; i<k; i++){
|
||||
neg1.in[i] <== in[1][i];
|
||||
}
|
||||
for(var i=0; i<k; i++){
|
||||
out[0][i] <== in[0][i];
|
||||
out[1][i] <== neg1.out[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// in = in0 + in1 * u, elt of Fp2
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4.1
|
||||
// NOTE: different from Wahby-Boneh paper https://eprint.iacr.org/2019/403.pdf and python reference code: https://github.com/algorand/bls_sigs_ref/blob/master/python-impl/opt_swu_g2.py
|
||||
template Fp2Sgn0(n, k, p){
|
||||
signal input in[2][k];
|
||||
signal output out;
|
||||
|
||||
component sgn[2];
|
||||
for(var i=0; i<2; i++){
|
||||
sgn[i] = FpSgn0(n, k, p);
|
||||
for(var idx=0; idx<k; idx++)
|
||||
sgn[i].in[idx] <== in[i][idx];
|
||||
}
|
||||
component isZero = BigIsZero(k);
|
||||
for(var idx=0; idx<k; idx++)
|
||||
isZero.in[idx] <== in[0][idx];
|
||||
|
||||
signal sgn1; // (in0 == 0) && (sgn[1])
|
||||
sgn1 <== isZero.out * sgn[1].out;
|
||||
out <== sgn[0].out + sgn1 - sgn[0].out * sgn1; // sgn[0] || ( (in0 == 0 && sgn[1]) )
|
||||
}
|
||||
|
||||
template Fp2IsZero(n, k, p){
|
||||
signal input in[2][k];
|
||||
signal output out;
|
||||
|
||||
// check that in[i] < p
|
||||
component lt[2];
|
||||
|
||||
component isZeros[2][k];
|
||||
var total = 2*k;
|
||||
for(var j=0; j<2; j++){
|
||||
lt[j] = BigLessThanEcdsa(n, k);
|
||||
for(var i = 0; i < k; i++) {
|
||||
lt[j].a[i] <== in[j][i];
|
||||
lt[j].b[i] <== p[i];
|
||||
|
||||
isZeros[j][i] = IsZero();
|
||||
isZeros[j][i].in <== in[j][i];
|
||||
total -= isZeros[j][i].out;
|
||||
}
|
||||
lt[j].out === 1;
|
||||
}
|
||||
component checkZero = IsZero();
|
||||
checkZero.in <== total;
|
||||
out <== checkZero.out;
|
||||
}
|
||||
|
||||
template Fp2IsEqual(n, k, p){
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
signal output out;
|
||||
|
||||
// check that a[i], b[i] < p
|
||||
component lta[2];
|
||||
component ltb[2];
|
||||
component isEquals[2][k];
|
||||
var total = 2*k;
|
||||
for(var j=0; j<2; j++){
|
||||
lta[j] = BigLessThanEcdsa(n, k);
|
||||
ltb[j] = BigLessThanEcdsa(n, k);
|
||||
for (var i = 0; i < k; i ++) {
|
||||
lta[j].a[i] <== a[j][i];
|
||||
lta[j].b[i] <== p[i];
|
||||
|
||||
ltb[j].a[i] <== b[j][i];
|
||||
ltb[j].b[i] <== p[i];
|
||||
|
||||
isEquals[j][i] = IsEqual();
|
||||
isEquals[j][i].in[0] <== a[j][i];
|
||||
isEquals[j][i].in[1] <== b[j][i];
|
||||
total -= isEquals[j][i].out;
|
||||
}
|
||||
lta[j].out === 1;
|
||||
ltb[j].out === 1;
|
||||
}
|
||||
component checkZero = IsZero();
|
||||
checkZero.in <== total;
|
||||
out <== checkZero.out;
|
||||
}
|
||||
108
circuits/circuits/utils/circom-ecdsa/p256.circom
Normal file
108
circuits/circuits/utils/circom-ecdsa/p256.circom
Normal file
@@ -0,0 +1,108 @@
|
||||
pragma circom 2.1.5;
|
||||
|
||||
include "curve.circom";
|
||||
include "p256_func.circom";
|
||||
|
||||
template P256AddUnequal(n, k) {
|
||||
assert(n == 43 && k == 6);
|
||||
|
||||
signal input a[2][k];
|
||||
signal input b[2][k];
|
||||
|
||||
var params[4][6] = get_p256_params();
|
||||
component adder = EllipticCurveAddUnequal(n, k, params[2]);
|
||||
for (var i = 0; i < 6; i++) {
|
||||
adder.a[0][i] <== a[0][i];
|
||||
adder.a[1][i] <== a[1][i];
|
||||
adder.b[0][i] <== b[0][i];
|
||||
adder.b[1][i] <== b[1][i];
|
||||
}
|
||||
|
||||
signal output out[2][k] <== adder.out;
|
||||
}
|
||||
|
||||
template P256Double(n, k) {
|
||||
assert(n == 43 && k == 6);
|
||||
|
||||
signal input in[2][k];
|
||||
|
||||
var params[4][6] = get_p256_params();
|
||||
component doubler = EllipticCurveDouble(n, k, params[0], params[1], params[2]);
|
||||
for (var i = 0; i < 6; i++) {
|
||||
doubler.in[0][i] <== in[0][i];
|
||||
doubler.in[1][i] <== in[1][i];
|
||||
}
|
||||
|
||||
signal output out[2][k] <== doubler.out;
|
||||
}
|
||||
|
||||
template P256ScalarMult(n, k) {
|
||||
signal input scalar[k];
|
||||
signal input point[2][k];
|
||||
|
||||
signal output out[2][k];
|
||||
|
||||
component n2b[k];
|
||||
for (var i = 0; i < k; i++) {
|
||||
n2b[i] = Num2Bits(n);
|
||||
n2b[i].in <== scalar[i];
|
||||
}
|
||||
|
||||
// has_prev_non_zero[n * i + j] == 1 if there is a nonzero bit in location [i][j] or higher order bit
|
||||
component has_prev_non_zero[k * n];
|
||||
for (var i = k - 1; i >= 0; i--) {
|
||||
for (var j = n - 1; j >= 0; j--) {
|
||||
has_prev_non_zero[n * i + j] = OR();
|
||||
if (i == k - 1 && j == n - 1) {
|
||||
has_prev_non_zero[n * i + j].a <== 0;
|
||||
has_prev_non_zero[n * i + j].b <== n2b[i].out[j];
|
||||
} else {
|
||||
has_prev_non_zero[n * i + j].a <== has_prev_non_zero[n * i + j + 1].out;
|
||||
has_prev_non_zero[n * i + j].b <== n2b[i].out[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signal partial[n * k][2][k];
|
||||
signal intermed[n * k - 1][2][k];
|
||||
component adders[n * k - 1];
|
||||
component doublers[n * k - 1];
|
||||
for (var i = k - 1; i >= 0; i--) {
|
||||
for (var j = n - 1; j >= 0; j--) {
|
||||
if (i == k - 1 && j == n - 1) {
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
partial[n * i + j][0][idx] <== point[0][idx];
|
||||
partial[n * i + j][1][idx] <== point[1][idx];
|
||||
}
|
||||
}
|
||||
if (i < k - 1 || j < n - 1) {
|
||||
adders[n * i + j] = P256AddUnequal(n, k);
|
||||
doublers[n * i + j] = P256Double(n, k);
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
doublers[n * i + j].in[0][idx] <== partial[n * i + j + 1][0][idx];
|
||||
doublers[n * i + j].in[1][idx] <== partial[n * i + j + 1][1][idx];
|
||||
}
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
adders[n * i + j].a[0][idx] <== doublers[n * i + j].out[0][idx];
|
||||
adders[n * i + j].a[1][idx] <== doublers[n * i + j].out[1][idx];
|
||||
adders[n * i + j].b[0][idx] <== point[0][idx];
|
||||
adders[n * i + j].b[1][idx] <== point[1][idx];
|
||||
}
|
||||
// partial[n * i + j]
|
||||
// = has_prev_non_zero[n * i + j + 1] * ((1 - n2b[i].out[j]) * doublers[n * i + j] + n2b[i].out[j] * adders[n * i + j])
|
||||
// + (1 - has_prev_non_zero[n * i + j + 1]) * point
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
intermed[n * i + j][0][idx] <== n2b[i].out[j] * (adders[n * i + j].out[0][idx] - doublers[n * i + j].out[0][idx]) + doublers[n * i + j].out[0][idx];
|
||||
intermed[n * i + j][1][idx] <== n2b[i].out[j] * (adders[n * i + j].out[1][idx] - doublers[n * i + j].out[1][idx]) + doublers[n * i + j].out[1][idx];
|
||||
partial[n * i + j][0][idx] <== has_prev_non_zero[n * i + j + 1].out * (intermed[n * i + j][0][idx] - point[0][idx]) + point[0][idx];
|
||||
partial[n * i + j][1][idx] <== has_prev_non_zero[n * i + j + 1].out * (intermed[n * i + j][1][idx] - point[1][idx]) + point[1][idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var idx = 0; idx < k; idx++) {
|
||||
out[0][idx] <== partial[0][0][idx];
|
||||
out[1][idx] <== partial[0][1][idx];
|
||||
}
|
||||
}
|
||||
193
circuits/circuits/utils/circom-ecdsa/p256_func.circom
Normal file
193
circuits/circuits/utils/circom-ecdsa/p256_func.circom
Normal file
@@ -0,0 +1,193 @@
|
||||
/// This file provides methods to grab curve parameter based on various representations
|
||||
|
||||
pragma circom 2.1.5;
|
||||
|
||||
function get_A(n, k) {
|
||||
assert((n == 86 && k == 3) || (n == 64 && k == 4) || (n == 43 && k ==6));
|
||||
var ret[100];
|
||||
if (n == 86 && k == 3) {
|
||||
ret[0] = 77371252455336267181195260;
|
||||
ret[1] = 1023;
|
||||
ret[2] = 19342813109330467168976896;
|
||||
}
|
||||
|
||||
if (n == 64 && k == 4) {
|
||||
ret[0] = 18446744073709551615;
|
||||
ret[1] = 4294967295;
|
||||
ret[2] = 0;
|
||||
ret[3] = 18446744069414584321;
|
||||
}
|
||||
|
||||
if (n == 43 && k == 6) {
|
||||
ret[0] = 8796093022204;
|
||||
ret[1] = 8796093022207;
|
||||
ret[2] = 1023;
|
||||
ret[3] = 0;
|
||||
ret[4] = 1048576;
|
||||
ret[5] = 2199023255040;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//done
|
||||
function get_B(n,k) {
|
||||
assert((n == 86 && k == 3) || (n == 64 && k == 4) || (n == 43 && k ==6));
|
||||
var ret[100];
|
||||
if (n == 86 && k == 3) {
|
||||
ret[0] = 23805269282153275520606283;
|
||||
ret[1] = 64478498050055519801623345;
|
||||
ret[2] = 6858709101169761702330043;
|
||||
}
|
||||
|
||||
if (n == 64 && k == 4) {
|
||||
ret[0] = 4309448131093880907;
|
||||
ret[1] = 7285987128567378166;
|
||||
ret[2] = 12964664127075681980;
|
||||
ret[3] = 6540974713487397863;
|
||||
}
|
||||
|
||||
if (n == 43 && k == 6) {
|
||||
ret[0] = 4665002582091;
|
||||
ret[1] = 2706345785799;
|
||||
ret[2] = 1737114698545;
|
||||
ret[3] = 7330356544350;
|
||||
ret[4] = 4025432620731;
|
||||
ret[5] = 779744948564;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
//done
|
||||
function get_p256_prime(n, k) {
|
||||
assert((n == 86 && k == 3) || (n == 64 && k == 4) || (n == 32 && k == 8) || (n == 43 && k == 6));
|
||||
|
||||
// done
|
||||
var ret[100];
|
||||
if (n == 86 && k == 3) {
|
||||
ret[0] = 77371252455336267181195263;
|
||||
ret[1] = 1023;
|
||||
ret[2] = 19342813109330467168976896;
|
||||
}
|
||||
|
||||
// done
|
||||
if (n == 64 && k == 4) {
|
||||
ret[0] = 18446744073709551615;
|
||||
ret[1] = 4294967295;
|
||||
ret[2] = 0;
|
||||
ret[3] = 18446744069414584321;
|
||||
}
|
||||
|
||||
if (n==32 && k==8) {
|
||||
ret[0] = 4294967295;
|
||||
ret[1] = 4294967295;
|
||||
ret[2] = 4294967295;
|
||||
ret[3] = 0;
|
||||
ret[4] = 0;
|
||||
ret[5] = 0;
|
||||
ret[6] = 1;
|
||||
ret[7] = 4294967295;
|
||||
}
|
||||
|
||||
if (n == 43 && k == 6) {
|
||||
ret[0] = 8796093022207;
|
||||
ret[1] = 8796093022207;
|
||||
ret[2] = 1023;
|
||||
ret[3] = 0;
|
||||
ret[4] = 1048576;
|
||||
ret[5] = 2199023255040;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//done
|
||||
function get_p256_order(n, k) {
|
||||
assert((n == 86 && k == 3) || (n == 64 && k == 4) || (n == 43 && k ==6));
|
||||
var ret[100];
|
||||
|
||||
//done
|
||||
if (n == 86 && k == 3) {
|
||||
ret[0] = 28553880287938765337601361;
|
||||
ret[1] = 77371252455335114450843292;
|
||||
ret[2] = 19342813109330467168976895;
|
||||
}
|
||||
|
||||
//done
|
||||
if (n == 64 && k == 4) {
|
||||
ret[0] = 17562291160714782033;
|
||||
ret[1] = 13611842547513532036;
|
||||
ret[2] = 18446744073709551615;
|
||||
ret[3] = 18446744069414584320;
|
||||
}
|
||||
|
||||
if (n == 43 && k == 6) {
|
||||
ret[0] = 3036481267025;
|
||||
ret[1] = 3246200354617;
|
||||
ret[2] = 7643362670236;
|
||||
ret[3] = 8796093022207;
|
||||
ret[4] = 1048575;
|
||||
ret[5] = 2199023255040;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// returns G * 2 ** 255
|
||||
// TODO check that this is correct...
|
||||
// DONE: change to P256 generator (double 255 times)
|
||||
function get_dummy_point(n, k) {
|
||||
assert(n == 86 && k == 3 || n == 64 && k == 4 || n == 43 && k == 6);
|
||||
var ret[2][100]; // should be [2][k]
|
||||
if (k == 3) {
|
||||
// done
|
||||
ret[0][0] = 49732730225977125179960148;
|
||||
ret[0][1] = 43852565336912664664048527;
|
||||
ret[0][2] = 9043934293559324284618350;
|
||||
ret[1][0] = 20405083474242608521046015;
|
||||
ret[1][1] = 57969189420107975911442337;
|
||||
ret[1][2] = 12380911704996683387468560;
|
||||
} else if (k == 4) {
|
||||
// done
|
||||
ret[0][0] = 4385603450014130423;
|
||||
ret[0][1] = 5129391198466743360;
|
||||
ret[0][2] = 5796222465970311708;
|
||||
ret[0][3] = 10225584345548510712;
|
||||
ret[1][0] = 8840623018579563820;
|
||||
ret[1][1] = 13067171496386086;
|
||||
ret[1][2] = 16363666747424765582;
|
||||
ret[1][3] = 9735895490635206920;
|
||||
} else if (k == 6) {
|
||||
ret[0][0] = 5013155818324;
|
||||
ret[0][1] = 5653956830653;
|
||||
ret[0][2] = 1357089440655;
|
||||
ret[0][3] = 4985459479134;
|
||||
ret[0][4] = 7362399503982;
|
||||
ret[0][5] = 1028176290396;
|
||||
ret[1][0] = 2185447106559;
|
||||
ret[1][1] = 2319789413632;
|
||||
ret[1][2] = 3837703653281;
|
||||
ret[1][3] = 6590333830457;
|
||||
ret[1][4] = 5404134177552;
|
||||
ret[1][5] = 1407546699851;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function get_p256_params() {
|
||||
var a[100] = get_A(43, 6);
|
||||
var b[100] = get_B(43, 6);
|
||||
var p[100] = get_p256_prime(43, 6);
|
||||
var n[100] = get_p256_order(43, 6);
|
||||
|
||||
var A[6];
|
||||
var B[6];
|
||||
var P[6];
|
||||
var N[6];
|
||||
for (var i = 0; i < 6; i++) {
|
||||
A[i] = a[i];
|
||||
B[i] = b[i];
|
||||
P[i] = p[i];
|
||||
N[i] = n[i];
|
||||
}
|
||||
|
||||
return [A,B,P,N];
|
||||
}
|
||||
411
circuits/circuits/utils/circom-ecdsa/p256_utils.circom
Normal file
411
circuits/circuits/utils/circom-ecdsa/p256_utils.circom
Normal file
@@ -0,0 +1,411 @@
|
||||
/// UNUSED FILE
|
||||
|
||||
pragma circom 2.1.5;
|
||||
|
||||
include "bigInt_func.circom";
|
||||
include "p256_func.circom";
|
||||
|
||||
// P = 2^256 - 2^224 + 2^192 + 2^96 - 1
|
||||
|
||||
// input: 10 registers, 64 bits each. registers can be overful
|
||||
// returns: reduced number with 8 32-bit registers, preserving residue mod P
|
||||
// changing the curve...
|
||||
// offset is too big to use immediately (on the order of 2^224)
|
||||
// need overflow to be at most 53, since there are 200-bit inputs (see p256.circom circuit AddUnequalCubicConstraint)
|
||||
// use 8 32-bit registers instead, calculate reps of 2**(64*i) in (32,8) representation directly so can only add 32-bit overflow
|
||||
template P256PrimeReduce10Registers() {
|
||||
signal input in[10];
|
||||
|
||||
// raw number = in[0] + in[1]*(2^64)^1 + in[2]*(2^64)^2 +in[3]*(2^64)^3 ... + in[7]*(2^64)^7 + ...
|
||||
// in[i]*(2^64)^i --> represent each of (2^64)^i (mod p) as an 8 digit (register) number in base 2^32, so that offsets to multiply in[i] by for each register at most 32 bits = small
|
||||
|
||||
// PrimeReduce10Registers:
|
||||
// TODO: remove hardcode
|
||||
var in_coeffs[10][8] = [[1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[1, 0, 0, 4294967295, 4294967295, 4294967295, 4294967294, 0],
|
||||
[4294967295, 0, 1, 1, 4294967295, 4294967294, 0, 4294967294],
|
||||
[4294967294, 4294967294, 4294967295, 2, 2, 0, 1, 4294967294],
|
||||
[4294967295, 4294967294, 4294967294, 4294967295, 0, 2, 3, 0],
|
||||
[3, 0, 4294967295, 4294967291, 4294967294, 4294967295, 4294967293, 4],
|
||||
[2, 5, 3, 4294967294, 4294967289, 4294967291, 4294967292, 4294967292]];
|
||||
|
||||
|
||||
|
||||
var tmp[8] = [0,0,0,0,0,0,0,0];
|
||||
for (var i=0; i<8; i++) {
|
||||
for (var j=0; j<10; j++) {
|
||||
tmp[i] += in_coeffs[j][i] * in[j];
|
||||
}
|
||||
}
|
||||
|
||||
signal output out[8]; // (32, 8)
|
||||
for (var i=0; i<8; i++) {
|
||||
out[i] <== tmp[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// DONE
|
||||
// input: 7 registers, 64 bits each. registers can be overful
|
||||
// returns: reduced number with 4 registers, preserving residue mod P
|
||||
// PrimeReduce7Registers only called in CheckQuadraticModPIsZero, which have inputs at most 132 bits
|
||||
// so can directly reduce 2^(64i)*in[i] directly in 4 registers of 64 bits -> 64 bit overflow for 4 <= i <= 6
|
||||
template P256PrimeReduce7Registers() {
|
||||
signal input in[7];
|
||||
|
||||
var in_coeffs[7][4] = [[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1],
|
||||
[1, 18446744069414584320, 18446744073709551615, 4294967294],
|
||||
[4294967295, 4294967297, 18446744069414584319, 18446744065119617024],
|
||||
[18446744069414584318, 12884901887, 2, 18446744065119617025]];
|
||||
|
||||
var tmp[4] = [0,0,0,0];
|
||||
for (var i=0; i<4; i++) {
|
||||
for (var j=0; j<7; j++) {
|
||||
tmp[i] += in_coeffs[j][i] * in[j];
|
||||
}
|
||||
}
|
||||
|
||||
signal output out[4]; // (64, 4)
|
||||
for (var i=0; i<4; i++) {
|
||||
out[i] <== tmp[i];
|
||||
}
|
||||
}
|
||||
|
||||
// DONE
|
||||
// check that in is in range [0, P-1]
|
||||
// want to look at P base 2^64 - as long as number is less than that, good (check ranges from largest to smallest digit)
|
||||
// P = 18446744073709551615 * 2^0 + 4294967295 * 2^64 + 0 * 2^128 + 18446744069414584321 * 2^192
|
||||
// TODO: remove hardcode
|
||||
template CheckInRangeP256 () {
|
||||
signal input in[4];
|
||||
|
||||
component firstPlaceLessThan = LessThan(64);
|
||||
firstPlaceLessThan.in[0] <== in[3];
|
||||
firstPlaceLessThan.in[1] <== 18446744069414584321;
|
||||
|
||||
component firstPlaceEqual = IsEqual();
|
||||
firstPlaceEqual.in[0] <== in[3];
|
||||
firstPlaceEqual.in[1] <== 18446744069414584321;
|
||||
|
||||
component secondPlaceLessThan = LessThan(64);
|
||||
secondPlaceLessThan.in[0] <== in[2];
|
||||
secondPlaceLessThan.in[1] <== 0;
|
||||
|
||||
component secondPlaceEqual = IsEqual();
|
||||
secondPlaceEqual.in[0] <== in[2];
|
||||
secondPlaceEqual.in[1] <== 0;
|
||||
|
||||
component thirdPlaceLessThan = LessThan(64);
|
||||
thirdPlaceLessThan.in[0] <== in[1];
|
||||
thirdPlaceLessThan.in[1] <== 4294967295;
|
||||
|
||||
component thirdPlaceEqual = IsEqual();
|
||||
thirdPlaceEqual.in[0] <== in[1];
|
||||
thirdPlaceEqual.in[1] <== 4294967295;
|
||||
|
||||
component fourthPlaceLessThan = LessThan(64);
|
||||
fourthPlaceLessThan.in[0] <== in[0];
|
||||
fourthPlaceLessThan.in[1] <== 18446744073709551615;
|
||||
|
||||
component fourthPlaceEqual = IsEqual();
|
||||
fourthPlaceEqual.in[0] <== in[0];
|
||||
fourthPlaceEqual.in[1] <== 18446744073709551615;
|
||||
|
||||
signal l1;
|
||||
l1 <== 1 - firstPlaceLessThan.out;
|
||||
signal e1;
|
||||
e1 <== 1 - firstPlaceEqual.out;
|
||||
signal l2;
|
||||
l2 <== 1 - secondPlaceLessThan.out;
|
||||
signal e2;
|
||||
e2 <== 1 - secondPlaceEqual.out;
|
||||
signal l3;
|
||||
l3 <== 1 - thirdPlaceLessThan.out;
|
||||
signal e3;
|
||||
e3 <== 1 - thirdPlaceEqual.out;
|
||||
signal l4;
|
||||
l4 <== 1 - fourthPlaceLessThan.out;
|
||||
signal e4;
|
||||
e4 <== 1 - fourthPlaceEqual.out;
|
||||
|
||||
// d1d2d3d4 < P <=> (d1 less) OR
|
||||
// (d1 equal and d2 less) OR
|
||||
// (d1 equal and d2 equal and d3 less) OR
|
||||
// (d1 equal and d2 equal and d3 equal and d4 less)
|
||||
|
||||
signal tmp1;
|
||||
tmp1 <== 1 * (e1 + l2);
|
||||
signal tmp2;
|
||||
tmp2 <== (e1 + e2 + l3) * (e1 + e2 + e3 + l4);
|
||||
|
||||
tmp1 * tmp2 === 0;
|
||||
|
||||
}
|
||||
|
||||
// DONE
|
||||
// 64 bit registers with m-bit overflow
|
||||
// registers (and overall number) are potentially negative
|
||||
template CheckCubicModPIsZero(m) {
|
||||
assert(m < 206); // since we deal with up to m+34 bit, potentially negative registers
|
||||
|
||||
signal input in[10];
|
||||
|
||||
log("===CheckCubicMod===");
|
||||
for (var i=0; i<10; i++) { // should be at most 200-bit registers
|
||||
log(in[i]);
|
||||
}
|
||||
|
||||
log(111);
|
||||
|
||||
// the p256 field size in (32,8)-rep
|
||||
signal p[8];
|
||||
var p_32_8[100] = get_p256_prime(32, 8);
|
||||
for (var i=0; i<8; i++) {
|
||||
p[i] <== p_32_8[i];
|
||||
log(p[i]);
|
||||
}
|
||||
|
||||
|
||||
// now, we compute a positive number congruent to `in` expressible in *8* overflowed registers.
|
||||
// for this representation, individual registers are allowed to be negative, but the final number
|
||||
// will be nonnegative overall.
|
||||
// first, we apply the p256 10-register reduction technique to reduce to *8* registers. this may result
|
||||
// in a negative number overall, but preserves congruence mod p.
|
||||
// our intermediate result is z = p256reduce(in)
|
||||
// second, we add a big multiple of p to z, to ensure that our final result is positive.
|
||||
// since the registers of z are m + 34 bits, its max abs value is 2^(m+34 + 224) + 2^(m+34 + 192) + 2^(m+34 + 160) + ...
|
||||
// < 2^(m+258)
|
||||
// so we add p * 2^(m+6) = (2^256-2^224 + eps) * 2^(m+6), which is a bit under 2^(m+262) and larger than |z| < 8 * 2^(m+34 + 224) = 2^(m+34 + 224 + 3) = 2^(m+261)
|
||||
|
||||
// notes:
|
||||
// what if we just didn't reduce any registers? like why are we reducing the input at all if all we're doing is long division? then
|
||||
// in < 2^(m + 64*9) + ... < 2^(m + 64*9)*10...
|
||||
|
||||
signal reduced[8];
|
||||
|
||||
component p256Reducer = P256PrimeReduce10Registers(); // (32, 8)
|
||||
for (var i = 0; i < 10; i++) {
|
||||
p256Reducer.in[i] <== in[i];
|
||||
}
|
||||
|
||||
log(0);
|
||||
|
||||
for (var i = 0; i < 8; i++) {
|
||||
log(p256Reducer.out[i]);
|
||||
}
|
||||
|
||||
log(222);
|
||||
|
||||
// also compute P as (32, 8) rep to add - multiple should still be the same since value stays same
|
||||
signal multipleOfP[8];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
multipleOfP[i] <== p[i] * (1 << (m+6)); // m + 6 + 32 = m+38 bits
|
||||
}
|
||||
|
||||
// reduced becomes (32, 8)
|
||||
for (var i = 0; i < 8; i++) {
|
||||
reduced[i] <== p256Reducer.out[i] + multipleOfP[i]; // max(m+34, m+38) + 1 = m+39 bits
|
||||
}
|
||||
|
||||
for (var i = 0; i < 8; i++) {
|
||||
log(reduced[i]);
|
||||
}
|
||||
|
||||
log(333);
|
||||
|
||||
// now we compute the quotient q, which serves as a witness. we can do simple bounding to show
|
||||
// q := reduced / P < (p256Reducer + multipleofP) / 2^255 < (2^(m+262) + 2^(m+261)) / 2^255 < 2^(m+8)
|
||||
// so the expected quotient q is always expressive in *7* 32-bit registers (i.e. < 2^224)
|
||||
// as long as m < 216 (and we only ever call m < 200)
|
||||
signal q[7];
|
||||
|
||||
// getProperRepresentation(m, n, k, in) spec:
|
||||
// m bits per overflowed register (values are potentially negative)
|
||||
// n bits per properly-sized register
|
||||
// in has k registers
|
||||
// out has k + ceil(m/n) - 1 + 1 registers. highest-order potentially negative,
|
||||
// all others are positive
|
||||
// - 1 since the last register is included in the last ceil(m/n) array
|
||||
// + 1 since the carries from previous registers could push you over
|
||||
// TODO: need to check if largest register of proper is negative
|
||||
var temp[100] = getProperRepresentation(m + 39, 32, 8, reduced); // SOME ERROR HERE
|
||||
|
||||
var proper[16];
|
||||
for (var i = 0; i<16; i++) {
|
||||
proper[i] = temp[i];
|
||||
log(proper[i]);
|
||||
}
|
||||
|
||||
log(999999999);
|
||||
|
||||
// long_div(n, k, m, a, b) spec:
|
||||
// n bits per register
|
||||
// a has k + m registers
|
||||
// b has k registers
|
||||
// out[0] has length m + 1 -- quotient
|
||||
// out[1] has length k -- remainder
|
||||
// implements algorithm of https://people.eecs.berkeley.edu/~fateman/282/F%20Wright%20notes/week4.pdf
|
||||
// b[k-1] must be nonzero!
|
||||
// var qVarTemp[2][100] = long_div(32, 8, 8, proper, p); // ERROR HERE
|
||||
// for (var i = 0; i < 7; i++) {
|
||||
// q[i] <-- qVarTemp[0][i];
|
||||
// log(q[i]);
|
||||
// }
|
||||
|
||||
var qVarTemp[7] = [0, 0, 0, 0, 813694976, 2338053171, 2054]; // try hardcoding expected q in?
|
||||
for (var i = 0; i < 7; i++) {
|
||||
q[i] <-- qVarTemp[i];
|
||||
log(q[i]);
|
||||
}
|
||||
|
||||
|
||||
// we need to constrain that q is in proper (7x32) representation
|
||||
component qRangeChecks[7];
|
||||
for (var i = 0; i < 7; i++) {
|
||||
qRangeChecks[i] = Num2Bits(32);
|
||||
qRangeChecks[i].in <== q[i];
|
||||
}
|
||||
|
||||
log(444);
|
||||
|
||||
// now we compute a representation qpProd = q * p
|
||||
signal qpProd[14];
|
||||
|
||||
// template BigMultNoCarry(n, ma, mb, ka, kb) spec:
|
||||
// a and b have n-bit registers
|
||||
// a has ka registers, each with NONNEGATIVE ma-bit values (ma can be > n)
|
||||
// b has kb registers, each with NONNEGATIVE mb-bit values (mb can be > n)
|
||||
// out has ka + kb - 1 registers, each with (ma + mb + ceil(log(max(ka, kb))))-bit values
|
||||
component qpProdComp = BigMultNoCarry(32, 32, 32, 7, 8); // qpProd = q*p
|
||||
for (var i = 0; i < 7; i++) {
|
||||
qpProdComp.a[i] <== q[i];
|
||||
}
|
||||
for (var i = 0; i < 8; i++) {
|
||||
qpProdComp.b[i] <== p[i];
|
||||
}
|
||||
for (var i = 0; i < 14; i++) {
|
||||
qpProd[i] <== qpProdComp.out[i]; // 67 bits
|
||||
}
|
||||
|
||||
for (var i = 0; i < 14; i++) {
|
||||
log(qpProd[i]); // 67 bits
|
||||
}
|
||||
|
||||
// log(444);
|
||||
// for (var i = 0; i < 26; i++) {
|
||||
// log(qpProdComp.out[i]); // 67 bits
|
||||
// }
|
||||
|
||||
|
||||
log(555);
|
||||
|
||||
// finally, check that qpProd == reduced
|
||||
// CheckCarryToZeroEcdsa(n, m, k) spec:
|
||||
// in[i] contains values in the range -2^(m-1) to 2^(m-1)
|
||||
// constrain that in[] as a big integer is zero
|
||||
// each limbs is n bits
|
||||
// FAILING HERE:
|
||||
component zeroCheck = CheckCarryToZeroEcdsa(32, m + 50, 14);
|
||||
for (var i = 0; i < 14; i++) {
|
||||
if (i < 8) { // reduced only has 8 registers
|
||||
zeroCheck.in[i] <== qpProd[i] - reduced[i]; // (m + 39) + 1 bits
|
||||
log(zeroCheck.in[i]);
|
||||
} else {
|
||||
zeroCheck.in[i] <== qpProd[i];
|
||||
log(zeroCheck.in[i]);
|
||||
}
|
||||
}
|
||||
|
||||
log(666);
|
||||
|
||||
}
|
||||
|
||||
// DONE
|
||||
// 64 bit registers with m-bit overflow
|
||||
// registers (and overall number) are potentially negative
|
||||
template CheckQuadraticModPIsZero(m) {
|
||||
assert(m < 147); // so that we can assume q has 2 registers
|
||||
|
||||
signal input in[7];
|
||||
|
||||
// the p256 field size, hardcoded
|
||||
signal p[4];
|
||||
p[0] <== 18446744073709551615;
|
||||
p[1] <== 4294967295;
|
||||
p[2] <== 0;
|
||||
p[3] <== 18446744069414584321;
|
||||
|
||||
// now, we compute a positive number congruent to `in` expressible in 4 overflowed registers.
|
||||
// for this representation, individual registers are allowed to be negative, but the final number
|
||||
// will be nonnegative overall.
|
||||
// first, we apply the p256 7-register reduction technique to reduce to 4 registers. this may result
|
||||
// in a negative number overall, but preserves congruence mod p.
|
||||
// our intermediate result is z = p256Reduce(in)
|
||||
// second, we add a big multiple of p to z, to ensure that our final result is positive.
|
||||
// since the registers of z are m + 33 bits, its max abs value is 2^(m+33 + 192) + 2^(m+33 + 128) + ...
|
||||
// so we add p * 2^(m-30), which is a bit under 2^(m+226) and larger than |z| < 2^(m+33+192) + eps
|
||||
signal reduced[4];
|
||||
component p256Reducer = P256PrimeReduce7Registers();
|
||||
for (var i = 0; i < 7; i++) {
|
||||
p256Reducer.in[i] <== in[i];
|
||||
}
|
||||
signal multipleOfP[4];
|
||||
for (var i = 0; i < 4; i++) {
|
||||
multipleOfP[i] <== p[i] * (1 << (m-30)); // m - 30 + 64 = m + 34 bits
|
||||
}
|
||||
for (var i = 0; i < 4; i++) {
|
||||
reduced[i] <== p256Reducer.out[i] + multipleOfP[i]; // max(m+33, m+34) + 1 = m+35 bits
|
||||
}
|
||||
|
||||
// now we compute the quotient q, which serves as a witness. we can do simple bounding to show
|
||||
// that the the expected quotient is always expressible in 2 registers (i.e. < 2^192)
|
||||
// so long as m < 147
|
||||
signal q[2];
|
||||
|
||||
var temp[100] = getProperRepresentation(m + 35, 64, 4, reduced);
|
||||
var proper[8];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
proper[i] = temp[i];
|
||||
}
|
||||
|
||||
var qVarTemp[2][100] = long_div_ecdsa(64, 4, 4, proper, p);
|
||||
for (var i = 0; i < 2; i++) {
|
||||
q[i] <-- qVarTemp[0][i];
|
||||
}
|
||||
|
||||
// we need to constrain that q is in proper (2x64) representation
|
||||
component qRangeChecks[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
qRangeChecks[i] = Num2Bits(64);
|
||||
qRangeChecks[i].in <== q[i];
|
||||
}
|
||||
|
||||
// now we compute a representation qpProd = q * p
|
||||
signal qpProd[5];
|
||||
component qpProdComp = BigMultNoCarry(64, 64, 64, 2, 4);
|
||||
for (var i = 0; i < 2; i++) {
|
||||
qpProdComp.a[i] <== q[i];
|
||||
}
|
||||
for (var i = 0; i < 4; i++) {
|
||||
qpProdComp.b[i] <== p[i];
|
||||
}
|
||||
for (var i = 0; i < 5; i++) {
|
||||
qpProd[i] <== qpProdComp.out[i]; // 130 bits
|
||||
}
|
||||
|
||||
// finally, check that qpProd == reduced
|
||||
component zeroCheck = CheckCarryToZeroEcdsa(64, m + 36, 5);
|
||||
for (var i = 0; i < 5; i++) {
|
||||
if (i < 4) { // reduced only has 4 registers
|
||||
zeroCheck.in[i] <== qpProd[i] - reduced[i]; // (m + 35) + 1 bits
|
||||
} else {
|
||||
zeroCheck.in[i] <== qpProd[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ashpect/smt": "https://github.com/ashpect/smt#main",
|
||||
"@noble/curves": "^1.4.2",
|
||||
"@types/chai-as-promised": "^7.1.6",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/node-forge": "^1.3.5",
|
||||
|
||||
186
circuits/tests/register/register_sha1_ecdsa.test.ts
Normal file
186
circuits/tests/register/register_sha1_ecdsa.test.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { describe } from 'mocha';
|
||||
import path from 'path';
|
||||
import { poseidon1, poseidon6 } from 'poseidon-lite';
|
||||
import { mockPassPortData_sha1_ecdsa } from '../../../common/src/constants/mockPassportData';
|
||||
import { generateCircuitInputsRegister } from '../../../common/src/utils/generateInputs';
|
||||
import {
|
||||
BigintToArray,
|
||||
extractRSFromSignature,
|
||||
hexToDecimal,
|
||||
packBytes,
|
||||
} from '../../../common/src/utils/utils';
|
||||
import { expect } from 'chai';
|
||||
import { getLeaf } from '../../../common/src/utils/pubkeyTree';
|
||||
const wasm_tester = require('circom_tester').wasm;
|
||||
|
||||
describe('Register - SHA1 WITH ECDSA', function () {
|
||||
this.timeout(0);
|
||||
let inputs: any;
|
||||
let circuit: any;
|
||||
let passportData = mockPassPortData_sha1_ecdsa;
|
||||
let attestation_id: string;
|
||||
const attestation_name = 'E-PASSPORT';
|
||||
const n_dsc = 43; // 43 * 6 = 258 > 254 Cirom field size
|
||||
const k_dsc = 6;
|
||||
|
||||
const secret = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
|
||||
const dscSecret = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
|
||||
attestation_id = poseidon1([BigInt(Buffer.from(attestation_name).readUIntBE(0, 6))]).toString();
|
||||
|
||||
inputs = generateCircuitInputsRegister(
|
||||
secret,
|
||||
dscSecret,
|
||||
attestation_id,
|
||||
passportData,
|
||||
n_dsc,
|
||||
k_dsc
|
||||
);
|
||||
|
||||
let qx = BigInt(hexToDecimal(inputs.dsc_modulus[0]));
|
||||
let qy = BigInt(hexToDecimal(inputs.dsc_modulus[1]));
|
||||
let dsc_modulus = [BigintToArray(43, 6, qx), BigintToArray(43, 6, qy)];
|
||||
|
||||
let signature = inputs.signature;
|
||||
let { r, s } = extractRSFromSignature(signature);
|
||||
let signature_r = BigintToArray(43, 6, BigInt(hexToDecimal(r)));
|
||||
let signature_s = BigintToArray(43, 6, BigInt(hexToDecimal(s)));
|
||||
|
||||
before(async () => {
|
||||
circuit = await wasm_tester(
|
||||
path.join(__dirname, '../../circuits/register/register_ecdsaWithSHA1Encryption.circom'),
|
||||
{
|
||||
include: [
|
||||
'node_modules',
|
||||
'./node_modules/@zk-kit/binary-merkle-root.circom/src',
|
||||
'./node_modules/circomlib/circuits',
|
||||
'./node_modules/dmpierre/sha1-circom/circuits',
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should compile and load the circuit', async function () {
|
||||
expect(circuit).to.not.be.undefined;
|
||||
});
|
||||
|
||||
it('should calculate the witness with correct inputs', async function () {
|
||||
let qx = BigInt(hexToDecimal(inputs.dsc_modulus[0]));
|
||||
let qy = BigInt(hexToDecimal(inputs.dsc_modulus[1]));
|
||||
let dsc_modulus = [BigintToArray(43, 6, qx), BigintToArray(43, 6, qy)];
|
||||
|
||||
let signature = inputs.signature;
|
||||
let { r, s } = extractRSFromSignature(signature);
|
||||
let signature_r = BigintToArray(43, 6, BigInt(hexToDecimal(r)));
|
||||
let signature_s = BigintToArray(43, 6, BigInt(hexToDecimal(s)));
|
||||
|
||||
const w = await circuit.calculateWitness({
|
||||
secret: inputs.secret,
|
||||
mrz: inputs.mrz,
|
||||
dg1_hash_offset: inputs.dg1_hash_offset[0],
|
||||
econtent: inputs.econtent,
|
||||
datahashes_padded_length: inputs.datahashes_padded_length[0],
|
||||
signed_attributes: inputs.signed_attributes,
|
||||
signature_r: signature_r,
|
||||
signature_s: signature_s,
|
||||
dsc_modulus: dsc_modulus,
|
||||
dsc_secret: inputs.dsc_secret,
|
||||
attestation_id: inputs.attestation_id,
|
||||
});
|
||||
|
||||
await circuit.checkConstraints(w);
|
||||
|
||||
const nullifier = (await circuit.getOutput(w, ['nullifier'])).nullifier;
|
||||
console.log('\x1b[34m%s\x1b[0m', 'nullifier', nullifier);
|
||||
const commitment_circom = (await circuit.getOutput(w, ['commitment'])).commitment;
|
||||
console.log('\x1b[34m%s\x1b[0m', 'commitment', commitment_circom);
|
||||
const blinded_dsc_commitment = (await circuit.getOutput(w, ['blinded_dsc_commitment']))
|
||||
.blinded_dsc_commitment;
|
||||
console.log('\x1b[34m%s\x1b[0m', 'blinded_dsc_commitment', blinded_dsc_commitment);
|
||||
|
||||
const mrz_bytes = packBytes(inputs.mrz);
|
||||
const leaf = getLeaf({
|
||||
signatureAlgorithm: passportData.signatureAlgorithm,
|
||||
publicKeyQ: passportData.pubKey.publicKeyQ,
|
||||
}).toString();
|
||||
|
||||
const commitment_bytes = poseidon6([
|
||||
inputs.secret[0],
|
||||
attestation_id,
|
||||
leaf,
|
||||
mrz_bytes[0],
|
||||
mrz_bytes[1],
|
||||
mrz_bytes[2],
|
||||
]);
|
||||
const commitment_js = commitment_bytes.toString();
|
||||
expect(commitment_circom).to.be.equal(commitment_js);
|
||||
});
|
||||
|
||||
it('should fail to calculate witness with invalid econtent', async function () {
|
||||
try {
|
||||
const invalidInputs = {
|
||||
secret: inputs.secret,
|
||||
mrz: inputs.mrz,
|
||||
dg1_hash_offset: inputs.dg1_hash_offset[0],
|
||||
econtent: inputs.econtent.map((byte: string) => String((parseInt(byte, 10) + 1) % 256)),
|
||||
datahashes_padded_length: inputs.datahashes_padded_length[0],
|
||||
signed_attributes: inputs.signed_attributes,
|
||||
signature_r: signature_r,
|
||||
signature_s: signature_s,
|
||||
dsc_modulus: dsc_modulus,
|
||||
dsc_secret: inputs.dsc_secret,
|
||||
attestation_id: inputs.attestation_id,
|
||||
};
|
||||
await circuit.calculateWitness(invalidInputs);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail to calculate witness with invalid mrz', async function () {
|
||||
try {
|
||||
const invalidInputs = {
|
||||
secret: inputs.secret,
|
||||
mrz: Array(93)
|
||||
.fill(0)
|
||||
.map((byte) => BigInt(byte).toString()),
|
||||
dg1_hash_offset: inputs.dg1_hash_offset[0],
|
||||
econtent: inputs.econtent,
|
||||
datahashes_padded_length: inputs.datahashes_padded_length[0],
|
||||
signed_attributes: inputs.signed_attributes,
|
||||
signature_r: signature_r,
|
||||
signature_s: signature_s,
|
||||
dsc_modulus: dsc_modulus,
|
||||
dsc_secret: inputs.dsc_secret,
|
||||
attestation_id: inputs.attestation_id,
|
||||
};
|
||||
await circuit.calculateWitness(invalidInputs);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail to calculate witness with invalid signature', async function () {
|
||||
let wrong_signature_s = BigintToArray(43, 6, BigInt(hexToDecimal(s) + 1));
|
||||
try {
|
||||
const invalidInputs = {
|
||||
secret: inputs.secret,
|
||||
mrz: inputs.mrz,
|
||||
dg1_hash_offset: inputs.dg1_hash_offset[0],
|
||||
econtent: inputs.econtent,
|
||||
datahashes_padded_length: inputs.datahashes_padded_length[0],
|
||||
signed_attributes: inputs.signed_attributes,
|
||||
signature_r: signature_r,
|
||||
signature_s: wrong_signature_s,
|
||||
dsc_modulus: dsc_modulus,
|
||||
dsc_secret: inputs.dsc_secret,
|
||||
attestation_id: inputs.attestation_id,
|
||||
};
|
||||
await circuit.calculateWitness(invalidInputs);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -27,6 +27,7 @@ describe('Register - SHA1 RSA', function () {
|
||||
'./node_modules/@zk-kit/binary-merkle-root.circom/src',
|
||||
'./node_modules/circomlib/circuits',
|
||||
'./node_modules/dmpierre/sha1-circom/circuits',
|
||||
'./node_modules/@zk-email/circuits ',
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
185
circuits/tests/register/register_sha256_ecdsa.test.ts
Normal file
185
circuits/tests/register/register_sha256_ecdsa.test.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { describe } from 'mocha';
|
||||
import path from 'path';
|
||||
import { poseidon1, poseidon6 } from 'poseidon-lite';
|
||||
import { mockPassportData_sha256_ecdsa } from '../../../common/src/constants/mockPassportData';
|
||||
import { generateCircuitInputsRegister } from '../../../common/src/utils/generateInputs';
|
||||
import {
|
||||
BigintToArray,
|
||||
extractRSFromSignature,
|
||||
hexToDecimal,
|
||||
packBytes,
|
||||
} from '../../../common/src/utils/utils';
|
||||
import { expect } from 'chai';
|
||||
import { getLeaf } from '../../../common/src/utils/pubkeyTree';
|
||||
const wasm_tester = require('circom_tester').wasm;
|
||||
|
||||
describe('Register - SHA256 WITH ECDSA', function () {
|
||||
this.timeout(0);
|
||||
let inputs: any;
|
||||
let circuit: any;
|
||||
let passportData = mockPassportData_sha256_ecdsa;
|
||||
let attestation_id: string;
|
||||
const attestation_name = 'E-PASSPORT';
|
||||
const n_dsc = 43; // 43 * 6 = 258 > 254 Cirom field size
|
||||
const k_dsc = 6;
|
||||
|
||||
const secret = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
|
||||
const dscSecret = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
|
||||
attestation_id = poseidon1([BigInt(Buffer.from(attestation_name).readUIntBE(0, 6))]).toString();
|
||||
|
||||
inputs = generateCircuitInputsRegister(
|
||||
secret,
|
||||
dscSecret,
|
||||
attestation_id,
|
||||
passportData,
|
||||
n_dsc,
|
||||
k_dsc
|
||||
);
|
||||
|
||||
let qx = BigInt(hexToDecimal(inputs.dsc_modulus[0]));
|
||||
let qy = BigInt(hexToDecimal(inputs.dsc_modulus[1]));
|
||||
let dsc_modulus = [BigintToArray(43, 6, qx), BigintToArray(43, 6, qy)];
|
||||
|
||||
let signature = inputs.signature;
|
||||
let { r, s } = extractRSFromSignature(signature);
|
||||
let signature_r = BigintToArray(43, 6, BigInt(hexToDecimal(r)));
|
||||
let signature_s = BigintToArray(43, 6, BigInt(hexToDecimal(s)));
|
||||
|
||||
before(async () => {
|
||||
circuit = await wasm_tester(
|
||||
path.join(__dirname, '../../circuits/register/register_ecdsaWithSHA256Encryption.circom'),
|
||||
{
|
||||
include: [
|
||||
'node_modules',
|
||||
'./node_modules/@zk-kit/binary-merkle-root.circom/src',
|
||||
'./node_modules/circomlib/circuits',
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should compile and load the circuit', async function () {
|
||||
expect(circuit).to.not.be.undefined;
|
||||
});
|
||||
|
||||
it('should calculate the witness with correct inputs', async function () {
|
||||
let qx = BigInt(hexToDecimal(inputs.dsc_modulus[0]));
|
||||
let qy = BigInt(hexToDecimal(inputs.dsc_modulus[1]));
|
||||
let dsc_modulus = [BigintToArray(43, 6, qx), BigintToArray(43, 6, qy)];
|
||||
|
||||
let signature = inputs.signature;
|
||||
let { r, s } = extractRSFromSignature(signature);
|
||||
let signature_r = BigintToArray(43, 6, BigInt(hexToDecimal(r)));
|
||||
let signature_s = BigintToArray(43, 6, BigInt(hexToDecimal(s)));
|
||||
|
||||
const w = await circuit.calculateWitness({
|
||||
secret: inputs.secret,
|
||||
mrz: inputs.mrz,
|
||||
dg1_hash_offset: inputs.dg1_hash_offset[0],
|
||||
econtent: inputs.econtent,
|
||||
datahashes_padded_length: inputs.datahashes_padded_length[0],
|
||||
signed_attributes: inputs.signed_attributes,
|
||||
signature_r: signature_r,
|
||||
signature_s: signature_s,
|
||||
dsc_modulus: dsc_modulus,
|
||||
dsc_secret: inputs.dsc_secret,
|
||||
attestation_id: inputs.attestation_id,
|
||||
});
|
||||
|
||||
await circuit.checkConstraints(w);
|
||||
|
||||
const nullifier = (await circuit.getOutput(w, ['nullifier'])).nullifier;
|
||||
console.log('\x1b[34m%s\x1b[0m', 'nullifier', nullifier);
|
||||
const commitment_circom = (await circuit.getOutput(w, ['commitment'])).commitment;
|
||||
console.log('\x1b[34m%s\x1b[0m', 'commitment', commitment_circom);
|
||||
const blinded_dsc_commitment = (await circuit.getOutput(w, ['blinded_dsc_commitment']))
|
||||
.blinded_dsc_commitment;
|
||||
console.log('\x1b[34m%s\x1b[0m', 'blinded_dsc_commitment', blinded_dsc_commitment);
|
||||
|
||||
const mrz_bytes = packBytes(inputs.mrz);
|
||||
const leaf = getLeaf({
|
||||
signatureAlgorithm: passportData.signatureAlgorithm,
|
||||
publicKeyQ: passportData.pubKey.publicKeyQ,
|
||||
}).toString();
|
||||
|
||||
const commitment_bytes = poseidon6([
|
||||
inputs.secret[0],
|
||||
attestation_id,
|
||||
leaf,
|
||||
mrz_bytes[0],
|
||||
mrz_bytes[1],
|
||||
mrz_bytes[2],
|
||||
]);
|
||||
const commitment_js = commitment_bytes.toString();
|
||||
expect(commitment_circom).to.be.equal(commitment_js);
|
||||
});
|
||||
|
||||
it('should fail to calculate witness with invalid econtent', async function () {
|
||||
try {
|
||||
const invalidInputs = {
|
||||
secret: inputs.secret,
|
||||
mrz: inputs.mrz,
|
||||
dg1_hash_offset: inputs.dg1_hash_offset[0],
|
||||
econtent: inputs.econtent.map((byte: string) => String((parseInt(byte, 10) + 1) % 256)),
|
||||
datahashes_padded_length: inputs.datahashes_padded_length[0],
|
||||
signed_attributes: inputs.signed_attributes,
|
||||
signature_r: signature_r,
|
||||
signature_s: signature_s,
|
||||
dsc_modulus: dsc_modulus,
|
||||
dsc_secret: inputs.dsc_secret,
|
||||
attestation_id: inputs.attestation_id,
|
||||
};
|
||||
await circuit.calculateWitness(invalidInputs);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail to calculate witness with invalid mrz', async function () {
|
||||
try {
|
||||
const invalidInputs = {
|
||||
secret: inputs.secret,
|
||||
mrz: Array(93)
|
||||
.fill(0)
|
||||
.map((byte) => BigInt(byte).toString()),
|
||||
dg1_hash_offset: inputs.dg1_hash_offset[0],
|
||||
econtent: inputs.econtent,
|
||||
datahashes_padded_length: inputs.datahashes_padded_length[0],
|
||||
signed_attributes: inputs.signed_attributes,
|
||||
signature_r: signature_r,
|
||||
signature_s: signature_s,
|
||||
dsc_modulus: dsc_modulus,
|
||||
dsc_secret: inputs.dsc_secret,
|
||||
attestation_id: inputs.attestation_id,
|
||||
};
|
||||
await circuit.calculateWitness(invalidInputs);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail to calculate witness with invalid signature', async function () {
|
||||
let wrong_signature_s = BigintToArray(43, 6, BigInt(hexToDecimal(s) + 1));
|
||||
try {
|
||||
const invalidInputs = {
|
||||
secret: inputs.secret,
|
||||
mrz: inputs.mrz,
|
||||
dg1_hash_offset: inputs.dg1_hash_offset[0],
|
||||
econtent: inputs.econtent,
|
||||
datahashes_padded_length: inputs.datahashes_padded_length[0],
|
||||
signed_attributes: inputs.signed_attributes,
|
||||
signature_r: signature_r,
|
||||
signature_s: wrong_signature_s,
|
||||
dsc_modulus: dsc_modulus,
|
||||
dsc_secret: inputs.dsc_secret,
|
||||
attestation_id: inputs.attestation_id,
|
||||
};
|
||||
await circuit.calculateWitness(invalidInputs);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -386,6 +386,18 @@
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@noble/curves@^1.4.2":
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9"
|
||||
integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==
|
||||
dependencies:
|
||||
"@noble/hashes" "1.4.0"
|
||||
|
||||
"@noble/hashes@1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
|
||||
integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==
|
||||
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
|
||||
|
||||
Reference in New Issue
Block a user