Skip to content

Commit 1d2f2da

Browse files
authored
Integrate device bound proofs for JWTs in Crescent (#105)
* Integrate device bound proofs for JWTs in Crescent - Generate device keys during circuit setup, add device public key to sample JWTs generated during setup - Add support for device bound credentials to proof specification - Create signatures with a software test device - Generate and verify proofs during show and verify operations -Add some documentation about device-bound cred example Add sub-prover for device bound credentials The new sub-prover takes a commitment to the public key from the main show proof, together with an ECDSA signature produced by the device and and creates a proof of posession of the signture. This feature is necessary to support device-bound credentials, such as mDLs. Simplify DLEQ interface and fix implementation - Reduce generality of interface to specify which positions are equal in a DLPoK to the case with two statements (all we currently need). - Fix the implementation to not re-use randomness across multiple pairs of equal positions. - Add unit tests for DLEQ and fix range proof unit test. Misc -Add unit tests for selective disclosure and device-bound credentials -Move Windows symlink docs to circuit_setup/README Signed-off-by: Greg Zaverucha <gregz@microsoft.com>
1 parent 30d0016 commit 1d2f2da

File tree

159 files changed

+37181
-112
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+37181
-112
lines changed

.github/workflows/CI.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ jobs:
5858
cd circuit_setup/scripts
5959
./run_setup.sh rs256-sd
6060
61+
- name: Run circuit setup for rs256-db
62+
run: |
63+
cd circuit_setup/scripts
64+
./run_setup.sh rs256-db
6165
6266
- name: Run circuit setup for mDL
6367
run: |
@@ -116,6 +120,27 @@ jobs:
116120
cd creds
117121
cargo run --bin crescent --release --features print-trace verify --name rs256-sd
118122
123+
# RS256-db Commands
124+
- name: Run ZKSetup for rs256-db
125+
run: |
126+
cd creds
127+
cargo run --bin crescent --release --features print-trace zksetup --name rs256-db
128+
129+
- name: Run Prove for rs256-db
130+
run: |
131+
cd creds
132+
cargo run --bin crescent --release --features print-trace prove --name rs256-db
133+
134+
- name: Run Show for rs256-db
135+
run: |
136+
cd creds
137+
cargo run --bin crescent --release --features print-trace show --name rs256-db
138+
139+
- name: Run Verify for rs256-db
140+
run: |
141+
cd creds
142+
cargo run --bin crescent --release --features print-trace verify --name rs256-db
143+
119144
# mDL Commands
120145
- name: Run ZKSetup for mDL
121146
run: |

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ sample/verifier/data
1919
circuit_setup/inputs/*/issuer.prv
2020
circuit_setup/inputs/*/issuer.pub
2121
circuit_setup/inputs/*/token.jwt
22+
circuit_setup/inputs/*/device.prv
23+
circuit_setup/inputs/*/device.pub
2224

2325
setup/generated_files/
2426
circuit_setup/circuits-mdl/circomlib

NOTICE.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,3 +1765,106 @@ SOFTWARE.
17651765
### Additional Attribution
17661766
None
17671767

1768+
---
1769+
1770+
### Component
1771+
circ_fields
1772+
https://github.com/circify/circ/tree/master/circ_fields
1773+
Note: forked to support the T-256 curve
1774+
Path: forks/Spartan-t256/
1775+
1776+
### Open Source License/Copyright Notice
1777+
MIT
1778+
https://github.com/circify/circ/blob/master/LICENSE-MIT
1779+
1780+
### Additional Attribution
1781+
Fork created by the authors of the paper:
1782+
Efficient Proofs of Possession for Legacy Signatures.
1783+
Anna Pui Yung Woo, University of Michigan
1784+
Alex Ozdemir, Stanford University
1785+
Chad Sharp, University of Michigan
1786+
Thomas Pornin, NCC Group
1787+
Paul Grubbs, University of Michigan
1788+
Published at IEEE S&P 2025
1789+
1790+
---
1791+
1792+
1793+
### Component
1794+
ff-derive-num
1795+
https://github.com/kwantam/ff-derive-num/pull/1
1796+
Note: Fork that includes PR#1
1797+
Path: forks/Spartan-t256/circ_fields/
1798+
1799+
### Open Source License/Copyright Notice
1800+
MIT
1801+
https://github.com/kwantam/ff-derive-num/blob/main/LICENSE-mit
1802+
1803+
### Additional Attribution
1804+
Riad S. Wahby
1805+
1806+
---
1807+
1808+
### Component
1809+
Spartan, Spartan2
1810+
https://github.com/microsoft/Spartan
1811+
https://github.com/microsoft/Spartan2
1812+
Path: forks/Spartan-t256
1813+
Note: Forked to support curve T-256 and include support for bellpepper circuits
1814+
1815+
### Open Source License/Copyright Notice
1816+
MIT
1817+
https://github.com/microsoft/Spartan/blob/master/LICENSE
1818+
https://github.com/microsoft/Spartan2/blob/master/LICENSE
1819+
1820+
### Additional Attributions
1821+
Sritnath Setty
1822+
Github contributors
1823+
1824+
---
1825+
1826+
### Component
1827+
Nova
1828+
https://github.com/microsoft/Nova/blob/main/src/provider/poseidon.rs
1829+
https://github.com/microsoft/Nova/blob/b7f5be7bb5d8cc4a93d1363347359743fa30d161/src/gadgets/ecc.rs#L1
1830+
Path: creds/src/device_binding/ecdsa-pop/
1831+
Note: Poseidon wrapper and ECC gadgets
1832+
1833+
### Open Source License/Copyright Notice
1834+
MIT
1835+
https://github.com/microsoft/Nova/blob/main/LICENSE
1836+
1837+
### Additional Attributions
1838+
Sritnath Setty
1839+
Github contributors
1840+
1841+
---
1842+
1843+
### Component
1844+
bellpepper-gadgets/emulated
1845+
https://github.com/lurk-lab/bellpepper-gadgets/tree/main/crates/emulated
1846+
Path: creds/src/device_binding/ecdsa-pop/
1847+
Note: Non-native arithmetic gadgets
1848+
1849+
### Open Source License/Copyright Notice
1850+
MIT
1851+
https://github.com/lurk-lab/bellpepper-gadgets/blob/main/LICENSE-MIT
1852+
1853+
### Additional Attributions
1854+
François Garillot (huitseeker)
1855+
1856+
---
1857+
1858+
### Component
1859+
Neptune
1860+
https://github.com/lurk-lab/neptune
1861+
Path: creds/src/device_binding/ecdsa-pop/
1862+
Note: forked to support older version of bellpepper-core
1863+
1864+
### Open Source License/Copyright Notice
1865+
MIT
1866+
https://github.com/lurk-lab/neptune/blob/main/LICENSE-MIT
1867+
1868+
### Additional Attributions
1869+
None
1870+

README.md

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,6 @@ cd creds
3333
cargo test --release
3434
```
3535

36-
### Enabling symlinks with git on Windows
37-
38-
This project uses symlinks to share directories within the project. On Windows, symlinks require administrator privileges. Git can be configured to create project symlinks when cloning the repository.
39-
To enable symlinks with git, run the following command:
40-
41-
```bash
42-
git config --global core.symlinks true
43-
```
44-
45-
If you have already cloned the repository, you can delete and re-clone the repository for the symlinks to be created or manually create the link by running the following CMD command in the project root directory:
46-
47-
```cmd
48-
mklink /J circuit_setup\circuits-mdl\circomlib circuit_setup\circuits\circomlib
49-
```
50-
51-
Verify `circuit_setup\circuits-mdl\circomlib` is now a directory.
52-
5336
## Running the demo steps from the command line
5437

5538
There is a command line tool that can be used to run the individual parts of the demo separately. This clearly separates the roles of prover and verifier, and shows what parameters are required by each. The filesystem is used to store data between steps, and also to "communicate" show proofs from prover to verifier.
@@ -99,6 +82,20 @@ The `reveal_digest` option is used for values that may be larger than 31 bytes;
9982

10083
As example ways to experiment with selective disclosure, try removing `aud` from the list of revealed attributes, or adding `given_name` to the list of revealed attributes in the proof specification file.
10184

85+
### Device-Bound Credentials
86+
The example `rs256-db` demonstrates a JWT credential that is *device bound*. This means that the JWT encodes the public key of an ECDSA signing key, where the private key is stored by a device (such as a hardware security module), and the device exposes only a signing API.
87+
When the credential is used, the verifier expects the holder to demonstrate possession of the device key, by signing a challenge. During circuit setup, the file `circuit_setup/inputs/rs256-db/config.json` has the line `"device_bound": true`, which indicates the sample credential should be generated with a device key. In the demo, a fresh ECDSA key pair is generated in software, no special hardware is required.
88+
89+
For show proofs, The file `creds/test-vectors/rs256-db/proof_spec.json` contains
90+
```
91+
{
92+
"revealed" : ["family_name", "tenant_ctry", "auth_time", "aud"],
93+
"device_bound" : true,
94+
"presentation_message" : [1, 2, 3, 4]
95+
}
96+
```
97+
which specifies a subset of attributes to disclose, as in the `rs256-sd` example. The `device-bound` flag is also set here, and the `presentation_message` is a byte string that that encodes a challenge from the verifier. The `presentation_message` is sent to the device, then the show proof creates a proof of knowledge of the device signature (unlinkably).
98+
10299
## Contributing
103100

104101
This project welcomes contributions and suggestions. Most contributions require you to agree to a

circuit_setup/README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ cd pyMDL-MDOC
6868
sed -i "s|i.replace(f'{_pkg_name}/', '')|i.replace(f'{_pkg_name}\\\\\\\\', '')|g" setup.py
6969
pip install .
7070
```
71-
7271
## Sample JWT and mDL
7372

7473
To work with Crescent, the prover and verifier both need the issuer's public key, and the prover needs a JWT.
@@ -99,6 +98,23 @@ Overall this is slow, but only needs to be run once for a given token issuer and
9998

10099
Once this script completes, all files required for showing and verifying proofs will have copied to `creds/test-vectors/rs256`.
101100

101+
## Enabling symlinks with git on Windows
102+
103+
This project uses symlinks to share directories within the project. On Windows, symlinks require administrator privileges. Git can be configured to create project symlinks when cloning the repository.
104+
To enable symlinks with git, run the following command:
105+
106+
```bash
107+
git config --global core.symlinks true
108+
```
109+
110+
If you have already cloned the repository, you can delete and re-clone the repository for the symlinks to be created or manually create the link by running the following CMD command in the project root directory:
111+
112+
```cmd
113+
mklink /J circuit_setup\circuits-mdl\circomlib circuit_setup\circuits\circomlib
114+
```
115+
116+
Verify `circuit_setup\circuits-mdl\circomlib` is now a directory.
117+
102118
# Troubleshooting
103119

104120
For some large circuits, Circom may use a large amount of RAM and be killed.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"acct": 0,
3+
"aud": "12345678-1234-abcd-1234-abcdef124567",
4+
"auth_time": 1725917899,
5+
"email": "matthew@example.com",
6+
"exp": 1759517346,
7+
"family_name": "Matthew",
8+
"given_name": "Matthewson",
9+
"iat": 1728067746,
10+
"ipaddr": "203.0.113.0",
11+
"iss": "https://login.microsoftonline.com/12345678-1234-abcd-1234-abcdef124567/v2.0",
12+
"jti": "AUJNzY3Cwon7pL_3k0-fdw",
13+
"login_hint": "O.aaaaabbbbbbbbbcccccccdddddddeeeeeeeffffffgggggggghhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmnnnnnnnnnnooooooopppppppqqqqrrrrrrsssssdddd",
14+
"name": "Matthew Matthewson",
15+
"nbf": 1728067746,
16+
"oid": "12345678-1234-abcd-1234-abcdef124567",
17+
"onprem_sid": "S-1-2-34-5678901234-1234567890-1234567890-1234567",
18+
"preferred_username": "matthew@example.com",
19+
"rh": "0.aaaaabbbbbccccddddeeeffff12345gggg12345_124_aaaaaaa.",
20+
"sid": "12345678-1234-abcd-1234-abcdef124567",
21+
"sub": "aaabbbbccccddddeeeeffffgggghhhh123456789012",
22+
"tenant_ctry": "US",
23+
"tenant_region_scope": "WW",
24+
"tid": "12345678-1234-abcd-1234-abcdef124567",
25+
"upn": "matthew@example.com",
26+
"uti": "AAABBBBccccdddd1234567",
27+
"ver": "2.0",
28+
"verified_primary_email": [
29+
"matthew@example.com"
30+
],
31+
"verified_secondary_email": [
32+
"matthew@service.example.com"
33+
],
34+
"xms_pdl": "NAM",
35+
"xms_tpl": "en"
36+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"alg": "RS256",
3+
"device_bound": true,
4+
"exp": {
5+
"type" : "number",
6+
"reveal" : true,
7+
"max_claim_byte_len" : 31
8+
},
9+
"email": {
10+
"type" : "string",
11+
"reveal" : true,
12+
"max_claim_byte_len" : 31
13+
},
14+
"family_name": {
15+
"type" : "string",
16+
"reveal" : true,
17+
"max_claim_byte_len" : 31
18+
},
19+
"given_name": {
20+
"type" : "string",
21+
"reveal" : true,
22+
"max_claim_byte_len" : 31
23+
},
24+
"tenant_ctry": {
25+
"type" : "string",
26+
"reveal" : true,
27+
"max_claim_byte_len" : 31
28+
},
29+
"tenant_region_scope": {
30+
"type" : "string",
31+
"reveal" : true,
32+
"max_claim_byte_len" : 31
33+
},
34+
"aud": {
35+
"type" : "string",
36+
"reveal_digest" : true,
37+
"max_claim_byte_len" : 62
38+
},
39+
"auth_time": {
40+
"type" : "number",
41+
"reveal_digest" : true,
42+
"max_claim_byte_len" : 31
43+
}
44+
45+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"revealed" : ["family_name", "tenant_ctry", "auth_time", "aud"],
3+
"device_bound" : true,
4+
"presentation_message" : [1, 2, 3, 4]
5+
}

circuit_setup/scripts/crescent_helper.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
import json, math, string
1111

1212
##### Constants ######
13-
MAX_FIELD_BYTE_LEN = 31 # Maximum length of a field element
13+
MAX_FIELD_BYTE_LEN = 31 # Maximum length of a field element (for BN254)
14+
MAX_FIELD_BASE10_LEN = 77
1415
CIRCOM_RS256_LIMB_BITS = 121
1516
CIRCOM_ES256K_LIMB_BITS = 64
1617
CIRCOM_ES256_LIMB_BITS = 43 # Required by the ecdsa-p256 circuit we use
17-
CRESCENT_CONFIG_KEYS = ['alg', 'credtype', 'reveal_all_claims', 'defer_sig_ver', 'max_cred_len'] # fields in config.json that are for crescent configuration and do not refer to claims in the token
18+
CRESCENT_CONFIG_KEYS = ['alg', 'credtype', 'reveal_all_claims', 'defer_sig_ver', 'max_cred_len', 'device_bound'] # fields in config.json that are for crescent configuration and do not refer to claims in the token
1819
CRESCENT_SUPPORTED_ALGS = ['RS256', 'ES256', 'ES256K'] # Signature algorithms used to sign JWT/mDL
1920

2021

@@ -190,6 +191,9 @@ def check_config(config):
190191

191192
if 'credtype' not in config:
192193
config['credtype'] = 'jwt'
194+
195+
if 'device_bound' not in config:
196+
config['device_bound'] = False
193197

194198
if 'max_cred_len' not in config:
195199
config['max_cred_len'] = 2048 # Maximum length of JWT, excluding the
@@ -209,6 +213,20 @@ def check_config(config):
209213
if config['alg'] != 'ES256K':
210214
print_debug("Error: the 'defer_sig_ver' option is only valid with the ES256K algorithm")
211215
return False
216+
217+
# If the token is device bound, assume it has the claims "device_key_0" and "device_key_1", and ensure
218+
# they will be revealed
219+
if config['device_bound']:
220+
config['device_key_0'] = {
221+
"type": "number",
222+
"reveal": True,
223+
"max_claim_byte_len": 2*MAX_FIELD_BYTE_LEN
224+
}
225+
config['device_key_1'] = {
226+
"type": "number",
227+
"reveal": True,
228+
"max_claim_byte_len": 2*MAX_FIELD_BYTE_LEN
229+
}
212230

213231

214232
# For all the config entries about claims (e.g, "email", "exp", etc.) make sure that if the claim
@@ -226,9 +244,14 @@ def check_config(config):
226244
return False
227245
if claim_reveal_unhashed(config[key]):
228246
max_claim_byte_len = config[key].get("max_claim_byte_len")
229-
if max_claim_byte_len > MAX_FIELD_BYTE_LEN:
230-
print_debug("Error: claim '{}' has reveal flag set but max_claim_byte_len={} exceeds MAX_FIELD_BYTE_LEN={}. To reveal larger claims use reveal_digest".format(key, max_claim_byte_len))
247+
if max_claim_byte_len > MAX_FIELD_BYTE_LEN and config[key].get("type") != "number":
248+
print_debug("Error: claim '{}' has reveal flag set but max_claim_byte_len={} exceeds MAX_FIELD_BYTE_LEN={}. To reveal larger claims use reveal_digest".format(key, max_claim_byte_len, MAX_FIELD_BYTE_LEN))
231249
return False
250+
# For number types, the number of bytes is the number of base-10 digits, which can
251+
# exceed the number bytes to represent the field (base-256 digits)
252+
if max_claim_byte_len > MAX_FIELD_BASE10_LEN and config[key].get("type") == "number":
253+
print_debug("Error: claim '{}' has reveal flag set but max_claim_byte_len={} exceeds MAX_FIELD_BASE10_LEN={}. To reveal larger claims use reveal_digest".format(key, max_claim_byte_len, MAX_FIELD_BASE10_LEN))
254+
return False
232255

233256
return True
234257

0 commit comments

Comments
 (0)