mirror of
https://github.com/plume-sig/zk-nullifier-sig.git
synced 2026-01-10 13:28:07 -05:00
Remove sha256 calculation from circuit.
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
pragma circom 2.1.2;
|
||||
|
||||
include "../verify_nullifier.circom";
|
||||
|
||||
component main = sha256_12_coordinates(64, 4);
|
||||
@@ -22,7 +22,7 @@ describe("Nullifier Circuit", () => {
|
||||
)
|
||||
const hash_to_curve_inputs = utils.stringifyBigInts(generate_inputs_from_array(message_bytes.concat(public_key_bytes)));
|
||||
|
||||
var sha_preimage_points: Point[] = [
|
||||
var points: Point[] = [
|
||||
Point.BASE,
|
||||
testPublicKeyPoint,
|
||||
hashMPkPoint,
|
||||
@@ -32,11 +32,8 @@ describe("Nullifier Circuit", () => {
|
||||
]
|
||||
|
||||
const sha256_preimage_bits = bufToSha256PaddedBitArr(Buffer.from(
|
||||
concatUint8Arrays(sha_preimage_points.map((point) => point.toRawBytes(true)))
|
||||
concatUint8Arrays(points.map((point) => point.toRawBytes(true)))
|
||||
));
|
||||
const sha256_preimage_bit_length = parseInt(sha256_preimage_bits.slice(-64), 2)
|
||||
|
||||
const binary_c = BigInt("0x" + c).toString(2).split('').map(Number);
|
||||
|
||||
test("hash_to_curve outputs same value", async () => {
|
||||
const p = join(__dirname, 'hash_to_curve_test.circom')
|
||||
@@ -48,30 +45,14 @@ describe("Nullifier Circuit", () => {
|
||||
await circuit.assertOut(w, {out: pointToCircuitValue(hashMPkPoint)});
|
||||
})
|
||||
|
||||
test("Correct sha256 value", async () => {
|
||||
var coordinates = [];
|
||||
sha_preimage_points.forEach((point) => {
|
||||
const cv = pointToCircuitValue(point);
|
||||
coordinates.push(cv[0]);
|
||||
coordinates.push(cv[1]);
|
||||
})
|
||||
|
||||
const p = join(__dirname, '12_point_sha_256_test.circom')
|
||||
const circuit = await wasm_tester(p, {"json":true, "sym": true})
|
||||
|
||||
const w = await circuit.calculateWitness({coordinates, preimage_bit_length: sha256_preimage_bit_length}, true)
|
||||
await circuit.checkConstraints(w);
|
||||
await circuit.assertOut(w, {out: binary_c})
|
||||
})
|
||||
|
||||
test("Correct compressed values are calculated", async () => {
|
||||
const p = join(__dirname, 'compression_test.circom')
|
||||
const circuit = await wasm_tester(p, {"json":true, "sym": true})
|
||||
|
||||
for (var i = 0; i < sha_preimage_points.length; i++) {
|
||||
const w = await circuit.calculateWitness({uncompressed: pointToCircuitValue(sha_preimage_points[i])}, true)
|
||||
for (var i = 0; i < points.length; i++) {
|
||||
const w = await circuit.calculateWitness({uncompressed: pointToCircuitValue(points[i])}, true)
|
||||
await circuit.checkConstraints(w);
|
||||
await circuit.assertOut(w, {compressed: Array.from(sha_preimage_points[i].toRawBytes(true))})
|
||||
await circuit.assertOut(w, {compressed: Array.from(points[i].toRawBytes(true))})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -79,11 +60,11 @@ describe("Nullifier Circuit", () => {
|
||||
const p = join(__dirname, 'compression_verification_test.circom')
|
||||
const circuit = await wasm_tester(p, {"json":true, "sym": true})
|
||||
|
||||
for (var i = 0; i < sha_preimage_points.length; i++) {
|
||||
for (var i = 0; i < points.length; i++) {
|
||||
for (var j = 0; j <= i; j++) {
|
||||
const inputs = {
|
||||
uncompressed: pointToCircuitValue(sha_preimage_points[i]),
|
||||
compressed: Array.from(sha_preimage_points[j].toRawBytes(true)),
|
||||
uncompressed: pointToCircuitValue(points[i]),
|
||||
compressed: Array.from(points[j].toRawBytes(true)),
|
||||
}
|
||||
|
||||
if (i === j) {
|
||||
|
||||
@@ -4,13 +4,11 @@ include "./node_modules/circom-ecdsa/circuits/ecdsa.circom";
|
||||
include "./node_modules/circom-ecdsa/circuits/secp256k1.circom";
|
||||
include "./node_modules/circom-ecdsa/circuits/secp256k1_func.circom";
|
||||
include "./node_modules/secp256k1_hash_to_curve_circom/circom/hash_to_curve.circom";
|
||||
include "./node_modules/secp256k1_hash_to_curve_circom/circom/Sha256.circom";
|
||||
include "./node_modules/circomlib/circuits/bitify.circom";
|
||||
|
||||
// Verifies that a nullifier belongs to a specific public key
|
||||
// This blog explains the intuition behind the construction https://blog.aayushg.com/posts/nullifier
|
||||
template verify_nullifier(n, k, msg_length) {
|
||||
signal input c[k];
|
||||
signal input s[k];
|
||||
signal input msg[msg_length];
|
||||
signal input public_key[2][k];
|
||||
@@ -29,9 +27,6 @@ template verify_nullifier(n, k, msg_length) {
|
||||
signal input q1_x_mapped[4];
|
||||
signal input q1_y_mapped[4];
|
||||
|
||||
// precomputed value for the sha256 component. TODO: calculate internally in circom to simplify API
|
||||
signal input sha256_preimage_bit_length;
|
||||
|
||||
// calculate g^r
|
||||
// g^r = g^s / pk^c (where g is the generator)
|
||||
// Note this implicitly checks the first equation in the blog
|
||||
@@ -102,40 +97,6 @@ template verify_nullifier(n, k, msg_length) {
|
||||
h_pow_r.b[1][i] <== nullifier[1][i];
|
||||
h_pow_r.c[i] <== c[i];
|
||||
}
|
||||
|
||||
// calculate c as sha256(g, pk, h, nullifier, g^r, h^r)
|
||||
component c_sha256 = sha256_12_coordinates(n, k);
|
||||
var g[2][100];
|
||||
g[0] = get_genx(n, k);
|
||||
g[1] = get_geny(n, k);
|
||||
c_sha256.preimage_bit_length <== sha256_preimage_bit_length;
|
||||
for (var i = 0; i < 2; i++) {
|
||||
for (var j = 0; j < k; j++) {
|
||||
c_sha256.coordinates[i][j] <== g[i][j];
|
||||
c_sha256.coordinates[2+i][j] <== public_key[i][j];
|
||||
c_sha256.coordinates[4+i][j] <== h.out[i][j];
|
||||
c_sha256.coordinates[6+i][j] <== nullifier[i][j];
|
||||
c_sha256.coordinates[8+i][j] <== g_pow_r.out[i][j];
|
||||
c_sha256.coordinates[10+i][j] <== h_pow_r.out[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
// check that the input c is the same as the hash value c
|
||||
component c_bits[k];
|
||||
for (var i = 0; i < k; i++) {
|
||||
c_bits[i] = Num2Bits(n);
|
||||
c_bits[i].in <== c[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < k; i++) {
|
||||
for (var j = 0; j < n; j++) {
|
||||
// We may have 3 registers of 86 bits, which means we end up getting two extra 0 bits which don't have to be equal to the sha256 hash
|
||||
// TODO: verify that we don't have to equate these to 0
|
||||
if (i*k + j < 256) {
|
||||
c_sha256.out[i*n + j] === c_bits[k-1-i].out[n-1-j]; // The sha256 output is little endian, whereas the c_bits is big endian (both at the register and bit level)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template a_div_b_pow_c(n, k) {
|
||||
@@ -178,68 +139,6 @@ template a_div_b_pow_c(n, k) {
|
||||
}
|
||||
}
|
||||
|
||||
template sha256_12_coordinates(n, k) {
|
||||
signal input coordinates[12][k];
|
||||
signal input preimage_bit_length;
|
||||
signal output out[256];
|
||||
|
||||
// compress coordinates
|
||||
component compressors[6];
|
||||
for (var i = 0; i < 6; i++) {
|
||||
compressors[i] = compress_ec_point(n, k);
|
||||
for (var j = 0; j < k; j++) {
|
||||
compressors[i].uncompressed[0][j] <== coordinates[2*i][j];
|
||||
compressors[i].uncompressed[1][j] <== coordinates[2*i + 1][j];
|
||||
}
|
||||
}
|
||||
|
||||
// decompose coordinates inputs into binary
|
||||
component binary[6*33];
|
||||
for (var i = 0; i < 6; i++) { // for each compressor
|
||||
for (var j = 0; j < 33; j++) { // for each byte
|
||||
binary[33*i + j] = Num2Bits(8);
|
||||
binary[33*i + j].in <== compressors[i].compressed[j];
|
||||
}
|
||||
}
|
||||
|
||||
var message_bits = 6*33*8; // 6 compressed coordinates of 33 bytes
|
||||
var total_bits = (message_bits \ 512) * 512;
|
||||
if (message_bits % 512 != 0) {
|
||||
total_bits += 512;
|
||||
}
|
||||
|
||||
component sha256 = Sha256Hash(total_bits);
|
||||
for (var i = 0; i < 6*33; i++) {
|
||||
for (var j = 0; j < 8; j++) {
|
||||
sha256.msg[8*i + 7 - j] <== binary[i].out[j]; // Num2Bits is little endian, but compressed EC key form is big endian
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = message_bits; i < total_bits; i++) {
|
||||
sha256.msg[i] <== 0;
|
||||
}
|
||||
|
||||
// Message is padded with 1, a series of 0s, then the bit length of the message https://en.wikipedia.org/wiki/SHA-2#Pseudocode:~:text=append%20a%20single%20%271%27%20bit
|
||||
// TODO: move padding calculating into upstream repo to simplify API
|
||||
for (var i = 0; i < total_bits - 64; i++) {
|
||||
if (i == 1584) {
|
||||
sha256.padded_bits[1584] <== 1;
|
||||
} else {
|
||||
sha256.padded_bits[i] <== sha256.msg[i];
|
||||
}
|
||||
}
|
||||
|
||||
component bit_length_binary = Num2Bits(64);
|
||||
bit_length_binary.in <== preimage_bit_length;
|
||||
for (var i = 0; i < 64; i++) {
|
||||
sha256.padded_bits[total_bits - i - 1] <== bit_length_binary.out[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < 256; i++) {
|
||||
out[i] <== sha256.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// We use elliptic curve points in uncompressed form to do elliptic curve arithmetic, but we use them in compressed form when
|
||||
// hashing to save constraints (as hash cost is generally parameterised in the input length).
|
||||
// Elliptic curves are symmteric about the x-axis, and for every possible x coordinate there are exactly
|
||||
|
||||
Reference in New Issue
Block a user