Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/rsassapss #136

Merged
merged 9 commits into from
Jun 22, 2024
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
4 changes: 2 additions & 2 deletions app/ios/ProofOfPassport.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@
CODE_SIGN_ENTITLEMENTS = ProofOfPassport/ProofOfPassport.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 46;
CURRENT_PROJECT_VERSION = 47;
DEVELOPMENT_TEAM = 5B29R5LYHQ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
Expand Down Expand Up @@ -743,7 +743,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ProofOfPassport/ProofOfPassport.entitlements;
CURRENT_PROJECT_VERSION = 46;
CURRENT_PROJECT_VERSION = 47;
DEVELOPMENT_TEAM = 5B29R5LYHQ;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
Expand Down
2 changes: 1 addition & 1 deletion circuits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Proof of Passport currently supports the following sig/hash algorithms:

- [x] sha256WithRSAEncryption
- [x] sha1WithRSAEncryption
- [ ] rsassaPss (under development)
- [x] sha256WithRSASSAPSS
- [ ] ecdsa-with-SHA384
- [ ] ecdsa-with-SHA1
- [ ] ecdsa-with-SHA256
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
pragma circom 2.1.5;

// include "@zk-email/circuits/lib/rsa.circom";
include "@zk-email/circuits/utils/bytes.circom";
include "@zk-email/circuits/lib/sha.circom";
include "@zk-email/circuits/utils/array.circom";
include "./utils/Sha256BytesStatic.circom";
include "./utils/RSASSAPSS.circom";


template PassportVerifier_sha256WithRSASSAPSS_65537(n, k, max_datahashes_bytes) {
var hashLen = 32;
var eContentBytesLength = 72 + hashLen; // 104

signal input mrz[93]; // formatted mrz (5 + 88) chars
signal input dg1_hash_offset;
signal input dataHashes[max_datahashes_bytes];
signal input datahashes_padded_length;
signal input eContentBytes[eContentBytesLength];

// pubkey that signed the passport
signal input pubkey[k];

// signature of the passport
signal input signature[k];

// compute sha256 of formatted mrz
signal mrzSha[256] <== Sha256BytesStatic(93)(mrz);

// mrzSha_bytes: list of 32 Bits2Num
component mrzSha_bytes[hashLen];

// cast the 256 bits from mrzSha into a list of 32 bytes
for (var i = 0; i < hashLen; i++) {
mrzSha_bytes[i] = Bits2Num(8);

for (var j = 0; j < 8; j++) {
mrzSha_bytes[i].in[7 - j] <== mrzSha[i * 8 + j];
}
}

// assert mrz_hash equals the one extracted from dataHashes input (bytes dg1_hash_offset to dg1_hash_offset + hashLen)
signal dg1Hash[hashLen] <== SelectSubArray(max_datahashes_bytes, hashLen)(dataHashes, dg1_hash_offset, hashLen);
for(var i = 0; i < hashLen; i++) {
dg1Hash[i] === mrzSha_bytes[i].out;
}

// hash dataHashes dynamically
signal dataHashesSha[256] <== Sha256Bytes(max_datahashes_bytes)(dataHashes, datahashes_padded_length);

// get output of dataHashes sha256 into bytes to check against eContent
component dataHashesSha_bytes[hashLen];
for (var i = 0; i < hashLen; i++) {
dataHashesSha_bytes[i] = Bits2Num(8);
for (var j = 0; j < 8; j++) {
dataHashesSha_bytes[i].in[7 - j] <== dataHashesSha[i * 8 + j];
}
}

// assert dataHashesSha is in eContentBytes in range bytes 72 to 104
for(var i = 0; i < hashLen; i++) {
eContentBytes[eContentBytesLength - hashLen + i] === dataHashesSha_bytes[i].out;
}

// decode signature to get encoded message
component rsaDecode = RSASSAPSS_Decode(n, k);
rsaDecode.signature <== signature;
rsaDecode.modulus <== pubkey;
signal encodedMessage[(n*k) \ 8] <== rsaDecode.eM;

// verify eContent signature
component rsaVerify = RSASSAPSSVerify_SHA256(n*k, eContentBytesLength);
rsaVerify.eM <== encodedMessage;
rsaVerify.message <== eContentBytes;
}
61 changes: 61 additions & 0 deletions circuits/circuits/register_sha256WithRSASSAPSS_65537.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
pragma circom 2.1.5;

include "circomlib/circuits/poseidon.circom";
include "@zk-email/circuits/utils/bytes.circom";
include "./passport_verifier_sha256WithRSASSAPSS_65537.circom";
include "./utils/chunk_data.circom";
include "./utils/compute_pubkey_leaf.circom";
include "binary-merkle-root.circom";

template register_sha256WithRSASSAPSS_65537(n, k, max_datahashes_bytes, nLevels, signatureAlgorithm) {
signal input secret;

signal input mrz[93];
signal input dg1_hash_offset;
signal input econtent[max_datahashes_bytes];
signal input datahashes_padded_length;
signal input signed_attributes[104];
signal input signature[k];

signal input pubkey[k];
signal input merkle_root;
signal input path[nLevels];
signal input siblings[nLevels];

signal input attestation_id;

// Verify inclusion of the pubkey in the pubkey tree
signal leaf <== ComputePubkeyLeaf(n, k, signatureAlgorithm)(pubkey);
signal computed_merkle_root <== BinaryMerkleRoot(nLevels)(leaf, nLevels, path, siblings);
merkle_root === computed_merkle_root;

// Verify passport validity
component PV = PassportVerifier_sha256WithRSASSAPSS_65537(n, k, max_datahashes_bytes);
PV.mrz <== mrz;
PV.dg1_hash_offset <== dg1_hash_offset;
PV.dataHashes <== econtent;
PV.datahashes_padded_length <== datahashes_padded_length;
PV.eContentBytes <== signed_attributes;
PV.pubkey <== pubkey;
PV.signature <== signature;

// Generate the commitment
component poseidon_hasher = Poseidon(6);
poseidon_hasher.inputs[0] <== secret;
poseidon_hasher.inputs[1] <== attestation_id;
poseidon_hasher.inputs[2] <== leaf;

signal mrz_packed[3] <== PackBytes(93)(mrz);
for (var i = 0; i < 3; i++) {
poseidon_hasher.inputs[i + 3] <== mrz_packed[i];
}
signal output commitment <== poseidon_hasher.out;

// Generate the nullifier
var chunk_size = 11; // Since ceil(32 / 3) in integer division is 11
signal chunked_signature[chunk_size] <== ChunkData(n, k, chunk_size)(signature);
signal output nullifier <== Poseidon(chunk_size)(chunked_signature);
}

// We hardcode 1 here for sha256WithRSAEncryption_65537
component main { public [ merkle_root, attestation_id ] } = register_sha256WithRSASSAPSS_65537(64, 32, 320, 16, 4);
2 changes: 1 addition & 1 deletion circuits/circuits/utils/Mgf1Sha256.circom
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ template Mgf1Sha256(seedLen, maskLen) { //in bytes

for (var j = 0; j < 32; j++) {
//concat seed and counter
concated[seedLenBits + j] = num2Bits[i].out[j];
concated[seedLenBits + j] = num2Bits[i].out[31-j];
}

sha256[i].in <== concated;
Expand Down
192 changes: 192 additions & 0 deletions circuits/circuits/utils/RSASSAPSS.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
pragma circom 2.1.5;

include "@zk-email/circuits/lib/rsa.circom";
include "@zk-email/circuits/lib/fp.circom";
include "@zk-email/circuits/lib/bigint-func.circom";
include "circomlib/circuits/bitify.circom";
include "circomlib/circuits/sha256/sha256.circom";
include "./Mgf1Sha256.circom";
include "./xor.circom";

/// @notice Returns the encoded message in 8bit chunks.
/// @param n Number of bits per chunk the modulus is split into.
/// @param k Number of chunks the modulus is split into.
template RSASSAPSS_Decode(n, k) {
signal input signature[k];
signal input modulus[k];
// signal output eM[k];
signal encoded[k];
signal eMsgInBits[n*k];
signal output eM[(n*k)\8]; //8 bit words

component bigPow = FpPow65537Mod(n, k);
for (var i = 0; i < k; i++) {
bigPow.base[i] <== signature[i];
bigPow.modulus[i] <== modulus[i];
}

encoded <== bigPow.out;

component num2Bits[k];
for (var i = 0; i < k; i++) {
num2Bits[i] = Num2Bits(n);
num2Bits[i].in <== encoded[k-1-i];

for (var j = 0; j < n; j++) {
eMsgInBits[i * n + j] <== num2Bits[i].out[n-j-1];
}
}

component bits2Num[(n*k)\8];
for (var i = 0; i < (n*k)\8; i++) {
bits2Num[i] = Bits2Num(8);
for (var j = 0; j < 8; j++) {
bits2Num[i].in[7-j] <== eMsgInBits[i*8 + j];
}
eM[(n*k)\8 - i -1] <== bits2Num[i].out;
}
}

/// @param emBits Length of the encoded message in bits.
/// @param messageLen Length of the message in bytes.
/// @param n Number of bits per chunk the modulus is split into.
/// @param k Number of chunks the modulus is split into.
template RSASSAPSSVerify_SHA256(emBits, messageLen) {
var emLen = div_ceil(emBits, 8);
signal input eM[emLen];
signal input message[messageLen];
signal mHash[256];
var hLen = 32;
var sLen = 32;
var hLenBits = 256; //sha256
var sLenBits = 256; //sha256
var emLenBits = emLen * 8;

signal messageBits[messageLen*8];
component num2BitsMessage[messageLen];
for (var i = 0; i < messageLen; i++) {
num2BitsMessage[i] = Num2Bits(8);
num2BitsMessage[i].in <== message[i];
for (var j = 0; j < 8; j++) {
messageBits[i*8 +j] <== num2BitsMessage[i].out[7-j];
}
}

//mHash
component sha256 = Sha256(832);
sha256.in <== messageBits;
for (var i = 0; i < 256; i++) {
mHash[i] <== sha256.out[i];
}

//If emLen < hLen + sLen + 2, output "inconsistent" and stop.
assert(emLen >= 32 + 32 +2);

//should end with 0xBC (188 in decimal)
assert(eM[0] == 188); //inconsistent

signal eMsgInBits[emLen * 8];
signal maskedDB[(emLen - hLen - 1) * 8];
signal hash[hLen * 8];
var dbMaskLen = emLen - hLen - 1;
signal dbMask[dbMaskLen * 8];
signal DB[dbMaskLen * 8];
signal salt[hLen * 8];

//split eM into bits
component num2Bits[emLen];
for (var i = 0; i < emLen; i++) {
num2Bits[i] = Num2Bits(8);
num2Bits[i].in <== eM[emLen-1-i];

for (var j = 0; j < 8; j++) {
eMsgInBits[i * 8 + j] <== num2Bits[i].out[8-j-1];
}
}

//extract maskedDB. leftmost emLen - hLen - 1 octets of EM
for (var i=0; i< (emLen - hLen -1) * 8; i++) {
maskedDB[i] <== eMsgInBits[i];
}

//Ref: https://github.com/directdemocracy-vote/app/blob/d0590b5515e749fa72fc50f05062273eb2465da1/httpdocs/app/js/rsa-blind.js#L183
signal mask <== 0xff00 >> (emLenBits / 8 - emBits) & 0xff;
signal maskBits[8];
component num2BitsMask = Num2Bits(8);
num2BitsMask.in <== mask;
for (var i = 0; i < 8; i++) {
maskBits[i] <== num2BitsMask.out[7-i];
}
for (var i=0; i<8; i++) {
assert(maskBits[i] & maskedDB[i] == 0);
}

//extract hash
for (var i=0; i<hLenBits; i++) {
hash[i] <== eMsgInBits[(emLenBits) - hLenBits-8 +i];
}

//DbMask MGF1
component MGF1 = Mgf1Sha256(hLen, dbMaskLen);
for (var i = 0; i < (hLenBits); i++) {
MGF1.seed[i] <== hash[i];
}
for (var i = 0; i < dbMaskLen * 8; i++) {
dbMask[i] <== MGF1.out[i];
}

//DB = maskedDB xor dbMask
component xor = Xor2(dbMaskLen * 8);
for (var i = 0; i < dbMaskLen * 8; i++) {
xor.a[i] <== maskedDB[i];
xor.b[i] <== dbMask[i];
}
// Ref: https://github.com/directdemocracy-vote/app/blob/d0590b5515e749fa72fc50f05062273eb2465da1/httpdocs/app/js/rsa-blind.js#L188-L190
for (var i = 0; i < dbMaskLen * 8; i++) {
//setting the first leftmost byte to 0
if (i==0) {
DB[i] <== 0;
} else {
DB[i] <== xor.out[i];
}
}

// If the emLen - hLen - sLen - 2 leftmost octets of DB are not
// zero, output "inconsistent" and stop.
for (var i = 0; i < (emLenBits-528); i++) { //hLenBits + sLenBits + 16 = 256 + 256 + 16 = 528
assert(DB[i] == 0);
}
// if the octet at position emLen - hLen - sLen - 1 (the
// leftmost position is "position 1") does not have hexadecimal
// value 0x01, output "inconsistent" and stop.
component bits2Num = Bits2Num(8);
for (var i = 0; i < 8; i++) {
bits2Num.in[7-i] <== DB[(emLenBits)- 528 +i]; //emLen - hLen - sLen - 1

}
assert(bits2Num.out == 1);

//extract salt
for (var i = 0; i < sLenBits; i++) {
//last sLenBits (256) bits of DB
salt[256 -1 - i] <== DB[(dbMaskLen * 8) -1 - i];
}

//M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ;
signal mDash[576]; // 8 + hLen + sLen = 8 + 32 + 32 = 72 bytes = 576 bits
for (var i = 0; i < 64; i++) {
mDash[i] <== 0;
}
for (var i = 0 ; i < 256; i++) {
mDash[64 + i] <== mHash[i];
}
for (var i = 0; i < 256; i++) {
mDash[320 + i] <== salt[i];
}

component hDash = Sha256(576);
hDash.in <== mDash;
for (var i = 0; i < 256; i++) {
assert(hDash.out[i] == hash[i]);
}
}
11 changes: 11 additions & 0 deletions circuits/circuits/utils/xor.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pragma circom 2.1.5;

template Xor2(n) {
signal input a[n];
signal input b[n];
signal output out[n];

for (var k=0; k<n; k++) {
out[k] <== a[k] + b[k] - 2*a[k]*b[k];
}
}
2 changes: 1 addition & 1 deletion circuits/scripts/build_circuits.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ build_circuit() {
echo "Size of ${CIRCUIT_NAME}_final.zkey: $(wc -c <build/${CIRCUIT_NAME}_final.zkey) bytes"
}

declare -a CIRCUITS=("register_sha256WithRSAEncryption_65537" "register_sha1WithRSAEncryption_65537" "disclose")
declare -a CIRCUITS=("register_sha256WithRSAEncryption_65537" "register_sha1WithRSAEncryption_65537" "register_sha256WithRSAEncryption_65537" "disclose")

TOTAL_START_TIME=$(date +%s)
for CIRCUIT_NAME in "${CIRCUITS[@]}"; do
Expand Down
Loading