mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
feat: register kyc circuit using tee secret
This commit is contained in:
@@ -3,7 +3,7 @@ pragma circom 2.1.9;
|
||||
include "circomlib/circuits/bitify.circom";
|
||||
include "../utils/kyc/constants.circom";
|
||||
include "../utils/passport/customHashers.circom";
|
||||
include "../utils/kyc/verifySignature.circom";
|
||||
|
||||
|
||||
|
||||
template REGISTER_KYC() {
|
||||
@@ -13,17 +13,9 @@ template REGISTER_KYC() {
|
||||
var id_number_length = ID_NUMBER_LENGTH();
|
||||
var idNumberIdx = ID_NUMBER_INDEX();
|
||||
|
||||
var compressed_bit_len = max_length/2;
|
||||
|
||||
signal input data_padded[max_length];
|
||||
|
||||
signal input s;
|
||||
signal input Tx;
|
||||
signal input Ty;
|
||||
signal input pubKeyX;
|
||||
signal input pubKeyY;
|
||||
signal input neg_r_inv[4];
|
||||
signal input secret;
|
||||
signal input tee_secret;
|
||||
signal input user_secret;
|
||||
signal input attestation_id;
|
||||
|
||||
//Calculate msg_hash
|
||||
@@ -37,37 +29,14 @@ template REGISTER_KYC() {
|
||||
bit_decompose.in <== msg_hasher.out;
|
||||
signal msg_hash_bits[256] <== bit_decompose.out;
|
||||
|
||||
|
||||
signal msg_hash_limbs[4];
|
||||
component bits2Num[4];
|
||||
|
||||
// Convert msg_hash_bits (little-endian) to 4 LE limbs
|
||||
for (var i = 0; i < 4; i++) {
|
||||
bits2Num[i] = Bits2Num(64);
|
||||
for (var j = 0; j < 64; j++) {
|
||||
bits2Num[i].in[j] <== msg_hash_bits[i * 64 + j];
|
||||
}
|
||||
msg_hash_limbs[i] <== bits2Num[i].out;
|
||||
}
|
||||
|
||||
|
||||
component verifyIdCommSig = VERIFY_KYC_SIGNATURE();
|
||||
verifyIdCommSig.s <== s;
|
||||
verifyIdCommSig.neg_r_inv <== neg_r_inv;
|
||||
verifyIdCommSig.msg_hash_limbs <== msg_hash_limbs;
|
||||
verifyIdCommSig.Tx <== Tx;
|
||||
verifyIdCommSig.Ty <== Ty;
|
||||
verifyIdCommSig.pubKeyX <== pubKeyX;
|
||||
verifyIdCommSig.pubKeyY <== pubKeyY;
|
||||
|
||||
signal id_num[id_number_length];
|
||||
for (var i = 0; i < id_number_length; i++) {
|
||||
id_num[i] <== data_padded[idNumberIdx + i];
|
||||
}
|
||||
signal output nullifier <== PackBytesAndPoseidon(id_number_length)(id_num);
|
||||
signal output commitment <== Poseidon(2)([secret, msg_hasher.out]);
|
||||
|
||||
signal output pubkey_hash <== Poseidon(2)([pubKeyX, pubKeyY]);
|
||||
signal output nullifier <== PackBytesAndPoseidon(id_number_length)(id_num);
|
||||
signal output commitment <== Poseidon(2)([user_secret, msg_hasher.out]);
|
||||
signal output tee_secret_hash <== Poseidon(1)([tee_secret]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
// --------------------------------------------------
|
||||
// Source: https://github.com/cursive-team/babyjubjub-ecdsa
|
||||
// File: packages/circuits/baby-jubjub-ecdsa/baby_jubjub_ecdsa.circom
|
||||
// License: MIT
|
||||
// Author(s): cursive-team
|
||||
// Changes: no changes
|
||||
// --------------------------------------------------
|
||||
pragma circom 2.1.9;
|
||||
|
||||
include "circomlib/circuits/babyjub.circom";
|
||||
include "circomlib/circuits/bitify.circom";
|
||||
include "circomlib/circuits/escalarmulany.circom";
|
||||
/**
|
||||
* BabyJubJubECDSA
|
||||
* ====================
|
||||
*
|
||||
* Converts inputted efficient ECDSA signature to an public key. There is no
|
||||
* public key validation included. Takes in points in Twisted Edwards form
|
||||
* and uses Edwards addition and scalar multiplication. Returns computed
|
||||
* public key in Edwards form.
|
||||
*/
|
||||
template BabyJubJubECDSA() {
|
||||
var bits = 256;
|
||||
signal input s;
|
||||
signal input Tx; // T = r^-1 * R
|
||||
signal input Ty; // T is represented in Twisted Edwards form
|
||||
signal input Ux; // U = -(m * r^-1 * G)
|
||||
signal input Uy; // U is represented in Twisted Edwards form
|
||||
|
||||
signal output pubKeyX; // Represented in Twisted Edwards form
|
||||
signal output pubKeyY;
|
||||
|
||||
// bitify s
|
||||
component sBits = Num2Bits(bits);
|
||||
sBits.in <== s;
|
||||
|
||||
// check T, U are on curve
|
||||
component checkT = BabyCheck();
|
||||
checkT.x <== Tx;
|
||||
checkT.y <== Ty;
|
||||
component checkU = BabyCheck();
|
||||
checkU.x <== Ux;
|
||||
checkU.y <== Uy;
|
||||
|
||||
// sMultT = s * T
|
||||
component sMultT = EscalarMulAny(bits);
|
||||
var i;
|
||||
for (i=0; i<bits; i++) {
|
||||
sMultT.e[i] <== sBits.out[i];
|
||||
}
|
||||
sMultT.p[0] <== Tx;
|
||||
sMultT.p[1] <== Ty;
|
||||
|
||||
// pubKey = sMultT + U
|
||||
component pubKey = BabyAdd();
|
||||
pubKey.x1 <== sMultT.out[0];
|
||||
pubKey.y1 <== sMultT.out[1];
|
||||
pubKey.x2 <== Ux;
|
||||
pubKey.y2 <== Uy;
|
||||
|
||||
pubKeyX <== pubKey.xout;
|
||||
pubKeyY <== pubKey.yout;
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
pragma circom 2.1.9;
|
||||
|
||||
include "circomlib/circuits/poseidon.circom";
|
||||
include "circomlib/circuits/escalarmulfix.circom";
|
||||
include "circomlib/circuits/bitify.circom";
|
||||
include "circomlib/circuits/compconstant.circom";
|
||||
include "circomlib/circuits/comparators.circom";
|
||||
include "@openpassport/zk-email-circuits/lib/bigint.circom";
|
||||
include "./babyEcdsa.circom";
|
||||
include "../crypto/bigInt/bigInt.circom";
|
||||
|
||||
template VERIFY_KYC_SIGNATURE(){
|
||||
|
||||
signal input s;
|
||||
signal input neg_r_inv[4];
|
||||
signal input msg_hash_limbs[4];
|
||||
signal input Tx;
|
||||
signal input Ty;
|
||||
signal input pubKeyX;
|
||||
signal input pubKeyY;
|
||||
|
||||
|
||||
var SUBGROUP_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041; //(251 bits)
|
||||
var BASE8[2] = [
|
||||
5299619240641551281634865583518297030282874472190772894086521144482721001553,
|
||||
16950150798460657717958625567821834550301663161624707787222815936182638968203
|
||||
];
|
||||
|
||||
component computes2bits = Num2Bits(254);
|
||||
computes2bits.in <== s;
|
||||
|
||||
// asserts s is a 251 bit number
|
||||
for(var i = 0; i < 3; i++){
|
||||
computes2bits.out[251 + i] === 0;
|
||||
}
|
||||
|
||||
|
||||
// Check s should be less than SUBGROUPT_ORDER - 1
|
||||
component compConst = CompConstant(SUBGROUP_ORDER - 1);
|
||||
compConst.in <== computes2bits.out;
|
||||
compConst.out === 0;
|
||||
|
||||
// Check if s is 0
|
||||
signal is_s_zero <== IsZero()(s);
|
||||
is_s_zero === 0;
|
||||
|
||||
|
||||
signal scalar_mod[4];
|
||||
// SUBGROUP ORDER in limbs
|
||||
scalar_mod[0] <== 7454187305358665457;
|
||||
scalar_mod[1] <== 12339561404529962506;
|
||||
scalar_mod[2] <== 3965992003123030795;
|
||||
scalar_mod[3] <== 435874783350371333;
|
||||
|
||||
//range check on neg_r_inv[i] < 2 ^ 64
|
||||
component range_check_neg_r_inv[4];
|
||||
for(var i = 0; i < 4; i++){
|
||||
range_check_neg_r_inv[i] = Num2Bits(64);
|
||||
range_check_neg_r_inv[i].in <== neg_r_inv[i];
|
||||
}
|
||||
|
||||
//Check is - r_inv < scalar_mod
|
||||
component scalar_range_check = BigLessThan(64,4);
|
||||
scalar_range_check.a <== neg_r_inv;
|
||||
scalar_range_check.b <== scalar_mod;
|
||||
scalar_range_check.out === 1 ;
|
||||
|
||||
//msg_hash % SUBORDER
|
||||
component msgReduced = BigMultModP(64, 4, 4, 4);
|
||||
for(var i = 0; i < 4; i++){
|
||||
msgReduced.in1[i]<== msg_hash_limbs[i];
|
||||
if(i == 0) {
|
||||
msgReduced.in2[i]<== 1;
|
||||
}
|
||||
else{
|
||||
msgReduced.in2[i]<== 0;
|
||||
}
|
||||
msgReduced.modulus[i]<== scalar_mod[i];
|
||||
}
|
||||
|
||||
// calculates (- r_inv * msg_hash) % SUBGROUP_ORDER
|
||||
component neg_r_inv_msg_hash = BabyScalarMul();
|
||||
for(var i = 0 ;i < 4 ;i++) {
|
||||
neg_r_inv_msg_hash.in1[i] <== neg_r_inv[i];
|
||||
neg_r_inv_msg_hash.in2[i] <== msgReduced.mod[i];
|
||||
}
|
||||
|
||||
signal neg_r_inv_msg_hash_bits[256];
|
||||
component num2bits[4];
|
||||
|
||||
// convert neg_r_inv_msg_hash limbs to bits
|
||||
for (var i = 0; i < 4; i++){
|
||||
num2bits[i]= Num2Bits(64);
|
||||
num2bits[i].in <== neg_r_inv_msg_hash.out[i];
|
||||
for(var j = 0; j < 64; j++){
|
||||
neg_r_inv_msg_hash_bits[i * 64 +j] <== num2bits[i].out[j];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
component mulFix = EscalarMulFix(254, BASE8);
|
||||
for (var i = 0; i < 254; i++) {
|
||||
mulFix.e[i] <== neg_r_inv_msg_hash_bits[i];
|
||||
}
|
||||
|
||||
component ecdsa = BabyJubJubECDSA();
|
||||
ecdsa.Tx <== Tx;
|
||||
ecdsa.Ty <== Ty;
|
||||
ecdsa.Ux <== mulFix.out[0];
|
||||
ecdsa.Uy <== mulFix.out[1];
|
||||
ecdsa.s <== s;
|
||||
|
||||
ecdsa.pubKeyX === pubKeyX;
|
||||
ecdsa.pubKeyY === pubKeyY;
|
||||
|
||||
}
|
||||
|
||||
|
||||
template BabyScalarMul(){
|
||||
signal input in1[4];
|
||||
signal input in2[4];
|
||||
|
||||
signal output out[4];
|
||||
|
||||
signal scalar_mod[4];
|
||||
//2736030358979909402780800718157159386076813972158567259200215660948447373041(SUBGROUP ORDER)
|
||||
scalar_mod[0] <== 7454187305358665457;
|
||||
scalar_mod[1] <== 12339561404529962506;
|
||||
scalar_mod[2] <== 3965992003123030795;
|
||||
scalar_mod[3] <== 435874783350371333;
|
||||
|
||||
component mulmod = BigMultModP(64,4,4,4);
|
||||
|
||||
for(var i = 0; i < 4; i++){
|
||||
mulmod.in1[i]<== in1[i];
|
||||
mulmod.in2[i]<== in2[i];
|
||||
mulmod.modulus[i]<== scalar_mod[i];
|
||||
}
|
||||
for(var i = 0; i < 4 ; i++){
|
||||
out[i] <== mulmod.mod[i];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { expect } from 'chai';
|
||||
import { wasm as wasmTester } from 'circom_tester';
|
||||
import path from 'path';
|
||||
import { packBytesAndPoseidon } from '@selfxyz/common/utils/hash';
|
||||
import { poseidon2 } from 'poseidon-lite';
|
||||
import { poseidon2 , poseidon1} from 'poseidon-lite';
|
||||
import { generateMockKycRegisterInput } from '@selfxyz/common/utils/kyc/generateInputs.js';
|
||||
import { KycRegisterInput } from '@selfxyz/common/utils/kyc/types';
|
||||
import { KYC_ID_NUMBER_INDEX, KYC_ID_NUMBER_LENGTH } from '@selfxyz/common/utils/kyc/constants';
|
||||
@@ -13,7 +13,7 @@ describe('REGISTER KYC Circuit Tests', () => {
|
||||
|
||||
before(async function () {
|
||||
this.timeout(0);
|
||||
input = generateMockKycRegisterInput(null, true, undefined);
|
||||
input = generateMockKycRegisterInput(true);
|
||||
circuit = await wasmTester(
|
||||
path.join(__dirname, '../../circuits/register/register_kyc.circom'),
|
||||
{
|
||||
@@ -34,13 +34,20 @@ describe('REGISTER KYC Circuit Tests', () => {
|
||||
const w = await circuit.calculateWitness(input);
|
||||
await circuit.checkConstraints(w);
|
||||
});
|
||||
|
||||
it('should generate the correct tee_secret_hash', async function () {
|
||||
this.timeout(0);
|
||||
const w = await circuit.calculateWitness(input);
|
||||
const tee_secret_hash = poseidon1(["1234"]);
|
||||
await circuit.checkConstraints(w);
|
||||
const caltee_secret_hash = (await circuit.getOutput(w, ['tee_secret_hash'])).tee_secret_hash;
|
||||
expect(tee_secret_hash.toString()).to.be.equal(caltee_secret_hash);
|
||||
});
|
||||
it('should generate the correct nullifier and commitment', async function () {
|
||||
this.timeout(0);
|
||||
|
||||
let idnumber = input.data_padded.slice(KYC_ID_NUMBER_INDEX, KYC_ID_NUMBER_INDEX + KYC_ID_NUMBER_LENGTH);
|
||||
const nullifier = packBytesAndPoseidon(idnumber.map((x) => Number(x)));
|
||||
const commitment = poseidon2([input.secret, packBytesAndPoseidon(input.data_padded.map((x) => Number(x)))]);
|
||||
const commitment = poseidon2([input.user_secret, packBytesAndPoseidon(input.data_padded.map((x) => Number(x)))]);
|
||||
|
||||
const w = await circuit.calculateWitness(input);
|
||||
await circuit.checkConstraints(w);
|
||||
@@ -49,35 +56,9 @@ describe('REGISTER KYC Circuit Tests', () => {
|
||||
expect(nullifier.toString()).to.be.equal(calnullifier);
|
||||
expect(commitment.toString()).to.be.equal(calcommitment);
|
||||
});
|
||||
|
||||
it('should not verify if the signature is invalid', async function () {
|
||||
this.timeout(0);
|
||||
input.s = (BigInt(input.s) + BigInt(1)).toString();
|
||||
try {
|
||||
const w = await circuit.calculateWitness(input);
|
||||
await circuit.checkConstraints(w);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail if data is tampered', async function () {
|
||||
this.timeout(0);
|
||||
input = generateMockKycRegisterInput(null, true, undefined);
|
||||
input.data_padded[5] = (Number(input.data_padded[5]) + 1).toString();
|
||||
try {
|
||||
const w = await circuit.calculateWitness(input);
|
||||
await circuit.checkConstraints(w);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail if data is not bytes', async function () {
|
||||
this.timeout(0);
|
||||
input = generateMockKycRegisterInput(null, true, undefined);
|
||||
input = generateMockKycRegisterInput(true);
|
||||
input.data_padded[5] = '8000';
|
||||
try {
|
||||
const w = await circuit.calculateWitness(input);
|
||||
@@ -87,43 +68,4 @@ describe('REGISTER KYC Circuit Tests', () => {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail if s is greater than subgroup order', async function () {
|
||||
this.timeout(0);
|
||||
input.s = "2736030358979909402780800718157159386076813972158567259200215660948447373041";
|
||||
try {
|
||||
const w = await circuit.calculateWitness(input);
|
||||
await circuit.checkConstraints(w);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail if s is 0', async function () {
|
||||
this.timeout(0);
|
||||
input = generateMockKycRegisterInput(null, true, undefined);
|
||||
input.s = '0';
|
||||
try {
|
||||
const w = await circuit.calculateWitness(input);
|
||||
await circuit.checkConstraints(w);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail if r_inv is greater than scalar field', async function () {
|
||||
this.timeout(0);
|
||||
input = generateMockKycRegisterInput(null, true, undefined);
|
||||
input.neg_r_inv = ["7454187305358665460", "12339561404529962506", "3965992003123030795", "435874783350371333"];
|
||||
|
||||
try {
|
||||
const w = await circuit.calculateWitness(input);
|
||||
await circuit.checkConstraints(w);
|
||||
expect.fail('Expected an error but none was thrown.');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Assert Failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -687,7 +687,6 @@
|
||||
"@openpassport/zk-kit-smt": "^0.0.1",
|
||||
"@peculiar/x509": "^1.12.3",
|
||||
"@stablelib/cbor": "^2.0.1",
|
||||
"@zk-kit/baby-jubjub": "^1.0.3",
|
||||
"asn1.js": "^5.4.1",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.7.2",
|
||||
|
||||
@@ -18,9 +18,6 @@ import {
|
||||
KycField,
|
||||
} from './constants.js';
|
||||
import { poseidon2 } from 'poseidon-lite';
|
||||
import { Base8, inCurve, mulPointEscalar, subOrder } from '@zk-kit/baby-jubjub';
|
||||
import { signECDSA, verifyECDSA, verifyEffECDSA } from './ecdsa/ecdsa.js';
|
||||
import { bigintTo64bitLimbs, getEffECDSAArgs, modInv, modulus } from './ecdsa/utils.js';
|
||||
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||
import { packBytesAndPoseidon } from '../hash.js';
|
||||
import { COMMITMENT_TREE_DEPTH } from '../../constants/constants.js';
|
||||
@@ -69,40 +66,15 @@ export const createKycDiscloseSelFromFields = (fieldsToReveal: KycField[]): stri
|
||||
};
|
||||
|
||||
|
||||
export const generateMockKycRegisterInput = (secretKey?: bigint, ofac?: boolean, secret?: string, attestationId?: string) => {
|
||||
export const generateMockKycRegisterInput = (ofac?: boolean, userSecret?: string, teeSecret?: string, attestationId?: string) => {
|
||||
const kycData = ofac ? OFAC_DUMMY_INPUT : NON_OFAC_DUMMY_INPUT;
|
||||
const serializedData = serializeKycData(kycData).padEnd(KYC_MAX_LENGTH, '\0');
|
||||
|
||||
const msgPadded = Array.from(serializedData, (x) => x.charCodeAt(0));
|
||||
|
||||
const sk = secretKey ? secretKey : BigInt(Math.floor(Math.random() * Number(subOrder - 2n))) + 1n;
|
||||
|
||||
const pk = mulPointEscalar(Base8, sk);
|
||||
console.assert(inCurve(pk), 'Point pk not on curve');
|
||||
console.assert(pk[0] != 0n && pk[1] != 0n, 'pk is zero');
|
||||
|
||||
const sig = signECDSA(sk, msgPadded);
|
||||
console.assert(verifyECDSA(msgPadded, sig, pk) == true, 'Invalid signature');
|
||||
|
||||
let { T, U } = getEffECDSAArgs(msgPadded, sig);
|
||||
console.assert(verifyEffECDSA(sig.s, T, U, pk) == true, 'Invalid signature');
|
||||
|
||||
console.assert(sig.s < subOrder, ' s is greater than scalar field');
|
||||
console.assert(inCurve(T), 'Point T not on curve');
|
||||
console.assert(inCurve(U), 'Point U not on curve');
|
||||
|
||||
const rInv = modInv(sig.R[0], subOrder);
|
||||
const neg_rInvLimbs = bigintTo64bitLimbs(modulus(-rInv, subOrder));
|
||||
|
||||
const kycRegisterInput: KycRegisterInput = {
|
||||
data_padded: msgPadded.map((x) => x.toString()),
|
||||
s: sig.s.toString(),
|
||||
Tx: T[0].toString(),
|
||||
Ty: T[1].toString(),
|
||||
pubKeyX: pk[0].toString(),
|
||||
pubKeyY: pk[1].toString(),
|
||||
neg_r_inv: neg_rInvLimbs.map((x) => x.toString()),
|
||||
secret: secret || "1234",
|
||||
tee_secret: teeSecret || "1234",
|
||||
user_secret: userSecret || "1234",
|
||||
attestation_id: attestationId || '4',
|
||||
};
|
||||
|
||||
|
||||
@@ -41,13 +41,8 @@ export const serializeKycData = (kycData: KycData) => {
|
||||
|
||||
export type KycRegisterInput = {
|
||||
data_padded: string[],
|
||||
s: string,
|
||||
Tx: string,
|
||||
Ty: string,
|
||||
pubKeyX: string,
|
||||
pubKeyY: string,
|
||||
neg_r_inv: string[],
|
||||
secret: string,
|
||||
tee_secret: string,
|
||||
user_secret: string,
|
||||
attestation_id: string,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user