From 90ccfe4f265f8648a89d8caf993b8259f4a7f8a2 Mon Sep 17 00:00:00 2001 From: d0x471b <0x471@protonmail.com> Date: Mon, 10 Nov 2025 14:27:36 +0000 Subject: [PATCH] feat(circuits): add GCP JWT verifier with TEE attestation claims extraction (#1317) * chore(scripts): update Power of Tau download source URL * fix(build): use correct node_modules paths in circom compile step * chore(deps): add dependencies for GCP JWT verifier * feat(circuits): add extractAndValidatePubkey utility circuit for GCP JWT * feat(circuits): add verifyCertificateSignature utility circuit for GCP JWT * feat(circuits): add verifyExtractedString utility circuit for GCP JWT * feat(circuits): implement GCP JWT verifier circuit * feat(scripts): add build script for GCP JWT verifier circuit * chore(deps): pin crypto-circuit dependencies to exact versions * build(circuits): bump jwt_verifier.circom version to 2.1.9 * build(scripts): improve gcp_jwt_verifier error handling * chore(gcp_jwt_verifier): remove string verifier * refactor: optimize and improve GCP JWT Verifier * fix(circuits): enforce colon after JSON key name * fix(circuits): add JSON parsing offset validation constraints * fix: enforce JSON array structure validation in field extraction * fix: add value_length validation to prevent partial extraction --------- Co-authored-by: Justin Hernandez --- circuits/circuits/gcp_jwt_verifier/README.md | 96 +++ .../circuits/gcp_jwt_verifier/example_jwt.txt | 1 + .../gcp_jwt_verifier/gcp_jwt_verifier.circom | 252 +++++++ .../gcp_jwt_verifier/jwt_verifier.circom | 203 ++++++ circuits/circuits/gcp_jwt_verifier/prepare.ts | 394 +++++++++++ .../gcp_jwt/extractAndValidatePubkey.circom | 97 +++ .../gcp_jwt/verifyCertificateSignature.circom | 40 ++ .../gcp_jwt/verifyJSONFieldExtraction.circom | 123 ++++ circuits/package.json | 3 + .../scripts/build/build_gcp_jwt_verifier.sh | 29 + circuits/scripts/build/common.sh | 1 - yarn.lock | 636 +++++++++++++++++- 12 files changed, 1839 insertions(+), 36 deletions(-) create mode 100644 circuits/circuits/gcp_jwt_verifier/README.md create mode 100644 circuits/circuits/gcp_jwt_verifier/example_jwt.txt create mode 100644 circuits/circuits/gcp_jwt_verifier/gcp_jwt_verifier.circom create mode 100644 circuits/circuits/gcp_jwt_verifier/jwt_verifier.circom create mode 100644 circuits/circuits/gcp_jwt_verifier/prepare.ts create mode 100644 circuits/circuits/utils/gcp_jwt/extractAndValidatePubkey.circom create mode 100644 circuits/circuits/utils/gcp_jwt/verifyCertificateSignature.circom create mode 100644 circuits/circuits/utils/gcp_jwt/verifyJSONFieldExtraction.circom create mode 100755 circuits/scripts/build/build_gcp_jwt_verifier.sh diff --git a/circuits/circuits/gcp_jwt_verifier/README.md b/circuits/circuits/gcp_jwt_verifier/README.md new file mode 100644 index 000000000..7091c7a9f --- /dev/null +++ b/circuits/circuits/gcp_jwt_verifier/README.md @@ -0,0 +1,96 @@ +# GCP JWT Verifier Circuit + +Zero-knowledge circuits for verifying [Google Cloud Platform Confidential Space JWT attestations](https://cloud.google.com/confidential-computing/confidential-space/docs/confidential-space-overview) with complete X.509 certificate chain validation. + +> **Warning**: This circuit uses [`zk-jwt`](https://github.com/zkemail/zk-jwt) which is not yet audited (as of October 2025). + +## Overview + +This circuit verifies GCP Confidential Space JWT attestations by validating the complete chain of trust: +1. JWT Signature: Verifies the JWT was signed by the leaf certificate (x5c[0]) +2. Leaf Certificate: Verifies x5c[0] was signed by the intermediate CA (x5c[1]) +3. Intermediate Certificate: Verifies x5c[1] was signed by the root CA (x5c[2]) + +## Public Outputs + +The circuit exposes the following data as public outputs: +- `publicKeyHash`, Poseidon hash of leaf certificate public key +- `header`, Decoded JWT header +- `payload`, Decoded JWT payload +- `eat_nonce_0_b64_output`, Base64URL encoded EAT nonce +- `image_hash`, Container image SHA256 hash + +## Architecture +The main circuit does: +1. JWT signature verification (using x5c[0] pubkey) +2. x5c[0] pubkey extraction and validation +3. x5c[0] certificate signature verification (using x5c[1] pubkey) +4. x5c[1] pubkey extraction and validation +5. x5c[1] certificate signature verification (using x5c[2] pubkey) +6. x5c[2] pubkey extraction and validation +7. EAT nonce extraction and validation +8. Container image digest extraction and validation + +## Usage + +### 1. Prepare Circuit Inputs + +Extract data from a GCP JWT attestation: + +```bash +cd circuits +yarn tsx circuits/gcp_jwt_verifier/prepare.ts \ + circuits/gcp_jwt_verifier/example_jwt.txt \ + circuit_inputs.json +``` + +The `prepare.ts` script: +- Parses the JWT header and payload +- Extracts all 3 x5c certificates from the header +- Extracts public keys and signatures from each certificate +- Computes certificate hashes with proper padding +- Locates EAT nonce and image digest in the payload +- Converts all data to circuit-compatible format + +### 2. Build Circuit + +Compile the circuit and generate proving/verification keys: + +```bash +yarn build-gcp-jwt-verifier +``` + +This runs `scripts/build/build_gcp_jwt_verifier.sh` which: +- Compiles the circuit to R1CS and WASM +- Generates zkey (proving key) +- Exports verification key + +### 3. Generate & Verify Proof + +```bash +# Generate witness +node build/gcp/gcp_jwt_verifier/gcp_jwt_verifier_js/generate_witness.js \ + build/gcp/gcp_jwt_verifier/gcp_jwt_verifier_js/gcp_jwt_verifier.wasm \ + circuit_inputs.json \ + witness.wtns + +# Generate proof +snarkjs groth16 prove \ + build/gcp/gcp_jwt_verifier/gcp_jwt_verifier_final.zkey \ + witness.wtns \ + proof.json \ + public.json + +# Verify proof +snarkjs groth16 verify \ + build/gcp/gcp_jwt_verifier/gcp_jwt_verifier_vkey.json \ + public.json \ + proof.json +``` + +## References + +- [GCP Confidential Space Documentation](https://cloud.google.com/confidential-computing/confidential-space/docs/confidential-space-overview) +- [GCP Token Claims Reference](https://cloud.google.com/confidential-computing/confidential-space/docs/reference/token-claims) +- [EAT Nonce Specification](https://cloud.google.com/confidential-computing/confidential-space/docs/connect-external-resources) +- [zk-jwt Library](https://github.com/zkemail/zk-jwt) diff --git a/circuits/circuits/gcp_jwt_verifier/example_jwt.txt b/circuits/circuits/gcp_jwt_verifier/example_jwt.txt new file mode 100644 index 000000000..baa633922 --- /dev/null +++ b/circuits/circuits/gcp_jwt_verifier/example_jwt.txt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsImtpZCI6IjhmbnpPUnJlZ3NfbEtpLXlNOUZTT19lU2RnVkdDUVZoWl9zT2x5TXViazgiLCJ0eXAiOiJKV1QiLCJ4NWMiOlsiTUlJRm1UQ0NBNEdnQXdJQkFnSVVBTDkwYitET0FwdUE0Z3NNRFZEaS82R0FuVzB3RFFZSktvWklodmNOQVFFTEJRQXdnWk14Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhFdzFOYjNWdWRHRnBiaUJXYVdWM01STXdFUVlEVlFRS0V3cEhiMjluYkdVZ1RFeERNUlV3RXdZRFZRUUxFd3hIYjI5bmJHVWdRMnh2ZFdReEt6QXBCZ05WQkFNVElrTnZibVpwWkdWdWRHbGhiQ0JUY0dGalpTQkpiblJsY20xbFpHbGhkR1VnUTBFd0hoY05NalV4TURFMk1EVXdPVEV3V2hjTk1qVXhNakUxTURVd09UQTVXakNCbERFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFUxdmRXNTBZV2x1SUZacFpYY3hFekFSQmdOVkJBb1RDa2R2YjJkc1pTQk1URU14RlRBVEJnTlZCQXNUREVkdmIyZHNaU0JEYkc5MVpERXNNQ29HQTFVRUF4TWpRMjl1Wm1sa1pXNTBhV0ZzSUZOd1lXTmxJRXhsWVdZZ1EyVnlkR2xtYVdOaGRHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDUlh6dEgySUQ3MU1USHpGQ3M1bnB5d1gvSzIzaFdoOGxWeVdVSGNzRVpSREt1eitwendPZC9PVXdydHZNYkJSWDZLVlFsejk4L2FoTjBUa1dXTExmLzVjdTFzWlk3bWc5OUh5VnV5YzF6YlBrUjBieTFqM3dPMi9weE8xRUdmY0JKd0hod0Zma0N0SE5rZVMrZFJ1T3g5VkM3KzFmSWtxekdvMm43MGEzVkFVVm56TXg2dmcwcE8rRkdlR1pPcUZIRWhMUno3eWRwemtFQkNiT2Z2Nm92Qk5wY0d3eFZubXBNdzcrTWhCUUZZazdCNDVDdkVwRk9xREQxcUpic2ZBOWZtdW5tQTdoTElyVzJ6cnFZbkl2SGJlaW5Tamt0YmNKZHJqZitMRnl4ZkZXcGw5UnUvYzNOMm5Sb3JtaWNmeWdibzg4dEMwV0ltS3ZYRXUrdTlSelJBZ01CQUFHamdlRXdnZDR3REFZRFZSMFRBUUgvQkFJd0FEQWRCZ05WSFE0RUZnUVVtMFlteThySFNOaDVuOTduQkpQUXl6VGd5V3N3SHdZRFZSMGpCQmd3Rm9BVWUwRi9pOU9SUkFCNmRJN1pEa1gxSFZvaGJ3UXdnWTBHQ0NzR0FRVUZCd0VCQklHQU1INHdmQVlJS3dZQkJRVUhNQUtHY0doMGRIQTZMeTl3Y21sMllYUmxZMkV0WTI5dWRHVnVkQzAyTldGaU5ERmxPQzB3TURBd0xUSmlOV1F0WW1NeVlpMWhZek5sWWpFMU1tUm1NMk11YzNSdmNtRm5aUzVuYjI5bmJHVmhjR2x6TG1OdmJTOWxOREkxWlRFMk9XVTVZekkzWVdOaU1XTXdOaTlqWVM1amNuUXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBQTg1MjdyWS9SM1FYdktnRUZIV1VWK1M4VTJhY2dTbDl1bkR6NFNaaHhyYVg2M1NyeEhYME5qeDB2c3FaZktKZ0pSSE9wOHA1RktjRzVpMG1RZzJrTWRnTmpnVmZxOVN4dzFRam81cVJwd1ZFdENTUS9TM0VPa2JZeTROd3p5eklvTzFSOGVEaTQwTVQyRk5XVXdsMDVRdG5SVlQ5c3FRczgrb3Q3VnphOHpraWRxZVlndkRwRGlMMnRVdnZIMjVLelhoUENlSEcxdmpKNWtvd1VZZTZHWVc3QkE0UmwrUThnSnZhVzBHV2Jtc1FHSGJob0VWOTFYY2Z5bXJNMmh0WDNvUldBb2F1REorRGhsbHZIcWp1WjBPZzdHTnZBVzVON2Z0S0hXSWRUeGM2QXkrd0JtdGhzTzVvUmZGUkZQUW8rZnFiYVFEYmJWUDJsUnpWaC85TmRlRlhLbzZEV2Vkd2pMdlBIUWZVZVplWEtvTHN6Ri9aNnJ3Um1BQkxyZ21GUmpTNFY5ZFdtUFFlckVKbHlQc3dJR1NqU3RXL3A1TkZRTHlZd3ZTYXFYUjVtUkRNMFZtczY5SEppZHFaQVFrd0VYdThQUWVKTHJ3S2F3TlpKMThZVlMzY0NZaUxrODZvY0I4aHAvaXdGN1NMZG1RY3l6aitudHN6VTVoTjN1UWMzZVY4T21TYVhaRGlOeFhRSnNCaWsveGVYcmZtWXpzTmF1dWxYOUxnZzlxSjNWNkRJd3NSc09aVDdSc2FuVzQzVmVjQ3k1a3dLQVZHc2g1Tll5RmdlcnVsajlGZjQ0OWdJd1oxMHB5RkJrZGJuUSs0b0pXMmNpTHZOcUdQcFVOSGltVWo4WmxTWE1CVTRTb2tUT0lBL0tLSTlnaWNmM3Bnb3pyNmw3c2pmY3IiLCJNSUlIUERDQ0JTU2dBd0lCQWdJVUFMZEhaaXo5eWcxMjRJV0EwaUhtQTNJdFJ3c3dEUVlKS29aSWh2Y05BUUVMQlFBd2dZc3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MU5iM1Z1ZEdGcGJpQldhV1YzTVJNd0VRWURWUVFLRXdwSGIyOW5iR1VnVEV4RE1SVXdFd1lEVlFRTEV3eEhiMjluYkdVZ1EyeHZkV1F4SXpBaEJnTlZCQU1UR2tOdmJtWnBaR1Z1ZEdsaGJDQlRjR0ZqWlNCU2IyOTBJRU5CTUI0WERUSTBNREV5TXpFM05Ua3hOMW9YRFRJM01ERXlNekV4TWpVek5Gb3dnWk14Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhFdzFOYjNWdWRHRnBiaUJXYVdWM01STXdFUVlEVlFRS0V3cEhiMjluYkdVZ1RFeERNUlV3RXdZRFZRUUxFd3hIYjI5bmJHVWdRMnh2ZFdReEt6QXBCZ05WQkFNVElrTnZibVpwWkdWdWRHbGhiQ0JUY0dGalpTQkpiblJsY20xbFpHbGhkR1VnUTBFd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUNRUW5ndmtXdEJLczEyQ0hBRllKand5UWdGL2Rnd2Z3SzArMTBDa3BuNjQ5dVg3dGlxLzZxVkllZWR5em56czRSWHhyRGhGRGY1d1RSUmFnOTFVeXE4ZXJyYS9sbStmQTRXVTNuQ1ViQkZxRnN6cC9majZhYTk1QUJFdEhlQ2Y4dUN3MDZsUHM4bXBnc0dCN2FscjNWdXFkR2dkWjBsV2t6N1N1RHNmbTh3aUpjaXFkREdpVk5rVUlScjI5Z0pGSlFCYzBIRXM5eTdEVnZyRGxYVHhSLzZBNnZ0L1NoekpNeVNrbUVyMzdSUTUzc0hmVW5iNFVPRTdGNGt2dUFGMU5JckRPaHdqaWx5ZWpuZnRFR1BsM3hsc0hXTXdZeSs0eCtZMXNUclRQTUFvNmJmeFZkaHlpNmUyWUlqenpuRWdvaFFMdGlmSjlBRWdMOUozTWNqdjZldTlIbGtSOGVwQ1ZpWkkydlV0THNVMEZiZzJ4ZWVmajV0eDZpTFB3dExQMjhoVTRhU1JPd0I2ZkFqUlhIcmd5czIyU2grOWtEM1AraVZCWVM5REhnVk5JakI3RXpGYmF1OHJnNFVOWk9kMEVldDBGMURVTjljKys1elhrbnlVRTdraHZMam1ZQ0g2RUMxOTcvM2hEYng5K09ZTWQ1NkJXYzltMGpTYjEranBaMW9Jbm9aTEhVdjNsRXN6dWVBZ1hXVHVkdVJKUW1OU2VKMktOSCs4REcrczZhNFBBdWtxN1d5bFZoeEUrclVHSG9uZFZ1TzdhSk9WdTVIcC9tSEUzcW5wQW5OYkdySkhXb2xsUDFvbXVoTTVKWEI3d3dzcDJMSHhXRzY0UTh1bVlQQ2NyclNha0JjcU5yaVhOWlR4d1BoUFdvV0dTMmNsRjVmdFR1YTY0bzBUd0lEQVFBQm80SUJqRENDQVlnd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJBR0ExVWRKUVFKTUFjR0JXZUJCUWdCTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkh0QmY0dlRrVVFBZW5TTzJRNUY5UjFhSVc4RU1COEdBMVVkSXdRWU1CYUFGQmZud1RoT2s0ZGJKNlNycnlHK2t2UDVyVHkrTUlHTkJnZ3JCZ0VGQlFjQkFRU0JnREIrTUh3R0NDc0dBUVVGQnpBQ2huQm9kSFJ3T2k4dmNISnBkbUYwWldOaExXTnZiblJsYm5RdE5qVmhZVFkyWWpRdE1EQXdNQzB5TXpGakxXRm1Oakl0TVRSak1UUmxaVFkyTkRVMExuTjBiM0poWjJVdVoyOXZaMnhsWVhCcGN5NWpiMjB2WkRNeE5HVmxOR1ptTkRoa1l6TmpNalUxTUdFdlkyRXVZM0owTUlHQ0JnTlZIUjhFZXpCNU1IZWdkYUJ6aG5Gb2RIUndPaTh2Y0hKcGRtRjBaV05oTFdOdmJuUmxiblF0TmpWaFlUWTJZalF0TURBd01DMHlNekZqTFdGbU5qSXRNVFJqTVRSbFpUWTJORFUwTG5OMGIzSmhaMlV1WjI5dloyeGxZWEJwY3k1amIyMHZaRE14TkdWbE5HWm1ORGhrWXpOak1qVTFNR0V2WTNKc0xtTnliREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBZlVjSjhnV1dtWStHajVPWmhBaVRqOFZLRDlhVlFRbFAzRFpzWTV6TWxyYlM2TkJsazRmc050dy9kd01GWHRXeUl6b3Q0RVB0QTNiV1hld0pRZ1pUY1ArZGwxcUgyZkhzRXR3Z0R2eDEreU9XaUpzOW1haDhScDlseHc2WFF0RGpRR2JRVU5FVUlpWGJjS0l4K3R2TzhmMndTaEtXSGxtNml0RkdxV3cyVFI5MzFnY2RtOElpUkFBc2dwTEZZeHcvbzhIVlMxRm9DSm1SYjUrSmRmZzJCQ0RsOSs5MTlKb09Sb3g4alpudUNadW5iMGhnOGRmUWFybG8yZHQ0S3VlcHptUjZaUWdRNjNVZEYvOUJ3WHVCcjlyaUozL2pWTUR5UkRuZVkvZzQxVWtKeEVrNTZRVndISXV1Qm5FeXZUZER6dXRvOXRjL0g5R2s0T2ZhbDhHNStyVkJVbXVvL3BEZmlVVDVYTUZTVkh0aVc4Y0dhdUNrWnB1ZmJINHpFQWc2REdpa0FrditGbmxyVFhlb0pYazA4cC9ZQnp5NXlUZ3BSbnk5a0RnWnRnY2hPREo2Y1lDT3ZrTDV4QlZONTJ0TWozL0RKLy85QkhRS01DbWkzRk1pRlY1YVhvTlFNdUhsRXBvY0tmbWR0YWNjbnMxcko5R3h5NUVGU3d4VG04SGx3OVUyRzdYRVJqbU1obGNsd2tlVU1nSHI0N3RsMWxPSzdHZGlrNWNEQTFQT3pTNnhENGFJYkNSVnpSRmoxZVVUQ1lBY3pPcE9ZanAyQVhUR0JITXpUNWlkWWNMYVFoL3lFZEN4V1hGLzlUZHRBOG92d2ZqanRONmJLb2cvWFFwRU51T21aR0V4ZDdIdE05bllrdERiQXJEVktaM3IzN0xuT2NYUlUzSEZTVWs9IiwiTUlJR0NEQ0NBL0NnQXdJQkFnSVRZQnZSeTVnOWFZWU1oN3RKUzdwRndhZkw2akFOQmdrcWhraUc5dzBCQVFzRkFEQ0JpekVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVTF2ZFc1MFlXbHVJRlpwWlhjeEV6QVJCZ05WQkFvVENrZHZiMmRzWlNCTVRFTXhGVEFUQmdOVkJBc1RERWR2YjJkc1pTQkRiRzkxWkRFak1DRUdBMVVFQXhNYVEyOXVabWxrWlc1MGFXRnNJRk53WVdObElGSnZiM1FnUTBFd0hoY05NalF3TVRFNU1qSXhNRFV3V2hjTk16UXdNVEUyTWpJeE1EUTVXakNCaXpFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFUxdmRXNTBZV2x1SUZacFpYY3hFekFSQmdOVkJBb1RDa2R2YjJkc1pTQk1URU14RlRBVEJnTlZCQXNUREVkdmIyZHNaU0JEYkc5MVpERWpNQ0VHQTFVRUF4TWFRMjl1Wm1sa1pXNTBhV0ZzSUZOd1lXTmxJRkp2YjNRZ1EwRXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDRHdBd2dnSUtBb0lDQVFDdlJ1WmFzY3pBcWhNWmUxT0RISjZNRkxYOEVZVlYrUk43eGlPOUdwdUE1M2l6bDlPeGdwM05YaWszRmJZbis3YmNJa01NU1FwQ3I2SzBqYlNRQ1pUNmQ1UDVQSlQ1RHBOR1lqTEhrVzY3L2ZsK0J1N2VTTWIwcVJDYTFqUyszT2hOSzd0N1NJYUhtMVhkbVNSZ2hqd29nbEtSdWszQ0dyRjRaaWE5UmNFL3AyTVU2OUd5SlpwcUhZd1RwbE5yM3g0ekYrMm5Kazg2R3l3RFArc0d3U1BXZmNtcVkwNFZRRDdaUERFWlovcWd6ZG9MNWlsRTkyZVFuQXN5KzZtNkx4QkVISFZjRnBmRHROVlVJdDJWTUNXTEJlT0tVUWNuNWpzNzU2eGJsSW5xdy9RdFFSUjBBbjB5ZlJqQnVHdm1NakF3RVREbzVFVFkvZmMrbmJRVllKek5RVGM5RU9wRkZXUHB3L1pqRmNOOUFtbmRkeFlVRVRGWFBtQlllck1lejBMS050R3BmS1lISGhNTVRJM21qMG0vVjlmQ2JmaDJZYkJVbk1TMlN3ZDIwWVNJTWkvSGlHYXFPcEdVcVhNZVFWdzdwaEdUUzNRWUs4Wk02NXNDL1FoSVF6WGRzaUxEZ0ZCaXRWbmxJdTNsSXY2Q3VpSHZYZVNKQlJsUnhROFZ1K3Q2SjdoQmRsMGV0V0JLQXU5VnRpNDZhZjVjakMwM2RzcGtIUjNNQVVHY3JMV0VUa1EwbXNRQUt2SUFsd3lRUkx1UU9JNUQ2cEYrNmFmMU5ibCt2UjdzTENiRFdkTXFtMUU5WDZLeUZLZDZlM3JuRTlPNGRrRkpwMzVXdlIyZ3FJQWtVb2ErVnExTVhMRllHNGltYW5aS0gwaWdySWJsYmF3UkNyM0dyMjRGWFFJREFRQUJvMk13WVRBT0JnTlZIUThCQWY4RUJBTUNBUVl3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVRitmQk9FNlRoMXNucEt1dkliNlM4L210UEw0d0h3WURWUjBqQkJnd0ZvQVVGK2ZCT0U2VGgxc25wS3V2SWI2UzgvbXRQTDR3RFFZSktvWklodmNOQVFFTEJRQURnZ0lCQUd0Q3VWNWVIeFdjZmZ5bEs5R1B1bWFENllqZGNzNzZLREJlM21reTVJdEJJckVPZVpxM3o0N3pNNGRiS1pIaEZ1b3E0eUFhTzFNeUFwbkcwdzl3SVFMQkRuZElvdnRrdzZqOS82NGFxUFdwTmFvQjVNQjBTYWhDVUNnSTgzRHg5U1JxR21qUEkvTVRNZndETGRFNUVGOWdGbVZJb0g2MlluRzJhYS9zYzZtLzh3SUs4V3RUSmF6RUkxNi84R1BHNFpVaHdUNmFSM0lHR25FQlBNYk1kNVZaUTBId1ZiSEJLV0szVXlrYVNDeG5FZzh1YU54L3JoTmFPV3VXdG9zNHFMMDBkWXlHVjdaWGc0ZnBBcTcyNDRRVWdrV1ZBdFZjVTJTUEJqRGQzME9GSEFTbmVuREhSelFkT3RIYXhMcDRhNFdhWTNqYjJWNlNuM0xmRTh6U3k2R2V2eG1OQ09JV1czeG5QRjhyd0t6NEFCRVBxRUNlMzd6enUzVzFuelpBRnRka2hQQk5ubFdZa0l1c1RNdFUrOHY2RVBLcEdJSVJwaHBhRGh0R1BKUXVrcEVOT2ZrMjcyOGxlblB5Y1Jmanh3QTk2VUtXcTBkS1pDNDVNd0JFSzlKbmduOFFjUG1wUG14N3BTTWtTeEVYMlZvczJKTmFObUNLSmQyVmFYejhNNkYyY3hzY1JkaDlUYkFZQWpHRUVqRTFuTFVIMllIRFM4WTd4WU5GSURTRmFKQWxxR2NDVWJ6akdocndIR2o0dm9UZTladmxtbmdyY0EvcHRTdUJpZHZzblJEd2tOUExvd0NkME5xeFlZU0xOTDdHcm9ZQ0ZQeG9CcHIrKys0dnNDYVhhbGJzOGlKeGRVMkVQcUc0TUI0eFdLWWd1eVQ1Q25KdWx4U0M1Q1QxIl19.eyJhdWQiOiJVU0VSIiwiZXhwIjoxNzYxMDAyODQ5LCJpYXQiOjE3NjA5OTkyNDksImlzcyI6Imh0dHBzOi8vY29uZmlkZW50aWFsY29tcHV0aW5nLmdvb2dsZWFwaXMuY29tIiwibmJmIjoxNzYwOTk5MjQ5LCJzdWIiOiJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9jb21wdXRlL3YxL3Byb2plY3RzL3NlbGYtcHJvdG9jb2wvem9uZXMvdXMtd2VzdDEtYi9pbnN0YW5jZXMvdGVlLXJlZ2lzdGVyLW1lZGl1bS1pbnN0YW5jZS1mZ3p3IiwiZWF0X25vbmNlIjpbIkFtdFBucmNqM3Z1aE9vMTBRWGpLZnNRMkpac0x0N0RxZWVUSHlMbGljZlVlIiwiQXFRcTMxZTZrVEpWenBPL0E5cjA4U3d6RGFvUGdyaDhCOXVzc29ZbVhFZGMiXSwiZWF0X3Byb2ZpbGUiOiJodHRwczovL2Nsb3VkLmdvb2dsZS5jb20vY29uZmlkZW50aWFsLWNvbXB1dGluZy9jb25maWRlbnRpYWwtc3BhY2UvZG9jcy9yZWZlcmVuY2UvdG9rZW4tY2xhaW1zIiwic2VjYm9vdCI6dHJ1ZSwib2VtaWQiOjExMTI5LCJod21vZGVsIjoiR0NQX0FNRF9TRVYiLCJzd25hbWUiOiJDT05GSURFTlRJQUxfU1BBQ0UiLCJzd3ZlcnNpb24iOlsiMjUxMDAxIl0sImRiZ3N0YXQiOiJkaXNhYmxlZC1zaW5jZS1ib290Iiwic3VibW9kcyI6eyJjb25maWRlbnRpYWxfc3BhY2UiOnsic3VwcG9ydF9hdHRyaWJ1dGVzIjpbIkxBVEVTVCIsIlNUQUJMRSIsIlVTQUJMRSJdLCJtb25pdG9yaW5nX2VuYWJsZWQiOnsibWVtb3J5IjpmYWxzZX19LCJjb250YWluZXIiOnsiaW1hZ2VfcmVmZXJlbmNlIjoidXMtZG9ja2VyLnBrZy5kZXYvc2VsZi1wcm90b2NvbC9zZWxmLXByb3RvY29sLXJlcG9zaXRvcnkvdGVlLXNlcnZlci1yZWdpc3Rlci1tZWRpdW06bGF0ZXN0IiwiaW1hZ2VfZGlnZXN0Ijoic2hhMjU2OmQyMjIxYTBlZTgzOTAxOTgwYzYwN2NlZmYyZWRiZWRmM2Y2Y2U1ZjQzN2VhZmE1ZDg5YmUzOWU5ZTc0ODdjMDQiLCJyZXN0YXJ0X3BvbGljeSI6Ik5ldmVyIiwiaW1hZ2VfaWQiOiJzaGEyNTY6MjVjMzdkYTY4ZDVmNGRiOWM1ZDJiNjYyY2ViNmU3MjgwNGFkZDQzZGZmZThiZDQwNGIyOGQ2NTJhM2ZkNjgxNyIsImVudl9vdmVycmlkZSI6eyJQT09MX05BTUUiOiJjcy1wb29sIiwiUFJPSkVDVF9JRCI6InNlbGYtcHJvdG9jb2wiLCJQUk9KRUNUX05VTUJFUiI6IjEwMjU0NjY5MTUwNjEiLCJTRUNSRVRfSUQiOiJEQl9VUkwifSwiZW52Ijp7IkhPU1ROQU1FIjoidGVlLXJlZ2lzdGVyLW1lZGl1bS1pbnN0YW5jZS1mZ3p3IiwiUEFUSCI6Ii91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL3NiaW46L3Vzci9iaW46L3NiaW46L2JpbiIsIlBPT0xfTkFNRSI6ImNzLXBvb2wiLCJQUk9KRUNUX0lEIjoic2VsZi1wcm90b2NvbCIsIlBST0pFQ1RfTlVNQkVSIjoiMTAyNTQ2NjkxNTA2MSIsIlNFQ1JFVF9JRCI6IkRCX1VSTCJ9LCJhcmdzIjpbIi91c3IvbG9jYWwvYmluL3N0YXJ0LnNoIl19LCJnY2UiOnsiem9uZSI6InVzLXdlc3QxLWIiLCJwcm9qZWN0X2lkIjoic2VsZi1wcm90b2NvbCIsInByb2plY3RfbnVtYmVyIjoiMTAyNTQ2NjkxNTA2MSIsImluc3RhbmNlX25hbWUiOiJ0ZWUtcmVnaXN0ZXItbWVkaXVtLWluc3RhbmNlLWZnenciLCJpbnN0YW5jZV9pZCI6IjQ5Nzc5MDczMTI5MDI0MDExNjEifX0sImdvb2dsZV9zZXJ2aWNlX2FjY291bnRzIjpbInNlbGYtcHJvdG9jb2wtd29ya2xvYWRAc2VsZi1wcm90b2NvbC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSJdfQ.A9zXBTrOFm7Wgg4yWS95IGkM7XIkn01jfMsuWBPi4Sm6JJOldJhcDukoPvmJ6q4Vf_zQCb_OiBtrZxoNumS1kJgW_4oDjGCJTwaMblDmyCzfTxlTL8TFuftcmeP38RutuD7WEeYXaoaZyMtxQC0uarEqrpQG-6qO75IdQ66vi3umxcSSD2hw7hRrLNGqmGFCKa75sHI_RM1AHkZaH10O0s37B3abYi3-pw-ZB1Jh_J2N02kre53UKYGrUhDp7s-vEp4UqVtoyKS2n6remxzIunXE0tGBtOjlXu55fN-zP5juKWSl66_RFVs5wEuNAcWACqUI_5q0cmniO3RYD2RNFg diff --git a/circuits/circuits/gcp_jwt_verifier/gcp_jwt_verifier.circom b/circuits/circuits/gcp_jwt_verifier/gcp_jwt_verifier.circom new file mode 100644 index 000000000..e8017d415 --- /dev/null +++ b/circuits/circuits/gcp_jwt_verifier/gcp_jwt_verifier.circom @@ -0,0 +1,252 @@ +pragma circom 2.1.9; + +include "./jwt_verifier.circom"; +include "../utils/passport/signatureAlgorithm.circom"; +include "../utils/passport/customHashers.circom"; +include "../utils/gcp_jwt/extractAndValidatePubkey.circom"; +include "../utils/gcp_jwt/verifyCertificateSignature.circom"; +include "../utils/gcp_jwt/verifyJSONFieldExtraction.circom"; +include "circomlib/circuits/comparators.circom"; +include "@openpassport/zk-email-circuits/utils/array.circom"; + +/// @title GCPJWTVerifier +/// @notice Verifies GCP JWT signature and full x5c certificate chain +/// @dev Complete chain-of-trust verification in-circuit: +/// x5c[0]: Leaf certificate (signs JWT) +/// x5c[1]: Intermediate CA (signs x5c[0]) +/// x5c[2]: Root CA (signs x5c[1]) +template GCPJWTVerifier( + signatureAlgorithm, // 1 for RSA-SHA256 + n, // RSA chunk size (120) + k // Number of chunks (35) +) { + // JWT parameters + var maxMessageLength = 11776; + var maxB64HeaderLength = 8832; + var maxB64PayloadLength = 2880; + + // Certificate parameters + var MAX_CERT_LENGTH = 2048; // Max DER-encoded certificate size + var MAX_PUBKEY_PREFIX = 33; // ASN.1 prefix length (from DSC) + var MAX_PUBKEY_LENGTH = n * k / 8; // Max RSA pubkey length in bytes + + var kLengthFactor = getKLengthFactor(signatureAlgorithm); + var kScaled = k * kLengthFactor; + var hashLength = getHashLength(signatureAlgorithm); + var suffixLength = kLengthFactor == 1 ? getSuffixLength(signatureAlgorithm) : 0; + + // JWT inputs + signal input message[maxMessageLength]; // JWT header.payload + signal input messageLength; + signal input periodIndex; + + // x5c[0] - Leaf certificate (DER encoded, padded for SHA) + signal input leaf_cert[MAX_CERT_LENGTH]; + signal input leaf_cert_padded_length; // Padded length for SHA256 + signal input leaf_pubkey_offset; // Offset to pubkey in cert + signal input leaf_pubkey_actual_size; // Actual pubkey size in bytes + + // x5c[1] - Intermediate CA certificate + signal input intermediate_cert[MAX_CERT_LENGTH]; + signal input intermediate_cert_padded_length; + signal input intermediate_pubkey_offset; + signal input intermediate_pubkey_actual_size; + + // x5c[2] - Root CA certificate + signal input root_cert[MAX_CERT_LENGTH]; + signal input root_cert_padded_length; + signal input root_pubkey_offset; + signal input root_pubkey_actual_size; + + // Public keys (extracted from certificates) + signal input leaf_pubkey[kScaled]; // From x5c[0] + signal input intermediate_pubkey[kScaled]; // From x5c[1] + signal input root_pubkey[kScaled]; // From x5c[2] + + // Signatures + signal input jwt_signature[kScaled]; // JWT signature + signal input leaf_signature[kScaled]; // x5c[0] signature + signal input intermediate_signature[kScaled]; // x5c[1] signature + + + // GCP spec: nonce must be 10-74 bytes decoded + // Base64url encoding: 10 bytes = 14 chars, 74 bytes = 99 chars + // https://cloud.google.com/confidential-computing/confidential-space/docs/connect-external-resources + // EAT nonce (payload.eat_nonce[0]) + var MAX_EAT_NONCE_B64_LENGTH = 99; // Max length for base64url string (74 bytes decoded = 99 b64url chars) + var MAX_EAT_NONCE_KEY_LENGTH = 10; // Length of "eat_nonce" key (without quotes) + signal input eat_nonce_0_b64_length; // Length of base64url string + signal input eat_nonce_0_key_offset; // Offset in payload where "eat_nonce" key starts (after opening quote) + signal input eat_nonce_0_value_offset; // Offset in payload where eat_nonce[0] value appears + + // Container image digest (payload.submods.container.image_digest) + var MAX_IMAGE_DIGEST_LENGTH = 71; // "sha256:" + 64 hex chars + var IMAGE_HASH_LENGTH = 64; // Just the hex hash portion + var MAX_IMAGE_DIGEST_KEY_LENGTH = 12; // Length of "image_digest" key (without quotes) + signal input image_digest_length; // Length of full string (should be 71) + signal input image_digest_key_offset; // Offset in payload where "image_digest" key starts (after opening quote) + signal input image_digest_value_offset; // Offset in payload where image_digest value appears + + var maxHeaderLength = (maxB64HeaderLength * 3) \ 4; + var maxPayloadLength = (maxB64PayloadLength * 3) \ 4; + + signal output rootCAPubkeyHash; // Root CA (x5c[2]) pubkey, trust anchor + signal output eat_nonce_0_b64_output[MAX_EAT_NONCE_B64_LENGTH]; // eat_nonce[0] base64url string + signal output image_hash[IMAGE_HASH_LENGTH]; // Container image SHA256 hash (without "sha256:" prefix) + + // Verify JWT Signature (using x5c[0] public key) + component jwtVerifier = JWTVerifier(n, k, maxMessageLength, maxB64HeaderLength, maxB64PayloadLength); + jwtVerifier.message <== message; + jwtVerifier.messageLength <== messageLength; + jwtVerifier.pubkey <== leaf_pubkey; + jwtVerifier.signature <== jwt_signature; + jwtVerifier.periodIndex <== periodIndex; + + // Poseidon hash of root CA pubkey (x5c[2]) + rootCAPubkeyHash <== CustomHasher(kScaled)(root_pubkey); + + signal payload[maxPayloadLength]; + payload <== jwtVerifier.payload; + + // Extract and validate x5c[0] Public Key + ExtractAndValidatePubkey(signatureAlgorithm, n, k, MAX_CERT_LENGTH, MAX_PUBKEY_PREFIX, MAX_PUBKEY_LENGTH)( + leaf_cert, + leaf_pubkey_offset, + leaf_pubkey_actual_size, + leaf_pubkey + ); + + // Extract and validate x5c[1] public key + ExtractAndValidatePubkey(signatureAlgorithm, n, k, MAX_CERT_LENGTH, MAX_PUBKEY_PREFIX, MAX_PUBKEY_LENGTH)( + intermediate_cert, + intermediate_pubkey_offset, + intermediate_pubkey_actual_size, + intermediate_pubkey + ); + + // Verify x5c[0] signature using x5c[1] public key + VerifyCertificateSignature(signatureAlgorithm, n, k, MAX_CERT_LENGTH)( + leaf_cert, + leaf_cert_padded_length, + intermediate_pubkey, + leaf_signature + ); + + // Extract and validate x5c[2] public key + ExtractAndValidatePubkey(signatureAlgorithm, n, k, MAX_CERT_LENGTH, MAX_PUBKEY_PREFIX, MAX_PUBKEY_LENGTH)( + root_cert, + root_pubkey_offset, + root_pubkey_actual_size, + root_pubkey + ); + + // Verify x5c[1] signature using x5c[2] public key + VerifyCertificateSignature(signatureAlgorithm, n, k, MAX_CERT_LENGTH)( + intermediate_cert, + intermediate_cert_padded_length, + root_pubkey, + intermediate_signature + ); + + // Make sure nonce is not empty + component length_nonzero = IsZero(); + length_nonzero.in <== eat_nonce_0_b64_length; + length_nonzero.out === 0; // Must NOT be zero + + // Validate nonce minimum length (10 bytes decoded = 14 base64url chars) + component length_min_check = GreaterEqThan(log2Ceil(MAX_EAT_NONCE_B64_LENGTH)); + length_min_check.in[0] <== eat_nonce_0_b64_length; + length_min_check.in[1] <== 14; + length_min_check.out === 1; + + // Validate nonce maximum length (74 bytes decoded = 99 base64url chars) + component length_max_check = LessEqThan(log2Ceil(MAX_EAT_NONCE_B64_LENGTH)); + length_max_check.in[0] <== eat_nonce_0_b64_length; + length_max_check.in[1] <== 99; + length_max_check.out === 1; + + // Validate nonce offset bounds (prevent reading beyond payload) + signal eat_nonce_end_position <== eat_nonce_0_value_offset + eat_nonce_0_b64_length; + component offset_bounds_check = LessEqThan(log2Ceil(maxPayloadLength)); + offset_bounds_check.in[0] <== eat_nonce_end_position; + offset_bounds_check.in[1] <== maxPayloadLength; + offset_bounds_check.out === 1; + + // Extract and verify EAT nonce field + signal expected_eat_nonce_key[MAX_EAT_NONCE_KEY_LENGTH]; + // "eat_nonce", ASCII + expected_eat_nonce_key[0] <== 101; // 'e' + expected_eat_nonce_key[1] <== 97; // 'a' + expected_eat_nonce_key[2] <== 116; // 't' + expected_eat_nonce_key[3] <== 95; // '_' + expected_eat_nonce_key[4] <== 110; // 'n' + expected_eat_nonce_key[5] <== 111; // 'o' + expected_eat_nonce_key[6] <== 110; // 'n' + expected_eat_nonce_key[7] <== 99; // 'c' + expected_eat_nonce_key[8] <== 101; // 'e' + expected_eat_nonce_key[9] <== 0; // padding + + component eatNonceExtractor = ExtractAndVerifyJSONField(maxPayloadLength, MAX_EAT_NONCE_KEY_LENGTH, MAX_EAT_NONCE_B64_LENGTH); + eatNonceExtractor.json <== payload; + eatNonceExtractor.key_offset <== eat_nonce_0_key_offset; + eatNonceExtractor.key_length <== 9; // actual key length "eat_nonce" + eatNonceExtractor.value_offset <== eat_nonce_0_value_offset; + eatNonceExtractor.value_length <== eat_nonce_0_b64_length; + eatNonceExtractor.expected_key_name <== expected_eat_nonce_key; + + // Output the extracted base64url string + eat_nonce_0_b64_output <== eatNonceExtractor.extracted_value; + + // Validate length is exactly 71 ("sha256:" + 64 hex chars) + image_digest_length === 71; + + // Validate offset bounds + signal image_digest_end_position <== image_digest_value_offset + image_digest_length; + component image_digest_bounds_check = LessEqThan(log2Ceil(maxPayloadLength)); + image_digest_bounds_check.in[0] <== image_digest_end_position; + image_digest_bounds_check.in[1] <== maxPayloadLength; + image_digest_bounds_check.out === 1; + + // Extract and verify image digest field + signal expected_image_digest_key[MAX_IMAGE_DIGEST_KEY_LENGTH]; + // "image_digest", ASCII + expected_image_digest_key[0] <== 105; // 'i' + expected_image_digest_key[1] <== 109; // 'm' + expected_image_digest_key[2] <== 97; // 'a' + expected_image_digest_key[3] <== 103; // 'g' + expected_image_digest_key[4] <== 101; // 'e' + expected_image_digest_key[5] <== 95; // '_' + expected_image_digest_key[6] <== 100; // 'd' + expected_image_digest_key[7] <== 105; // 'i' + expected_image_digest_key[8] <== 103; // 'g' + expected_image_digest_key[9] <== 101; // 'e' + expected_image_digest_key[10] <== 115; // 's' + expected_image_digest_key[11] <== 116; // 't' + + component imageDigestExtractor = ExtractAndVerifyJSONField(maxPayloadLength, MAX_IMAGE_DIGEST_KEY_LENGTH, MAX_IMAGE_DIGEST_LENGTH); + imageDigestExtractor.json <== payload; + imageDigestExtractor.key_offset <== image_digest_key_offset; + imageDigestExtractor.key_length <== 12; // actual key length "image_digest" + imageDigestExtractor.value_offset <== image_digest_value_offset; + imageDigestExtractor.value_length <== image_digest_length; + imageDigestExtractor.expected_key_name <== expected_image_digest_key; + + signal extracted_image_digest[MAX_IMAGE_DIGEST_LENGTH]; + extracted_image_digest <== imageDigestExtractor.extracted_value; + + // "sha256:", ASCII + extracted_image_digest[0] === 115; // 's' + extracted_image_digest[1] === 104; // 'h' + extracted_image_digest[2] === 97; // 'a' + extracted_image_digest[3] === 50; // '2' + extracted_image_digest[4] === 53; // '5' + extracted_image_digest[5] === 54; // '6' + extracted_image_digest[6] === 58; // ':' + + // Extract and output only the 64-char hash (skip "sha256:" prefix) + for (var i = 0; i < IMAGE_HASH_LENGTH; i++) { + image_hash[i] <== extracted_image_digest[7 + i]; + } +} + +component main = GCPJWTVerifier(1, 120, 35); diff --git a/circuits/circuits/gcp_jwt_verifier/jwt_verifier.circom b/circuits/circuits/gcp_jwt_verifier/jwt_verifier.circom new file mode 100644 index 000000000..890959ac4 --- /dev/null +++ b/circuits/circuits/gcp_jwt_verifier/jwt_verifier.circom @@ -0,0 +1,203 @@ +pragma circom 2.1.9; + +include "circomlib/circuits/poseidon.circom"; +include "circomlib/circuits/bitify.circom"; +include "@openpassport/zk-email-circuits/utils/array.circom"; +include "@openpassport/zk-email-circuits/utils/hash.circom"; +include "@openpassport/zk-email-circuits/lib/sha.circom"; +include "@openpassport/zk-email-circuits/lib/base64.circom"; +include "../utils/passport/signatureVerifier.circom"; +include "../utils/passport/customHashers.circom"; +include "../utils/crypto/bitify/bytes.circom"; + +/// @title SelectSubArrayBase64 +/// @notice Select sub array from an array and pad with 'A' for Base64 +/// @notice This is similar to `SelectSubArray` but pads with 'A' (ASCII 65) instead of zero +/// @notice Useful for preparing Base64 encoded data for decoding +/// @param maxArrayLen: the maximum number of bytes in the input array +/// @param maxSubArrayLen: the maximum number of integers in the output array +/// @input in: the input array +/// @input startIndex: the start index of the sub array; assumes a valid index +/// @input length: the length of the sub array; assumes to fit in `ceil(log2(maxArrayLen))` bits +/// @output out: array of `maxSubArrayLen` size, items starting from `startIndex`, and items after `length` set to 'A' (ASCII 65) +/// @see https://github.com/zkemail/zk-jwt/blob/3a50a9b/packages/circuits/utils/array.circom#L15 +template SelectSubArrayBase64(maxArrayLen, maxSubArrayLen) { + assert(maxSubArrayLen <= maxArrayLen); + + signal input in[maxArrayLen]; + signal input startIndex; + signal input length; + + signal output out[maxSubArrayLen]; + + component shifter = VarShiftLeft(maxArrayLen, maxSubArrayLen); + shifter.in <== in; + shifter.shift <== startIndex; + + component gts[maxSubArrayLen]; + for (var i = 0; i < maxSubArrayLen; i++) { + gts[i] = GreaterThan(log2Ceil(maxSubArrayLen)); + gts[i].in[0] <== length; + gts[i].in[1] <== i; + + // Pad with 'A' (ASCII 65) instead of zero + out[i] <== gts[i].out * shifter.out[i] + (1 - gts[i].out) * 65; + } +} + +/// @title FindRealMessageLength +/// @notice Finds the length of the real message in a padded array by locating the first occurrence of 128 +/// @dev This template is specifically designed for Base64 encoded strings followed by SHA-256 padding. +/// It works because: +/// 1. Base64 uses characters with ASCII values < 128 +/// 2. SHA-256 padding starts with 128 (10000000 in binary) +/// 3. The first 128 encountered marks the end of the Base64 string and start of padding +/// @input in[maxLength] The padded message array +/// @input maxLength The maximum possible length of the padded message +/// @output realLength The length of the real message (before padding) +/// @see https://github.com/zkemail/zk-jwt/blob/3a50a9b/packages/circuits/utils/bytes.circom#L15 +template FindRealMessageLength(maxLength) { + signal input in[maxLength]; + signal output realLength; + + // Signal to track if we've found 128 + signal found[maxLength + 1]; + found[0] <== 0; + + // Signal to accumulate the length + signal lengthAcc[maxLength + 1]; + lengthAcc[0] <== 0; + + signal is128[maxLength]; + + // Iterate through the array + for (var i = 0; i < maxLength; i++) { + // Check if current element is 128 + is128[i] <== IsEqual()([in[i], 128]); + + // Update found signal + found[i + 1] <== found[i] + is128[i] - found[i] * is128[i]; + + // If 128 not found yet, increment length + lengthAcc[i + 1] <== lengthAcc[i] + 1 - found[i + 1]; + } + + // The final accumulated length is our real message length + realLength <== lengthAcc[maxLength]; + + // Constraint to ensure 128 was really found + found[maxLength] === 1; +} + +/// @title CountCharOccurrences +/// @notice Counts the number of occurrences of a specified character in an array +/// @dev This template iterates through the input array and counts how many times the specified character appears. +/// @input in[maxLength] The input array in which to count occurrences of the character +/// @input char The character to count within the input array +/// @output count The number of times the specified character appears in the input array +/// @see https://github.com/zkemail/zk-jwt/blob/3a50a9b/packages/circuits/utils/bytes.circom#L54 +template CountCharOccurrences(maxLength) { + signal input in[maxLength]; + signal input char; + signal output count; + + signal match[maxLength]; + signal counter[maxLength]; + + match[0] <== IsEqual()([in[0], char]); + counter[0] <== match[0]; + + for (var i = 1; i < maxLength; i++) { + match[i] <== IsEqual()([in[i], char]); + counter[i] <== counter[i-1] + match[i]; + } + + count <== counter[maxLength-1]; +} + +/// @title JWTVerifier +/// @notice Verifies JWT signatures and extracts header/payload components +/// @dev This template verifies RSA-SHA256 signed JWTs and decodes Base64 encoded components. +/// It works by: +/// 1. Verifying message length and padding +/// 2. Computing SHA256 hash of `header.payload` +/// 3. Verifying RSA signature against public key +/// 4. Extracting and decoding Base64 header/payload +/// 5. Computing public key hash for external reference +/// @param n RSA chunk size in bits (n < 127 for field arithmetic) +/// @param k Number of RSA chunks (n*k > 2048 for RSA-2048) +/// @param maxMessageLength Maximum JWT string length (must be multiple of 64 for SHA256) +/// @param maxB64HeaderLength Maximum Base64 header length (must be multiple of 4) +/// @param maxB64PayloadLength Maximum Base64 payload length (must be multiple of 4) +/// @input message[maxMessageLength] JWT string (header.payload) +/// @input messageLength Actual length of JWT string +/// @input pubkey[k] RSA public key in k chunks +/// @input signature[k] RSA signature in k chunks +/// @input periodIndex Location of period separating header.payload +/// @output publicKeyHash Poseidon hash of public key +/// @output header[maxHeaderLength] Decoded JWT header +/// @output payload[maxPayloadLength] Decoded JWT payload +/// @notice Modified version of ZK-Email's `JWTVerifier`, adapted to use Self's `SignatureVerifier` circuit for RSA verification +/// @see https://github.com/zkemail/zk-jwt/blob/3a50a9b/packages/circuits/jwt-verifier.circom#L35 +template JWTVerifier( + n, + k, + maxMessageLength, + maxB64HeaderLength, + maxB64PayloadLength +) { + signal input message[maxMessageLength]; // JWT message (header + payload) + signal input messageLength; // Length of the message signed in the JWT + signal input pubkey[k]; // RSA public key split into k chunks + signal input signature[k]; // RSA signature split into k chunks + signal input periodIndex; // Index of the period in the JWT message + + var maxHeaderLength = (maxB64HeaderLength * 3) \ 4; + var maxPayloadLength = (maxB64PayloadLength * 3) \ 4; + + signal output publicKeyHash; + signal output header[maxHeaderLength]; + signal output payload[maxPayloadLength]; + + // Assert message length fits in ceil(log2(maxMessageLength)) + component n2bMessageLength = Num2Bits(log2Ceil(maxMessageLength)); + n2bMessageLength.in <== messageLength; + + // Assert message data after messageLength are zeros + AssertZeroPadding(maxMessageLength)(message, messageLength); + + // Calculate SHA256 hash of the JWT message + signal sha[256] <== Sha256Bytes(maxMessageLength)(message, messageLength); + + SignatureVerifier(1, n, k)( + sha, + pubkey, + signature + ); + + // Calculate the pubkey hash + publicKeyHash <== CustomHasher(k)(pubkey); + + // Assert that period exists at periodIndex + signal period <== ItemAtIndex(maxMessageLength)(message, periodIndex); + period === 46; + + // Assert that period is unique + signal periodCount <== CountCharOccurrences(maxMessageLength)(message, 46); + periodCount === 1; + + // Find the real message length + signal realMessageLength <== FindRealMessageLength(maxMessageLength)(message); + + // Calculate the length of the Base64 encoded header and payload + signal b64HeaderLength <== periodIndex; + signal b64PayloadLength <== realMessageLength - b64HeaderLength - 1; + + // Extract the Base64 encoded header and payload from the message + signal b64Header[maxB64HeaderLength] <== SelectSubArrayBase64(maxMessageLength, maxB64HeaderLength)(message, 0, b64HeaderLength); + signal b64Payload[maxB64PayloadLength] <== SelectSubArrayBase64(maxMessageLength, maxB64PayloadLength)(message, b64HeaderLength + 1, b64PayloadLength); + + // Decode the Base64 encoded header and payload + header <== Base64Decode(maxHeaderLength)(b64Header); + payload <== Base64Decode(maxPayloadLength)(b64Payload); +} diff --git a/circuits/circuits/gcp_jwt_verifier/prepare.ts b/circuits/circuits/gcp_jwt_verifier/prepare.ts new file mode 100644 index 000000000..5849d71e5 --- /dev/null +++ b/circuits/circuits/gcp_jwt_verifier/prepare.ts @@ -0,0 +1,394 @@ +/** + * Prepares GCP JWT attestations with full certificate chain verification + * + * Extracts all 3 x5c certificates, their public keys, signatures, and generates + * circuit inputs for complete chain-of-trust verification: + * 1. JWT signature (signed by x5c[0]) + * 2. x5c[0] certificate signature (signed by x5c[1]) + * 3. x5c[1] certificate signature (signed by x5c[2]) + * + * Usage: tsx prepare.ts [output_file.json] + */ + +import * as fs from 'fs'; +import * as forge from 'node-forge'; +import { generateJWTVerifierInputs } from '@zk-email/jwt-tx-builder-helpers/src/input-generators.js'; +import type { RSAPublicKey } from '@zk-email/jwt-tx-builder-helpers/src/types.js'; + +const MAX_CERT_LENGTH = 2048; +const MAX_EAT_NONCE_B64_LENGTH = 99; // Base64url string max length (74 bytes decoded = 99 b64url chars) +const MAX_IMAGE_DIGEST_LENGTH = 71; // "sha256:" + 64 hex chars + +interface CertificateInfo { + der: Buffer; + derPadded: Buffer; + paddedLength: number; + publicKey: forge.pki.rsa.PublicKey; + pubkeyOffset: number; + pubkeyLength: number; + signature: Buffer; + cert: forge.pki.Certificate; +} + +function parseCertificate(certDer: Buffer): CertificateInfo { + const asn1Cert = forge.asn1.fromDer(forge.util.createBuffer(certDer.toString('binary'))); + const cert = forge.pki.certificateFromAsn1(asn1Cert); + const publicKey = cert.publicKey as forge.pki.rsa.PublicKey; + + const signature = Buffer.from(cert.signature, 'binary'); + + const tbsAsn1 = asn1Cert.value[0]; + if (typeof tbsAsn1 === 'string') { + throw new Error('Expected ASN.1 object for TBS certificate, got string'); + } + const tbsDer = forge.asn1.toDer(tbsAsn1); + const tbsBytes = Buffer.from(tbsDer.getBytes(), 'binary'); + + const tbsHex = tbsBytes.toString('hex'); + const pubkeyDer = Buffer.from(publicKey.n.toByteArray()); + const pubkeyHex = pubkeyDer.toString('hex'); + + const rawOffset = tbsHex.indexOf(pubkeyHex); + if (rawOffset === -1) { + throw new Error('Could not find public key in TBS certificate DER encoding'); + } + const pubkeyOffset = (rawOffset / 2) + 1; + + const pubkeyLength = pubkeyDer.length > 256 ? pubkeyDer.length - 1 : pubkeyDer.length; + + // Validate TBS certificate size before padding + if (tbsBytes.length > MAX_CERT_LENGTH) { + throw new Error( + `TBS certificate size ${tbsBytes.length} exceeds MAX_CERT_LENGTH ${MAX_CERT_LENGTH}` + ); + } + + const paddedLength = Math.ceil((tbsBytes.length + 9) / 64) * 64; + + // Validate padded length doesn't exceed buffer bounds + if (paddedLength > MAX_CERT_LENGTH) { + throw new Error( + `Padded TBS length ${paddedLength} exceeds MAX_CERT_LENGTH ${MAX_CERT_LENGTH}. ` + + `TBS size was ${tbsBytes.length} bytes. Consider increasing MAX_CERT_LENGTH or using a smaller certificate.` + ); + } + + const tbsPadded = Buffer.alloc(MAX_CERT_LENGTH); + tbsBytes.copy(tbsPadded, 0); + + tbsPadded[tbsBytes.length] = 0x80; + const lengthInBits = tbsBytes.length * 8; + tbsPadded.writeBigUInt64BE(BigInt(lengthInBits), paddedLength - 8); + + console.log(`[INFO] Certificate: ${cert.subject.attributes[0]?.value || 'Unknown'}`); + console.log(` Full cert DER size: ${certDer.length} bytes`); + console.log(` TBS cert DER size: ${tbsBytes.length} bytes`); + console.log(` TBS padded length: ${paddedLength} bytes`); + console.log(` Public key offset (in TBS): ${pubkeyOffset}`); + console.log(` Public key length: ${pubkeyLength} bytes`); + console.log(` Signature length: ${signature.length} bytes`); + + return { + der: tbsBytes, + derPadded: tbsPadded, + paddedLength, + publicKey, + pubkeyOffset, + pubkeyLength, + signature, + cert + }; +} + +/** + * Convert RSA public key to circuit format + * Circuit uses n=120, k=35 for RSA-4096 support + * RSA-2048 keys are padded with zeros to fill 35 chunks + */ +function pubkeyToChunks(publicKey: forge.pki.rsa.PublicKey): string[] { + const n = publicKey.n; + const chunks: string[] = []; + const chunkSize = 120; // bits (circuit parameter n=120) + const k = 35; // number of chunks (circuit parameter k=35 for RSA-4096) + + for (let i = 0; i < k; i++) { + const shift = BigInt(i * chunkSize); + const mask = (BigInt(1) << BigInt(chunkSize)) - BigInt(1); + const chunk = (BigInt(n.toString()) >> shift) & mask; + chunks.push(chunk.toString()); + } + + return chunks; +} + +/** + * Convert signature to circuit format (chunked) + * Circuit uses n=120, k=35 for RSA-4096 support + * RSA-2048 signatures are padded with zeros to fill 35 chunks + */ +function signatureToChunks(signature: Buffer): string[] { + const sigBigInt = BigInt('0x' + signature.toString('hex')); + const chunks: string[] = []; + const chunkSize = 120; // bits (circuit parameter n=120) + const k = 35; // number of chunks (circuit parameter k=35 for RSA-4096) + + for (let i = 0; i < k; i++) { + const shift = BigInt(i * chunkSize); + const mask = (BigInt(1) << BigInt(chunkSize)) - BigInt(1); + const chunk = (sigBigInt >> shift) & mask; + chunks.push(chunk.toString()); + } + + return chunks; +} + +/** + * Re-chunk signature from library format (n=121, k=17) to circuit format (n=120, k=35) + * ZK-Email's JWT library uses 121-bit chunks (RSA-2048), but our circuit uses 120-bit chunks (RSA-4096) + */ +function rechunkSignatureToK35(signatureChunks: string[]): string[] { + // Reconstruct BigInt from library's 121-bit chunks (k=17) + let sigBigInt = BigInt(0); + const libraryChunkSize = 121; // JWT library uses 121-bit chunks + + for (let i = 0; i < signatureChunks.length; i++) { + const chunk = BigInt(signatureChunks[i]); + sigBigInt += chunk << BigInt(i * libraryChunkSize); + } + + // Re-chunk as 120-bit chunks for circuit (k=35) + const chunks: string[] = []; + const circuitChunkSize = 120; // Circuit uses 120-bit chunks + const k = 35; // Circuit expects 35 chunks for RSA-4096 + + for (let i = 0; i < k; i++) { + const shift = BigInt(i * circuitChunkSize); + const mask = (BigInt(1) << BigInt(circuitChunkSize)) - BigInt(1); + const chunk = (sigBigInt >> shift) & mask; + chunks.push(chunk.toString()); + } + + return chunks; +} + +function bufferToByteArray(buffer: Buffer, maxLength: number): string[] { + const arr = new Array(maxLength).fill('0'); + for (let i = 0; i < buffer.length && i < maxLength; i++) { + arr[i] = buffer[i].toString(); + } + return arr; +} + +async function main() { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error('Usage: tsx prepare.ts [output_file.json]'); + console.error('Example: tsx prepare.ts example_jwt.txt circuit_inputs.json'); + process.exit(1); + } + + const inputFile = args[0]; + const outputFile = args[1] || 'circuit_inputs.json'; + + try { + // Read raw JWT string (header.payload.signature) + const rawJWT = fs.readFileSync(inputFile, 'utf8').trim(); + + // Parse header to extract x5c certificate chain + const [headerB64, payloadB64, jwtSigB64] = rawJWT.split('.'); + const header = JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf8')); + const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf8')); + + console.log('[INFO] Loaded raw JWT from', inputFile); + console.log(`[INFO] Issuer: ${payload.iss}`); + console.log(`[INFO] Subject: ${payload.sub}`); + + // Extract x5c certificate chain + if (!header.x5c || !Array.isArray(header.x5c) || header.x5c.length !== 3) { + throw new Error(`[ERROR] Expected 3 certificates in x5c, got ${header.x5c?.length || 0}`); + } + + console.log('\n[INFO] Processing certificate chain...\n'); + + // Parse all 3 certificates + const leafCertDer = Buffer.from(header.x5c[0], 'base64'); + const intermediateCertDer = Buffer.from(header.x5c[1], 'base64'); + const rootCertDer = Buffer.from(header.x5c[2], 'base64'); + + const leafCert = parseCertificate(leafCertDer); + console.log(); + const intermediateCert = parseCertificate(intermediateCertDer); + console.log(); + const rootCert = parseCertificate(rootCertDer); + console.log(); + + // Generate JWT verifier inputs (for JWT signature verification) + const rsaPublicKey: RSAPublicKey = { + n: Buffer.from(leafCert.publicKey.n.toByteArray()).toString('base64'), + e: leafCert.publicKey.e.intValue() + }; + + console.log('[INFO] Generating JWT verifier inputs...'); + const jwtInputs = await generateJWTVerifierInputs(rawJWT, rsaPublicKey, { + maxMessageLength: 11776 + }); + + console.log('[INFO] JWT signature verified'); + + // Extract eat_nonce[0] from payload + if (!payload.eat_nonce || !Array.isArray(payload.eat_nonce) || payload.eat_nonce.length === 0) { + throw new Error('[ERROR] No eat_nonce found in JWT payload'); + } + + const eatNonce0Base64url = payload.eat_nonce[0]; + console.log(`\n[INFO] eat_nonce[0] (base64url): ${eatNonce0Base64url}`); + console.log(`[INFO] eat_nonce[0] string length: ${eatNonce0Base64url.length} characters`); + + if (eatNonce0Base64url.length > MAX_EAT_NONCE_B64_LENGTH) { + throw new Error(`[ERROR] eat_nonce[0] length ${eatNonce0Base64url.length} exceeds max ${MAX_EAT_NONCE_B64_LENGTH}`); + } + + // Decode for verification/logging (not used in circuit) + const eatNonce0Buffer = Buffer.from(eatNonce0Base64url, 'base64url'); + console.log(`[INFO] eat_nonce[0] decoded: ${eatNonce0Buffer.length} bytes`); + + // Find offset of eat_nonce[0] in the decoded payload JSON + // Decode the payload from base64url to get the exact JSON string + const payloadJSON = Buffer.from(payloadB64, 'base64url').toString('utf8'); + + // Find key offset: position after the opening quote of "eat_nonce" + const eatNonceKeyPattern = '"eat_nonce"'; + const eatNonceKeyStart = payloadJSON.indexOf(eatNonceKeyPattern); + if (eatNonceKeyStart === -1) { + throw new Error('[ERROR] Could not find "eat_nonce" key in payload JSON'); + } + const eatNonce0KeyOffset = eatNonceKeyStart + 1; // Position after opening quote + + // Find value offset: position of the actual value + const eatNonce0ValueOffset = payloadJSON.indexOf(eatNonce0Base64url); + if (eatNonce0ValueOffset === -1) { + console.error('[ERROR] Could not find eat_nonce[0] value in decoded payload JSON'); + console.error('[DEBUG] Payload JSON:', payloadJSON); + console.error('[DEBUG] Looking for:', eatNonce0Base64url); + throw new Error('[ERROR] Could not find eat_nonce[0] value in decoded payload JSON'); + } + + console.log(`[INFO] eat_nonce key offset in payload: ${eatNonce0KeyOffset}`); + console.log(`[INFO] eat_nonce[0] value offset in payload: ${eatNonce0ValueOffset}`); + + // Convert base64url string to character codes (ASCII values) for circuit + const eatNonce0CharCodes = new Array(MAX_EAT_NONCE_B64_LENGTH).fill(0); + for (let i = 0; i < eatNonce0Base64url.length; i++) { + eatNonce0CharCodes[i] = eatNonce0Base64url.charCodeAt(i); + } + + // Extract image_digest from payload.submods.container.image_digest + if (!payload.submods?.container?.image_digest) { + throw new Error('[ERROR] No image_digest found in payload.submods.container'); + } + + const imageDigest = payload.submods.container.image_digest; + console.log(`\n[INFO] image_digest: ${imageDigest}`); + console.log(`[INFO] image_digest string length: ${imageDigest.length} characters`); + + if (!imageDigest.startsWith('sha256:')) { + throw new Error(`[ERROR] image_digest must start with "sha256:", got: ${imageDigest}`); + } + + if (imageDigest.length !== 71) { + throw new Error(`[ERROR] image_digest must be 71 characters ("sha256:" + 64 hex), got: ${imageDigest.length}`); + } + + if (imageDigest.length > MAX_IMAGE_DIGEST_LENGTH) { + throw new Error(`[ERROR] image_digest length ${imageDigest.length} exceeds max ${MAX_IMAGE_DIGEST_LENGTH}`); + } + + // Find offset of image_digest in the decoded payload JSON + // Find key offset: position after the opening quote of "image_digest" + const imageDigestKeyPattern = '"image_digest"'; + const imageDigestKeyStart = payloadJSON.indexOf(imageDigestKeyPattern); + if (imageDigestKeyStart === -1) { + throw new Error('[ERROR] Could not find "image_digest" key in payload JSON'); + } + const imageDigestKeyOffset = imageDigestKeyStart + 1; // Position after opening quote + + // Find value offset: position of the actual value + const imageDigestValueOffset = payloadJSON.indexOf(imageDigest); + if (imageDigestValueOffset === -1) { + console.error('[ERROR] Could not find image_digest value in decoded payload JSON'); + console.error('[DEBUG] Payload JSON:', payloadJSON); + console.error('[DEBUG] Looking for:', imageDigest); + throw new Error('[ERROR] Could not find image_digest value in decoded payload JSON'); + } + + console.log(`[INFO] image_digest key offset in payload: ${imageDigestKeyOffset}`); + console.log(`[INFO] image_digest value offset in payload: ${imageDigestValueOffset}`); + + // Convert image_digest string to character codes (ASCII values) for circuit + const imageDigestCharCodes = new Array(MAX_IMAGE_DIGEST_LENGTH).fill(0); + for (let i = 0; i < imageDigest.length; i++) { + imageDigestCharCodes[i] = imageDigest.charCodeAt(i); + } + + // Build circuit inputs + const circuitInputs = { + // JWT inputs + message: jwtInputs.message, + messageLength: jwtInputs.messageLength, + periodIndex: jwtInputs.periodIndex, + + // x5c[0] - Leaf certificate + leaf_cert: bufferToByteArray(leafCert.derPadded, MAX_CERT_LENGTH), + leaf_cert_padded_length: leafCert.paddedLength.toString(), + leaf_pubkey_offset: leafCert.pubkeyOffset.toString(), + leaf_pubkey_actual_size: leafCert.pubkeyLength.toString(), + + // x5c[1] - Intermediate certificate + intermediate_cert: bufferToByteArray(intermediateCert.derPadded, MAX_CERT_LENGTH), + intermediate_cert_padded_length: intermediateCert.paddedLength.toString(), + intermediate_pubkey_offset: intermediateCert.pubkeyOffset.toString(), + intermediate_pubkey_actual_size: intermediateCert.pubkeyLength.toString(), + + // x5c[2] - Root certificate + root_cert: bufferToByteArray(rootCert.derPadded, MAX_CERT_LENGTH), + root_cert_padded_length: rootCert.paddedLength.toString(), + root_pubkey_offset: rootCert.pubkeyOffset.toString(), + root_pubkey_actual_size: rootCert.pubkeyLength.toString(), + + // Public keys (chunked for RSA circuit) + leaf_pubkey: pubkeyToChunks(leafCert.publicKey), + intermediate_pubkey: pubkeyToChunks(intermediateCert.publicKey), + root_pubkey: pubkeyToChunks(rootCert.publicKey), + + // Signatures (chunked for RSA circuit) + // JWT signature comes from library as n=121,k=17, re-chunk to n=120,k=35 for circuit + jwt_signature: rechunkSignatureToK35(jwtInputs.signature), + leaf_signature: signatureToChunks(leafCert.signature), + intermediate_signature: signatureToChunks(intermediateCert.signature), + + // EAT nonce[0] (circuit will extract value directly from payload) + eat_nonce_0_b64_length: eatNonce0Base64url.length.toString(), + eat_nonce_0_key_offset: eatNonce0KeyOffset.toString(), + eat_nonce_0_value_offset: eatNonce0ValueOffset.toString(), + + // Container image digest (circuit will extract value directly from payload) + image_digest_length: imageDigest.length.toString(), + image_digest_key_offset: imageDigestKeyOffset.toString(), + image_digest_value_offset: imageDigestValueOffset.toString(), + }; + + fs.writeFileSync(outputFile, JSON.stringify(circuitInputs, null, 2)); + + console.log('\n[INFO] Circuit inputs saved to', outputFile); + + } catch (error) { + console.error('[ERROR]', error instanceof Error ? error.message : error); + if (error instanceof Error && error.stack) { + console.error(error.stack); + } + process.exit(1); + } +} + +main(); diff --git a/circuits/circuits/utils/gcp_jwt/extractAndValidatePubkey.circom b/circuits/circuits/utils/gcp_jwt/extractAndValidatePubkey.circom new file mode 100644 index 000000000..3a8ce2cf8 --- /dev/null +++ b/circuits/circuits/utils/gcp_jwt/extractAndValidatePubkey.circom @@ -0,0 +1,97 @@ +pragma circom 2.1.9; + +include "../passport/checkPubkeyPosition.circom"; +include "../passport/checkPubkeysEqual.circom"; +include "../passport/signatureAlgorithm.circom"; +include "circomlib/circuits/comparators.circom"; +include "circomlib/circuits/bitify.circom"; +include "@openpassport/zk-email-circuits/utils/array.circom"; + +/// @title ExtractAndValidatePubkey +/// @notice Extract the public key from certificate and validate position and equality +/// @dev Pubkey extraction and validation logic for X.509 certificate chain verification: +/// - Calculates the region containing prefix + pubkey + suffix +/// - Validates indices are within certificate bounds +/// - Extracts the pubkey region using SelectSubArray +/// - Validates the ASN.1 structure matches expected format +/// - Extracts the raw pubkey bytes (without prefix/suffix) +/// - Verifies extracted pubkey matches the provided input pubkey +template ExtractAndValidatePubkey( + signatureAlgorithm, // Algorithm ID (e.g., 1 for RSA-SHA256) + n, // RSA chunk size (e.g., 120) + k, // Number of chunks (e.g., 35) + MAX_CERT_LENGTH, // Maximum certificate size in bytes + MAX_PUBKEY_PREFIX, // ASN.1 prefix length (typically 33) + MAX_PUBKEY_LENGTH // Maximum pubkey length in bytes +) { + var kLengthFactor = getKLengthFactor(signatureAlgorithm); + var kScaled = k * kLengthFactor; + var suffixLength = kLengthFactor == 1 ? getSuffixLength(signatureAlgorithm) : 0; + + signal input cert[MAX_CERT_LENGTH]; + signal input pubkey_offset; + signal input pubkey_actual_size; + signal input input_pubkey[kScaled]; + + // Validate pubkey_actual_size is within bounds (prevent OOB attacks) + component size_max_check = LessEqThan(log2Ceil(MAX_PUBKEY_LENGTH)); + size_max_check.in[0] <== pubkey_actual_size; + size_max_check.in[1] <== MAX_PUBKEY_LENGTH; + size_max_check.out === 1; + + // Validate pubkey_offset is within bounds (prevent underflow in prefix calculation) + component offset_min_check = GreaterEqThan(log2Ceil(MAX_CERT_LENGTH)); + offset_min_check.in[0] <== pubkey_offset; + offset_min_check.in[1] <== MAX_PUBKEY_PREFIX; + offset_min_check.out === 1; + + // Calculate prefix start index and net length + signal pubkey_prefix_start_index <== pubkey_offset - MAX_PUBKEY_PREFIX; + signal pubkey_net_length <== MAX_PUBKEY_PREFIX + pubkey_actual_size + suffixLength; + + // Validate indices are in range + component prefix_idx_valid = Num2Bits(log2Ceil(MAX_CERT_LENGTH)); + prefix_idx_valid.in <== pubkey_prefix_start_index; + + component net_len_valid = Num2Bits(log2Ceil(MAX_CERT_LENGTH)); + net_len_valid.in <== pubkey_net_length; + + component prefix_in_range = LessEqThan(log2Ceil(MAX_CERT_LENGTH)); + prefix_in_range.in[0] <== pubkey_prefix_start_index + pubkey_net_length; + prefix_in_range.in[1] <== MAX_CERT_LENGTH; + prefix_in_range.out === 1; + + // Extract pubkey region with prefix and suffix + signal pubkey_region[MAX_PUBKEY_PREFIX + MAX_PUBKEY_LENGTH + suffixLength] <== SelectSubArray( + MAX_CERT_LENGTH, + MAX_PUBKEY_PREFIX + MAX_PUBKEY_LENGTH + suffixLength + )( + cert, + pubkey_prefix_start_index, + pubkey_net_length + ); + + // Validate pubkey position (checks ASN.1 prefix and suffix) + CheckPubkeyPosition( + MAX_PUBKEY_PREFIX, + MAX_PUBKEY_LENGTH, + suffixLength, + signatureAlgorithm + )( + pubkey_region, + pubkey_actual_size + ); + + // Extract pubkey without prefix + signal extracted_pubkey[MAX_PUBKEY_LENGTH]; + for (var i = 0; i < MAX_PUBKEY_LENGTH; i++) { + extracted_pubkey[i] <== pubkey_region[MAX_PUBKEY_PREFIX + i]; + } + + // Verify extracted pubkey matches input pubkey + CheckPubkeysEqual(n, kScaled, kLengthFactor, MAX_PUBKEY_LENGTH)( + input_pubkey, + extracted_pubkey, + pubkey_actual_size + ); +} diff --git a/circuits/circuits/utils/gcp_jwt/verifyCertificateSignature.circom b/circuits/circuits/utils/gcp_jwt/verifyCertificateSignature.circom new file mode 100644 index 000000000..691f5abad --- /dev/null +++ b/circuits/circuits/utils/gcp_jwt/verifyCertificateSignature.circom @@ -0,0 +1,40 @@ +pragma circom 2.1.9; + +include "../passport/signatureVerifier.circom"; +include "../passport/signatureAlgorithm.circom"; +include "../crypto/hasher/shaBytes/shaBytesDynamic.circom"; + +/// @title VerifyCertificateSignature +/// @notice Hash the certificate and verify its signature +/// @dev Certificate hashing and signature verification for X.509 chain validation: +/// - Hashes the DER-encoded certificate using SHA (dynamic length) +/// - Verifies the signature using the signer's public key +/// - Supports multiple signature algorithms +template VerifyCertificateSignature( + signatureAlgorithm, // Algorithm ID (e.g., 1 for RSA-SHA256) + n, // RSA chunk size (e.g., 120) + k, // Number of chunks (e.g., 35) + MAX_CERT_LENGTH // Maximum certificate size in bytes +) { + var kLengthFactor = getKLengthFactor(signatureAlgorithm); + var kScaled = k * kLengthFactor; + var hashLength = getHashLength(signatureAlgorithm); + + signal input cert[MAX_CERT_LENGTH]; + signal input cert_padded_length; + signal input signer_pubkey[kScaled]; + signal input signature[kScaled]; + + // Hash certificate using dynamic-length SHA + signal hashed_cert[hashLength] <== ShaBytesDynamic(hashLength, MAX_CERT_LENGTH)( + cert, + cert_padded_length + ); + + // Verify signature using signer's public key + SignatureVerifier(signatureAlgorithm, n, k)( + hashed_cert, + signer_pubkey, + signature + ); +} diff --git a/circuits/circuits/utils/gcp_jwt/verifyJSONFieldExtraction.circom b/circuits/circuits/utils/gcp_jwt/verifyJSONFieldExtraction.circom new file mode 100644 index 000000000..e806055b8 --- /dev/null +++ b/circuits/circuits/utils/gcp_jwt/verifyJSONFieldExtraction.circom @@ -0,0 +1,123 @@ +pragma circom 2.1.9; + +include "circomlib/circuits/comparators.circom"; +include "@openpassport/zk-email-circuits/utils/array.circom"; + +/// @title ExtractAndVerifyJSONField +/// @notice Verifies JSON key name and extracts the related value +/// @dev Validates the JSON key name and position, then extracts and outputs the value directly. +/// @param maxJSONLength Maximum length of the JSON string +/// @param maxKeyNameLength Maximum length of the JSON key name (without quotes) +/// @param maxValueLength Maximum length of the extracted value +/// @input json The JSON string to extract from +/// @input key_offset Offset where the JSON key name starts (position after opening quote) +/// @input key_length Actual length of the key name +/// @input value_offset Offset where the value starts (raw value, without quotes if string) +/// @input value_length Actual length of the value +/// @input expected_key_name Expected key name as array of ASCII codes (without quotes) +/// @output extracted_value The value extracted from the JSON at the specified offset +template ExtractAndVerifyJSONField( + maxJSONLength, + maxKeyNameLength, + maxValueLength +) { + signal input json[maxJSONLength]; + signal input key_offset; + signal input key_length; + signal input value_offset; + signal input value_length; + + signal input expected_key_name[maxKeyNameLength]; + + signal output extracted_value[maxValueLength]; + + // Ensure key_offset is at least 1 (prevents underflow in key_offset - 1) + component key_offset_min = GreaterEqThan(log2Ceil(maxJSONLength)); + key_offset_min.in[0] <== key_offset; + key_offset_min.in[1] <== 1; + key_offset_min.out === 1; + + // Verify opening quote before key + signal key_quote_before <== ItemAtIndex(maxJSONLength)(json, key_offset - 1); + key_quote_before === 34; // ASCII code for " + + // Extract key name from JSON + signal extracted_key_name[maxKeyNameLength] <== SelectSubArray( + maxJSONLength, + maxKeyNameLength + )(json, key_offset, key_length); + + // Verify key name matches expected (with padding validation) + component key_char_match[maxKeyNameLength]; + for (var i = 0; i < maxKeyNameLength; i++) { + key_char_match[i] = GreaterThan(log2Ceil(maxKeyNameLength)); + key_char_match[i].in[0] <== key_length; + key_char_match[i].in[1] <== i; + + // If within length: extracted must equal expected + // If beyond length: expected must be 0 (padding) + key_char_match[i].out * (extracted_key_name[i] - expected_key_name[i]) === 0; + (1 - key_char_match[i].out) * expected_key_name[i] === 0; + } + + // Verify closing quote after key + signal key_quote_after <== ItemAtIndex(maxJSONLength)(json, key_offset + key_length); + key_quote_after === 34; // ASCII code for " + + // Verify colon after closing quote (ensures valid JSON key:value structure) + signal colon_after_key <== ItemAtIndex(maxJSONLength)(json, key_offset + key_length + 1); + colon_after_key === 58; // ASCII code for ':' + + // Validate JSON array structure: "key":["value"] or "key": ["value"] + signal colon_position <== key_offset + key_length + 1; + + // Check character at colon+1: must be '[' (91) or space (32) + signal char_after_colon <== ItemAtIndex(maxJSONLength)(json, colon_position + 1); + + // is_bracket: 1 if char is '[', 0 otherwise + component is_bracket = IsEqual(); + is_bracket.in[0] <== char_after_colon; + is_bracket.in[1] <== 91; // '[' + + // is_space: 1 if char is space, 0 otherwise + component is_space = IsEqual(); + is_space.in[0] <== char_after_colon; + is_space.in[1] <== 32; // ' ' + + // Exactly one must be true: char is either '[' or space + is_bracket.out + is_space.out === 1; + + // If bracket at colon+1: check quote at colon+2, value at colon+3 + // If space at colon+1: check bracket at colon+2, quote at colon+3, value at colon+4 + + // When is_bracket=1 (no space): expect quote at colon+2 + signal char_at_plus2 <== ItemAtIndex(maxJSONLength)(json, colon_position + 2); + // When is_space=1: expect bracket at colon+2 + // Constraint: if is_bracket=1, char_at_plus2 must be quote(34) + // if is_space=1, char_at_plus2 must be bracket(91) + is_bracket.out * (char_at_plus2 - 34) === 0; // If bracket at +1, quote at +2 + is_space.out * (char_at_plus2 - 91) === 0; // If space at +1, bracket at +2 + + // When is_space=1: check quote at colon+3 + signal char_at_plus3 <== ItemAtIndex(maxJSONLength)(json, colon_position + 3); + is_space.out * (char_at_plus3 - 34) === 0; // If space at +1, quote at +3 + + // Enforce value_offset based on pattern + // Pattern 1 (no space): :[" -> value at colon+3 + // Pattern 2 (space): : [" -> value at colon+4 + signal expected_value_offset <== colon_position + 3 + is_space.out; + value_offset === expected_value_offset; + + // Extract value from JSON and output directly + extracted_value <== SelectSubArray( + maxJSONLength, + maxValueLength + )(json, value_offset, value_length); + + // Validate value ends with closing quote and bracket: "value"] + signal closing_quote <== ItemAtIndex(maxJSONLength)(json, value_offset + value_length); + closing_quote === 34; // ASCII code for " + + signal closing_bracket <== ItemAtIndex(maxJSONLength)(json, value_offset + value_length + 1); + closing_bracket === 93; // ASCII code for ] +} diff --git a/circuits/package.json b/circuits/package.json index 40e46640e..d9a7a70b1 100644 --- a/circuits/package.json +++ b/circuits/package.json @@ -8,6 +8,7 @@ "build-all": "bash scripts/build/build_register_circuits.sh && bash scripts/build/build_register_circuits_id.sh && bash scripts/build/build_register_aadhaar.sh && bash scripts/build/build_dsc_circuits.sh && bash scripts/build/build_disclose_circuits.sh", "build-disclose": "bash scripts/build/build_disclose_circuits.sh", "build-dsc": "bash scripts/build/build_dsc_circuits.sh", + "build-gcp-jwt-verifier": "bash scripts/build/build_gcp_jwt_verifier.sh", "build-register": "bash scripts/build/build_register_circuits.sh", "build-register-id": "bash scripts/build/build_register_circuits_id.sh", "build:deps": "yarn workspaces foreach --from @selfxyz/circuits --topological-dev --recursive run build", @@ -45,6 +46,8 @@ "@selfxyz/common": "workspace:^", "@zk-email/circuits": "^6.3.2", "@zk-email/helpers": "^6.1.1", + "@zk-email/jwt-tx-builder-circuits": "0.1.0", + "@zk-email/jwt-tx-builder-helpers": "0.1.0", "@zk-email/zk-regex-circom": "^1.2.1", "@zk-kit/binary-merkle-root.circom": "https://gitpkg.vercel.app/Vishalkulkarni45/zk-kit.circom/packages/binary-merkle-root?fix/bin-merkle-tree", "@zk-kit/circuits": "^1.0.0-beta", diff --git a/circuits/scripts/build/build_gcp_jwt_verifier.sh b/circuits/scripts/build/build_gcp_jwt_verifier.sh new file mode 100755 index 000000000..b6a8ee9d0 --- /dev/null +++ b/circuits/scripts/build/build_gcp_jwt_verifier.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -euo pipefail + +# Get script directory for stable sourcing +SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +source "${SCRIPT_DIR}/common.sh" + +# Set environment (change this value as needed) +# ENV="prod" +ENV="staging" + +echo -e "${GREEN}Building GCP JWT verifier circuit for $ENV environment${NC}" + +# Circuit-specific configurations +CIRCUIT_TYPE="gcp_jwt_verifier" +CIRCUIT_NAME="gcp_jwt_verifier" +OUTPUT_DIR="build/gcp" +POWEROFTAU=24 +MAX_MEMORY=204800 + +# Download power of tau if needed +download_ptau $POWEROFTAU + +# Build the circuit +build_circuit "$CIRCUIT_NAME" "$CIRCUIT_TYPE" "$POWEROFTAU" "$OUTPUT_DIR" "$MAX_MEMORY" + +echo "" +echo -e "${BLUE}To generate circuit inputs, run:${NC}" +echo -e "${YELLOW} yarn tsx circuits/gcp_jwt_verifier/prepare.ts example_jwt.txt circuit_inputs.json${NC}" diff --git a/circuits/scripts/build/common.sh b/circuits/scripts/build/common.sh index ef60dd26b..0c9ccbf95 100644 --- a/circuits/scripts/build/common.sh +++ b/circuits/scripts/build/common.sh @@ -75,7 +75,6 @@ build_circuit() { -l node_modules \ -l node_modules/@zk-kit/binary-merkle-root.circom/src \ -l node_modules/circomlib/circuits \ - -l node_modules \ --r1cs --O1 --wasm -c \ --output ${OUTPUT_DIR}/${CIRCUIT_NAME}/ diff --git a/yarn.lock b/yarn.lock index b254538c7..1c1432101 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4497,6 +4497,25 @@ __metadata: languageName: node linkType: hard +"@mapbox/node-pre-gyp@npm:^1.0": + version: 1.0.11 + resolution: "@mapbox/node-pre-gyp@npm:1.0.11" + dependencies: + detect-libc: "npm:^2.0.0" + https-proxy-agent: "npm:^5.0.0" + make-dir: "npm:^3.1.0" + node-fetch: "npm:^2.6.7" + nopt: "npm:^5.0.0" + npmlog: "npm:^5.0.1" + rimraf: "npm:^3.0.2" + semver: "npm:^7.3.5" + tar: "npm:^6.1.11" + bin: + node-pre-gyp: bin/node-pre-gyp + checksum: 10c0/2b24b93c31beca1c91336fa3b3769fda98e202fb7f9771f0f4062588d36dcc30fcf8118c36aa747fa7f7610d8cf601872bdaaf62ce7822bb08b545d1bbe086cc + languageName: node + linkType: hard + "@modelcontextprotocol/sdk@npm:1.17.3": version: 1.17.3 resolution: "@modelcontextprotocol/sdk@npm:1.17.3" @@ -5368,6 +5387,23 @@ __metadata: languageName: node linkType: hard +"@octokit/rest@npm:^15.9.5": + version: 15.18.3 + resolution: "@octokit/rest@npm:15.18.3" + dependencies: + before-after-hook: "npm:^1.1.0" + btoa-lite: "npm:^1.0.0" + debug: "npm:^3.1.0" + http-proxy-agent: "npm:^2.1.0" + https-proxy-agent: "npm:^2.2.0" + lodash: "npm:^4.17.4" + node-fetch: "npm:^2.1.1" + universal-user-agent: "npm:^2.0.0" + url-template: "npm:^2.0.8" + checksum: 10c0/e608ef82f4b9342c1abbabf7d6aa56dfcbfa88e68c69b23377cc5f66c53d4ba538bce819f3af060b8525946eda111fe3a7d9c7fb593afb6c2cf8feb234231697 + languageName: node + linkType: hard + "@openpassport/zk-email-circuits@npm:^6.1.2": version: 6.1.2 resolution: "@openpassport/zk-email-circuits@npm:6.1.2" @@ -7125,6 +7161,8 @@ __metadata: "@yarnpkg/sdks": "npm:^3.2.0" "@zk-email/circuits": "npm:^6.3.2" "@zk-email/helpers": "npm:^6.1.1" + "@zk-email/jwt-tx-builder-circuits": "npm:^0.1.0" + "@zk-email/jwt-tx-builder-helpers": "npm:^0.1.0" "@zk-email/zk-regex-circom": "npm:^1.2.1" "@zk-kit/binary-merkle-root.circom": "https://gitpkg.vercel.app/Vishalkulkarni45/zk-kit.circom/packages/binary-merkle-root?fix/bin-merkle-tree" "@zk-kit/circuits": "npm:^1.0.0-beta" @@ -13064,6 +13102,26 @@ __metadata: languageName: node linkType: hard +"@zk-email/circuits@npm:6.2.0": + version: 6.2.0 + resolution: "@zk-email/circuits@npm:6.2.0" + dependencies: + "@zk-email/zk-regex-circom": "npm:^2.1.0" + circomlib: "npm:^2.0.5" + checksum: 10c0/cae73c0965bf5dda2d4f641b2e2ab8e5fb0fc9d281f02a632abaa9e1472045ba11afa4f43109c1d0d4d160461ce7a62c853578147313621bcb8abe460560f5f5 + languageName: node + linkType: hard + +"@zk-email/circuits@npm:=6.3.2": + version: 6.3.2 + resolution: "@zk-email/circuits@npm:6.3.2" + dependencies: + "@zk-email/zk-regex-circom": "npm:^2.3.1" + circomlib: "npm:^2.0.5" + checksum: 10c0/e8a3914f24ebb26afd0d6061ea3b590a08c5a6760af15f90806f17025b8bced361eda10a96cdb618a584ddbb3c0dfd57d8e8a67386e06936f2d09872021d7bbb + languageName: node + linkType: hard + "@zk-email/circuits@npm:^6.1.1, @zk-email/circuits@npm:^6.3.2": version: 6.3.4 resolution: "@zk-email/circuits@npm:6.3.4" @@ -13074,7 +13132,20 @@ __metadata: languageName: node linkType: hard -"@zk-email/helpers@npm:^6.1.1": +"@zk-email/email-tx-builder-circom@npm:1.0.0": + version: 1.0.0 + resolution: "@zk-email/email-tx-builder-circom@npm:1.0.0" + dependencies: + "@zk-email/circuits": "npm:=6.3.2" + "@zk-email/relayer-utils": "npm:=0.3.7" + "@zk-email/zk-regex-circom": "npm:=2.3.1" + commander: "npm:^12.1.0" + snarkjs: "npm:=0.7.5" + checksum: 10c0/d5618333fbcf5cbf967fd197abc61c434a2f25ae13db7ecf852ee97b173e9e738c9cb9c0e9dde19e76e4b579eb8db98ef2edb55c7d8bba619656651066f67945 + languageName: node + linkType: hard + +"@zk-email/helpers@npm:^6.1.1, @zk-email/helpers@npm:^6.3.1": version: 6.4.2 resolution: "@zk-email/helpers@npm:6.4.2" dependencies: @@ -13091,7 +13162,61 @@ __metadata: languageName: node linkType: hard -"@zk-email/zk-regex-circom@npm:2.3.2, @zk-email/zk-regex-circom@npm:^2.1.0": +"@zk-email/jwt-tx-builder-circuits@npm:^0.1.0": + version: 0.1.0 + resolution: "@zk-email/jwt-tx-builder-circuits@npm:0.1.0" + dependencies: + "@zk-email/circuits": "npm:6.2.0" + "@zk-email/email-tx-builder-circom": "npm:1.0.0" + "@zk-email/relayer-utils": "npm:=0.3.7" + "@zk-email/zk-regex-circom": "npm:^2.1.1" + circomlib: "npm:^2.0.5" + snarkjs: "npm:^0.7.4" + peerDependencies: + "@babel/core": ^7.22.5 + checksum: 10c0/11476f2ee33f7374c5196dcd86b0b6c6fca705fe726d58494cddf5f019779dc6c406de288b80a3dd978b8754cb56e831ff92efb5767271356473e22216d31a11 + languageName: node + linkType: hard + +"@zk-email/jwt-tx-builder-helpers@npm:^0.1.0": + version: 0.1.0 + resolution: "@zk-email/jwt-tx-builder-helpers@npm:0.1.0" + dependencies: + "@types/circomlibjs": "npm:^0.1.6" + "@zk-email/helpers": "npm:^6.3.1" + addressparser: "npm:^1.0.1" + atob: "npm:^2.1.2" + axios: "npm:^1.7.7" + circomlibjs: "npm:^0.1.7" + crypto: "npm:^1.0.1" + jsonwebtoken: "npm:^9.0.2" + libmime: "npm:^5.2.1" + localforage: "npm:^1.10.0" + node-forge: "npm:^1.3.1" + node-rsa: "npm:^1.1.1" + pako: "npm:^2.1.0" + psl: "npm:^1.9.0" + snarkjs: "npm:^0.7.4" + peerDependencies: + "@babel/core": ^7.22.5 + eslint: ^8.0.0 + eslint-plugin-import: ^2.29.1 + checksum: 10c0/e0bc4d1dec4a50e3e056e10aaf0e43f4719fabc88ff9c5c33e8805c909cc962a81966d99cc50523557b9bd8f7d96d5e7a8803eec98b5924a5fcc849459f6339f + languageName: node + linkType: hard + +"@zk-email/relayer-utils@npm:=0.3.7": + version: 0.3.7 + resolution: "@zk-email/relayer-utils@npm:0.3.7" + dependencies: + "@mapbox/node-pre-gyp": "npm:^1.0" + cargo-cp-artifact: "npm:^0.1" + node-pre-gyp-github: "git+https://github.com/ultamatt/node-pre-gyp-github.git" + checksum: 10c0/1ddb2c7a7d609bfdc22863e2b48476afb279397f641697d2090fad76d0a25fbf7ed6c5d28ab26be1d26d4af6b4422210a3519be0873ba49402a01b2fe2a597ab + languageName: node + linkType: hard + +"@zk-email/zk-regex-circom@npm:2.3.2, @zk-email/zk-regex-circom@npm:^2.1.0, @zk-email/zk-regex-circom@npm:^2.1.1, @zk-email/zk-regex-circom@npm:^2.3.1": version: 2.3.2 resolution: "@zk-email/zk-regex-circom@npm:2.3.2" dependencies: @@ -13101,6 +13226,16 @@ __metadata: languageName: node linkType: hard +"@zk-email/zk-regex-circom@npm:=2.3.1": + version: 2.3.1 + resolution: "@zk-email/zk-regex-circom@npm:2.3.1" + dependencies: + commander: "npm:^11.0.0" + snarkjs: "npm:^0.7.5" + checksum: 10c0/6d4d06cbf0f45d58b0420cca9b44ce762f94f9d2a2e75a139588052c197f2791786a50fbec6c18fbed936c388e216e1ed1920e9af02386f105d41bc65c1323a6 + languageName: node + linkType: hard + "@zk-email/zk-regex-circom@npm:^1.2.1": version: 1.3.0 resolution: "@zk-email/zk-regex-circom@npm:1.3.0" @@ -13319,6 +13454,15 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:4, agent-base@npm:^4.3.0": + version: 4.3.0 + resolution: "agent-base@npm:4.3.0" + dependencies: + es6-promisify: "npm:^5.0.0" + checksum: 10c0/a618d4e4ca7c0c2023b2664346570773455c501a930718764f65016a8a9eea6d2ab5ba54255589e46de529bab4026a088523dce17f94e34ba385af1f644febe1 + languageName: node + linkType: hard + "agent-base@npm:6": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -13613,6 +13757,23 @@ __metadata: languageName: node linkType: hard +"aproba@npm:^1.0.3 || ^2.0.0": + version: 2.1.0 + resolution: "aproba@npm:2.1.0" + checksum: 10c0/ec8c1d351bac0717420c737eb062766fb63bde1552900e0f4fdad9eb064c3824fef23d1c416aa5f7a80f21ca682808e902d79b7c9ae756d342b5f1884f36932f + languageName: node + linkType: hard + +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: "npm:^1.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10c0/375f753c10329153c8d66dc95e8f8b6c7cc2aa66e05cb0960bd69092b10dae22900cacc7d653ad11d26b3ecbdbfe1e8bfb6ccf0265ba8077a7d979970f16b99c + languageName: node + linkType: hard + "arg@npm:^4.1.0": version: 4.1.3 resolution: "arg@npm:4.1.3" @@ -13854,7 +14015,7 @@ __metadata: languageName: node linkType: hard -"asn1@npm:^0.2.6": +"asn1@npm:^0.2.4, asn1@npm:^0.2.6": version: 0.2.6 resolution: "asn1@npm:0.2.6" dependencies: @@ -14018,7 +14179,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.5.1, axios@npm:^1.6.2, axios@npm:^1.7.2": +"axios@npm:^1.5.1, axios@npm:^1.6.2, axios@npm:^1.7.2, axios@npm:^1.7.7": version: 1.12.2 resolution: "axios@npm:1.12.2" dependencies: @@ -14373,6 +14534,13 @@ __metadata: languageName: node linkType: hard +"before-after-hook@npm:^1.1.0": + version: 1.4.0 + resolution: "before-after-hook@npm:1.4.0" + checksum: 10c0/d9367c017b3a0eb5660f13b2911a03ea70c44f3af84119c6fb8cbf75b2c40b347543e9a35a117f9b25088b15b5377cae82308926f21dc6710832177a72dada8f + languageName: node + linkType: hard + "bfj@npm:^7.0.2": version: 7.1.0 resolution: "bfj@npm:7.1.0" @@ -14662,6 +14830,13 @@ __metadata: languageName: node linkType: hard +"btoa-lite@npm:^1.0.0": + version: 1.0.0 + resolution: "btoa-lite@npm:1.0.0" + checksum: 10c0/7a4f0568ae3c915464650f98fde7901ae07b13a333a614515a0c86876b3528670fafece28dfef9745d971a613bb83341823afb0c20c6f318b384c1e364b9eb95 + languageName: node + linkType: hard + "buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -14669,6 +14844,13 @@ __metadata: languageName: node linkType: hard +"buffer-equal-constant-time@npm:^1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0, buffer-from@npm:^1.1.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -14885,6 +15067,15 @@ __metadata: languageName: node linkType: hard +"cargo-cp-artifact@npm:^0.1": + version: 0.1.9 + resolution: "cargo-cp-artifact@npm:0.1.9" + bin: + cargo-cp-artifact: bin/cargo-cp-artifact.js + checksum: 10c0/60eb1845917cfb021920fcf600a72379890b385396f9c69107face3b16b347960b66cd3d82cc169c6ac8b1212cf0706584125bc36fbc08353b033310c17ca0a6 + languageName: node + linkType: hard + "caseless@npm:^0.12.0, caseless@npm:~0.12.0": version: 0.12.0 resolution: "caseless@npm:0.12.0" @@ -15515,6 +15706,15 @@ __metadata: languageName: node linkType: hard +"color-support@npm:^1.1.2": + version: 1.1.3 + resolution: "color-support@npm:1.1.3" + bin: + color-support: bin.js + checksum: 10c0/8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6 + languageName: node + linkType: hard + "color2k@npm:^2.0.2": version: 2.0.3 resolution: "color2k@npm:2.0.3" @@ -15607,7 +15807,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^12.0.0": +"commander@npm:^12.0.0, commander@npm:^12.1.0": version: 12.1.0 resolution: "commander@npm:12.1.0" checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9 @@ -15621,7 +15821,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.20.0": +"commander@npm:^2.17.0, commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 @@ -15758,6 +15958,13 @@ __metadata: languageName: node linkType: hard +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": + version: 1.1.0 + resolution: "console-control-strings@npm:1.1.0" + checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 + languageName: node + linkType: hard + "constants-browserify@npm:^1.0.0": version: 1.0.0 resolution: "constants-browserify@npm:1.0.0" @@ -16002,6 +16209,19 @@ __metadata: languageName: node linkType: hard +"cross-spawn@npm:^6.0.0": + version: 6.0.6 + resolution: "cross-spawn@npm:6.0.6" + dependencies: + nice-try: "npm:^1.0.4" + path-key: "npm:^2.0.1" + semver: "npm:^5.5.0" + shebang-command: "npm:^1.2.0" + which: "npm:^1.2.9" + checksum: 10c0/bf61fb890e8635102ea9bce050515cf915ff6a50ccaa0b37a17dc82fded0fb3ed7af5478b9367b86baee19127ad86af4be51d209f64fd6638c0862dca185fe1d + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -16270,6 +16490,15 @@ __metadata: languageName: node linkType: hard +"debug@npm:3.1.0": + version: 3.1.0 + resolution: "debug@npm:3.1.0" + dependencies: + ms: "npm:2.0.0" + checksum: 10c0/5bff34a352d7b2eaa31886eeaf2ee534b5461ec0548315b2f9f80bd1d2533cab7df1fa52e130ce27bc31c3945fbffb0fc72baacdceb274b95ce853db89254ea4 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" @@ -16282,7 +16511,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^3.2.7": +"debug@npm:^3.1.0, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" dependencies: @@ -16502,6 +16731,13 @@ __metadata: languageName: node linkType: hard +"delegates@npm:^1.0.0": + version: 1.0.0 + resolution: "delegates@npm:1.0.0" + checksum: 10c0/ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 + languageName: node + linkType: hard + "depd@npm:2.0.0, depd@npm:^2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" @@ -16546,7 +16782,7 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.1": +"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1": version: 2.1.2 resolution: "detect-libc@npm:2.1.2" checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 @@ -16802,6 +17038,15 @@ __metadata: languageName: node linkType: hard +"ecdsa-sig-formatter@npm:1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c + languageName: node + linkType: hard + "edit-json-file@npm:^1.7.0": version: 1.8.1 resolution: "edit-json-file@npm:1.8.1" @@ -17236,6 +17481,22 @@ __metadata: languageName: node linkType: hard +"es6-promise@npm:^4.0.3": + version: 4.2.8 + resolution: "es6-promise@npm:4.2.8" + checksum: 10c0/2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3 + languageName: node + linkType: hard + +"es6-promisify@npm:^5.0.0": + version: 5.0.0 + resolution: "es6-promisify@npm:5.0.0" + dependencies: + es6-promise: "npm:^4.0.3" + checksum: 10c0/23284c6a733cbf7842ec98f41eac742c9f288a78753c4fe46652bae826446ced7615b9e8a5c5f121a08812b1cd478ea58630f3e1c3d70835bd5dcd69c7cd75c9 + languageName: node + linkType: hard + "es7-shim@npm:^6.0.0": version: 6.0.0 resolution: "es7-shim@npm:6.0.0" @@ -18429,6 +18690,21 @@ __metadata: languageName: node linkType: hard +"execa@npm:^1.0.0": + version: 1.0.0 + resolution: "execa@npm:1.0.0" + dependencies: + cross-spawn: "npm:^6.0.0" + get-stream: "npm:^4.0.0" + is-stream: "npm:^1.1.0" + npm-run-path: "npm:^2.0.0" + p-finally: "npm:^1.0.0" + signal-exit: "npm:^3.0.0" + strip-eof: "npm:^1.0.0" + checksum: 10c0/cc71707c9aa4a2552346893ee63198bf70a04b5a1bc4f8a0ef40f1d03c319eae80932c59191f037990d7d102193e83a38ec72115fff814ec2fb3099f3661a590 + languageName: node + linkType: hard + "execa@npm:^5.0.0, execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" @@ -19409,6 +19685,23 @@ __metadata: languageName: node linkType: hard +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + dependencies: + aproba: "npm:^1.0.3 || ^2.0.0" + color-support: "npm:^1.1.2" + console-control-strings: "npm:^1.0.0" + has-unicode: "npm:^2.0.1" + object-assign: "npm:^4.1.1" + signal-exit: "npm:^3.0.0" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wide-align: "npm:^1.1.2" + checksum: 10c0/75230ccaf216471e31025c7d5fcea1629596ca20792de50c596eb18ffb14d8404f927cd55535aab2eeecd18d1e11bd6f23ec3c2e9878d2dda1dc74bccc34b913 + languageName: node + linkType: hard + "generator-function@npm:^2.0.0": version: 2.0.1 resolution: "generator-function@npm:2.0.1" @@ -19496,6 +19789,15 @@ __metadata: languageName: node linkType: hard +"get-stream@npm:^4.0.0": + version: 4.1.0 + resolution: "get-stream@npm:4.1.0" + dependencies: + pump: "npm:^3.0.0" + checksum: 10c0/294d876f667694a5ca23f0ca2156de67da950433b6fb53024833733975d32582896dbc7f257842d331809979efccf04d5e0b6b75ad4d45744c45f193fd497539 + languageName: node + linkType: hard + "get-stream@npm:^5.1.0": version: 5.2.0 resolution: "get-stream@npm:5.2.0" @@ -19984,6 +20286,13 @@ __metadata: languageName: node linkType: hard +"has-unicode@npm:^2.0.1": + version: 2.0.1 + resolution: "has-unicode@npm:2.0.1" + checksum: 10c0/ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c + languageName: node + linkType: hard + "hash-base@npm:^3.0.0, hash-base@npm:^3.1.2": version: 3.1.2 resolution: "hash-base@npm:3.1.2" @@ -20247,6 +20556,16 @@ __metadata: languageName: node linkType: hard +"http-proxy-agent@npm:^2.1.0": + version: 2.1.0 + resolution: "http-proxy-agent@npm:2.1.0" + dependencies: + agent-base: "npm:4" + debug: "npm:3.1.0" + checksum: 10c0/526294de33953bacb21b883d8bbc01a82e1e9f5a721785345dd538b15b62c7a5d4080b729eb3177ad15d842f931f44002431d5cf9b036cc8cea4bfb5ec172228 + languageName: node + linkType: hard + "http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1, http-proxy-agent@npm:^7.0.2": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" @@ -20329,6 +20648,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^2.2.0": + version: 2.2.4 + resolution: "https-proxy-agent@npm:2.2.4" + dependencies: + agent-base: "npm:^4.3.0" + debug: "npm:^3.1.0" + checksum: 10c0/4bdde8fcd9ea0adc4a77282de2b4f9e27955e0441425af0f27f0fe01006946b80eaee6749e08e838d350c06ed2ebd5d11347d3beb88c45eacb0667e27276cdad + languageName: node + linkType: hard + "https-proxy-agent@npm:^5.0.0": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -21056,6 +21385,13 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^1.1.0": + version: 1.1.0 + resolution: "is-stream@npm:1.1.0" + checksum: 10c0/b8ae7971e78d2e8488d15f804229c6eed7ed36a28f8807a1815938771f4adff0e705218b7dab968270433f67103e4fef98062a0beea55d64835f705ee72c7002 + languageName: node + linkType: hard + "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -22227,6 +22563,24 @@ __metadata: languageName: node linkType: hard +"jsonwebtoken@npm:^9.0.2": + version: 9.0.2 + resolution: "jsonwebtoken@npm:9.0.2" + dependencies: + jws: "npm:^3.2.2" + lodash.includes: "npm:^4.3.0" + lodash.isboolean: "npm:^3.0.3" + lodash.isinteger: "npm:^4.0.4" + lodash.isnumber: "npm:^3.0.3" + lodash.isplainobject: "npm:^4.0.6" + lodash.isstring: "npm:^4.0.1" + lodash.once: "npm:^4.0.0" + ms: "npm:^2.1.1" + semver: "npm:^7.5.4" + checksum: 10c0/d287a29814895e866db2e5a0209ce730cbc158441a0e5a70d5e940eb0d28ab7498c6bf45029cc8b479639bca94056e9a7f254e2cdb92a2f5750c7f358657a131 + languageName: node + linkType: hard + "jsrsasign@npm:^11.1.0": version: 11.1.0 resolution: "jsrsasign@npm:11.1.0" @@ -22246,6 +22600,27 @@ __metadata: languageName: node linkType: hard +"jwa@npm:^1.4.1": + version: 1.4.2 + resolution: "jwa@npm:1.4.2" + dependencies: + buffer-equal-constant-time: "npm:^1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/210a544a42ca22203e8fc538835205155ba3af6a027753109f9258bdead33086bac3c25295af48ac1981f87f9c5f941bc8f70303670f54ea7dcaafb53993d92c + languageName: node + linkType: hard + +"jws@npm:^3.2.2": + version: 3.2.2 + resolution: "jws@npm:3.2.2" + dependencies: + jwa: "npm:^1.4.1" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/e770704533d92df358adad7d1261fdecad4d7b66fa153ba80d047e03ca0f1f73007ce5ed3fbc04d2eba09ba6e7e6e645f351e08e5ab51614df1b0aa4f384dfff + languageName: node + linkType: hard + "karma-source-map-support@npm:1.4.0": version: 1.4.0 resolution: "karma-source-map-support@npm:1.4.0" @@ -22677,6 +23052,20 @@ __metadata: languageName: node linkType: hard +"lodash.includes@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.includes@npm:4.3.0" + checksum: 10c0/7ca498b9b75bf602d04e48c0adb842dfc7d90f77bcb2a91a2b2be34a723ad24bc1c8b3683ec6b2552a90f216c723cdea530ddb11a3320e08fa38265703978f4b + languageName: node + linkType: hard + +"lodash.isboolean@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isboolean@npm:3.0.3" + checksum: 10c0/0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7 + languageName: node + linkType: hard + "lodash.isempty@npm:^4.4.0": version: 4.4.0 resolution: "lodash.isempty@npm:4.4.0" @@ -22698,6 +23087,20 @@ __metadata: languageName: node linkType: hard +"lodash.isinteger@npm:^4.0.4": + version: 4.0.4 + resolution: "lodash.isinteger@npm:4.0.4" + checksum: 10c0/4c3e023a2373bf65bf366d3b8605b97ec830bca702a926939bcaa53f8e02789b6a176e7f166b082f9365bfec4121bfeb52e86e9040cb8d450e64c858583f61b7 + languageName: node + linkType: hard + +"lodash.isnumber@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isnumber@npm:3.0.3" + checksum: 10c0/2d01530513a1ee4f72dd79528444db4e6360588adcb0e2ff663db2b3f642d4bb3d687051ae1115751ca9082db4fdef675160071226ca6bbf5f0c123dbf0aa12d + languageName: node + linkType: hard + "lodash.isobject@npm:^3.0.2": version: 3.0.2 resolution: "lodash.isobject@npm:3.0.2" @@ -22705,6 +23108,13 @@ __metadata: languageName: node linkType: hard +"lodash.isplainobject@npm:^4.0.6": + version: 4.0.6 + resolution: "lodash.isplainobject@npm:4.0.6" + checksum: 10c0/afd70b5c450d1e09f32a737bed06ff85b873ecd3d3d3400458725283e3f2e0bb6bf48e67dbe7a309eb371a822b16a26cca4a63c8c52db3fc7dc9d5f9dd324cbb + languageName: node + linkType: hard + "lodash.isstring@npm:^4.0.1": version: 4.0.1 resolution: "lodash.isstring@npm:4.0.1" @@ -22719,6 +23129,13 @@ __metadata: languageName: node linkType: hard +"lodash.once@npm:^4.0.0": + version: 4.1.1 + resolution: "lodash.once@npm:4.1.1" + checksum: 10c0/46a9a0a66c45dd812fcc016e46605d85ad599fe87d71a02f6736220554b52ffbe82e79a483ad40f52a8a95755b0d1077fba259da8bfb6694a7abbf4a48f1fc04 + languageName: node + linkType: hard + "lodash.sortby@npm:^4.7.0": version: 4.7.0 resolution: "lodash.sortby@npm:4.7.0" @@ -22740,7 +23157,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.21": +"lodash@npm:4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.17.4": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -22925,6 +23342,13 @@ __metadata: languageName: node linkType: hard +"macos-release@npm:^2.2.0": + version: 2.5.1 + resolution: "macos-release@npm:2.5.1" + checksum: 10c0/fd03674e0b91e88a82cabecb75d75bc562863b186a22eac857f7d90c117486e44e02bede0926315637749aaaa934415bd1c2d0c0b53b78a86b729f3c165c5850 + languageName: node + linkType: hard + "magic-string@npm:0.30.17": version: 0.30.17 resolution: "magic-string@npm:0.30.17" @@ -22953,7 +23377,7 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.0.2": +"make-dir@npm:^3.0.2, make-dir@npm:^3.1.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" dependencies: @@ -23476,7 +23900,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.19, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -24217,6 +24641,13 @@ __metadata: languageName: node linkType: hard +"nice-try@npm:^1.0.4": + version: 1.0.5 + resolution: "nice-try@npm:1.0.5" + checksum: 10c0/95568c1b73e1d0d4069a3e3061a2102d854513d37bcfda73300015b7ba4868d3b27c198d1dbbd8ebdef4112fc2ed9e895d4a0f2e1cce0bd334f2a1346dc9205f + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -24297,7 +24728,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7, node-fetch@npm:^2.7.0": +"node-fetch@npm:^2.1.1, node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -24376,6 +24807,19 @@ __metadata: languageName: node linkType: hard +"node-pre-gyp-github@git+https://github.com/ultamatt/node-pre-gyp-github.git": + version: 1.4.3 + resolution: "node-pre-gyp-github@https://github.com/ultamatt/node-pre-gyp-github.git#commit=e4961827f77751489bc8d4760a0479f3f985f34f" + dependencies: + "@octokit/rest": "npm:^15.9.5" + commander: "npm:^2.17.0" + mime-types: "npm:^2.1.19" + bin: + node-pre-gyp-github: ./bin/node-pre-gyp-github.js + checksum: 10c0/010b18a0f79cb9e33761abb1e8d321b9bc81affc42124c3a227c35c39907019c1d8568d3b798f84d462da311e40339c73725474da02b19425f4877d53cb34678 + languageName: node + linkType: hard + "node-releases@npm:^2.0.21": version: 2.0.23 resolution: "node-releases@npm:2.0.23" @@ -24383,6 +24827,15 @@ __metadata: languageName: node linkType: hard +"node-rsa@npm:^1.1.1": + version: 1.1.1 + resolution: "node-rsa@npm:1.1.1" + dependencies: + asn1: "npm:^0.2.4" + checksum: 10c0/af3b6534844dfeaa8290a7bc20d5e4d1a134f05d456725e56d75b7661d4d63cd63914ce335ee8889adc2458112b996d4ba8a83ba105bde744a26e61d9160f639 + languageName: node + linkType: hard + "node-stream-zip@npm:^1.9.1": version: 1.15.0 resolution: "node-stream-zip@npm:1.15.0" @@ -24408,6 +24861,17 @@ __metadata: languageName: node linkType: hard +"nopt@npm:^5.0.0": + version: 5.0.0 + resolution: "nopt@npm:5.0.0" + dependencies: + abbrev: "npm:1" + bin: + nopt: bin/nopt.js + checksum: 10c0/fc5c4f07155cb455bf5fc3dd149fac421c1a40fd83c6bfe83aa82b52f02c17c5e88301321318adaa27611c8a6811423d51d29deaceab5fa158b585a61a551061 + languageName: node + linkType: hard + "nopt@npm:^8.0.0": version: 8.1.0 resolution: "nopt@npm:8.1.0" @@ -24527,6 +24991,15 @@ __metadata: languageName: node linkType: hard +"npm-run-path@npm:^2.0.0": + version: 2.0.2 + resolution: "npm-run-path@npm:2.0.2" + dependencies: + path-key: "npm:^2.0.0" + checksum: 10c0/95549a477886f48346568c97b08c4fda9cdbf7ce8a4fbc2213f36896d0d19249e32d68d7451bdcbca8041b5fba04a6b2c4a618beaf19849505c05b700740f1de + languageName: node + linkType: hard + "npm-run-path@npm:^4.0.1": version: 4.0.1 resolution: "npm-run-path@npm:4.0.1" @@ -24536,6 +25009,18 @@ __metadata: languageName: node linkType: hard +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: "npm:^2.0.0" + console-control-strings: "npm:^1.1.0" + gauge: "npm:^3.0.0" + set-blocking: "npm:^2.0.0" + checksum: 10c0/489ba519031013001135c463406f55491a17fc7da295c18a04937fe3a4d523fd65e88dd418a28b967ab743d913fdeba1e29838ce0ad8c75557057c481f7d49fa + languageName: node + linkType: hard + "nth-check@npm:^2.0.1, nth-check@npm:^2.1.1": version: 2.1.1 resolution: "nth-check@npm:2.1.1" @@ -24887,6 +25372,16 @@ __metadata: languageName: node linkType: hard +"os-name@npm:^3.0.0": + version: 3.1.0 + resolution: "os-name@npm:3.1.0" + dependencies: + macos-release: "npm:^2.2.0" + windows-release: "npm:^3.1.0" + checksum: 10c0/da45495c391606afbefc4fcc5bf2ac37f92a32280a2ec8ed66d723b52a71783d65c1ad9e63f5f4a6d172f90e904de854627588cce9555bcad417d0e615d7e217 + languageName: node + linkType: hard + "os-tmpdir@npm:~1.0.2": version: 1.0.2 resolution: "os-tmpdir@npm:1.0.2" @@ -25018,6 +25513,13 @@ __metadata: languageName: node linkType: hard +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 + languageName: node + linkType: hard + "p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" @@ -25313,6 +25815,13 @@ __metadata: languageName: node linkType: hard +"path-key@npm:^2.0.0, path-key@npm:^2.0.1": + version: 2.0.1 + resolution: "path-key@npm:2.0.1" + checksum: 10c0/dd2044f029a8e58ac31d2bf34c34b93c3095c1481942960e84dd2faa95bbb71b9b762a106aead0646695330936414b31ca0bd862bf488a937ad17c8c5d73b32b + languageName: node + linkType: hard + "path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" @@ -28064,6 +28573,15 @@ __metadata: languageName: node linkType: hard +"shebang-command@npm:^1.2.0": + version: 1.2.0 + resolution: "shebang-command@npm:1.2.0" + dependencies: + shebang-regex: "npm:^1.0.0" + checksum: 10c0/7b20dbf04112c456b7fc258622dafd566553184ac9b6938dd30b943b065b21dabd3776460df534cc02480db5e1b6aec44700d985153a3da46e7db7f9bd21326d + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -28073,6 +28591,13 @@ __metadata: languageName: node linkType: hard +"shebang-regex@npm:^1.0.0": + version: 1.0.0 + resolution: "shebang-regex@npm:1.0.0" + checksum: 10c0/9abc45dee35f554ae9453098a13fdc2f1730e525a5eb33c51f096cc31f6f10a4b38074c1ebf354ae7bffa7229506083844008dfc3bb7818228568c0b2dc1fff2 + languageName: node + linkType: hard + "shebang-regex@npm:^3.0.0": version: 3.0.0 resolution: "shebang-regex@npm:3.0.0" @@ -28162,7 +28687,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 @@ -28342,6 +28867,26 @@ __metadata: languageName: node linkType: hard +"snarkjs@npm:=0.7.5, snarkjs@npm:^0.7.0, snarkjs@npm:^0.7.1, snarkjs@npm:^0.7.3, snarkjs@npm:^0.7.4, snarkjs@npm:^0.7.5": + version: 0.7.5 + resolution: "snarkjs@npm:0.7.5" + dependencies: + "@iden3/binfileutils": "npm:0.0.12" + bfj: "npm:^7.0.2" + blake2b-wasm: "npm:^2.4.0" + circom_runtime: "npm:0.1.28" + ejs: "npm:^3.1.6" + fastfile: "npm:0.0.20" + ffjavascript: "npm:0.3.1" + js-sha3: "npm:^0.8.0" + logplease: "npm:^1.2.15" + r1csfile: "npm:0.0.48" + bin: + snarkjs: build/cli.cjs + checksum: 10c0/bc9eb1dac9c5248a4952635edc015185c5f9f268f6d2d29b32934e0b08bc284caaeba7fbc6d712ecff8a4e17c66433ba6b2f2ab5d1a6bb4704c30110fb18e9aa + languageName: node + linkType: hard + "snarkjs@npm:^0.4.10": version: 0.4.27 resolution: "snarkjs@npm:0.4.27" @@ -28362,26 +28907,6 @@ __metadata: languageName: node linkType: hard -"snarkjs@npm:^0.7.0, snarkjs@npm:^0.7.1, snarkjs@npm:^0.7.3, snarkjs@npm:^0.7.4, snarkjs@npm:^0.7.5": - version: 0.7.5 - resolution: "snarkjs@npm:0.7.5" - dependencies: - "@iden3/binfileutils": "npm:0.0.12" - bfj: "npm:^7.0.2" - blake2b-wasm: "npm:^2.4.0" - circom_runtime: "npm:0.1.28" - ejs: "npm:^3.1.6" - fastfile: "npm:0.0.20" - ffjavascript: "npm:0.3.1" - js-sha3: "npm:^0.8.0" - logplease: "npm:^1.2.15" - r1csfile: "npm:0.0.48" - bin: - snarkjs: build/cli.cjs - checksum: 10c0/bc9eb1dac9c5248a4952635edc015185c5f9f268f6d2d29b32934e0b08bc284caaeba7fbc6d712ecff8a4e17c66433ba6b2f2ab5d1a6bb4704c30110fb18e9aa - languageName: node - linkType: hard - "socket.io-client@npm:^4.8.1": version: 4.8.1 resolution: "socket.io-client@npm:4.8.1" @@ -28817,7 +29342,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -29044,6 +29569,13 @@ __metadata: languageName: node linkType: hard +"strip-eof@npm:^1.0.0": + version: 1.0.0 + resolution: "strip-eof@npm:1.0.0" + checksum: 10c0/f336beed8622f7c1dd02f2cbd8422da9208fae81daf184f73656332899978919d5c0ca84dc6cfc49ad1fc4dd7badcde5412a063cf4e0d7f8ed95a13a63f68f45 + languageName: node + linkType: hard + "strip-final-newline@npm:^2.0.0": version: 2.0.0 resolution: "strip-final-newline@npm:2.0.0" @@ -30472,6 +31004,15 @@ __metadata: languageName: node linkType: hard +"universal-user-agent@npm:^2.0.0": + version: 2.1.0 + resolution: "universal-user-agent@npm:2.1.0" + dependencies: + os-name: "npm:^3.0.0" + checksum: 10c0/527b029e01991712f757b69d072b4d8258a8a6526c8eb1eb8ff73907792a1536ce18e0a35bc99b3d676a3325e4a293be146b66cc0d0abf53cb6ed4d00854b855 + languageName: node + linkType: hard + "universalify@npm:^0.1.0": version: 0.1.2 resolution: "universalify@npm:0.1.2" @@ -30583,6 +31124,13 @@ __metadata: languageName: node linkType: hard +"url-template@npm:^2.0.8": + version: 2.0.8 + resolution: "url-template@npm:2.0.8" + checksum: 10c0/56a15057eacbcf05d52b0caed8279c8451b3dd9d32856a1fdd91c6dc84dcb1646f12bafc756b7ade62ca5b1564da8efd7baac5add35868bafb43eb024c62805b + languageName: node + linkType: hard + "use-callback-ref@npm:^1.3.3": version: 1.3.3 resolution: "use-callback-ref@npm:1.3.3" @@ -31523,7 +32071,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.1.1, which@npm:^1.3.1": +"which@npm:^1.1.1, which@npm:^1.2.9, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: @@ -31568,6 +32116,15 @@ __metadata: languageName: node linkType: hard +"wide-align@npm:^1.1.2": + version: 1.1.5 + resolution: "wide-align@npm:1.1.5" + dependencies: + string-width: "npm:^1.0.2 || 2 || 3 || 4" + checksum: 10c0/1d9c2a3e36dfb09832f38e2e699c367ef190f96b82c71f809bc0822c306f5379df87bab47bed27ea99106d86447e50eb972d3c516c2f95782807a9d082fbea95 + languageName: node + linkType: hard + "widest-line@npm:^3.1.0": version: 3.1.0 resolution: "widest-line@npm:3.1.0" @@ -31584,6 +32141,15 @@ __metadata: languageName: node linkType: hard +"windows-release@npm:^3.1.0": + version: 3.3.3 + resolution: "windows-release@npm:3.3.3" + dependencies: + execa: "npm:^1.0.0" + checksum: 10c0/d81add605d94583724f0e7f4257e5f074cc3e6583b69ff79852cc191fa9e4686412476928adb28799fb27929db7eb1f07b282348ae072c80f6973ea42dc6dc74 + languageName: node + linkType: hard + "word-wrap@npm:^1.2.5, word-wrap@npm:~1.2.3": version: 1.2.5 resolution: "word-wrap@npm:1.2.5"