mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
rework public inputs
This commit is contained in:
21
circuits/circuits/helpers/LICENSE
Normal file
21
circuits/circuits/helpers/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 zk-email
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
175
circuits/circuits/helpers/extract.circom
Normal file
175
circuits/circuits/helpers/extract.circom
Normal file
@@ -0,0 +1,175 @@
|
||||
pragma circom 2.1.2;
|
||||
include "./utils.circom";
|
||||
|
||||
// circuits in this folder copied from zk-email, credits to them
|
||||
|
||||
// A set of utils for shifting and packing signal arrays
|
||||
// Performs extraction of reveal signals and packed signals
|
||||
|
||||
// From https://github.com/iden3/circomlib/blob/master/circuits/multiplexer.circom
|
||||
function log2(a) {
|
||||
if (a == 0) {
|
||||
return 0;
|
||||
}
|
||||
var n = 1;
|
||||
var r = 1;
|
||||
while (n<a) {
|
||||
r++;
|
||||
n *= 2;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Pack size is # of chunks i.e. number of char signals that fit into a signal (default 7 but can be 30)
|
||||
template PackBytes(max_in_signals, max_out_signals, pack_size) {
|
||||
assert(max_out_signals == ((max_in_signals - 1) \ pack_size + 1)); // Packing constant is wrong
|
||||
|
||||
signal input in[max_in_signals];
|
||||
signal output out[max_out_signals];
|
||||
|
||||
component packer[max_out_signals];
|
||||
for (var i = 0; i < max_out_signals; i++) {
|
||||
packer[i] = Bytes2Packed(pack_size);
|
||||
for (var j = 0; j < pack_size; j++) {
|
||||
var reveal_idx = i * pack_size + j;
|
||||
if (reveal_idx < max_in_signals) {
|
||||
packer[i].in[j] <== in[i * pack_size + j];
|
||||
} else {
|
||||
packer[i].in[j] <== 0;
|
||||
}
|
||||
}
|
||||
out[i] <== packer[i].out;
|
||||
}
|
||||
}
|
||||
|
||||
// Shift the input left by variable size of bytes.
|
||||
// From https://demo.hedgedoc.org/s/Le0R3xUhB
|
||||
// Note that if len_bits < max_substr * C, C around 1, then
|
||||
// it's more efficient to use Sampriti's O(nk) solution instead
|
||||
template VarShiftLeft(in_array_len, out_array_len) {
|
||||
var len_bits = log2(in_array_len);
|
||||
assert(in_array_len <= (1 << len_bits));
|
||||
signal input in[in_array_len]; // x
|
||||
signal input shift; // k
|
||||
|
||||
signal output out[out_array_len]; // y
|
||||
|
||||
component n2b = Num2Bits(len_bits);
|
||||
n2b.in <== shift;
|
||||
|
||||
signal tmp[len_bits][in_array_len];
|
||||
for (var j = 0; j < len_bits; j++) {
|
||||
for (var i = 0; i < in_array_len; i++) {
|
||||
var offset = (i + (1 << j)) % in_array_len;
|
||||
// Shift left by 2^j indices if bit is 1
|
||||
if (j == 0) {
|
||||
tmp[j][i] <== n2b.out[j] * (in[offset] - in[i]) + in[i];
|
||||
} else {
|
||||
tmp[j][i] <== n2b.out[j] * (tmp[j-1][offset] - tmp[j-1][i]) + tmp[j-1][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return last row
|
||||
// TODO: Assert the rest of the values are 0
|
||||
for (var i = 0; i < out_array_len; i++) {
|
||||
out[i] <== tmp[len_bits - 1][i];
|
||||
}
|
||||
}
|
||||
|
||||
// Shift the input left by variable size of bytes.
|
||||
// Its input and output are the same as those of VarShiftLeft.
|
||||
// However, it assumes the input is the masked bytes and checks that shift is the first index of the non-masked bytes.
|
||||
template VarShiftMaskedStr(in_array_len, out_array_len) {
|
||||
signal input in[in_array_len]; // x
|
||||
signal input shift; // k
|
||||
signal output out[out_array_len] <== VarShiftLeft(in_array_len, out_array_len)(in, shift);
|
||||
|
||||
signal is_target_idx[in_array_len];
|
||||
signal prev_byte[in_array_len];
|
||||
signal is_this_zero[in_array_len];
|
||||
signal is_prev_zero[in_array_len];
|
||||
for(var i = 0; i < in_array_len; i++) {
|
||||
is_target_idx[i] <== IsEqual()([i, shift]);
|
||||
is_this_zero[i] <== IsZero()(in[i]);
|
||||
is_target_idx[i] * is_this_zero[i] === 0;
|
||||
if(i == 0) {
|
||||
is_prev_zero[i] <== 1;
|
||||
} else {
|
||||
is_prev_zero[i] <== IsZero()(in[i-1]);
|
||||
}
|
||||
is_target_idx[i] * (1 - is_prev_zero[i]) === 0;
|
||||
}
|
||||
}
|
||||
|
||||
// From https://demo.hedgedoc.org/s/Le0R3xUhB -- unused
|
||||
template ClearSubarrayAfterEndIndex(n, nBits) {
|
||||
signal input in[n]; // x
|
||||
signal input end; // k
|
||||
|
||||
signal output out[n]; // y
|
||||
|
||||
component lt[n];
|
||||
for (var i = 0; i < n; i++) {
|
||||
lt[i] = LessThan(nBits);
|
||||
lt[i].in[0] <== i;
|
||||
lt[i].in[1] <== end;
|
||||
|
||||
// y[i] = (i < k) * x[i]
|
||||
out[i] <== lt[i].out * in[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Lengths here are in signals, even though the final output array is 1/7 the size of max_substr_len
|
||||
// TODO: Maybe a better architectural decision to avoid mistakes is to require both values and assert their equality
|
||||
template ShiftAndPack(in_array_len, max_substr_len, pack_size) {
|
||||
var max_substr_len_packed = ((max_substr_len - 1) \ pack_size + 1);
|
||||
|
||||
component shifter = VarShiftLeft(in_array_len, max_substr_len);
|
||||
component packer = PackBytes(max_substr_len, max_substr_len_packed, pack_size);
|
||||
|
||||
signal input in[in_array_len];
|
||||
signal input shift;
|
||||
signal output out[max_substr_len_packed];
|
||||
|
||||
for (var i = 0; i < in_array_len; i++) {
|
||||
shifter.in[i] <== in[i];
|
||||
}
|
||||
shifter.shift <== shift;
|
||||
|
||||
// Note that this technically doesn't constrain the rest øf the bits after the max_substr_len to be 0/unmatched/unrevealed
|
||||
// Because of the constraints on signed inputs, it seems this should be OK security wise
|
||||
// But still, TODO unconstrained assert to double check they are 0
|
||||
for (var i = 0; i < max_substr_len; i++) {
|
||||
packer.in[i] <== shifter.out[i];
|
||||
}
|
||||
for (var i = 0; i < max_substr_len_packed; i++) {
|
||||
out[i] <== packer.out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Shift the input left by variable size of bytes and pack the shifted bytes into fields under pack_size.
|
||||
// Its input and output are the same as those of ShiftAndPack.
|
||||
// However, it assumes the input is the masked bytes and checks that shift is the first index of the non-masked bytes.
|
||||
template ShiftAndPackMaskedStr(in_array_len, max_substr_len, pack_size) {
|
||||
var max_substr_len_packed = ((max_substr_len - 1) \ pack_size + 1);
|
||||
|
||||
component shifter = VarShiftMaskedStr(in_array_len, max_substr_len);
|
||||
component packer = PackBytes(max_substr_len, max_substr_len_packed, pack_size);
|
||||
|
||||
signal input in[in_array_len];
|
||||
signal input shift;
|
||||
signal output out[max_substr_len_packed];
|
||||
|
||||
for (var i = 0; i < in_array_len; i++) {
|
||||
shifter.in[i] <== in[i];
|
||||
}
|
||||
shifter.shift <== shift;
|
||||
|
||||
for (var i = 0; i < max_substr_len; i++) {
|
||||
packer.in[i] <== shifter.out[i];
|
||||
}
|
||||
for (var i = 0; i < max_substr_len_packed; i++) {
|
||||
out[i] <== packer.out[i];
|
||||
}
|
||||
}
|
||||
149
circuits/circuits/helpers/utils.circom
Normal file
149
circuits/circuits/helpers/utils.circom
Normal file
@@ -0,0 +1,149 @@
|
||||
pragma circom 2.1.5;
|
||||
|
||||
include "../../node_modules/circomlib/circuits/bitify.circom";
|
||||
include "../../node_modules/circomlib/circuits/comparators.circom";
|
||||
include "../../node_modules/circomlib/circuits/mimcsponge.circom";
|
||||
include "./fp.circom";
|
||||
|
||||
// returns ceil(log2(a+1))
|
||||
function log2_ceil(a) {
|
||||
var n = a+1;
|
||||
var r = 0;
|
||||
while (n>0) {
|
||||
r++;
|
||||
n \= 2;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// returns ceil(log2(a+1))
|
||||
function count_packed(n, chunks) {
|
||||
return (n - 1) \ chunks + 1;
|
||||
}
|
||||
|
||||
// Lifted from MACI https://github.com/privacy-scaling-explorations/maci/blob/v1/circuits/circom/trees/incrementalQuinTree.circom#L29
|
||||
// Bits is ceil(log2 choices)
|
||||
template QuinSelector(choices, bits) {
|
||||
signal input in[choices];
|
||||
signal input index;
|
||||
signal output out;
|
||||
|
||||
// Ensure that index < choices
|
||||
component lessThan = LessThan(bits);
|
||||
lessThan.in[0] <== index;
|
||||
lessThan.in[1] <== choices;
|
||||
lessThan.out === 1;
|
||||
|
||||
component calcTotal = CalculateTotal(choices);
|
||||
component eqs[choices];
|
||||
|
||||
// For each item, check whether its index equals the input index.
|
||||
for (var i = 0; i < choices; i ++) {
|
||||
eqs[i] = IsEqual();
|
||||
eqs[i].in[0] <== i;
|
||||
eqs[i].in[1] <== index;
|
||||
|
||||
// eqs[i].out is 1 if the index matches. As such, at most one input to
|
||||
// calcTotal is not 0.
|
||||
calcTotal.nums[i] <== eqs[i].out * in[i];
|
||||
}
|
||||
|
||||
// Returns 0 + 0 + ... + item
|
||||
out <== calcTotal.sum;
|
||||
}
|
||||
|
||||
template CalculateTotal(n) {
|
||||
signal input nums[n];
|
||||
signal output sum;
|
||||
|
||||
signal sums[n];
|
||||
sums[0] <== nums[0];
|
||||
|
||||
for (var i=1; i < n; i++) {
|
||||
sums[i] <== sums[i - 1] + nums[i];
|
||||
}
|
||||
|
||||
sum <== sums[n - 1];
|
||||
}
|
||||
|
||||
// Written by us
|
||||
// n bytes per signal, n = 31 usually
|
||||
template Packed2Bytes(n){
|
||||
signal input in; // < 2 ^ (8 * 31)
|
||||
signal output out[n]; // each out is < 64
|
||||
// Rangecheck in and out?
|
||||
|
||||
// Constrain bits
|
||||
component nbytes = Num2Bits(8 * n);
|
||||
nbytes.in <== in;
|
||||
component bytes[n];
|
||||
|
||||
for (var k = 0; k < n; k++){
|
||||
// Witness gen out
|
||||
out[k] <-- (in >> (k * 8)) % 256;
|
||||
|
||||
// Constrain bits to match
|
||||
bytes[k] = Num2Bits(8);
|
||||
bytes[k].in <== out[k];
|
||||
for (var j = 0; j < 8; j++) {
|
||||
nbytes.out[k * 8 + j] === bytes[k].out[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// n bytes per signal, n = 31 usually (i.e. 31 8-bit values being packed into 248 bits)
|
||||
// when calling this, you must constrain each 'in' value yourself to be < 256
|
||||
// TODO: Rangecheck in and out?
|
||||
template Bytes2Packed(n){
|
||||
signal input in[n]; // each in value is < 256 (i.e. 2^8)
|
||||
signal pow2[n+1]; // [k] is 2^k
|
||||
signal in_prefix_sum[n+1]; // each [k] is in[0] + 2^8 in[1]... 2^{8k-8} in[k-1]. cont.
|
||||
// [0] is 0. [1] is in[0]. [n+1] is out.
|
||||
signal output out; // < 2 ^ (8 * 31)
|
||||
// Rangecheck in and out?
|
||||
|
||||
// Witness gen out
|
||||
in_prefix_sum[0] <-- 0;
|
||||
for (var k = 0; k < n; k++){
|
||||
in_prefix_sum[k+1] <-- in_prefix_sum[k] + in[k] * (2 ** (k * 8));
|
||||
}
|
||||
out <-- in_prefix_sum[n];
|
||||
|
||||
// Constrain out bits
|
||||
component nbytes = Num2Bits(8 * n);
|
||||
nbytes.in <== out; // I think this auto-rangechecks out to be < 8*n bits.
|
||||
component bytes[n];
|
||||
|
||||
for (var k = 0; k < n; k++){
|
||||
bytes[k] = Num2Bits(8);
|
||||
bytes[k].in <== in[k];
|
||||
for (var j = 0; j < 8; j++) {
|
||||
nbytes.out[k * 8 + j] === bytes[k].out[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// salt_is_message_id_from, custom_anon_from_hashed_salt = MakeAnonEmailSalt(max_email_from_len, max_message_id_len)(email_from, custom_message_id_from, shifted_message_id)
|
||||
template MakeAnonEmailSalt(email_len, blinder_len) {
|
||||
signal input email[email_len];
|
||||
signal input custom_message_id[blinder_len]; // previous message id, used to source past account
|
||||
signal input original_message_id[blinder_len]; // previous message id, used to source past account
|
||||
signal intermediate_is_message_id_from[blinder_len + 1];
|
||||
signal isEq[blinder_len];
|
||||
signal output blinder_matches;
|
||||
signal output anon_salt;
|
||||
|
||||
component hasher = MiMCSponge(email_len + blinder_len, 220, 1);
|
||||
hasher.k <== 123;
|
||||
for (var i = 0; i < email_len; i++) {
|
||||
hasher.ins[i] <== email[i];
|
||||
}
|
||||
intermediate_is_message_id_from[0] <== 1;
|
||||
for (var i = 0; i < blinder_len; i++) {
|
||||
hasher.ins[i + email_len] <== custom_message_id[i];
|
||||
isEq[i] <== IsEqual()([custom_message_id[i], original_message_id[i]]);
|
||||
intermediate_is_message_id_from[i + 1] <== isEq[i] * intermediate_is_message_id_from[i];
|
||||
}
|
||||
blinder_matches <== intermediate_is_message_id_from[blinder_len];
|
||||
anon_salt <== hasher.outs[0];
|
||||
}
|
||||
@@ -3,84 +3,123 @@ pragma circom 2.1.5;
|
||||
include "./rsa/rsa.circom";
|
||||
include "./sha256Bytes.circom";
|
||||
include "../node_modules/circomlib/circuits/sha256/sha256.circom";
|
||||
include "../node_modules/circomlib/circuits/poseidon.circom";
|
||||
include "./helpers/extract.circom";
|
||||
|
||||
template PassportVerifier(n, k) {
|
||||
signal input mrz[93]; // formatted mrz (5 + 88) chars
|
||||
signal input reveal_bitmap[88];
|
||||
signal input dataHashes[297];
|
||||
signal input eContentBytes[104];
|
||||
signal input mrz[93]; // formatted mrz (5 + 88) chars
|
||||
signal input reveal_bitmap[88];
|
||||
signal input dataHashes[297];
|
||||
signal input eContentBytes[104];
|
||||
|
||||
signal input pubkey[k];
|
||||
signal input signature[k];
|
||||
signal input pubkey[k];
|
||||
signal input signature[k];
|
||||
signal input address;
|
||||
|
||||
// compute sha256 of formatted mrz
|
||||
signal mrzSha[256] <== Sha256Bytes(93)(mrz);
|
||||
// compute sha256 of formatted mrz
|
||||
signal mrzSha[256] <== Sha256Bytes(93)(mrz);
|
||||
|
||||
// get output of sha256 into bytes to check against dataHashes
|
||||
component sha256_bytes[32];
|
||||
for (var i = 0; i < 32; i++) {
|
||||
sha256_bytes[i] = Bits2Num(8);
|
||||
for (var j = 0; j < 8; j++) {
|
||||
sha256_bytes[i].in[7 - j] <== mrzSha[i * 8 + j];
|
||||
}
|
||||
}
|
||||
// get output of sha256 into bytes to check against dataHashes
|
||||
component sha256_bytes[32];
|
||||
for (var i = 0; i < 32; i++) {
|
||||
sha256_bytes[i] = Bits2Num(8);
|
||||
for (var j = 0; j < 8; j++) {
|
||||
sha256_bytes[i].in[7 - j] <== mrzSha[i * 8 + j];
|
||||
}
|
||||
}
|
||||
|
||||
// check that it is in the right position in dataHashes
|
||||
for(var i = 0; i < 32; i++) {
|
||||
dataHashes[31 + i] === sha256_bytes[i].out;
|
||||
}
|
||||
// check that it is in the right position in dataHashes
|
||||
for(var i = 0; i < 32; i++) {
|
||||
dataHashes[31 + i] === sha256_bytes[i].out;
|
||||
}
|
||||
|
||||
// hash dataHashes
|
||||
signal dataHashesSha[256] <== Sha256Bytes(297)(dataHashes);
|
||||
// hash dataHashes
|
||||
signal dataHashesSha[256] <== Sha256Bytes(297)(dataHashes);
|
||||
|
||||
// get output of dataHashes sha256 into bytes to check against eContent
|
||||
component dataHashes_sha256_bytes[32];
|
||||
for (var i = 0; i < 32; i++) {
|
||||
dataHashes_sha256_bytes[i] = Bits2Num(8);
|
||||
for (var j = 0; j < 8; j++) {
|
||||
dataHashes_sha256_bytes[i].in[7 - j] <== dataHashesSha[i * 8 + j];
|
||||
}
|
||||
}
|
||||
// get output of dataHashes sha256 into bytes to check against eContent
|
||||
component dataHashes_sha256_bytes[32];
|
||||
for (var i = 0; i < 32; i++) {
|
||||
dataHashes_sha256_bytes[i] = Bits2Num(8);
|
||||
for (var j = 0; j < 8; j++) {
|
||||
dataHashes_sha256_bytes[i].in[7 - j] <== dataHashesSha[i * 8 + j];
|
||||
}
|
||||
}
|
||||
|
||||
// check that it is in the right position in eContent
|
||||
for(var i = 0; i < 32; i++) {
|
||||
eContentBytes[72 + i] === dataHashes_sha256_bytes[i].out;
|
||||
}
|
||||
// check that it is in the right position in eContent
|
||||
for(var i = 0; i < 32; i++) {
|
||||
eContentBytes[72 + i] === dataHashes_sha256_bytes[i].out;
|
||||
}
|
||||
|
||||
// hash eContentBytes
|
||||
signal eContentSha[256] <== Sha256Bytes(104)(eContentBytes);
|
||||
// hash eContentBytes
|
||||
signal eContentSha[256] <== Sha256Bytes(104)(eContentBytes);
|
||||
|
||||
// get output of eContentBytes sha256 into k chunks of n bits each
|
||||
var msg_len = (256 + n) \ n;
|
||||
// get output of eContentBytes sha256 into k chunks of n bits each
|
||||
var msg_len = (256 + n) \ n;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// verify eContentHash signature
|
||||
component rsa = RSAVerify65537(64, 32);
|
||||
for (var i = 0; i < msg_len; i++) {
|
||||
rsa.base_message[i] <== eContentHash[i].out;
|
||||
}
|
||||
for (var i = msg_len; i < k; i++) {
|
||||
rsa.base_message[i] <== 0;
|
||||
}
|
||||
rsa.modulus <== pubkey;
|
||||
rsa.signature <== signature;
|
||||
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;
|
||||
}
|
||||
|
||||
// verify eContentHash signature
|
||||
component rsa = RSAVerify65537(64, 32);
|
||||
for (var i = 0; i < msg_len; i++) {
|
||||
rsa.base_message[i] <== eContentHash[i].out;
|
||||
}
|
||||
for (var i = msg_len; i < k; i++) {
|
||||
rsa.base_message[i] <== 0;
|
||||
}
|
||||
rsa.modulus <== pubkey;
|
||||
rsa.signature <== signature;
|
||||
|
||||
signal output reveal[88];
|
||||
signal reveal[88];
|
||||
|
||||
// reveal reveal_bitmap bits of MRZ
|
||||
for (var i = 0; i < 88; i++) {
|
||||
reveal[i] <== mrz[5+i] * reveal_bitmap[i];
|
||||
}
|
||||
// reveal reveal_bitmap bits of MRZ
|
||||
for (var i = 0; i < 88; i++) {
|
||||
reveal[i] <== mrz[5+i] * reveal_bitmap[i];
|
||||
}
|
||||
|
||||
signal output reveal_packed[3] <== PackBytes(88, 3, 31)(reveal);
|
||||
|
||||
signal output nullifier;
|
||||
nullifier <== (signature[0] << 64) + signature[1];
|
||||
|
||||
// Calculate the Poseidon hash of public public key and outputs it
|
||||
// This can be used to verify the public key is correct in contract without requiring the actual key
|
||||
// We are converting pub_key (modulus) in to 9 chunks of 242 bits, assuming original n, k are 121 and 17.
|
||||
// This is because Posiedon circuit only support array of 16 elements.
|
||||
// Otherwise we would have to output the ceil(256/31) = 9 field elements of the public key
|
||||
var k2_chunked_size = k >> 1;
|
||||
if(k % 2 == 1) {
|
||||
k2_chunked_size += 1;
|
||||
}
|
||||
signal pubkey_hash_input[k2_chunked_size];
|
||||
for(var i = 0; i < k2_chunked_size; i++) {
|
||||
if(i==k2_chunked_size-1 && k2_chunked_size % 2 == 1) {
|
||||
pubkey_hash_input[i] <== pubkey[2*i];
|
||||
} else {
|
||||
pubkey_hash_input[i] <== pubkey[2*i] + (1<<n) * pubkey[2*i+1];
|
||||
}
|
||||
}
|
||||
signal output pubkey_hash <== Poseidon(k2_chunked_size)(pubkey_hash_input);
|
||||
}
|
||||
|
||||
component main{public [pubkey, signature]} = PassportVerifier(64, 32);
|
||||
component main { public [ address ] } = PassportVerifier(64, 32);
|
||||
|
||||
// Us:
|
||||
// 1 + 2 + 3 + 1
|
||||
// pubkey_hash + nullifier + reveal_packed + address
|
||||
// we take nullifier = signature[0, 1] which it 64 + 64 bits long, so chance of collision is 2^128
|
||||
|
||||
// Them:
|
||||
// 1 + 3 + 1
|
||||
// pubkey_hash + reveal_twitter_packed + address
|
||||
|
||||
// Soit on on garde la bitmap privée et on rend l'output publique => on doit sortir 8*88 bits
|
||||
// Soit on rend l'input publique et on rend seulement les output révélés publics => on doit sortir 88 bits + 8*reveal_chars bits
|
||||
@@ -1,6 +1,6 @@
|
||||
pragma circom 2.1.5;
|
||||
|
||||
include "./fp.circom";
|
||||
include "../helpers/fp.circom";
|
||||
|
||||
// Computes base^65537 mod modulus
|
||||
// Does not necessarily reduce fully mod modulus (the answer could be
|
||||
|
||||
@@ -50,16 +50,17 @@ describe('Circuit tests', function () {
|
||||
reveal_bitmap: reveal_bitmap.map(byte => String(byte)),
|
||||
dataHashes: concatenatedDataHashes.map(toUnsignedByte).map(byte => String(byte)),
|
||||
eContentBytes: passportData.eContent.map(toUnsignedByte).map(byte => String(byte)),
|
||||
signature: splitToWords(
|
||||
BigInt(bytesToBigDecimal(passportData.encryptedDigest)),
|
||||
BigInt(64),
|
||||
BigInt(32)
|
||||
),
|
||||
pubkey: splitToWords(
|
||||
BigInt(passportData.modulus),
|
||||
BigInt(64),
|
||||
BigInt(32)
|
||||
),
|
||||
signature: splitToWords(
|
||||
BigInt(bytesToBigDecimal(passportData.encryptedDigest)),
|
||||
BigInt(64),
|
||||
BigInt(32)
|
||||
),
|
||||
address: "0x9D392187c08fc28A86e1354aD63C70897165b982",
|
||||
}
|
||||
|
||||
})
|
||||
@@ -76,7 +77,7 @@ describe('Circuit tests', function () {
|
||||
console.log('proof done');
|
||||
|
||||
const revealChars = publicSignals.slice(0, 88).map((byte: string) => String.fromCharCode(parseInt(byte, 10))).join('');
|
||||
// console.log('reveal chars', revealChars);
|
||||
console.log('reveal chars', revealChars);
|
||||
|
||||
const vKey = JSON.parse(fs.readFileSync("build/verification_key.json"));
|
||||
const verified = await groth16.verify(
|
||||
@@ -134,7 +135,7 @@ describe('Circuit tests', function () {
|
||||
issuing_state: [2, 5],
|
||||
name: [5, 44],
|
||||
passport_number: [44, 52],
|
||||
nationality: [54, 57],
|
||||
nationality: [54, 56],
|
||||
date_of_birth: [57, 63],
|
||||
gender: [64, 65],
|
||||
expiry_date: [65, 71],
|
||||
@@ -174,31 +175,49 @@ describe('Circuit tests', function () {
|
||||
|
||||
console.log('proof done');
|
||||
console.log('proof:', proof);
|
||||
const revealChars = publicSignals.slice(0, 88).map((byte: string) => String.fromCharCode(parseInt(byte, 10)))
|
||||
console.log('publicSignals', publicSignals)
|
||||
|
||||
console.log('revealChars', revealChars)
|
||||
const firstThreeElements = publicSignals.slice(0, 3);
|
||||
const bytesCount = [31, 31, 26]; // bytes for each of the first three elements
|
||||
|
||||
for(let i = 0; i < revealChars.length; i++) {
|
||||
if (bitmap[i] == '1') {
|
||||
assert(revealChars[i] != '\x00', 'Should reveal');
|
||||
} else {
|
||||
assert(revealChars[i] == '\x00', 'Should not reveal');
|
||||
}
|
||||
}
|
||||
|
||||
const reveal: Record<string, string | undefined> = {};
|
||||
|
||||
Object.keys(attributeToPosition).forEach((attribute) => {
|
||||
if (attributeToReveal[attribute]) {
|
||||
const [start, end] = attributeToPosition[attribute];
|
||||
const value = revealChars.slice(start, end + 1).join('');
|
||||
reveal[attribute] = value;
|
||||
} else {
|
||||
reveal[attribute] = undefined;
|
||||
}
|
||||
const bytesArray = firstThreeElements.flatMap((element, index) => {
|
||||
const bytes = bytesCount[index];
|
||||
const elementBigInt = BigInt(element);
|
||||
const byteMask = BigInt(255); // 0xFF
|
||||
|
||||
const bytesOfElement = [...Array(bytes)].map((_, byteIndex) => {
|
||||
return (elementBigInt >> (BigInt(byteIndex) * BigInt(8))) & byteMask;
|
||||
});
|
||||
|
||||
return bytesOfElement.reverse();
|
||||
});
|
||||
const result = bytesArray.reverse().map((byte) => String.fromCharCode(Number(byte)));
|
||||
|
||||
console.log('reveal', reveal)
|
||||
console.log(result);
|
||||
|
||||
// console.log('revealChars', revealChars)
|
||||
|
||||
// for(let i = 0; i < revealChars.length; i++) {
|
||||
// if (bitmap[i] == '1') {
|
||||
// assert(revealChars[i] != '\x00', 'Should reveal');
|
||||
// } else {
|
||||
// assert(revealChars[i] == '\x00', 'Should not reveal');
|
||||
// }
|
||||
// }
|
||||
|
||||
// const reveal: Record<string, string | undefined> = {};
|
||||
|
||||
// Object.keys(attributeToPosition).forEach((attribute) => {
|
||||
// if (attributeToReveal[attribute]) {
|
||||
// const [start, end] = attributeToPosition[attribute];
|
||||
// const value = revealChars.slice(start, end + 1).join('');
|
||||
// reveal[attribute] = value;
|
||||
// } else {
|
||||
// reveal[attribute] = undefined;
|
||||
// }
|
||||
// });
|
||||
|
||||
// console.log('reveal', reveal)
|
||||
|
||||
const vKey = JSON.parse(fs.readFileSync("build/verification_key.json"));
|
||||
const verified = await groth16.verify(
|
||||
|
||||
Reference in New Issue
Block a user