feat: register kyc circuit using tee secret

This commit is contained in:
vishal
2025-11-21 00:42:10 +05:30
parent 0c3891fdce
commit fb6e900bc0
7 changed files with 23 additions and 352 deletions

View File

@@ -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]);
}

View File

@@ -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;
}

View File

@@ -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];
}
}

View File

@@ -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');
}
});
});

View File

@@ -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",

View File

@@ -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',
};

View File

@@ -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,
}