Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 132 additions & 62 deletions wallet-unit-poc/circom/circuits/components/age-verifier.circom
Original file line number Diff line number Diff line change
@@ -1,126 +1,196 @@
pragma circom 2.1.6;

include "circomlib/circuits/comparators.circom";
include "@zk-email/circuits/utils/array.circom";

template AgeExtractor() {
signal input YYMMDD[7];
signal input currentYear;
signal input currentMonth;
signal input currentDay;

signal output age;

signal birthROCYear <== YYMMDD[0]*100 + YYMMDD[1]*10 + YYMMDD[2];
signal birthYear <== birthROCYear + 1911;
signal birthMonth <== YYMMDD[3]*10 + YYMMDD[4];
signal birthDay <== YYMMDD[5]*10 + YYMMDD[6];

// raw year difference
signal rawAge <== currentYear - birthYear;

// month > birthMonth?
component mGt = GreaterThan(4);
mGt.in[0] <== currentMonth;
mGt.in[1] <== birthMonth;

// month == birthMonth?
component mEq = IsEqual();
mEq.in[0] <== currentMonth;
mEq.in[1] <== birthMonth;

// currentDay ≥ birthDay?
component dGe = GreaterEqThan(5);
dGe.in[0] <== currentDay;
dGe.in[1] <== birthDay;

// had birthday this year?
signal hadBirthday;
hadBirthday <== mGt.out + mEq.out * dGe.out;

// final age = rawAge - 1 + hadBirthday
age <== rawAge - 1 + hadBirthday;
}

// ROC birthday format: "YYYMMDD" (7 digits, no separators)
template AgeVerifier(decodedLen) {
signal input claim[decodedLen]; // ASCII codes of decoded base64: ["…","roc_birthday","0750101"]
signal input claim[decodedLen];
signal input currentYear;
signal input currentMonth;
signal input currentDay;

signal output ageAbove18;


// Step 1: Find the 5th quote (opening quote of value)
component isQuoteCmp[decodedLen];
signal isQuote[decodedLen];
signal isQuote[decodedLen];
signal quoteCount[decodedLen];
for (var i = 0; i < decodedLen; i++) {
isQuoteCmp[i] = IsEqual();
isQuoteCmp[i].in[0] <== claim[i];
isQuoteCmp[i].in[1] <== 34;
isQuote[i] <== isQuoteCmp[i].out;
}

signal quoteCount[decodedLen];
quoteCount[0] <== isQuote[0];
for (var i = 1; i < decodedLen; i++) {
quoteCount[i] <== quoteCount[i-1] + isQuote[i];
isQuote[i] <== isQuoteCmp[i].out;
quoteCount[i] <== (i == 0 ? 0 : quoteCount[i-1]) + isQuote[i];
}
quoteCount[decodedLen-1] === 6;

component isThirdQuoteChar[decodedLen];
signal isThird[decodedLen];
// Step 2: Find index of 5th quote using one-hot encoding
// The 5th quote is where quoteCount transitions from 4 to 5
component isFifthQuote[decodedLen];
signal fifthQuoteIdx[decodedLen];
signal shiftAcc[decodedLen];
for (var i = 0; i < decodedLen; i++) {
isThirdQuoteChar[i] = IsEqual();
isThirdQuoteChar[i].in[0] <== quoteCount[i];
isThirdQuoteChar[i].in[1] <== 5;
isThird[i] <== isThirdQuoteChar[i].out;
isFifthQuote[i] = IsEqual();
isFifthQuote[i].in[0] <== quoteCount[i];
isFifthQuote[i].in[1] <== 5;
// One-hot: 1 only at the exact position of the 5th quote
fifthQuoteIdx[i] <== isFifthQuote[i].out * isQuote[i];
// Accumulate index: sum(i * oneHot[i]) gives the position
shiftAcc[i] <== (i == 0 ? 0 : shiftAcc[i-1]) + fifthQuoteIdx[i] * i;
}


signal thirdCount[decodedLen];
thirdCount[0] <== isThird[0];
for (var i = 1; i < decodedLen; i++) {
thirdCount[i] <== thirdCount[i-1] + isThird[i];
}
thirdCount[decodedLen - 1] === 8;// one opening-quote + seven digits YYMMDD + one closing-quote


component digitEq[7][decodedLen];
signal matchDigit[7][decodedLen];
signal digitAcc[7][decodedLen];
signal birthDigits[7];

for (var j = 0; j < 7; j++) {
for (var i = 0; i < decodedLen; i++) {
digitEq[j][i] = IsEqual();
digitEq[j][i].in[0] <== thirdCount[i];
digitEq[j][i].in[1] <== j + 2;

matchDigit[j][i] <== digitEq[j][i].out * isThird[i];

if (i == 0) {
digitAcc[j][0] <== matchDigit[j][0] * (claim[0] - 48);
} else {
digitAcc[j][i] <== digitAcc[j][i-1]
+ matchDigit[j][i] * (claim[i] - 48);
}
}
birthDigits[j] <== digitAcc[j][decodedLen - 1];
// shift = position of 5th quote + 1 (skip the quote itself to get first digit)
signal shift <== shiftAcc[decodedLen-1] + 1;

// Step 3: Use VarShiftLeft to extract 7 consecutive bytes starting at the value
component shifter = VarShiftLeft(decodedLen, 7);
for (var i = 0; i < decodedLen; i++) {
shifter.in[i] <== claim[i];
}
shifter.shift <== shift;

// Step 4: Convert ASCII digits to numeric values
signal birthDigits[7];
for (var i = 0; i < 7; i++) {
birthDigits[i] <== shifter.out[i] - 48;
}

component ageExtractor = AgeExtractor();
ageExtractor.YYMMDD <== birthDigits;
ageExtractor.currentYear <== currentYear;
ageExtractor.currentMonth <== currentMonth;
ageExtractor.currentDay <== currentDay;

// log("ageExtractor.age: ", ageExtractor.age);
component ageCheck = GreaterThan(8);
ageCheck.in[0] <== ageExtractor.age;
ageCheck.in[1] <== 18;
ageAbove18 <== ageCheck.out;
}

component ageAbove18Checker = GreaterThan(8);
ageAbove18Checker.in[0] <== ageExtractor.age;
ageAbove18Checker.in[1] <== 18;
ageAbove18 <== ageAbove18Checker.out;
template AgeExtractorISO() {
signal input digits[8]; // YYYYMMDD
signal input currentYear;
signal input currentMonth;
signal input currentDay;
signal output age;

signal birthYear <== digits[0]*1000 + digits[1]*100 + digits[2]*10 + digits[3];
signal birthMonth <== digits[4]*10 + digits[5];
signal birthDay <== digits[6]*10 + digits[7];

signal rawAge <== currentYear - birthYear;

component mGt = GreaterThan(4);
mGt.in[0] <== currentMonth;
mGt.in[1] <== birthMonth;

component mEq = IsEqual();
mEq.in[0] <== currentMonth;
mEq.in[1] <== birthMonth;

component dGe = GreaterEqThan(5);
dGe.in[0] <== currentDay;
dGe.in[1] <== birthDay;

// log("ageAbove18: ", ageAbove18);
signal hadBirthday;
hadBirthday <== mGt.out + mEq.out * dGe.out;
age <== rawAge - 1 + hadBirthday;
}

// ISO 8601 date format: "YYYY-MM-DD" (10 chars between quotes)
template AgeVerifierISO(decodedLen) {
signal input claim[decodedLen];
signal input currentYear;
signal input currentMonth;
signal input currentDay;
signal output ageAbove18;

// Step 1: Find the 5th quote
component isQuoteCmp[decodedLen];
signal isQuote[decodedLen];
signal quoteCount[decodedLen];
for (var i = 0; i < decodedLen; i++) {
isQuoteCmp[i] = IsEqual();
isQuoteCmp[i].in[0] <== claim[i];
isQuoteCmp[i].in[1] <== 34;
isQuote[i] <== isQuoteCmp[i].out;
quoteCount[i] <== (i == 0 ? 0 : quoteCount[i-1]) + isQuote[i];
}
quoteCount[decodedLen-1] === 6;

// Step 2: Find index of 5th quote
component isFifthQuote[decodedLen];
signal fifthQuoteIdx[decodedLen];
signal shiftAcc[decodedLen];
for (var i = 0; i < decodedLen; i++) {
isFifthQuote[i] = IsEqual();
isFifthQuote[i].in[0] <== quoteCount[i];
isFifthQuote[i].in[1] <== 5;
fifthQuoteIdx[i] <== isFifthQuote[i].out * isQuote[i];
shiftAcc[i] <== (i == 0 ? 0 : shiftAcc[i-1]) + fifthQuoteIdx[i] * i;
}
// shift past the opening quote to first char: "YYYY-MM-DD"
signal shift <== shiftAcc[decodedLen-1] + 1;

// Step 3: Use VarShiftLeft to extract 10 consecutive bytes (YYYY-MM-DD)
component shifter = VarShiftLeft(decodedLen, 10);
for (var i = 0; i < decodedLen; i++) {
shifter.in[i] <== claim[i];
}
shifter.shift <== shift;

// Step 4: Extract digits, skipping dashes at positions 4 and 7
// "YYYY-MM-DD" → positions 0-3: year, 4: dash, 5-6: month, 7: dash, 8-9: day
signal birthDigits[8];
birthDigits[0] <== shifter.out[0] - 48;
birthDigits[1] <== shifter.out[1] - 48;
birthDigits[2] <== shifter.out[2] - 48;
birthDigits[3] <== shifter.out[3] - 48;
shifter.out[4] === 45; // assert '-'
birthDigits[4] <== shifter.out[5] - 48;
birthDigits[5] <== shifter.out[6] - 48;
shifter.out[7] === 45; // assert '-'
birthDigits[6] <== shifter.out[8] - 48;
birthDigits[7] <== shifter.out[9] - 48;

component ageExtractor = AgeExtractorISO();
ageExtractor.digits <== birthDigits;
ageExtractor.currentYear <== currentYear;
ageExtractor.currentMonth <== currentMonth;
ageExtractor.currentDay <== currentDay;

component ageCheck = GreaterThan(8);
ageCheck.in[0] <== ageExtractor.age;
ageCheck.in[1] <== 18;
ageAbove18 <== ageCheck.out;
}
4 changes: 2 additions & 2 deletions wallet-unit-poc/circom/circuits/ecdsa/p256/mul.circom
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,10 @@ template K_add() {
signal klo <== (slo + tQlo + borrow.out * (2 ** 128)) - isQuotientOne.out * qlo;
signal khi <== (shi + tQhi - borrow.out * 1) - isQuotientOne.out * qhi;

component kloBits = Num2Bits(256);
component kloBits = Num2Bits(128);
kloBits.in <== klo;

component khiBits = Num2Bits(256);
component khiBits = Num2Bits(128);
khiBits.in <== khi;

for (var i = 0; i < 128; i++) {
Expand Down
12 changes: 6 additions & 6 deletions wallet-unit-poc/circom/scripts/compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ case "$1" in
jwt)
npx circomkit compile jwt || { echo "Error: Failed to compile JWT."; exit 1; }
cd build/jwt/ || { echo "Error: 'build/jwt/' directory not found."; exit 1; }
mv jwt.r1cs jwt_js/ || { echo "Error: Failed to move jwt.r1cs."; exit 1; }
cp jwt.r1cs jwt_js/ || { echo "Error: Failed to copy jwt.r1cs."; exit 1; }
cd ../.. || exit 1
mkdir -p build/cpp || { echo "Error: Failed to create cpp directory."; exit 1; }
[ ! -f build/cpp/jwt.cpp ] && cp build/jwt/jwt_cpp/jwt.cpp build/cpp/ || true
Expand All @@ -28,7 +28,7 @@ case "$1" in
show)
npx circomkit compile show || { echo "Error: Failed to compile Show."; exit 1; }
cd build/show/ || { echo "Error: 'build/show/' directory not found."; exit 1; }
mv show.r1cs show_js/ || { echo "Error: Failed to move show.r1cs."; exit 1; }
cp show.r1cs show_js/ || { echo "Error: Failed to copy show.r1cs."; exit 1; }
cd ../.. || exit 1
mkdir -p build/cpp || { echo "Error: Failed to create cpp directory."; exit 1; }
[ ! -f build/cpp/show.cpp ] && cp build/show/show_cpp/show.cpp build/cpp/ || true
Expand All @@ -38,7 +38,7 @@ case "$1" in
ecdsa)
npx circomkit compile ecdsa || { echo "Error: Failed to compile ECDSA."; exit 1; }
cd build/ecdsa/ || { echo "Error: 'build/ecdsa/' directory not found."; exit 1; }
mv ecdsa.r1cs ecdsa_js/ || { echo "Error: Failed to move ecdsa.r1cs."; exit 1; }
cp ecdsa.r1cs ecdsa_js/ || { echo "Error: Failed to copy ecdsa.r1cs."; exit 1; }
cd ../.. || exit 1
mkdir -p build/cpp || { echo "Error: Failed to create cpp directory."; exit 1; }
[ ! -f build/cpp/ecdsa.cpp ] && cp build/ecdsa/ecdsa_cpp/ecdsa.cpp build/cpp/ || true
Expand All @@ -49,15 +49,15 @@ case "$1" in
echo "Compiling all circuits..."
mkdir -p build/cpp || { echo "Error: Failed to create cpp directory."; exit 1; }
npx circomkit compile jwt || { echo "Error: Failed to compile JWT."; exit 1; }
cd build/jwt/ && mv jwt.r1cs jwt_js/ && cd ../.. || { echo "Error: Failed to process JWT."; exit 1; }
cd build/jwt/ && cp jwt.r1cs jwt_js/ && cd ../.. || { echo "Error: Failed to process JWT."; exit 1; }
[ ! -f build/cpp/jwt.cpp ] && cp build/jwt/jwt_cpp/jwt.cpp build/cpp/ || true
[ ! -f build/cpp/jwt.dat ] && cp build/jwt/jwt_cpp/jwt.dat build/cpp/ || true
npx circomkit compile show || { echo "Error: Failed to compile Show."; exit 1; }
cd build/show/ && mv show.r1cs show_js/ && cd ../.. || { echo "Error: Failed to process Show."; exit 1; }
cd build/show/ && cp show.r1cs show_js/ && cd ../.. || { echo "Error: Failed to process Show."; exit 1; }
[ ! -f build/cpp/show.cpp ] && cp build/show/show_cpp/show.cpp build/cpp/ || true
[ ! -f build/cpp/show.dat ] && cp build/show/show_cpp/show.dat build/cpp/ || true
npx circomkit compile ecdsa || { echo "Error: Failed to compile ECDSA."; exit 1; }
cd build/ecdsa/ && mv ecdsa.r1cs ecdsa_js/ && cd ../.. || { echo "Error: Failed to process ECDSA."; exit 1; }
cd build/ecdsa/ && cp ecdsa.r1cs ecdsa_js/ && cd ../.. || { echo "Error: Failed to process ECDSA."; exit 1; }
[ ! -f build/cpp/ecdsa.cpp ] && cp build/ecdsa/ecdsa_cpp/ecdsa.cpp build/cpp/ || true
[ ! -f build/cpp/ecdsa.dat ] && cp build/ecdsa/ecdsa_cpp/ecdsa.dat build/cpp/ || true
echo "All circuits compiled successfully."
Expand Down
Loading
Loading