# Bitcoin Armory Crypto Audit Plan

Date: 2026-06-30

Scope: full cryptography audit of Bitcoin Armory, with special focus on fund safety for HD derivation, SegWit, Taproot, Silent Payments, and offline/online signing flows.

Primary goal: prove that the app never generates, exports, detects, signs for, or accepts a Bitcoin output that it cannot later spend with the intended wallet backup material.

## 1. Audit objectives

This audit is not just a review of cryptographic primitives. It is a money-flow correctness audit across the full wallet lifecycle:

1. Wallet creation and backup material
2. Key derivation and address generation
3. Watch-only export and descriptor import
4. Transaction construction on the online side
5. Offline PSBT signing and change recognition
6. Silent Payment send, scan, receive, and spend
7. Wallet persistence and encryption at rest

The audit must answer these questions with evidence:

1. For every supported receive path, can the offline wallet always re-derive the right key later?
2. For every output type the online wallet can detect, can the offline wallet always spend it?
3. Can any mismatch between derivation path, descriptor origin, scriptPubKey, tweak, or PSBT metadata strand funds?
4. Can any mismatch cause signing with the wrong key, signing the wrong transaction, or misclassifying recipient outputs as change?

## 2. Code surface in scope

Money-critical code paths:

- [core/keys.py](/Users/juthicamallela/bitcoinarmory/core/keys.py:1)
- [core/wallet.py](/Users/juthicamallela/bitcoinarmory/core/wallet.py:1)
- [core/silent_payment.py](/Users/juthicamallela/bitcoinarmory/core/silent_payment.py:1)
- [offline/signer.py](/Users/juthicamallela/bitcoinarmory/offline/signer.py:1)
- [online/setup.py](/Users/juthicamallela/bitcoinarmory/online/setup.py:1)
- [core/tx.py](/Users/juthicamallela/bitcoinarmory/core/tx.py:1)
- [core/script.py](/Users/juthicamallela/bitcoinarmory/core/script.py:1)
- [core/encoding.py](/Users/juthicamallela/bitcoinarmory/core/encoding.py:1)
- [core/crypto.py](/Users/juthicamallela/bitcoinarmory/core/crypto.py:1)
- [core/encryption.py](/Users/juthicamallela/bitcoinarmory/core/encryption.py:1)

Existing relevant tests:

- [tests/test_keys.py](/Users/juthicamallela/bitcoinarmory/tests/test_keys.py:1)
- [tests/test_wallet.py](/Users/juthicamallela/bitcoinarmory/tests/test_wallet.py:1)
- [tests/test_signer.py](/Users/juthicamallela/bitcoinarmory/tests/test_signer.py:1)
- [tests/test_silent_payment.py](/Users/juthicamallela/bitcoinarmory/tests/test_silent_payment.py:1)

Existing security summary:

- [docs/security/security-report.md](/Users/juthicamallela/bitcoinarmory/docs/security/security-report.md:1)

## 3. Supported derivation matrix to audit

These are the current implemented derivation families that must be proven end to end.

### BIP84 native SegWit

- Account path: `m/84'/coin'/account'`
- Receive path: `.../0/index`
- Change path: `.../1/index`
- Address type: P2WPKH
- Relevant code:
  - [core/keys.py](/Users/juthicamallela/bitcoinarmory/core/keys.py:254)
  - [core/wallet.py](/Users/juthicamallela/bitcoinarmory/core/wallet.py:173)
  - [online/setup.py](/Users/juthicamallela/bitcoinarmory/online/setup.py:532)
  - [offline/signer.py](/Users/juthicamallela/bitcoinarmory/offline/signer.py:284)

### BIP86 Taproot

- Account path: `m/86'/coin'/account'`
- Receive path: `.../0/index`
- Change path: `.../1/index`
- Address type: P2TR key-path
- Relevant code:
  - [core/keys.py](/Users/juthicamallela/bitcoinarmory/core/keys.py:260)
  - [core/wallet.py](/Users/juthicamallela/bitcoinarmory/core/wallet.py:173)
  - [online/setup.py](/Users/juthicamallela/bitcoinarmory/online/setup.py:532)
  - [offline/signer.py](/Users/juthicamallela/bitcoinarmory/offline/signer.py:345)

### BIP352 Silent Payments

- Base account path: `m/352'/coin'/account'`
- Spend key path: `.../0'/0`
- Scan key path: `.../1'/0`
- Receive address payload: scan pubkey + spend pubkey
- Received UTXOs become P2TR outputs with spend key plus tweak
- Relevant code:
  - [core/keys.py](/Users/juthicamallela/bitcoinarmory/core/keys.py:266)
  - [core/silent_payment.py](/Users/juthicamallela/bitcoinarmory/core/silent_payment.py:38)
  - [core/wallet.py](/Users/juthicamallela/bitcoinarmory/core/wallet.py:127)
  - [online/setup.py](/Users/juthicamallela/bitcoinarmory/online/setup.py:2072)
  - [offline/signer.py](/Users/juthicamallela/bitcoinarmory/offline/signer.py:89)

## 4. Audit invariants

These invariants are the core of the audit. Every workstream below should produce evidence for them.

### A. Backup and recovery invariants

1. Full-wallet backup material is sufficient to recover all BIP84, BIP86, and BIP352 funds.
2. Watch-only export never includes enough material to spend standard or Silent Payment funds.
3. Watch-only export includes enough material to detect all outputs it is supposed to detect.
4. Wallet save/load never mutates master fingerprint, account xpubs, SP scan key, or SP spend key.

### B. Address derivation invariants

1. Every generated BIP84 address corresponds to a unique private key and expected script.
2. Every generated BIP86 address corresponds to the correctly tweaked Taproot output key.
3. Every Silent Payment address encodes the intended scan and spend public keys.
4. The same seed always reproduces the same addresses, scripts, descriptors, and SP keys.
5. Full and watch-only wallets derive the same public receive addresses for supported public derivation paths.

### C. Detection and recognition invariants

1. Any output the online wallet marks as ours must be recoverable by the offline wallet.
2. Offline signer change detection must never misclassify recipient outputs as change.
3. Script matching and fallback derivation scans must not accept false matches.
4. Silent Payment scan must never create a false positive spendable UTXO record.

### D. Signing invariants

1. A PSBT input is signed only if the intended wallet controls the exact corresponding key.
2. BIP32 derivation hints and Taproot derivation hints must agree with the derived public key.
3. BIP86 Taproot spending must apply the correct private-key tweak for key-path signing.
4. Silent Payment spends must derive the correct tweaked private key and must not apply the BIP86 tweak path by mistake.
5. Wrong-wallet PSBTs must fail closed without falling back into unsafe or ambiguous behavior.

### E. Descriptor and online/offline split invariants

1. Imported descriptors exactly match offline account derivation for BIP84 and BIP86.
2. Descriptor origin metadata uses the correct fingerprint and origin path.
3. Any address or change output constructed online can be re-identified offline through PSBT metadata or deterministic derivation.
4. No online-only state is required to spend coins later.

## 5. Highest-risk audit themes

The following themes have the highest probability of causing irreversible loss if wrong.

### 5.1 Derivation-path mismatches

Audit for any path mismatch between:

- account xpub export
- descriptor origin paths
- address generation
- PSBT derivation metadata
- offline signer key lookup
- recovery after save/load

This is the highest-value work because a path mismatch can leave funds visible but unspendable.

### 5.2 Taproot tweak correctness

Audit all places where Taproot behavior differs:

- standard BIP86 receive path
- standard BIP86 spend path
- Silent Payment received UTXO spend path
- Silent Payment send path when sender input is itself Taproot

The signer already distinguishes BIP86 from SP-derived Taproot outputs. That logic must be proven correct with vectors and negative tests.

### 5.3 Silent Payment end-to-end compatibility

Audit the implementation against BIP-352, not just against itself. Internal send and scan code agreeing with each other is not sufficient evidence.

Required checks:

1. official test vectors
2. x-only key handling
3. even-Y normalization
4. input aggregation
5. input hash construction
6. shared-secret and tweak derivation
7. received UTXO detection
8. spending-key derivation
9. multi-input and multi-recipient behavior

### 5.4 Change recognition and offline review correctness

The offline signer and review flow must never:

1. label an external output as change
2. fail to recognize actual change because the derivation window lags
3. silently derive past expected ranges in a way that masks metadata bugs

This area is especially important because the code currently supports:

- stored address pool lookups
- extended script search
- brute-force derivation fallback
- PSBT derivation hints

Those are useful recovery paths, but each fallback can also hide a logic bug if not constrained and tested.

## 6. Workstreams

### Workstream 1: wallet creation, backup, and persistence

Targets:

- [core/wallet.py](/Users/juthicamallela/bitcoinarmory/core/wallet.py:87)
- [core/encryption.py](/Users/juthicamallela/bitcoinarmory/core/encryption.py:1)

Questions:

1. Does a full-wallet save contain exactly the required private material and no more?
2. Does a watch-only save contain exactly the required public and scan-only material?
3. Are SP keys re-derived correctly for older wallets loaded from disk?

Evidence to collect:

1. field inventory for full-wallet JSON and watch-only JSON
2. restore tests from mnemonic and from encrypted file
3. negative tests showing watch-only cannot sign

### Workstream 2: deterministic derivation and script construction

Targets:

- [core/keys.py](/Users/juthicamallela/bitcoinarmory/core/keys.py:1)
- [core/wallet.py](/Users/juthicamallela/bitcoinarmory/core/wallet.py:173)
- [core/encoding.py](/Users/juthicamallela/bitcoinarmory/core/encoding.py:1)
- [core/script.py](/Users/juthicamallela/bitcoinarmory/core/script.py:1)

Questions:

1. Are BIP84 and BIP86 path helpers correct?
2. Is Taproot output key construction consistent between address encoding and script creation?
3. Does `find_by_script_extended` only recover scripts that are truly ours?

Evidence to collect:

1. vector-based derivation tests
2. address-to-script round-trip tests
3. cross-checks between full and watch-only derivation

### Workstream 3: descriptor correctness and watch-only import

Targets:

- [online/setup.py](/Users/juthicamallela/bitcoinarmory/online/setup.py:532)
- [core/wallet.py](/Users/juthicamallela/bitcoinarmory/core/wallet.py:460)

Questions:

1. Do descriptor strings exactly encode the same BIP84 and BIP86 trees the offline wallet uses?
2. Is fingerprint/origin metadata correct when built from stored xpubs versus passed master fingerprint?
3. Can any descriptor/import mismatch make the online side hand out an address the offline side cannot spend?

Evidence to collect:

1. descriptor snapshots for known seeds
2. import/export round-trip tests
3. Bitcoin Core cross-checks where feasible

### Workstream 4: PSBT construction and offline signing

Targets:

- [online/setup.py](/Users/juthicamallela/bitcoinarmory/online/setup.py:2072)
- [online/setup.py](/Users/juthicamallela/bitcoinarmory/online/setup.py:4781)
- [online/setup.py](/Users/juthicamallela/bitcoinarmory/online/setup.py:4815)
- [offline/signer.py](/Users/juthicamallela/bitcoinarmory/offline/signer.py:31)

Questions:

1. Are derivation hints attached to every relevant input and change output?
2. Can the signer derive the wrong key from malformed metadata?
3. Are wrong-wallet failures deterministic and early?
4. Is fallback scanning safe and bounded?

Evidence to collect:

1. per-input PSBT metadata inspection tests
2. wrong-fingerprint and wrong-pubkey negative tests
3. mixed-input tests: BIP84, BIP86, SP
4. change-output recognition tests across derivation-window gaps

### Workstream 5: Silent Payments

Targets:

- [core/silent_payment.py](/Users/juthicamallela/bitcoinarmory/core/silent_payment.py:1)
- [offline/signer.py](/Users/juthicamallela/bitcoinarmory/offline/signer.py:89)
- [online/setup.py](/Users/juthicamallela/bitcoinarmory/online/setup.py:2072)

Reference:

- BIP-352: https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki

Questions:

1. Are scan and spend keys derived at the correct paths?
2. Does SP address encoding and decoding match BIP-352?
3. Does sender output construction match the BIP for single and multiple inputs?
4. Does scan logic match the BIP for single and multiple outputs?
5. Does spend-key derivation produce the exact key that spends the detected UTXO?
6. Does the online/offline split for SP preserve privacy without leaking spend authority?

Evidence to collect:

1. official BIP-352 vector tests
2. independent derivation checks against external implementations or known vectors
3. full send-detect-spend round trips
4. negative tests for malformed addresses, wrong tweaks, wrong scan keys, and wrong spend keys

### Workstream 6: cryptographic backend and encryption at rest

Targets:

- [core/crypto.py](/Users/juthicamallela/bitcoinarmory/core/crypto.py:1)
- [core/encryption.py](/Users/juthicamallela/bitcoinarmory/core/encryption.py:1)

Questions:

1. Are runtime secp256k1 backends consistent in behavior?
2. Should pure-Python fallback be allowed for money-critical operations?
3. Are wallet encryption parameters sufficient for the current threat model?

Evidence to collect:

1. backend behavior matrix
2. sign/verify consistency checks
3. KDF parameter review and unlock UX tradeoff notes

## 7. Test plan for the audit

The audit should produce new tests, not just prose.

### Priority 0: must-have before trusting real funds

1. Official BIP-352 vector coverage in [tests/test_silent_payment.py](/Users/juthicamallela/bitcoinarmory/tests/test_silent_payment.py:1)
2. Seed-to-address-to-spend round-trip tests for BIP84 and BIP86
3. PSBT wrong-wallet and wrong-derivation negative tests
4. Change recognition tests for both BIP84 and BIP86 paths
5. Silent Payment received-UTXO spend tests from persisted wallet state

### Priority 1: cross-implementation and regression coverage

1. Descriptor snapshots for fixed seeds
2. Taproot tweak cross-checks against external known-good implementations
3. Wallet save/load invariants for encrypted and unencrypted files
4. Mixed-input transaction signing tests

### Priority 2: hardening and abuse cases

1. malformed descriptor metadata
2. malformed Taproot derivation metadata
3. malformed SP address payloads
4. duplicate or conflicting derivation hints
5. scan-window edge cases and large index recovery

## 8. Recommended audit sequence

This order is optimized for fund-loss risk first.

### Phase 1: derivation source of truth

1. document every supported derivation path
2. verify wallet export/import material
3. verify descriptor construction against paths

Output:

- derivation matrix
- backup/recovery matrix
- list of ambiguities or path mismatches

### Phase 2: address and script correctness

1. BIP84 address/script proofs
2. BIP86 address/script/tweak proofs
3. script recognition and change detection proofs

Output:

- address/script invariant checklist
- missing tests list

### Phase 3: PSBT and signing correctness

1. input metadata audit
2. change metadata audit
3. signer fallback audit
4. wrong-wallet negative tests

Output:

- signing invariant checklist
- signer findings log

### Phase 4: Silent Payments

1. BIP-352 path and key derivation review
2. official vector integration
3. send/scan/spend end-to-end review
4. online/offline SP threat-model review

Output:

- SP compatibility findings
- SP theft/loss risk findings
- required fixes before relying on SP

### Phase 5: crypto backend and encryption review

1. secp256k1 backend audit
2. wallet encryption review
3. operational hardening notes

Output:

- hardening findings
- non-blocking improvement list

## 9. Findings template

Every finding should be recorded with:

1. title
2. severity
3. fund-loss risk, theft risk, privacy risk, or hardening
4. affected files and functions
5. exact invariant violated
6. exploit or failure mode
7. reproduction or test evidence
8. recommended fix
9. whether fix requires migration or wallet-compatibility handling

Suggested severity levels:

- Critical: can directly strand or steal funds
- High: likely to enable fund loss under realistic use
- Medium: dangerous inconsistency or weak control that needs fixing before broader release
- Low: hardening, maintainability, or defense-in-depth

## 10. Immediate next steps

The first execution pass of this audit should do the following:

1. Build a derivation and export matrix from current code.
2. Add official BIP-352 vector coverage.
3. Trace every path by which the offline signer identifies an input or change output.
4. Enumerate all cases where fallback scanning can hide a metadata bug.
5. Produce a first findings document with fund-loss issues separated from general hardening.

## 11. Initial hypotheses to validate

These are not yet findings. They are areas most likely to produce them.

1. Silent Payments need external vector confirmation before they can be trusted with funds.
2. Descriptor origin metadata and offline derivation logic need exact cross-checking to rule out path drift.
3. The combination of stored addresses, extended script search, and brute-force fallback in the signer may be masking edge-case derivation mismatches rather than proving correctness.
4. Taproot handling needs careful separation between BIP86 tweaks and SP-derived tweaks in all spend paths.
5. Change handling should be audited as aggressively as recipient handling, because a change-path bug strands funds just as effectively as a bad receive address.

