Skip to content
Open
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
8 changes: 4 additions & 4 deletions .github/workflows/unit-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Update git submodules
run: git submodule update --init
Expand All @@ -31,15 +31,15 @@ jobs:
# cargo build --release

- name: Set up Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20.16.0
node-version: 20.19.0

- name: Enable Corepack
run: |
npm i -g corepack@latest
corepack enable
corepack prepare yarn@4.6.0 --activate
corepack prepare pnpm@9.12.0 --activate

- name: Install circom 2.1.9
run: |
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "zk-email-verify"]
path = zk-email-verify
url = https://github.com/olomix/zk-email-verify
[submodule "self"]
path = self
url = https://github.com/selfxyz/self
4 changes: 2 additions & 2 deletions circuits/credential/credential.circom
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ include "../utils/iden3/strings.circom";
include "../utils/passport/parser/extractors.circom";
include "../utils/passport/date/dateDiffGreaterThanYear.circom";

include "self/circuits/circuits/utils/crypto/bitify/bytes.circom";
include "self/circuits/circuits/utils/crypto/hasher/hash.circom";
include "circuits/circuits/utils/crypto/bitify/bytes.circom"; // from self
include "circuits/circuits/utils/crypto/hasher/hash.circom"; // from self
include "@openpassport/zk-email-circuits/utils/array.circom";
include "circomlib/circuits/poseidon.circom";

Expand Down
113 changes: 113 additions & 0 deletions circuits/idcard/constants.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
pragma circom 2.1.9;

// dg1TagSize size of tag before dg1 content
function dg1TagSize() {
return 5;
}

// Document code: Line 1, Position 1, Size 2 (0-indexed: 0)
function documentCodePosition() {
return 0 + dg1TagSize();
}
function documentCodeSize() {
return 2;
}

// Issuing State or organization: Line 1, Position 3, Size 3 (0-indexed: 2)
function issuingStatePosition() {
return 2 + dg1TagSize();
}
function issuingStateSize() {
return 3;
}

// Document number: Line 1, Position 6, Size 9 (0-indexed: 5)
function documentNumberPosition() {
return 5 + dg1TagSize();
}
function documentNumberSize() {
return 9;
}

// DOB: Line 2, Position 1, Size 6 (0-indexed: 30 in concatenated)
function dobPosition() {
return 30 + dg1TagSize();
}
function dobSize() {
return 6;
}

// Sex: Line 2, Position 8, Size 1 (0-indexed: 37 in concatenated)
function sexPosition() {
return 37 + dg1TagSize();
}
function sexSize() {
return 1;
}

// Date of expiry: Line 2, Position 9, Size 6 (0-indexed: 38 in concatenated)
function dateOfExpiryPosition() {
return 38 + dg1TagSize();
}
function dateOfExpirySize() {
return 6;
}

// Nationality: Line 2, Position 16, Size 3 (0-indexed: 45 in concatenated)
function nationalityPosition() {
return 45 + dg1TagSize();
}
function nationalitySize() {
return 3;
}

// Name of holder: Line 3, Size 30 (0-indexed: 60 in concatenated)
function nameOfHolderPosition() {
return 60 + dg1TagSize();
}
function nameOfHolderSize() {
return 30; // Holder size for TD1
}

function getMaxDSCLength(){
return 1792;
}

// Symbol '<' in ASCII encoding
function dg1DelimiterSymbol() {
return 60; // <
}

// Symbol ' ' (space) in ASCII encoding
function spaceSymbol() {
return 32;
}

// Size of DG1 (with tag) - TD1 has 90 characters + 5 tag bytes
function DG1_TD1_SIZE() {
return 95;
}

// Max chunks bytes for Poseidon chunk
function chunkSize() {
return 31;
}

// Max Poseidon chunks
function chunkCount() {
return 16;
}

// Max Poseidon message size
function messageMaxSize() {
return chunkSize() * chunkCount();
}

// Part of hashIndex. V0
function v0() {
return 14586288774771787882356214884390414373502;
}

function DG1_TD1_SIZE_BITS() {
return DG1_TD1_SIZE() * 8;
}
216 changes: 216 additions & 0 deletions circuits/idcard/idcard.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
pragma circom 2.1.9;

include "./constants.circom";

include "../utils/iden3/claimbuilder.circom";
include "../utils/iden3/linkId.circom";
include "../utils/iden3/poseidon.circom";
include "../utils/iden3/constants.circom";
include "../utils/iden3/numbers.circom";
include "../utils/iden3/strings.circom";
include "../utils/passport/parser/extractors.circom";
include "../utils/passport/date/dateDiffGreaterThanYear.circom";

include "circuits/circuits/utils/crypto/bitify/bytes.circom"; // from self
include "circuits/circuits/utils/crypto/hasher/hash.circom"; // from self
include "@openpassport/zk-email-circuits/utils/array.circom";
include "circomlib/circuits/poseidon.circom";

template Integrity(hashAlgo) {
signal input dg1[DG1_TD1_SIZE()];
signal output poseidonDg1Hash;

var hashAlgBytesSize = hashAlgo / 8;
signal dg1Bits[DG1_TD1_SIZE_BITS()] <== BytesToBitsArray(DG1_TD1_SIZE())(dg1);
signal dg1ShaBits[hashAlgo] <== ShaHashBits(DG1_TD1_SIZE_BITS(), hashAlgo)(dg1Bits);

signal dg1ShaBytes[hashAlgBytesSize];
dg1ShaBytes <== BitsToBytesArray(hashAlgo)(dg1ShaBits);
poseidonDg1Hash <== PaddingAndPoseidon(hashAlgBytesSize)(dg1ShaBytes);
}

template ValidateHolderNameSizeInput() {
signal input dg1[DG1_TD1_SIZE()];
signal input holderNameSize;

component rawNameOfHolder = SelectSubArray(DG1_TD1_SIZE(), nameOfHolderSize());
rawNameOfHolder.in <== dg1;
rawNameOfHolder.startIndex <== nameOfHolderPosition();
rawNameOfHolder.length <== nameOfHolderSize();

component originalDelimiterSize = CountTrailing(nameOfHolderSize());
originalDelimiterSize.string <== rawNameOfHolder.out;
originalDelimiterSize.symbol <== dg1DelimiterSymbol();
originalDelimiterSize.count + holderNameSize === nameOfHolderSize();
}

/*
The template DG1FieldParser cuts fields as from DG1 (TD1 format):
1. Document code: Line 1, Position 1, Size 2
2. Issuing State or organization: Line 1, Position 3, Size 3
3. Document number: Line 1, Position 6, Size 9
4. DOB: Line 2, Position 1, Size 6
5. Sex: Line 2, Position 8, Size 1
6. Date of expiry: Line 2, Position 9, Size 6
7. Nationality: Line 2, Position 16, Size 3
8. Name of holder: Line 3, Size 30
*/
template DG1FieldParser(hashAlgo, nLevels, smtChanges) {
signal input dg1[DG1_TD1_SIZE()];
signal input holderNameSize;

signal input credentialStatusID;
signal input credentialSubjectID;
signal input userID;

signal input linkNonce;
signal input siblings[smtChanges][nLevels];

signal output hashIndex;
signal output hashValue;
signal output linkId;

// public inputs
signal input currentDate; // Format: YYMMDD
signal input issuanceDate;
signal input templateRoot;
signal input issuer;
signal input revocationNonce;

// check if currentDate exists between 0 and 1,048,575;
// to prevent pass any value between p/2 and p-1 (negative)
component currentDateFitsTo20Bits = CheckMaxBits(20);
currentDateFitsTo20Bits.inputInteger <== currentDate;

component documentCodeExtractor = Extractor(DG1_TD1_SIZE(), documentCodePosition(), documentCodeSize());
documentCodeExtractor.dg1 <== dg1;
signal documentCodeHash <== documentCodeExtractor.hash;

component documentIssuerExtractor = Extractor(DG1_TD1_SIZE(), issuingStatePosition(), issuingStateSize());
documentIssuerExtractor.dg1 <== dg1;
signal documentIssuerHash <== documentIssuerExtractor.hash;

ValidateHolderNameSizeInput()(dg1, holderNameSize);

component holderNameExtractor = ExtractorHolder(DG1_TD1_SIZE(), nameOfHolderSize());
holderNameExtractor.dg1 <== dg1;
holderNameExtractor.start <== nameOfHolderPosition();
holderNameExtractor.end <== holderNameSize;
signal holderNameHash <== holderNameExtractor.hash;

component documentNumberExtractor = Extractor(DG1_TD1_SIZE(), documentNumberPosition(), documentNumberSize());
documentNumberExtractor.dg1 <== dg1;
signal documentNumberHash <== documentNumberExtractor.hash;

component documentNationalityExtractor = Extractor(DG1_TD1_SIZE(), nationalityPosition(), nationalitySize());
documentNationalityExtractor.dg1 <== dg1;
signal documentNationalityHash <== documentNationalityExtractor.hash;

component documentDOBExtractor = ExtractorDOB(DG1_TD1_SIZE(), dobPosition(), dobSize());
documentDOBExtractor.dg1 <== dg1;
documentDOBExtractor.currentDate <== currentDate;
signal documentDOB <== documentDOBExtractor.out;

component documentSexExtractor = Extractor(DG1_TD1_SIZE(), sexPosition(), sexSize());
documentSexExtractor.dg1 <== dg1;
signal documentSexHash <== documentSexExtractor.hash;

component documentDOEExtractor = ExtractorDOE(DG1_TD1_SIZE(), dateOfExpiryPosition(), dateOfExpirySize());
documentDOEExtractor.dg1 <== dg1;
documentDOEExtractor.currentDate <== currentDate;
signal documentDOE <== documentDOEExtractor.out;
signal documentDOETimestamp <== documentDOEExtractor.timestamp;

var keysToUpdate[smtChanges] = [
GetDateOfBirth(), // credentialSubject.dateOfBirth
GetDocumentExpirationDate(), // credentialSubject.documentExpirationDate
GetFullName(), // credentialSubject.fullName
GetGovernmentIdentifier(), // credentialSubject.governmentIdentifier
GetGovernmentIdentifierType(), // credentialSubject.governmentIdentifierType
GetSex(), // credentialSubject.sex
GetRevocationNonce(), // credentialStatus.revocationNonce
GetCredentialStatusID(), // credentialStatus.id
GetCredentialSubjectID(), // credentialSubject.id
GetExpirationDate(), // expirationDate.id
GetIssuanceDate(), // issuanceDate.id
GetIssuer(), // issuer.id
GetDocumentNationality(), // credentialSubject.nationalities
GetDocumentIssuer() // credentialSubject.nationalities
];

// check if issuanceDate exists between 0 and int64;
// to prevent pass any value between p/2 and p-1 (negative)
component issuanceDateFitsTo64Bits = CheckMaxBits(64);
issuanceDateFitsTo64Bits.inputInteger <== issuanceDate;

// issuanceDate and documentDOETimestamp are in UnixTimestamp format
signal credentialExpiration <== DateDiffGreaterThanYear()(issuanceDate, documentDOETimestamp);


// For debuging mt update
/*
log(documentDOB);
log(documentDOE); // expiration date in format YYYYMMDD == passport mrz
log(holderNameHash);
log(documentNumberHash);
log(documentCodeHash);
log(documentSexHash);
log(revocationNonce);
log(credentialStatusID);
log(credentialSubjectID);
log(credentialExpiration * 1000000000);
log(issuanceDate * 1000000000);
log(issuer);
log(documentNationalityHash);*/


var valuesToUpdate[smtChanges] = [
documentDOB, // credentialSubject.dateOfBirth
documentDOE, // credentialSubject.documentExpirationDate
holderNameHash, // credentialSubject.fullName
documentNumberHash, // credentialSubject.govermentIdentifier
documentCodeHash, // credentialSubject.governmentIdentifierType
documentSexHash, // credentialSubject.sex
revocationNonce, // credentialStatus.revocationNonce
credentialStatusID, // credentialStatus.id
credentialSubjectID, // credentialSubject.id
credentialExpiration * 1000000000, // expirationDate.id
issuanceDate * 1000000000, // issuanceDate.id
issuer, // issuer.id
documentNationalityHash, // credentialSubject.nationalities
documentIssuerHash // credentialSubject.nationalities
];

component c = ClaimRootBuilder(nLevels, smtChanges);
c.templateRoot <== templateRoot;
c.siblings <== siblings;
c.keys <== keysToUpdate;
c.values <== valuesToUpdate;

// For debuging mt root
// log(c.newRoot);

// The value was calculated using the go-iden3-core library
var i0 = v0();
component hI = Poseidon(4);
hI.inputs[0] <== i0;
hI.inputs[1] <== userID;
hI.inputs[2] <== c.newRoot;
hI.inputs[3] <== 0;

component V0Calc = V0Calculator();
V0Calc.revocation <== revocationNonce;
V0Calc.expiration <== credentialExpiration;

component hV = Poseidon(4);
hV.inputs[0] <== V0Calc.out;
hV.inputs[1] <== 0;
hV.inputs[2] <== 0;
hV.inputs[3] <== 0;

hashIndex <== hI.out;
hashValue <== hV.out;

signal poseidonDg1Hash <== Integrity(hashAlgo)(dg1);
linkId <== LinkID()(poseidonDg1Hash, linkNonce);
}
5 changes: 5 additions & 0 deletions circuits/idcard/instances/idcard_sha1.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.9;

include "../idcard.circom";

component main { public [currentDate, issuanceDate, templateRoot, issuer, revocationNonce] } = DG1FieldParser(160, 8, 14);
Comment thread
ilya-korotya marked this conversation as resolved.
5 changes: 5 additions & 0 deletions circuits/idcard/instances/idcard_sha224.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.9;

include "../idcard.circom";

component main { public [currentDate, issuanceDate, templateRoot, issuer, revocationNonce] } = DG1FieldParser(224, 8, 14);
5 changes: 5 additions & 0 deletions circuits/idcard/instances/idcard_sha256.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.9;

include "../idcard.circom";

component main { public [currentDate, issuanceDate, templateRoot, issuer, revocationNonce] } = DG1FieldParser(256, 8, 14);
Loading