# DogeOS Chikyu adversarial fund-loss audit — 2026-10-01

**Remediation update:** Both source findings have been addressed in local frontend and relayer worktrees. See [REMEDIATION.md](/audits/groth16/dogeos-remediation/) for changes, regression results and rollout limits. The audit below describes the pre-fix behavior.

## Scope and conclusion

This review covers the new `/dogeos` DogeOS Chikyu integration (chain ID
6281971): the browser's wallet/key derivation and JoinSplit V2 send/recovery
paths, the EVM relayer's fee and submission journals, and the deployed-contract
source and tests. It does **not** cover native Dogecoin UTXO transfers. Chikyu
is configured as a testnet; no mainnet DogeOS deployment is enabled in the
reviewed network record.

I found one **high-severity conditional loss-of-access path** and one
**medium-severity reorganization/reconciliation defect**. I did not reproduce
a contract-level unauthorized withdrawal or an automatic loss of vault funds.
The high-severity path becomes irreversible if a non-deterministic wallet's
first derived spending signature is lost after receiving notes. The reorg
defect leaves funds in the pool in the reproduction, but falsely reports a
completed withdrawal and blocks normal recovery.

## Findings

### D1 — High: non-deterministic wallet signatures can strand received notes

**Location:** `privacy-frontend/src/base-app/hooks/useBaseWallet.ts:110-125`,
`privacy-frontend/src/base-app/helpers/baseDerivation.ts:88-135`,
`privacy-frontend/src/base-app/helpers/storage.ts:57-71,168-211`.

`connectWallet` requests the derivation EIP-712 signature once, hashes the raw
signature bytes into the spending seed, and marks the wallet connected. There
is no determinism check. The DogeOS integration plan explicitly requires a
two-signature D8 gate because embedded/MPC wallets may sign the same EIP-712
challenge non-deterministically (`privacy-protocol/DOGEOS_MYDOGE_JOINSPLIT_PLAN.md:301-304`).
If the wallet produces signature A on initial connect and B after a restart,
both valid for the same account and challenge, the application derives two
different private accounts. A note delivered to A cannot be spent with B.
Encrypted local caches and remote recovery records are also keyed to the
signature-derived account; they do not restore A from B. The raw signature is
kept in a React ref for the session, not as a recoverable wallet secret.

**Reproduction:**
`derivation-signature-reproduction.cjs` constructs two distinct, valid EIP-712
signatures by the same test account for one DogeOS derivation challenge.
`ethers.verifyTypedData` recovers the same account from both; the exact
SHA-256-over-raw-signature operation used by `baseDerivation.ts` yields
different seeds. It passes locally. This establishes the failure mode, **not**
that any particular production MyDoge device currently signs this way. The
MyDoge V3 device qualification matrix is still blank, including row 5 for
signature determinism (`privacy-frontend/docs/dogeos-mydoge-qualification.md`).

**Fix before accepting deposits:** Fail closed when two signatures of the
identical locally constructed challenge differ; preserve a signature
fingerprint across sessions and reject changed derivation before exposing a
receive address. Provide a durable, user-controlled recovery method for the
original seed/signature, because a later wallet implementation change could
otherwise strand already received notes. Qualify actual MyDoge injected,
WalletConnect, and embedded/social wallet paths on devices.

### D2 — Medium: an included withdrawal reorgs out but relayer replays stale success

**Location:** `stealthos-relayer/src/relayer/evm-pool-v2/journal.js:117-139`,
`stealthos-relayer/src/relayer/transaction-handler.js:1285-1323`,
`stealthos-relayer/src/relayer/evm-submission-journal.js:87-148`.

The settlement journal returns a cached `confirmed` row before checking a
receipt. The transaction handler then returns `status: "success"` with its old
hash. On Chikyu, the underlying submission journal permits the next signer
nonce after canonical L2 inclusion, before L1 finality, and explicitly handles
a later missing receipt as a reorg. These two journals disagree about what
`confirmed` means.

**Reproduction:**
`run-relayer-reorg-reproduction.ps1` runs a test against the real handler:
submit a DogeOS withdrawal with a canonical receipt while the finalized head
lags; remove the receipt and reset the signer nonce to simulate a reorg; retry
the same withdrawal. The handler returns the old hash as `confirmed` without
reading the receipt or rebroadcasting. A second withdrawal is then blocked by
the submission journal's unresolved nonce gate. The test passes and the source
checkout remains unchanged.

**Impact:** The original withdrawal is not paid on the simulated canonical
chain, yet the app can report payment complete. Its notes remain in the pool;
the reproduction does not show irreversible loss or a double spend. The user
can nevertheless act on a false payment confirmation, and normal retries do
not clear the condition.

**Fix:** Treat a settlement as final only after the configured finality
criterion and a canonical receipt check. For an included but unfinalized
attempt, return a pending/reconciliation state and retain the durable signed
bytes, nullifier claims, gas reservation, and signer nonce gate. On reorg,
rebroadcast or reconcile the original signed attempt; never allocate a new
nonce merely because a timeout elapsed. A retry with the same nullifiers but
different calldata must locate the outstanding original attempt rather than
opening a competing settlement.

## Verification and limits

- Foundry JoinSplit/DogeOS selection: **157 passed, 0 failed, 10 skipped**
  (`contracts-baseline.log`). The skipped tests require fork conditions or
  external setup; they are not proof of deployed bytecode behavior.
- EVM spend-policy, real handler, submission journal, and pool tests:
  **55 passed** (`submission-baseline.log`). Additional quote, route,
  registry, gateway, fee, and journal tests: **27 passed**
  (`relayer-no-db.log`).
- Selected frontend suites including DogeOS configuration, authentication,
  recovery, settlement, and note sync: **155 suites / 1,158 tests passed**
  (`frontend-baseline.log`).
- The PostgreSQL-backed engine handle tests could not run because this
  workspace has no configured `ENGINE_DATA_TEST_POSTGRES_URL` or local server.
  Their failed setup is recorded in `relayer-baseline.log`; it is an
  environment limitation, not a product failure.
- No live MyDoge device signing, Chikyu RPC finality, deployed bytecode, or
  end-to-end funded transfer was verified in this audit. No transaction was
  broadcast by the reproduction tests.

The contract tests and source review show useful protections, including exact
ERC-20 deposit balance accounting and fail-closed proof/domain checks. Those
checks do not mitigate D1 or D2, which occur in key derivation and the relayer
state machine.
