Feat/sumsub (#1654)

* fix: circuits and contracts

* feat: add reverse ofac logic

* feat: add onlyRole modifiers to functions

* style: replace onlyOwner reference in comment code to role-based access

* test: unskip and update governance tests for access control

* test: fix PCR0 setup in kyc test

---------

Co-authored-by: Evi Nova <tranquil_flow@protonmail.com>
This commit is contained in:
Nesopie
2026-01-27 14:49:34 +05:30
committed by GitHub
parent ba856226d8
commit bcfd284ca8
30 changed files with 242 additions and 139 deletions

View File

@@ -50,7 +50,7 @@ template VC_AND_DISCLOSE_KYC(
var country_length = COUNTRY_LENGTH();
var id_number_length = ID_NUMBER_LENGTH();
var idNumberIdx = ID_NUMBER_INDEX();
var compressed_bit_len = max_length/2;
var compressed_bit_len = max_length % 2 == 1 ? (max_length - 1)/2 : max_length / 2;
signal input data_padded[max_length];
signal input compressed_disclose_sel[2];
@@ -86,14 +86,14 @@ template VC_AND_DISCLOSE_KYC(
low_bits.in <== compressed_disclose_sel[0];
// Convert disclose_sel_high (next 133 bits) to bit array
component high_bits = Num2Bits(compressed_bit_len);
component high_bits = Num2Bits(max_length - compressed_bit_len);
high_bits.in <== compressed_disclose_sel[1];
// Combine the bit arrays (little-endian format)
for(var i = 0; i < compressed_bit_len; i++){
disclose_sel[i] <== low_bits.out[i];
}
for(var i = 0; i < compressed_bit_len; i++){
for(var i = 0; i < max_length - compressed_bit_len; i++){
disclose_sel[compressed_bit_len + i] <== high_bits.out[i];
}
@@ -135,3 +135,14 @@ template VC_AND_DISCLOSE_KYC(
signal output nullifier <== Poseidon(2)([secret, scope]);
signal output attestation_id <== 4;
}
component main {
public [
scope,
merkle_root,
ofac_name_dob_smt_root,
ofac_name_yob_smt_root,
user_identifier,
current_date
]
} = VC_AND_DISCLOSE_KYC(40, 64, 64, 33);

View File

@@ -1,15 +0,0 @@
pragma circom 2.1.9;
include "./vc_and_disclose_kyc.circom";
component main {
public [
scope,
merkle_root,
ofac_name_dob_smt_root,
ofac_name_yob_smt_root,
user_identifier,
current_date,
attestation_id
]
} = VC_AND_DISCLOSE_KYC(40, 64, 64, 33);

View File

@@ -11,6 +11,8 @@ template REGISTER_KYC() {
var max_length = KYC_MAX_LENGTH();
var country_length = COUNTRY_LENGTH();
var id_number_length = ID_NUMBER_LENGTH();
var id_type_length = ID_TYPE_LENGTH();
var id_type_index = ID_TYPE_INDEX();
var idNumberIdx = ID_NUMBER_INDEX();
signal input data_padded[max_length];
@@ -49,7 +51,22 @@ template REGISTER_KYC() {
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 nullifier_inputs[6 + id_number_length + id_type_length];
nullifier_inputs[0] <== 115; //s
nullifier_inputs[1] <== 117; //u
nullifier_inputs[2] <== 109; //m
nullifier_inputs[3] <== 115; //s
nullifier_inputs[4] <== 117; //u
nullifier_inputs[5] <== 98; //b
for (var i = 0; i < id_number_length; i++) {
nullifier_inputs[i + 6] <== id_num[i];
}
for (var i = 0; i < id_type_length; i++) {
nullifier_inputs[i + 6 + id_number_length] <== data_padded[id_type_index + i];
}
signal output nullifier <== PackBytesAndPoseidon(6 + id_number_length + id_type_length)(nullifier_inputs);
signal output commitment <== Poseidon(2)([secret, msg_hasher.out]);
signal output pubkey_hash <== Poseidon(2)([verifyIdCommSig.Ax, verifyIdCommSig.Ay]);

View File

@@ -72,20 +72,12 @@ function PHONE_NUMBER_LENGTH() {
return 12;
}
function DOCUMENT_INDEX() {
function GENDER_INDEX() {
return PHONE_NUMBER_INDEX() + PHONE_NUMBER_LENGTH();
}
function DOCUMENT_LENGTH() {
return 32;
}
function GENDER_INDEX() {
return DOCUMENT_INDEX() + DOCUMENT_LENGTH();
}
function GENDER_LENGTH() {
return 6;
return 1;
}
function ADDRESS_INDEX() {

View File

@@ -18,7 +18,7 @@ CIRCUITS=(
# "vc_and_disclose:20:true"
# "vc_and_disclose_id:20:true"
# "vc_and_disclose_aadhaar:20:true"
"vc_and_disclose_selfrica:17:true"
"vc_and_disclose_kyc:17:true"
)
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"

View File

@@ -15,7 +15,7 @@ OUTPUT_DIR="build/${CIRCUIT_TYPE}"
# Define circuits and their configurations
# format: name:poweroftau:build_flag
CIRCUITS=(
"register_selfrica:15:true"
"register_kyc:15:true"
)
build_circuits "$CIRCUIT_TYPE" "$OUTPUT_DIR" "${CIRCUITS[@]}"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -154,6 +154,33 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
deepEqual(ofac_results, ['\x00', '\x00']);
});
it('should return 0 for an OFAC person with reverse', async function () {
this.timeout(0);
const input = generateKycDiscloseInput(
true,
namedob_smt,
nameyob_smt,
tree as any,
true,
'0',
'1234567890',
undefined,
undefined,
undefined,
true,
'1234',
true
);
const witness = await circuit.calculateWitness(input);
await circuit.checkConstraints(witness);
const revealedData_packed = await getRevealedDataPacked(witness);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const ofac_results = revealedDataUnpacked.slice(maxLength, maxLength + 2);
deepEqual(ofac_results, ['\x00', '\x00']);
});
it('should return 1 for a non OFAC person', async function () {
this.timeout(0);
const input = generateKycDiscloseInput(
@@ -193,7 +220,6 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
'DOB',
'PHOTO_HASH',
'PHONE_NUMBER',
'DOCUMENT',
'GENDER',
'ADDRESS',
];
@@ -251,7 +277,6 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
'DOB',
'PHOTO_HASH',
'PHONE_NUMBER',
'DOCUMENT',
'GENDER',
'ADDRESS',
];

View File

@@ -5,7 +5,7 @@ import { packBytesAndPoseidon } from '@selfxyz/common/utils/hash';
import { poseidon2 } 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';
import { KYC_ID_NUMBER_INDEX, KYC_ID_NUMBER_LENGTH, KYC_ID_TYPE_INDEX, KYC_ID_TYPE_LENGTH } from '@selfxyz/common/utils/kyc/constants';
describe('REGISTER KYC Circuit Tests', () => {
let circuit: any;
@@ -15,7 +15,7 @@ describe('REGISTER KYC Circuit Tests', () => {
this.timeout(0);
input = await generateMockKycRegisterInput(null, true, undefined);
circuit = await wasmTester(
path.join(__dirname, '../../circuits/register/instances/register_selfrica.circom'),
path.join(__dirname, '../../circuits/register/instances/register_kyc.circom'),
{
verbose: true,
logOutput: true,
@@ -42,7 +42,8 @@ describe('REGISTER KYC Circuit Tests', () => {
KYC_ID_NUMBER_INDEX,
KYC_ID_NUMBER_INDEX + KYC_ID_NUMBER_LENGTH
);
const nullifier = packBytesAndPoseidon(idnumber.map((x) => Number(x)));
const nullifierInputs = [...'sumsub'.split('').map((x) => x.charCodeAt(0)), ...idnumber, ...input.data_padded.slice(KYC_ID_TYPE_INDEX, KYC_ID_TYPE_INDEX + KYC_ID_TYPE_LENGTH)];
const nullifier = packBytesAndPoseidon(nullifierInputs);
const commitment = poseidon2([
input.secret,
packBytesAndPoseidon(input.data_padded.map((x) => Number(x))),