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

Signature Circuits #1

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
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
7 changes: 5 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ jobs:
nasm \
nlohmann-json3-dev

- name: Download Circom Binary v2.1.5
- name: Download Circom Binary v2.1.9
run: |
wget -qO /home/runner/work/circom https://github.com/iden3/circom/releases/download/v2.1.5/circom-linux-amd64
wget -qO /home/runner/work/circom https://github.com/iden3/circom/releases/download/v2.1.9/circom-linux-amd64
chmod +x /home/runner/work/circom
sudo mv /home/runner/work/circom /bin/circom

- name: Print Circom version
run: circom --version

- name: Download Hash Circuits repo
run: git submodule update --init --recursive

- name: Install dependencies
run: yarn
Expand Down
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "circuits/hash-circuits"]
path = circuits/hash-circuits
url = https://github.com/bkomuves/hash-circuits/
[submodule "circuits/sha256-var-module"]
path = circuits/sha256-var-module
url = https://github.com/crema-labs/sha256-var-circom
2 changes: 1 addition & 1 deletion .mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"extension": ["ts"],
"require": "ts-node/register",
"spec": "tests/**/*.test.ts",
"timeout": 100000,
"timeout": 100000000000,
"exit": true
}
10 changes: 0 additions & 10 deletions circuits/add.circom

This file was deleted.

66 changes: 66 additions & 0 deletions circuits/attestation.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
pragma circom 2.1.9;

include "@crema-labs/ecdsa-p384-circom/circuits/ecdsa.circom";
include "hash-circuits/circuits/sha2/sha384/sha384_hash_bits.circom";
include "sha256-var-module/circuits/sha256Var.circom";
include "utils.circom";
include "ecdsa-with-sha384.circom";

template VerifyCertChain(TBS2Size,TBS3Size,BlockSpace){
signal input r[3][8];
signal input s[3][8];
signal input TBS1Size;

var BLOCK_LEN = 512;
var MaxBlockCount = pow(2, BlockSpace);
var MaxLen = BLOCK_LEN * MaxBlockCount; // max size of TBS1Data in bits as per sha256var spec
signal input TBS1Data[MaxLen];

signal input TBS2Data[TBS2Size];
signal input TBS3Data[TBS3Size];
signal input PubKeys[3][2][8];

signal output out;
signal status[3];

component ecdsaWith384[2];
component sha256Hash = Sha256Var(4);

// 4 bits for 16 blocks each of 512 bits
// variable length hash of leaf certificate raw tbs data
sha256Hash.in <== TBS1Data;
sha256Hash.len <== TBS1Size;
component padding = PadBits(256, 384);
padding.in <== sha256Hash.out;
component bitsToWords = SplitToWords(384, 48, 8);
bitsToWords.in <== padding.out;

// signature verification with public key of next certificate in chain
component p384Ecdsa = ECDSAVerifyNoPubkeyCheck(48, 8);
p384Ecdsa.msghash <== bitsToWords.out;
p384Ecdsa.r <== r[0];
p384Ecdsa.s <== s[0];
p384Ecdsa.pubkey <== PubKeys[0];
p384Ecdsa.result ==> status[0];
p384Ecdsa.result === 1;


ecdsaWith384[0] = Sha384ECDSAVerify(TBS2Size);
ecdsaWith384[0].TBSData <== TBS2Data;
ecdsaWith384[0].r <== r[1];
ecdsaWith384[0].s <== s[1];
ecdsaWith384[0].PubKey <== PubKeys[1];
ecdsaWith384[0].status ==> status[1];
ecdsaWith384[0].status === 1;


ecdsaWith384[1] = Sha384ECDSAVerify(TBS3Size);
ecdsaWith384[1].TBSData <== TBS3Data;
ecdsaWith384[1].r <== r[2];
ecdsaWith384[1].s <== s[2];
ecdsaWith384[1].PubKey <== PubKeys[2];
ecdsaWith384[1].status ==> status[2];
ecdsaWith384[1].status === 1;

out <== status[0] + status[1] + status[2];
}
31 changes: 31 additions & 0 deletions circuits/ecdsa-with-sha384.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
pragma circom 2.1.9;

include "@crema-labs/ecdsa-p384-circom/circuits/ecdsa.circom";
include "hash-circuits/circuits/sha2/sha384/sha384_hash_bits.circom";
include "utils.circom";

template Sha384ECDSAVerify(TBSSize){
signal input TBSData[TBSSize];

signal input r[8];
signal input s[8];

signal input PubKey[2][8];
signal output status;

component sha384Hash = Sha384_hash_bits_digest(TBSSize);
sha384Hash.inp_bits <== TBSData;

component bytesToBits = BytesToBits(48);
bytesToBits.in <== sha384Hash.hash_bytes;

component bitsToWords = SplitToWords(384, 48, 8);
bitsToWords.in <== bytesToBits.out;

component p384Ecdsa = ECDSAVerifyNoPubkeyCheck(48, 8);
p384Ecdsa.msghash <== bitsToWords.out;
p384Ecdsa.r <== r;
p384Ecdsa.s <== s;
p384Ecdsa.pubkey <== PubKey;
p384Ecdsa.result ==> status;
}
1 change: 1 addition & 0 deletions circuits/hash-circuits
Submodule hash-circuits added at 3ae151
1 change: 1 addition & 0 deletions circuits/sha256-var-module
Submodule sha256-var-module added at cee3d3
51 changes: 51 additions & 0 deletions circuits/utils.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
pragma circom 2.1.9;

include "circomlib/circuits/bitify.circom";

template SplitToWords(nBits, wordsize, numberElement) {
signal input in[nBits];
signal output out[numberElement];

assert(numberElement * wordsize == nBits);

component bitsToNum[numberElement];

for (var i = 0; i < numberElement; i++) {
bitsToNum[i] = Bits2Num(wordsize);
for (var j = 0; j < wordsize; j++) {
bitsToNum[i].in[wordsize-1-j] <== in[i*wordsize + j];
}
bitsToNum[i].out ==> out[numberElement-1-i];
}
}

template BytesToBits(nBytes) {
signal input in[nBytes];
signal output out[nBytes*8];

component num2Bits[nBytes];

for (var i=0; i < nBytes; i++) {
num2Bits[i] = Num2Bits(8);
num2Bits[i].in <== in[i];
for (var j=0; j < 8; j++) {
out[i*8 + (7-j)] <== num2Bits[i].out[j];
// For big-endian order ==> out[i*8 + (7-j)]
// For little-endian order ==> use: out[i*8 + j]
}
}
}

template PadBits(nBits,target){
assert(nBits <= target);

signal input in[nBits];
signal output out[target];

for (var i=0; i < target-nBits; i++) {
out[i] <== 0;
}
for (var i= target-nBits; i < target; i++) {
out[i] <== in[i-(target-nBits)];
}
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
"compile:test": "npx circomkit compile add && npx circomkit prove add default && npx circomkit verify add default"
},
"dependencies": {
"@crema-labs/ecdsa-p384-circom": "^0.0.1",
"@types/elliptic": "^6.4.18",
"circomkit": "^0.0.22",
"circomlib": "^2.0.5"
"circomlib": "^2.0.5",
"elliptic": "^6.5.7"
},
"devDependencies": {
"@types/mocha": "^10.0.1",
Expand Down
65 changes: 65 additions & 0 deletions specs/appattest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Apple app attest Architecture
This document describes the architecture of the Apple app attest system.
### Actors

- **Apple App Attest Server**: The server that issues the attestation keys to the app.
- **An iOS App**: The app that wants to use the attestation keys.
- **App Backend Server**: The server that the app communicates with.

### Flow

Phase 1: Attestation
1. The iOS app creates a public-private key pair in the Secure Enclave.
2. The iOS app requests the server for some random data.
3. The iOS app send the data and the public key to the Apple App Attest Server.
4. The Apple App Attest Server signs and creates an attestation object and sends it back to the iOS app.
5. The iOS app sends the attestation object to the App Backend Server.
6. The App Backend Server verifies the attestation object with the Apple Certificate Authority which is publicly available and other verification steps as mentioned in the [Apple documentation for attestation validity](https://developer.apple.com/documentation/devicecheck/validating-apps-that-connect-to-your-server#Verify-the-attestation).
7. The App Backed also store the public key hash of the leaf X.509 certificate in the attestation object for future verification.
8. Optionally, the App Backend Server will also verify receipt of the attestation object with the Apple App Attest Server and stores it.

Phase 2: Assertion

The iOS app after attestation can request the server for some data with an assertion. The backend server will verify the assertion with the public key hash stored from attestation object.The backend can decide to when when assertion is required based on the business logic.

1. The iOS app requests the server for some random data .
2. The iOS app generates an assertion using the private key in the Secure Enclave and `clientDataHash`.
3. The iOS app sends the assertion to the App Backend Server which verifies the assertion and checks if the hash of the public key has been attested before. Refer to the [Apple documentation for assertion validity](https://developer.apple.com/documentation/devicecheck/validating-apps-that-connect-to-your-server#Verify-the-assertion).

### Notes
1. Attestation object contains three X.509 certificates:
- **Leaf X.509 certificate**: The certificate that contains the public key of the app.
- **Intermediate X.509 certificate**: The certificate that signs the leaf certificate.
- **Root X.509 certificate**: The certificate that signs the intermediate certificate and is the root certificate of the Apple App Attest Server available at [Apple CA](https://www.apple.com/certificateauthority/private/).
- All the certificate for attestations are generated using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-384 curve.
- Assuming there will only be 3 certificates in the attestation object.
- The leaf certificate is the first certificate in the `x5c` array needs P-384 curve verification with SHA-256 of RawTBSData from leaf certificate and public key of intermediate certificate.
- The intermediate certificate is the second certificate in the `x5c` array needs P-384 curve verification with SHA-384 of RawTBSData from intermediate certificate and public key of root certificate.
- THe root certificate is the third certificate in the `x5c` array needs P-384 curve verification with SHA-384 of RawTBSData from root certificate and public key of root certificate itself.
- The attestation object is in CBOR encoding.
```cbor
{
fmt: 'apple-appattest',
attStmt: {
x5c: [
<Buffer 30 82 02 cc ... >,
<Buffer 30 82 02 36 ... >
],
receipt: <Buffer 30 80 06 09 ... >
},
authData: <Buffer 21 c9 9e 00 ... >
}
```
2. Assertions are signed using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve.


### POC and Demo Plan
We use the flow to prove a user in in Asia without revealing which country you are in.
1. Create a simple iOS app that address to the above flow to generate attestation object and verify on chain.
2. Verifier contract that verifies the attestation object and stores the public key hash.
3. iOS application will generate a assertion with proof that the device is in a particular location.
4. Verifier contract will verify the assertion and location proof.

### Blockers

1. Implementation of variable length sha-384.
48 changes: 48 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export function splitToWords(number: bigint, wordsize: bigint, numberElement: bigint): bigint[] {
let t = number;
const words: bigint[] = [];
const mask = BigInt(BigInt(1) << wordsize) - 1n;
for (let i = BigInt(0); i < numberElement; ++i) {
const word = t & mask;
words.push(word);
t >>= wordsize;
}
if (!(t == BigInt(0))) {
throw `Number ${number} does not fit in ${(wordsize * numberElement).toString()} bits`;
}
return words;
}

export function hexToBigInt(hex: string) {
return BigInt(`0x${hex}`);
}

export function bufferToBigIntByteArray(buffer: Buffer): bigint[] {
const bigIntArray: bigint[] = [];

for (const byte of buffer) {
bigIntArray.push(BigInt(byte));
}

return bigIntArray;
}

export function bufferToBigIntBitArray(buffer: Buffer): bigint[] {
const bigIntArray: bigint[] = [];

for (const byte of buffer) {
for (let i = 7; i >= 0; i--) {
bigIntArray.push(BigInt((byte >> i) & 1));
}
}

return bigIntArray;
}

export function numToBits(num: number): bigint[] {
const bits: bigint[] = [];
for (let i = 7; i >= 0; i--) {
bits.push(BigInt((num >> i) & 1));
}
return bits;
}
20 changes: 0 additions & 20 deletions tests/add.test.ts

This file was deleted.

Loading
Loading