NOVA-STYLE-DEPOSITS CROSS-REPOSITORY AUDIT Date: 2026-10-07 DECISION Fix the two confirmed integration regressions below before enabling this feature. One is a high-priority wallet availability regression; the other is a medium-priority recovery/index correctness regression. No unauthorized-spend, inflation, or fee-bypass exploit was established in the inspected changes and executed tests. That conclusion does not establish cryptographic soundness or qualify these artifacts for production. Production key authorization and deployment qualification remain outstanding. SCOPE AND METHOD Compared each fetched nova-style-deposits head against its merge base with staging, the actual PR target. Reviewed the cumulative branch changes, their interacting callers, recovery consumers, proof boundaries, and deployment gates. Generated SDK output and package changes were assessed with their source/build changes. privacy-protocol, PR 21: https://github.com/lexie-crypto/privacy-protocol/pull/21 head: b3f70c4bb59d0e3e4c6d8a26f72c0b37a6eb1b91 base: 59225f7461596ca86c95f6b25549570a4c6c63c3 49 commits; 274 changed files; +8344/-2897 lines. privacy-frontend, PR 66: https://github.com/lexie-crypto/privacy-frontend/pull/66 head: c7456bb5afaa9eb071bd786016b301debab07a0c base: d805dc86c788268f826759f80bbb37ef26dd12e3 47 commits; 81 changed files; +9449/-411 lines. stealthos-relayer, PR 26: https://github.com/lexie-crypto/stealthos-relayer/pull/26 head: cb851fdaf040fb706809f0818e99e2185ad75f1a base: 92370a78279ae30cd23f0cfc72c9a7b2b39ecf6d 3 commits; 26 changed files; +3422/-13 lines. Total: 381 changed paths across the three repositories. The companion *-changed-files.txt files give the complete per-repository line-count inventory. Tests ran in separate snapshots of these heads. The original three checkouts were left on their existing branches and remained clean. Audit-only reproductions, dependencies, build output, and logs are in this audit directory; the Linux contract test snapshot is under /home/markm/.cache/nova-style-deposits-audit-20261007 in WSL. WHAT THE CHANGES DO The main feature is a funded private-note top-up. Instead of creating another note for every deposit, it consumes one existing note and creates one replacement owned by the same identity. For example, an existing private 30 plus a new net deposit of 20 becomes one private 50 note. In the fee variant, fees apply to the new deposit, not to the old balance. The deposit amount remains public; the old balance and the replacement's total value remain private. This is a separate DepositMergeV1 proof family, using Groth16, rather than recursive Nova proof aggregation. privacy-protocol: - Adds a seven-public-input circuit and Rust engine for one existing input plus public net credit, producing one fresh same-owner output. It checks ownership, Merkle membership, leaf-index/nullifier consistency, token domain, nonzero values, 120-bit value bounds, and value conservation. The output can subsequently be spent through the ordinary JoinSplit proof path. - Adds a funding transcript that binds chain/program domain, pool, current tree, token, payer, gross amount, net amount, fee commitments, deadline, and nonce. - Adds baseline EVM native/ERC20 merged deposits, plus a separate private-fee pool and controller. The fee variant also implements fee-authorized ordinary deposits, private transfers, withdrawals, and migration. Shared authorization hooks prevent inherited legacy entrypoints from bypassing its fee policy; legacy batch entrypoints that cannot carry the authorization are disabled. - Adds SOL/SPL merged deposits, account/PDA validation, event encoding, and verifying keys. Atomic execution couples the new funding, old nullifier consumption, and replacement insertion. Deadline and current-tree checks prevent stale merges. - Adds SDK builders, event decoders, transcripts, browser worker/client support, development fixtures, CI scripts, and verification documentation. Browser proving stays local; witness/private keys are not part of the relayer API. - Converts the SDK distribution to ESM/NodeNext and updates imports and exports. This is a compatibility change beyond deposit merging: CommonJS consumers need an appropriate dynamic-import/migration path. privacy-frontend: - Automatically selects an authenticated, confirmed, unspent, unreserved same-owner note for eligible self-deposits in the current pool/tree/asset. It falls back to an ordinary deposit when no compatible note or supported merge capability exists. EVM MAX deposits explicitly retain the ordinary path. - Loads/proves in a dedicated browser worker, binds the funded transaction, handles native/token and private-fee variants, and checks final canonical event/leaf evidence. - Persists encrypted recovery material before exposing a copyable spend proof. The private-fee flow backs up an immutable template before acquiring final authorization. It reserves inputs and reconciles successful/expired attempts across recovery flows. - Adds bounded background consolidation: two compatible notes become one through an existing JoinSplit circuit, using a fully sponsored relay. It rechecks the quote, pauses for interaction, caps attempts, and avoids a wallet-paid fallback. EVM private- fee pools are explicitly excluded from this background optimization. - Changes shared cancellation, wallet locking, journal conflict checks, and recovery logic. These shared changes also affect existing ordinary EVM transactions. - Updates packaged SDK/prover artifacts, identity checks, configuration, UI hooks, API forwarding, and substantial test coverage. stealthos-relayer: - Adds prepare/backup/authorize handling for private-fee merged deposits, strict public-only request schemas, server-selected deployment/artifact policy, proof checks, exact funding/fee bindings, and transaction simulation. The user's wallet supplies the public funds. - Adds a durable encrypted-recovery phase before authorization, content-bound backup acknowledgements, ledger restoration support, and merged-deposit receipt checks. - Adds EVM/Solana sponsorship quotes for two-input/one-output consolidation, with deployment, balance, fee, gas, and budget checks appropriate to each path. - Does not update the general EVM recovery ingestion event list for DepositMerged; this is finding F2 below. CONFIRMED FINDINGS F1 -- HIGH / P1 -- Failed or cancelled encrypted backup can indefinitely reserve ordinary EVM spending inputs without a normal retry path. Locations in privacy-frontend: src/joinsplit-v2/pendingJournal.ts:980-1000 (new reservation conflict check) src/joinsplit-v2/proveAndSend.ts:380-401 (write local record, then await backup) src/joinsplit-v2/settlement.ts:265 (preparation occurs before the try block) src/joinsplit-v2/journalReconciliation.ts:607-663 (unspent attempt stays pending) src/base-app/hooks/useBaseConsolidation.ts:172-175 (interaction cancellation) Trigger: a send or background consolidation writes its local prepared record, then the encrypted backup fails, times out, or is cancelled. The exception escapes before the caller receives the journal ID or reaches simulation/submission. The new overlap check treats the prepared record as an active reservation. A fresh attempt with the same input is rejected. This is a new regression: the branch adds the conflict check and expands cancellation into the backup path, although some surrounding persistence code existed previously. Reconciliation cannot complete an ordinary undisclosed JoinSplit: the input is still unspent, the outputs do not exist, and the new safe-expiry logic is only for funded deposit merges. Ordinary JoinSplits have no corresponding deadline/retry transition. Background consolidation makes the cancellation race easier to encounter because a pointer/key interaction aborts the active attempt. Impact: wallet availability/denial of normal spending for the affected notes, including ordinary EVM transactions when deposit merging is disabled. Funds are not proven lost on chain; another properly recovered/manual path may still spend them. Do not describe this as an on-chain freeze or irreversible theft. Reproduction: auditDepositMergeGaps.test.ts invokes real settlement/journal code and simulates a backup rejection immediately after the encrypted local write. It verifies that simulation and wallet submission were never called, the record remains prepared, the source is reserved, and an immediate new attempt fails with the reservation error. Result: passed, confirming the failure mode. See audit-gap-reproduction.log. Fix: explicitly track whether public proof disclosure has occurred. Safely retire an undisclosed failed attempt while retaining necessary encrypted recovery material, or resume the identical immutable intent idempotently. Provide a completion/resubmission path for exposed attempts; do not simply release exposed proofs on a timer. Add tests for backup rejection, abort, timeout, and restart/retry on an ordinary EVM send and background consolidation. F2 -- MEDIUM / P2 -- EVM DepositMerged nullifiers never enter the canonical recovery index, so incremental sync can treat a consumed input as spendable. Locations: privacy-protocol/contracts/src/ShieldedPoolDepositMerge.sol:112-120 stealthos-relayer/src/backend/engine-api/recovery-ingestion.js:9-18,190-192,480-482,664 privacy-frontend/src/joinsplit-v2/syncIndex.ts:309-310,394-398 privacy-frontend/src/joinsplit-v2/syncIndex.ts:77,203-204 (24-hour full-check fallback) The new contract marks its source nullifier used and emits DepositMerged; it does not also emit JoinSplit. The specialized fee receipt code knows this new event. The general recovery ingestion ABI does not. Its eth_getLogs filter is derived from that incomplete ABI, so it never requests DepositMerged logs. The parser also assumes spend events contain a nullifiers array; the new event carries a single nullifier. The replacement's ordinary LeafInsertedInTree event is still indexed, so leaf-count checks can succeed and the index can report ready while its spent-nullifier stream is incomplete. Incremental client sync uses this stream to set spent:false when a nullifier is absent. An already-synchronized second client, or another delta refresh, can therefore retain/revalidate the consumed source, display excess available balance, and select an input that later fails chain validation. Limits: the chain's nullifier check still prevents double spending. Direct full note validation and local transaction reconciliation can correct local state; the current delta path requires a full validation after 24 hours. These mitigations reduce the severity but do not repair the incomplete canonical server history. Reproduction: audit-event-gap.mjs encodes a genuine DepositMerged event using the branch's fee-receipt ABI. The fee interface recognizes it, the canonical recovery interface returns no event, and its topic is absent from the ingestion filter. See audit-event-gap.log. Fix: add the event to the canonical ABI/filter, ingest its scalar nullifier, and rebuild/backfill already-indexed ranges with an appropriate generation change. Add an end-to-end test from funded merge receipt through recovery delta to spent state, covering baseline/private-fee and native/token paths. Adding receipt support alone is insufficient. REMAINING RELEASE GAPS AND BEHAVIORAL LIMITS 1. Production key authorization is outstanding. The newly included setup fixtures have public deterministic test entropy. They are development artifacts, not a ceremony-backed production setup. EVM production-domain rejection and Solana mainnet build rejection are present; no bypass was established. Preserve those gates and produce authorized production keys/artifact manifests before real-value enablement. The branch explicitly documents this unresolved gate. 2. Real EVM merge-to-withdraw proofs were exercised for the baseline pool, but the seven private-fee contract tests use mock merge/deposit/router/allocation verifiers. They test flow/accounting, not a complete real-proof private-fee merged deposit. Add real native/ERC20 fee-bearing round trips with authorized matching keys. 3. Solana tests here used native program execution, not compiled SBF. They do not establish deployed stack limits, compute budget, or target-cluster behavior. Physical iOS/Android browser/WASM qualification and a DogeOS target deployment/RPC check are also still outstanding, as the branch documentation states. 4. This is a coordinated deployment across pools/programs, keys, browser packages, server pins, and enabled capabilities. Existing deployed contracts do not acquire new selectors from an SDK upgrade. The fee pool is a distinct variant; its support for ordinary transfers/exits/migration does not establish private swap/bridge parity. Capability/rollout mismatches require explicit integration qualification. 5. Privacy tradeoff: the public payer/deposit is in the same transaction as the old nullifier and replacement commitment. The circuit hides the old note's balance and leaf selection, but does not hide this public transaction-level association. This is a property of the feature, not a demonstrated accidental key/witness leak. 6. The observed GitHub Actions jobs at the audited heads did not start because of account billing/spending-limit restrictions. They are not evidence of code-test failures, but they also provide no independent passing CI result at these heads. VALIDATION EXECUTED - Frontend: 187 suites / 1,459 existing tests passed. - Frontend TypeScript check passed. - Frontend deposit-merge package identity check passed. - Frontend optimized production build passed, with warnings (including bundle size). CI=false was used for that local build; it does not certify a warning-free CI build. - Relayer focused deposit-merge suite: 55 tests passed. - Rust deposit-merge circuit/engine suite: 9 tests passed, including real proof and altered-input rejection cases. - EVM: 16 tests passed, including 3 real-proof merge-to-ordinary-withdrawal cases (native, ERC20, and DogeOS-domain native). The remaining tests include 7 private-fee mock-verifier cases and 6 baseline contract cases. - Solana: 2 real-proof SOL/SPL native-runtime integration tests passed, including expected rejection/atomic rollback cases. compiled SBF=false. - Audit-only regression test: 1 passed, reproducing F1. - Audit-only event compatibility reproduction confirmed F2. The logs beside this report record commands' output. No live deployment, transaction, fund movement, source fix, PR comment, or PR merge was performed during this audit. ORDER OF FOLLOW-UP First fix F1's failed-preparation lifecycle and F2's event ingestion/backfill together with regression tests. Then complete the real private-fee proof lane, restore CI, and satisfy production-key and target-runtime/deployment qualification before enabling the new funded merge capability for real-value users.