From be7d8442c20a04cc46c80399480ef64979c05353 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 12 May 2026 17:14:55 -0400 Subject: [PATCH] feat(disclosure)!: migrate to ECDH on-circuit encryption (circuits v0.8.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The disclosure.circom circuit now encrypts selected note fields inside the proof using ECDH over Baby Jubjub instead of revealing them as plaintext public signals. Only the registered auditor (holding sk_A) can decrypt: shared = sk_A · epk plaintext_i = enc_i - Poseidon(shared.x, shared.y, i) mod BN254_P BREAKING CHANGES: - generateDisclosureProof() requires three new parameters: auditorPkX: bigint, auditorPkY: bigint, r: bigint (ephemeral scalar) - DisclosureProofOutput.revealedData removed; replaced by encryptedData: { epkX, epkY, encValue, encAssetId, encOwnerHash, commitment } - Public signal count: 4 → 8 [epk_x, epk_y, enc_value, enc_asset_id, enc_owner_hash, commitment, auditor_pk_x, auditor_pk_y] - circomlibjs (off-circuit Poseidon owner hash) removed from proof gen Bump @orbinum/circuits 0.7.0 → 0.8.0. --- CHANGELOG.md | 41 +++++++ package.json | 4 +- pnpm-lock.yaml | 192 ++++++++++++++++----------------- src/circuits/config.ts | 3 +- src/disclosure/index.ts | 86 ++++++++++----- src/disclosure/types.ts | 29 +++-- tests/circuits/config.test.ts | 3 +- tests/disclosure/index.test.ts | 171 +++++++++++++++-------------- tests/generate/index.test.ts | 14 ++- 9 files changed, 321 insertions(+), 222 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 504b97d..c3ad4a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.6.0] - 2026-05-12 + +### Changed + +- **`@orbinum/circuits`** bumped `0.7.0` → `0.8.0` — the `disclosure.circom` circuit now performs **ECDH encryption on-circuit** over Baby Jubjub instead of revealing note fields as plaintext public signals. +- **`src/disclosure/index.ts`** — `generateDisclosureProof()` updated to match the new circuit interface: + - Three new required parameters: `auditorPkX: bigint`, `auditorPkY: bigint`, `r: bigint` (ephemeral scalar — must be random, `< Baby Jubjub suborder`). + - `buildCircuitInputs` is now synchronous — the off-circuit `buildPoseidon()` call (circomlibjs) is removed because the owner hash is now computed inside the circuit. + - Circuit inputs updated: `revealed_value`, `revealed_asset_id`, `revealed_owner_hash` replaced by `auditor_pk_x`, `auditor_pk_y`, `r`. + - Output `revealedData` replaced by `encryptedData` (see types). +- **`src/disclosure/types.ts`** — `DisclosureProofOutput.revealedData` replaced by `encryptedData`: + ``` + encryptedData: { + epkX: string; // ephemeral public key x (Baby Jubjub) + epkY: string; // ephemeral public key y (Baby Jubjub) + encValue: string; // encrypted note value (0 if not disclosed) + encAssetId: string; // encrypted asset ID (0 if not disclosed) + encOwnerHash: string;// encrypted Poseidon(owner_pubkey) (0 if not disclosed) + commitment: string; // note commitment (always present, not encrypted) + } + ``` + Public signal order updated: `[epk_x, epk_y, enc_value, enc_asset_id, enc_owner_hash, commitment, auditor_pk_x, auditor_pk_y]`. +- **`src/circuits/config.ts`** — `expectedPublicSignals` for `Disclosure` updated: 4 → 8. +- **`tests/circuits/config.test.ts`** — expectation updated to match new signal count. +- **`tests/disclosure/index.test.ts`** — tests rewritten for new ECDH-based signature, auditor key inputs, and `encryptedData` output shape. +- **`tests/generate/index.test.ts`** — disclosure test cases updated. + +### Removed + +- **`circomlibjs`** removed as a runtime import from `src/disclosure/index.ts` — off-circuit `Poseidon(owner_pubkey)` hashing is no longer required, as the circuit handles all encryption internally. + +### Breaking Changes + +- `generateDisclosureProof(value, ownerPubkey, blinding, assetId, commitment, mask, options?)` → `generateDisclosureProof(value, ownerPubkey, blinding, assetId, commitment, auditorPkX, auditorPkY, r, mask, options?)`. +- `DisclosureProofOutput.revealedData` is removed. Use `encryptedData` instead. The auditor decrypts offline: + ``` + shared = sk_A · epk (Baby Jubjub scalar multiplication) + plaintext_i = enc_i - Poseidon(shared.x, shared.y, i) mod BN254_P + ``` +- Public signal count for `Disclosure` circuit: 4 → 8. + ## [3.5.4] - 2026-05-08 ### Fixed diff --git a/package.json b/package.json index 98d60ec..2af0d52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orbinum/proof-generator", - "version": "3.5.4", + "version": "3.6.0", "description": "ZK-SNARK proof generator for Orbinum. Combines snarkjs (witness) with arkworks WASM (proof generation) to produce 128-byte Groth16 proofs.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -39,7 +39,7 @@ "orbinum" ], "dependencies": { - "@orbinum/circuits": "0.7.0", + "@orbinum/circuits": "0.8.0", "@orbinum/groth16-proofs": "3.0.0", "circomlibjs": "0.1.7", "snarkjs": "0.7.6" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 606974c..2033b86 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@orbinum/circuits': - specifier: 0.7.0 - version: 0.7.0 + specifier: 0.8.0 + version: 0.8.0 '@orbinum/groth16-proofs': specifier: 3.0.0 version: 3.0.0 @@ -41,7 +41,7 @@ importers: version: 6.0.2 vitest: specifier: 4.1.3 - version: 4.1.3(@types/node@25.5.2)(@vitest/coverage-v8@4.1.3)(vite@8.0.10(@types/node@25.5.2)) + version: 4.1.3(@types/node@25.5.2)(@vitest/coverage-v8@4.1.3)(vite@8.0.12(@types/node@25.5.2)) packages: @@ -191,114 +191,114 @@ packages: resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} - '@orbinum/circuits@0.7.0': - resolution: {integrity: sha512-Iwd+Gv9PM5++qgwgRkzKUvlA6CiG+h+zLi1XvJoXYgfJizyHN0WgrPPEu79BwQxuKczmXFWf9P9kQxTWCeLi/w==} + '@orbinum/circuits@0.8.0': + resolution: {integrity: sha512-8BslXDY3iSjumyj5VNC1vjm8nbuxenla4Wb0xPLd5OOK2LPsRWF88eC+ieZdNT81+WprTkAKqxCx+Kb3ZOkm9w==} engines: {node: '>=18.0.0'} '@orbinum/groth16-proofs@3.0.0': resolution: {integrity: sha512-1I8/wMkCKn5zpS4qVoaXnUfNiuHoo7Ym8hlG6avaijGtITLY6BzLEcwjBjVHZ2vpI+CjL557ORfyjuGrFDzJSw==} engines: {node: '>=18.0.0'} - '@oxc-project/types@0.127.0': - resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.129.0': + resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} - '@rolldown/binding-android-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + '@rolldown/binding-android-arm64@1.0.0': + resolution: {integrity: sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + '@rolldown/binding-darwin-arm64@1.0.0': + resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.17': - resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + '@rolldown/binding-darwin-x64@1.0.0': + resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.17': - resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + '@rolldown/binding-freebsd-x64@1.0.0': + resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': - resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + '@rolldown/binding-linux-arm64-gnu@1.0.0': + resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': - resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + '@rolldown/binding-linux-arm64-musl@1.0.0': + resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0': + resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + '@rolldown/binding-linux-s390x-gnu@1.0.0': + resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': - resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + '@rolldown/binding-linux-x64-gnu@1.0.0': + resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': - resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + '@rolldown/binding-linux-x64-musl@1.0.0': + resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': - resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + '@rolldown/binding-openharmony-arm64@1.0.0': + resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': - resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + '@rolldown/binding-wasm32-wasi@1.0.0': + resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': - resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + '@rolldown/binding-win32-arm64-msvc@1.0.0': + resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': - resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + '@rolldown/binding-win32-x64-msvc@1.0.0': + resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.17': - resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rolldown/pluginutils@1.0.0': + resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==} '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -315,8 +315,8 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} '@types/node@25.5.2': resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==} @@ -699,8 +699,8 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - rolldown@1.0.0-rc.17: - resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + rolldown@1.0.0: + resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -710,8 +710,8 @@ packages: scrypt-js@3.0.1: resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} engines: {node: '>=10'} hasBin: true @@ -781,13 +781,13 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vite@8.0.10: - resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} + vite@8.0.12: + resolution: {integrity: sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.0 + '@vitejs/devtools': ^0.1.18 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -1204,62 +1204,62 @@ snapshots: '@noble/hashes@1.8.0': {} - '@orbinum/circuits@0.7.0': {} + '@orbinum/circuits@0.8.0': {} '@orbinum/groth16-proofs@3.0.0': {} - '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.129.0': {} - '@rolldown/binding-android-arm64@1.0.0-rc.17': + '@rolldown/binding-android-arm64@1.0.0': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + '@rolldown/binding-darwin-arm64@1.0.0': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.17': + '@rolldown/binding-darwin-x64@1.0.0': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + '@rolldown/binding-freebsd-x64@1.0.0': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-arm64-gnu@1.0.0': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + '@rolldown/binding-linux-arm64-musl@1.0.0': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-ppc64-gnu@1.0.0': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-s390x-gnu@1.0.0': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + '@rolldown/binding-linux-x64-gnu@1.0.0': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + '@rolldown/binding-linux-x64-musl@1.0.0': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + '@rolldown/binding-openharmony-arm64@1.0.0': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + '@rolldown/binding-wasm32-wasi@1.0.0': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + '@rolldown/binding-win32-arm64-msvc@1.0.0': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + '@rolldown/binding-win32-x64-msvc@1.0.0': optional: true - '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rolldown/pluginutils@1.0.0': {} '@standard-schema/spec@1.1.0': {} @@ -1277,7 +1277,7 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} '@types/node@25.5.2': dependencies: @@ -1297,7 +1297,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.3(@types/node@25.5.2)(@vitest/coverage-v8@4.1.3)(vite@8.0.10(@types/node@25.5.2)) + vitest: 4.1.3(@types/node@25.5.2)(@vitest/coverage-v8@4.1.3)(vite@8.0.12(@types/node@25.5.2)) '@vitest/expect@4.1.3': dependencies: @@ -1308,13 +1308,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.3(vite@8.0.10(@types/node@25.5.2))': + '@vitest/mocker@4.1.3(vite@8.0.12(@types/node@25.5.2))': dependencies: '@vitest/spy': 4.1.3 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.10(@types/node@25.5.2) + vite: 8.0.12(@types/node@25.5.2) '@vitest/pretty-format@4.1.3': dependencies: @@ -1453,7 +1453,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esutils@2.0.3: {} @@ -1637,7 +1637,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.4 + semver: 7.8.0 minimalistic-assert@1.0.1: {} @@ -1684,32 +1684,32 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - rolldown@1.0.0-rc.17: + rolldown@1.0.0: dependencies: - '@oxc-project/types': 0.127.0 - '@rolldown/pluginutils': 1.0.0-rc.17 + '@oxc-project/types': 0.129.0 + '@rolldown/pluginutils': 1.0.0 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-x64': 1.0.0-rc.17 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + '@rolldown/binding-android-arm64': 1.0.0 + '@rolldown/binding-darwin-arm64': 1.0.0 + '@rolldown/binding-darwin-x64': 1.0.0 + '@rolldown/binding-freebsd-x64': 1.0.0 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0 + '@rolldown/binding-linux-arm64-gnu': 1.0.0 + '@rolldown/binding-linux-arm64-musl': 1.0.0 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0 + '@rolldown/binding-linux-s390x-gnu': 1.0.0 + '@rolldown/binding-linux-x64-gnu': 1.0.0 + '@rolldown/binding-linux-x64-musl': 1.0.0 + '@rolldown/binding-openharmony-arm64': 1.0.0 + '@rolldown/binding-wasm32-wasi': 1.0.0 + '@rolldown/binding-win32-arm64-msvc': 1.0.0 + '@rolldown/binding-win32-x64-msvc': 1.0.0 safe-buffer@5.2.1: {} scrypt-js@3.0.1: {} - semver@7.7.4: {} + semver@7.8.0: {} siginfo@2.0.0: {} @@ -1770,21 +1770,21 @@ snapshots: util-deprecate@1.0.2: {} - vite@8.0.10(@types/node@25.5.2): + vite@8.0.12(@types/node@25.5.2): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 postcss: 8.5.14 - rolldown: 1.0.0-rc.17 + rolldown: 1.0.0 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 25.5.2 fsevents: 2.3.3 - vitest@4.1.3(@types/node@25.5.2)(@vitest/coverage-v8@4.1.3)(vite@8.0.10(@types/node@25.5.2)): + vitest@4.1.3(@types/node@25.5.2)(@vitest/coverage-v8@4.1.3)(vite@8.0.12(@types/node@25.5.2)): dependencies: '@vitest/expect': 4.1.3 - '@vitest/mocker': 4.1.3(vite@8.0.10(@types/node@25.5.2)) + '@vitest/mocker': 4.1.3(vite@8.0.12(@types/node@25.5.2)) '@vitest/pretty-format': 4.1.3 '@vitest/runner': 4.1.3 '@vitest/snapshot': 4.1.3 @@ -1801,7 +1801,7 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@25.5.2) + vite: 8.0.12(@types/node@25.5.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.5.2 diff --git a/src/circuits/config.ts b/src/circuits/config.ts index 6302395..0a884e4 100644 --- a/src/circuits/config.ts +++ b/src/circuits/config.ts @@ -24,7 +24,8 @@ function getExpectedPublicSignals(circuitType: CircuitType): number { // [merkle_root, nullifiers[2], commitments[2], asset_id, fee] return 7; case CircuitType.Disclosure: - return 4; + // outputs: [epk_x, epk_y, enc_value, enc_asset_id, enc_owner_hash] + inputs: [commitment, auditor_pk_x, auditor_pk_y] + return 8; case CircuitType.PrivateLink: return 2; default: diff --git a/src/disclosure/index.ts b/src/disclosure/index.ts index f5fff9d..cb4fdfd 100644 --- a/src/disclosure/index.ts +++ b/src/disclosure/index.ts @@ -3,20 +3,22 @@ * * Generates Groth16 proofs for the `disclosure.circom` circuit. * - * ## Circuit Public Inputs (in order) - * 0. commitment – Note commitment (always revealed) - * 1. revealed_value – Note value, or 0 if not disclosed - * 2. revealed_asset_id – Asset ID, or 0 if not disclosed - * 3. revealed_owner_hash – Poseidon(owner_pubkey), or 0 if not disclosed + * ## Circuit Public Inputs (in order, from snarkjs publicSignals) + * 0. epk_x – Ephemeral public key x (Baby Jubjub) + * 1. epk_y – Ephemeral public key y (Baby Jubjub) + * 2. enc_value – Encrypted note value (0 if not disclosed) + * 3. enc_asset_id – Encrypted asset ID (0 if not disclosed) + * 4. enc_owner_hash – Encrypted Poseidon(owner_pubkey) (0 if not disclosed) + * 5. commitment – Note commitment (always present) + * 6. auditor_pk_x – Auditor Baby Jubjub public key x + * 7. auditor_pk_y – Auditor Baby Jubjub public key y * * @module @orbinum/proof-generator/disclosure */ -// @ts-ignore - circomlibjs has no type declarations -import { buildPoseidon } from 'circomlibjs'; import { generateProof } from '../generate'; import { CircuitType, ProofResult } from '../circuits/types'; -import { bigIntToHex, hexSignalToBigInt, u64ToFieldStr } from '../utils'; +import { u64ToFieldStr } from '../utils'; import { ArtifactProvider } from '../providers/interface'; import { DisclosureMask, DisclosureProofOutput } from './types'; @@ -32,26 +34,22 @@ export type { DisclosureMask, DisclosureProofOutput } from './types'; * All values are decimal BigInt strings — the native format that snarkjs * expects for scalar field elements. */ -async function buildCircuitInputs( +function buildCircuitInputs( value: bigint, ownerPubkey: bigint, blinding: bigint, assetId: bigint, commitment: bigint, + auditorPkX: bigint, + auditorPkY: bigint, + r: bigint, mask: DisclosureMask -): Promise> { - const poseidon = await buildPoseidon(); - const F = poseidon.F; - - // viewing_key = Poseidon(owner_pubkey) — matches the circom constraint - const viewingKey: bigint = F.toObject(poseidon([ownerPubkey])); - +): Record { return { // Public inputs commitment: commitment.toString(), - revealed_value: (mask.discloseValue ? value : 0n).toString(), - revealed_asset_id: (mask.discloseAssetId ? assetId : 0n).toString(), - revealed_owner_hash: (mask.discloseOwner ? viewingKey : 0n).toString(), + auditor_pk_x: auditorPkX.toString(), + auditor_pk_y: auditorPkY.toString(), // Private inputs value: u64ToFieldStr(value), asset_id: u64ToFieldStr(assetId), @@ -60,6 +58,7 @@ async function buildCircuitInputs( disclose_value: mask.discloseValue ? '1' : '0', disclose_asset_id: mask.discloseAssetId ? '1' : '0', disclose_owner: mask.discloseOwner ? '1' : '0', + r: r.toString(), }; } @@ -70,11 +69,19 @@ async function buildCircuitInputs( /** * Generate a selective disclosure Groth16 proof. * + * The circuit encrypts the selected fields on-circuit using ECDH over Baby + * Jubjub. The auditor decrypts offline with their spending key: + * shared = sk_A · epk + * plaintext_i = enc_i - Poseidon(shared.x, shared.y, i) (mod BN254_P) + * * @param value – Note value as BigInt (u64 field element) * @param ownerPubkey – Owner public key as BigInt (BN254 scalar) * @param blinding – Blinding factor as BigInt * @param assetId – Asset ID as BigInt (u32) * @param commitment – Note commitment as BigInt + * @param auditorPkX – Auditor Baby Jubjub public key x-coordinate + * @param auditorPkY – Auditor Baby Jubjub public key y-coordinate + * @param r – Ephemeral scalar (must be random, < Baby Jubjub order) * @param mask – Which fields to disclose to the auditor * @param options – Optional artifact provider override */ @@ -84,6 +91,9 @@ export async function generateDisclosureProof( blinding: bigint, assetId: bigint, commitment: bigint, + auditorPkX: bigint, + auditorPkY: bigint, + r: bigint, mask: DisclosureMask, options: { provider?: ArtifactProvider; verbose?: boolean } = {} ): Promise { @@ -93,21 +103,39 @@ export async function generateDisclosureProof( ); } - const inputs = await buildCircuitInputs(value, ownerPubkey, blinding, assetId, commitment, mask); + const inputs = buildCircuitInputs( + value, + ownerPubkey, + blinding, + assetId, + commitment, + auditorPkX, + auditorPkY, + r, + mask + ); - // Public signal order from disclosure.circom: - // [0] commitment [1] revealed_value [2] revealed_asset_id [3] revealed_owner_hash + // Public signal order from disclosure.circom (outputs first, then public inputs): + // [0] epk_x [1] epk_y [2] enc_value [3] enc_asset_id [4] enc_owner_hash + // [5] commitment [6] auditor_pk_x [7] auditor_pk_y const result: ProofResult = await generateProof(CircuitType.Disclosure, inputs, { provider: options.provider, verbose: options.verbose, }); - const [sigCommitment, sigValue, sigAssetId, sigOwnerHash] = result.publicSignals; + const [sigEpkX, sigEpkY, sigEncValue, sigEncAssetId, sigEncOwnerHash, sigCommitment] = + result.publicSignals; - const revealedData: DisclosureProofOutput['revealedData'] = { commitment: sigCommitment }; - if (mask.discloseValue) revealedData.value = hexSignalToBigInt(sigValue).toString(10); - if (mask.discloseAssetId) revealedData.assetId = Number(hexSignalToBigInt(sigAssetId)); - if (mask.discloseOwner) revealedData.ownerHash = bigIntToHex(hexSignalToBigInt(sigOwnerHash)); - - return { proof: result.proof, publicSignals: result.publicSignals, revealedData }; + return { + proof: result.proof, + publicSignals: result.publicSignals, + encryptedData: { + epkX: sigEpkX, + epkY: sigEpkY, + encValue: sigEncValue, + encAssetId: sigEncAssetId, + encOwnerHash: sigEncOwnerHash, + commitment: sigCommitment, + }, + }; } diff --git a/src/disclosure/types.ts b/src/disclosure/types.ts index 45cb537..9c71a6d 100644 --- a/src/disclosure/types.ts +++ b/src/disclosure/types.ts @@ -19,18 +19,27 @@ export interface DisclosureProofOutput { proof: string; /** * Raw public signals in hex (0x-prefixed, 32 bytes each). - * Order: [commitment, revealed_value, revealed_asset_id, revealed_owner_hash] + * Order: [epk_x, epk_y, enc_value, enc_asset_id, enc_owner_hash, commitment, auditor_pk_x, auditor_pk_y] */ publicSignals: string[]; - /** Human-readable revealed data */ - revealedData: { - /** Revealed note value as decimal string, or undefined if not disclosed */ - value?: string; - /** Revealed asset ID as number, or undefined if not disclosed */ - assetId?: number; - /** Revealed owner hash as 0x-prefixed hex, or undefined if not disclosed */ - ownerHash?: string; - /** Note commitment (always present) */ + /** + * ECDH-encrypted disclosure data. + * The auditor decrypts offline using their Baby Jubjub spending key: + * shared = sk_A · epk + * plaintext_i = enc_i - Poseidon(shared.x, shared.y, i) (mod BN254_P) + */ + encryptedData: { + /** Ephemeral public key x-coordinate (Baby Jubjub) */ + epkX: string; + /** Ephemeral public key y-coordinate (Baby Jubjub) */ + epkY: string; + /** Encrypted note value (0 if not disclosed) */ + encValue: string; + /** Encrypted asset ID (0 if not disclosed) */ + encAssetId: string; + /** Encrypted owner hash Poseidon(owner_pubkey) (0 if not disclosed) */ + encOwnerHash: string; + /** Note commitment (always present, not encrypted) */ commitment: string; }; } diff --git a/tests/circuits/config.test.ts b/tests/circuits/config.test.ts index e5078ac..78dd8f2 100644 --- a/tests/circuits/config.test.ts +++ b/tests/circuits/config.test.ts @@ -29,7 +29,8 @@ describe('getCircuitConfig', () => { expect(config.wasmPath).toBe('disclosure.wasm'); expect(config.zkeyPath).toBe('disclosure_pk.zkey'); expect(config.provingKeyPath).toBe('disclosure_pk.ark'); - expect(config.expectedPublicSignals).toBe(4); + // [epk_x, epk_y, enc_value, enc_asset_id, enc_owner_hash, commitment, auditor_pk_x, auditor_pk_y] + expect(config.expectedPublicSignals).toBe(8); }); it('returns correct config for PrivateLink', () => { diff --git a/tests/disclosure/index.test.ts b/tests/disclosure/index.test.ts index 35e9cd4..7a1e65b 100644 --- a/tests/disclosure/index.test.ts +++ b/tests/disclosure/index.test.ts @@ -1,7 +1,7 @@ /** * Tests: disclosure/index.ts — generateDisclosureProof() * - * generateProof and circomlibjs are mocked so no real circuit artifacts + * generateProof is mocked so no real circuit artifacts * or cryptographic operations are required. */ @@ -10,28 +10,21 @@ import { generateDisclosureProof } from '../../src/disclosure'; import type { DisclosureMask } from '../../src/disclosure'; import type { ArtifactProvider } from '../../src/providers/interface'; -// ─── Mock circomlibjs ───────────────────────────────────────────────────────── - -vi.mock('circomlibjs', () => { - const poseidon = Object.assign(vi.fn().mockReturnValue('stub'), { - F: { toObject: vi.fn().mockReturnValue(999n) }, - }); - return { - buildPoseidon: vi.fn().mockResolvedValue(poseidon), - }; -}); - // ─── Mock generateProof ─────────────────────────────────────────────────────── vi.mock('../../src/generate', () => ({ generateProof: vi.fn().mockResolvedValue({ proof: '0x' + 'aa'.repeat(128), publicSignals: [ - // commitment, revealed_value, revealed_asset_id, revealed_owner_hash - '0x' + '01'.repeat(32), - '0x' + '02'.repeat(32), - '0x' + '03'.repeat(32), - '0x' + '04'.repeat(32), + // epk_x, epk_y, enc_value, enc_asset_id, enc_owner_hash, commitment, auditor_pk_x, auditor_pk_y + '0x' + '01'.repeat(32), // epk_x + '0x' + '02'.repeat(32), // epk_y + '0x' + '03'.repeat(32), // enc_value + '0x' + '04'.repeat(32), // enc_asset_id + '0x' + '05'.repeat(32), // enc_owner_hash + '0x' + '06'.repeat(32), // commitment + '0x' + '07'.repeat(32), // auditor_pk_x + '0x' + '08'.repeat(32), // auditor_pk_y ], circuitType: 'disclosure', }), @@ -44,6 +37,9 @@ const OWNER_PUBKEY = 12345n; const BLINDING = 999n; const ASSET_ID = 1n; const COMMITMENT = 42n; +const AUDITOR_PK_X = 111n; +const AUDITOR_PK_Y = 222n; +const R = 333n; const MASK_ALL: DisclosureMask = { discloseValue: true, @@ -63,137 +59,156 @@ describe('generateDisclosureProof', () => { vi.clearAllMocks(); }); - it('returns proof, publicSignals and revealedData', async () => { + it('returns proof, publicSignals and encryptedData', async () => { const result = await generateDisclosureProof( VALUE, OWNER_PUBKEY, BLINDING, ASSET_ID, COMMITMENT, + AUDITOR_PK_X, + AUDITOR_PK_Y, + R, MASK_ALL, { provider: MOCK_PROVIDER } ); expect(result.proof).toBe('0x' + 'aa'.repeat(128)); - expect(result.publicSignals).toHaveLength(4); - expect(result.revealedData.commitment).toBeDefined(); - }); - - it('populates revealedData.value when discloseValue is true', async () => { - const result = await generateDisclosureProof( - VALUE, - OWNER_PUBKEY, - BLINDING, - ASSET_ID, - COMMITMENT, - MASK_ALL, - { provider: MOCK_PROVIDER } - ); - expect(result.revealedData.value).toBeDefined(); - expect(typeof result.revealedData.value).toBe('string'); + expect(result.publicSignals).toHaveLength(8); + expect(result.encryptedData.commitment).toBeDefined(); }); - it('populates revealedData.assetId when discloseAssetId is true', async () => { + it('encryptedData contains epk and encrypted fields', async () => { const result = await generateDisclosureProof( VALUE, OWNER_PUBKEY, BLINDING, ASSET_ID, COMMITMENT, + AUDITOR_PK_X, + AUDITOR_PK_Y, + R, MASK_ALL, { provider: MOCK_PROVIDER } ); - expect(result.revealedData.assetId).toBeDefined(); - expect(typeof result.revealedData.assetId).toBe('number'); + expect(result.encryptedData.epkX).toBeDefined(); + expect(result.encryptedData.epkY).toBeDefined(); + expect(result.encryptedData.encValue).toBeDefined(); + expect(result.encryptedData.encAssetId).toBeDefined(); + expect(result.encryptedData.encOwnerHash).toBeDefined(); }); - it('populates revealedData.ownerHash when discloseOwner is true', async () => { + it('encryptedData maps correctly to publicSignals positions', async () => { const result = await generateDisclosureProof( VALUE, OWNER_PUBKEY, BLINDING, ASSET_ID, COMMITMENT, + AUDITOR_PK_X, + AUDITOR_PK_Y, + R, MASK_ALL, { provider: MOCK_PROVIDER } ); - expect(result.revealedData.ownerHash).toBeDefined(); - expect(result.revealedData.ownerHash).toMatch(/^0x/); + const s = result.publicSignals; + expect(result.encryptedData.epkX).toBe(s[0]); + expect(result.encryptedData.epkY).toBe(s[1]); + expect(result.encryptedData.encValue).toBe(s[2]); + expect(result.encryptedData.encAssetId).toBe(s[3]); + expect(result.encryptedData.encOwnerHash).toBe(s[4]); + expect(result.encryptedData.commitment).toBe(s[5]); }); - it('omits revealedData.value when discloseValue is false', async () => { - const mask: DisclosureMask = { + it('throws when all mask fields are false', async () => { + const emptyMask: DisclosureMask = { discloseValue: false, - discloseAssetId: true, + discloseAssetId: false, discloseOwner: false, }; - const result = await generateDisclosureProof( + await expect( + generateDisclosureProof( + VALUE, + OWNER_PUBKEY, + BLINDING, + ASSET_ID, + COMMITMENT, + AUDITOR_PK_X, + AUDITOR_PK_Y, + R, + emptyMask + ) + ).rejects.toThrow('DisclosureMask'); + }); + + it('passes the provider option through to generateProof', async () => { + const { generateProof } = await import('../../src/generate'); + await generateDisclosureProof( VALUE, OWNER_PUBKEY, BLINDING, ASSET_ID, COMMITMENT, - mask, + AUDITOR_PK_X, + AUDITOR_PK_Y, + R, + MASK_ALL, { provider: MOCK_PROVIDER } ); - expect(result.revealedData.value).toBeUndefined(); + expect(generateProof).toHaveBeenCalledWith( + 'disclosure', + expect.any(Object), + expect.objectContaining({ provider: MOCK_PROVIDER }) + ); }); - it('omits revealedData.assetId when discloseAssetId is false', async () => { - const mask: DisclosureMask = { - discloseValue: true, - discloseAssetId: false, - discloseOwner: false, - }; - const result = await generateDisclosureProof( + it('passes auditor pk and r as circuit inputs', async () => { + const { generateProof } = await import('../../src/generate'); + await generateDisclosureProof( VALUE, OWNER_PUBKEY, BLINDING, ASSET_ID, COMMITMENT, - mask, + AUDITOR_PK_X, + AUDITOR_PK_Y, + R, + MASK_ALL, { provider: MOCK_PROVIDER } ); - expect(result.revealedData.assetId).toBeUndefined(); + expect(generateProof).toHaveBeenCalledWith( + 'disclosure', + expect.objectContaining({ + auditor_pk_x: AUDITOR_PK_X.toString(), + auditor_pk_y: AUDITOR_PK_Y.toString(), + r: R.toString(), + }), + expect.any(Object) + ); }); - it('omits revealedData.ownerHash when discloseOwner is false', async () => { + it('sets disclose_* inputs correctly from mask', async () => { + const { generateProof } = await import('../../src/generate'); const mask: DisclosureMask = { discloseValue: true, discloseAssetId: false, discloseOwner: false, }; - const result = await generateDisclosureProof( + await generateDisclosureProof( VALUE, OWNER_PUBKEY, BLINDING, ASSET_ID, COMMITMENT, + AUDITOR_PK_X, + AUDITOR_PK_Y, + R, mask, { provider: MOCK_PROVIDER } ); - expect(result.revealedData.ownerHash).toBeUndefined(); - }); - - it('throws when all mask fields are false', async () => { - const emptyMask: DisclosureMask = { - discloseValue: false, - discloseAssetId: false, - discloseOwner: false, - }; - await expect( - generateDisclosureProof(VALUE, OWNER_PUBKEY, BLINDING, ASSET_ID, COMMITMENT, emptyMask) - ).rejects.toThrow('DisclosureMask'); - }); - - it('passes the provider option through to generateProof', async () => { - const { generateProof } = await import('../../src/generate'); - await generateDisclosureProof(VALUE, OWNER_PUBKEY, BLINDING, ASSET_ID, COMMITMENT, MASK_ALL, { - provider: MOCK_PROVIDER, - }); expect(generateProof).toHaveBeenCalledWith( 'disclosure', - expect.any(Object), - expect.objectContaining({ provider: MOCK_PROVIDER }) + expect.objectContaining({ disclose_value: '1', disclose_asset_id: '0', disclose_owner: '0' }), + expect.any(Object) ); }); }); diff --git a/tests/generate/index.test.ts b/tests/generate/index.test.ts index 115c768..0e2f6f2 100644 --- a/tests/generate/index.test.ts +++ b/tests/generate/index.test.ts @@ -25,6 +25,10 @@ vi.mock('@orbinum/groth16-proofs', () => ({ '0x' + '02'.repeat(32), '0x' + '03'.repeat(32), '0x' + '04'.repeat(32), + '0x' + '05'.repeat(32), + '0x' + '06'.repeat(32), + '0x' + '07'.repeat(32), + '0x' + '08'.repeat(32), ], }) ), @@ -45,12 +49,12 @@ vi.mock('snarkjs', () => ({ ], pi_c: ['7', '8', '1'], }, - publicSignals: ['10', '20', '30', '40'], + publicSignals: ['10', '20', '30', '40', '50', '60', '70', '80'], }), }, wtns: { calculate: vi.fn().mockResolvedValue(undefined), - exportJson: vi.fn().mockResolvedValue([1n, 10n, 20n, 30n, 40n]), + exportJson: vi.fn().mockResolvedValue([1n, 10n, 20n, 30n, 40n, 50n, 60n, 70n, 80n]), }, })); @@ -90,7 +94,7 @@ describe('generateProof — snarkjs backend (default)', () => { it('returns ProofResult with proof and publicSignals', async () => { const result = await generateProof(CircuitType.Disclosure, VALID_INPUTS, { provider }); expect(result.proof).toMatch(/^0x[0-9a-f]+$/i); - expect(result.publicSignals).toHaveLength(4); + expect(result.publicSignals).toHaveLength(8); expect(result.circuitType).toBe(CircuitType.Disclosure); }); @@ -153,7 +157,7 @@ describe('generateProof — arkworks backend', () => { backend: 'arkworks', }); expect(result.proof).toMatch(/^0x[0-9a-f]+$/i); - expect(result.publicSignals).toHaveLength(4); + expect(result.publicSignals).toHaveLength(8); expect(result.circuitType).toBe(CircuitType.Disclosure); }); @@ -178,7 +182,7 @@ describe('generateProof — arkworks backend', () => { const [numSigs, witnessJson, pkBytes] = ( wasm.generate_proof_from_decimal_wasm as ReturnType ).mock.calls[0]; - expect(numSigs).toBe(4); + expect(numSigs).toBe(8); const parsed = JSON.parse(witnessJson); expect(Array.isArray(parsed)).toBe(true); expect(parsed[0]).toBe('1');