Skip to content

Chapter 38: Wallets, addresses, and key rotation

A hierarchical deterministic (HD) wallet is a derivation tree from one seed to many signing keys. BIP-32 (Wuille, 2012) walks the tree using a parent key, a chain code, and a 32-bit index. Its HMAC-SHA-512 output is split into a scalar tweak and a child chain code. Private child derivation adds the tweak to the parent secp256k1 scalar; non-hardened public derivation adds the corresponding point tweak to the parent public key. Post-quantum primitives change the shape of the tree on two axes. The first axis is per-key state: stateful hash-based schemes (XMSS-MT (Hülsing et al., 2018) and LMS (McGrew et al., 2019)) require that the signer never reuse a one-time-key index. A multi-device wallet that does not synchronize the index across phone, desktop, and hardware risks reusing an index across devices. A reused one-time key signing two different messages can collapse the scheme’s security and admit forgery.

The second axis is the public-key derivation map. Under ECDSA over secp256k1, a child public key derives from the parent public key via a scalar offset on the curve, which is the BIP-32 watch-only-wallet property. Lattice and hash-based primitives have no analogous public-key-only derivation map, because their secret keys do not live in a scalar-additive group compatible with the parent public key. BIP-32 in its literal form is therefore secp256k1-specific.

A PQ wallet can keep BIP-32 path syntax and the HMAC-SHA-512 chain-code schedule, and this chapter’s designs do, but every step that produces a PQ child key runs the PQ scheme’s deterministic key-expansion layer from a fresh derived seed. No Bitcoin Improvement Proposal (BIP), SatoshiLabs Improvement Proposal (SLIP), or IETF RFC defines a post-quantum analogue of BIP-32 at chain-tip 2026. These designs do not inherit BIP-32’s Ki=Kpar+tGK_i = K_\text{par} + tG child-public-key rule, and the watch-only property does not survive without a redesign.

Ch 37 established four candidates (classical-secp256k1 baseline, ML-DSA-65, SLH-DSA-128s, Ed25519+ML-DSA-65 composite) for the per-spend transaction surface. Ch 38 adds two stateful hash-based candidates (XMSS-MT and LMS) that re-enter consideration for hardware-wallet-only single-device custody. The Ch 37 reasoning that disqualified stateful schemes from the transaction surface (no reliable multi-device synchronization) returns here as a per-custody-shape filter rather than a global dismissal.

A Strand wallet migrating from ECDSA over BIP-32 to ML-DSA-65

Section titled “A Strand wallet migrating from ECDSA over BIP-32 to ML-DSA-65”

The Strand wallet surface from the Ch 36 fixture deploys BIP-32 HD derivation over ECDSA-secp256k1, with X=10X = 10 years (typical seed lifetime) and Y=4Y = 4 years (custody operator’s address-book full migration). Under Z=9Z = 9 (NCSC 2035 working assumption) the wallet surface breaches the Mosca window by 5 years per Ch 36 Exercise 4. The operator running Strand picks a post-quantum primitive per custody shape and redesigns the derivation tree against the BIP-32 invariants the new primitive supports.

The legacy derivation chain follows BIP-44 (Palatinus & Rusnak, 2014): a master seed expands to a master extended key via HMAC-SHA-512 with the constant "Bitcoin seed". Child keys derive at depth 1 (purpose), 2 (coin type), 3 (account), 4 (change), and 5 (address index). The first three levels are hardened branches; the last two are non-hardened, which lets a watch-only wallet derive child public keys from a parent public key alone. Each address derives to a 33-byte compressed secp256k1 public key. The address itself is a 20-byte hash for legacy P2PKH or P2WPKH outputs, or a 32-byte tweaked public key for P2TR.

Under ML-DSA-65 (National Institute of Standards and Technology, 2024) the chain shape changes. The wallet reuses BIP-32’s HMAC-SHA-512 chain-code schedule as a BIP-32-like seed schedule: each step produces 32 bytes of child seed and 32 bytes of child chain code. This is not literal BIP-32 child-key derivation, which is defined in terms of a secp256k1 scalar offset. Under ML-DSA the BIP-32 path syntax and chain-code propagation survive, but the child key is produced by feeding the derived 32-byte seed into the scheme’s deterministic key-expansion layer, analogous to ML-DSA.KeyGen_internal(ξ\xi) in FIPS 204. The external ML-DSA.KeyGen() API takes no caller seed: it draws its own 32-byte seed from an approved RBG and calls KeyGen_internal. A FIPS-validation profile of this BIP-32-like PQ-HD construction would need to specify how the deterministic seed schedule interfaces with the approved RBG and key-generation boundary.

The hardened branches survive because hardened derivation operates on bytes alone: the wallet runs the deterministic seed expansion from each child seed to produce an ML-DSA-65 keypair (1952-byte public key, 3309-byte signature per FIPS 204 Table 2). The non-hardened branches do NOT survive because ML-DSA has no public-key-only derivation map. A watch-only wallet over ML-DSA-65 either degrades to a hardened-only tree (the wallet keeps the parent secret to derive any branch), or accepts that watch-only addresses come from a different parent than spending addresses (a redesign of the derivation hierarchy).

The on-chain address-binding piece comes from Ch 37. On Bitcoin the wallet ships into a two-layer path. BIP-360 P2MR (Beast et al., 2024) is the long-exposure key-path mitigation: a Taproot-adjacent script-only output that drops the always-exposed key path and commits to a script-tree Merkle root. The chapter treats it as the most concrete public proposal and uses it as the running long-exposure-mitigation example.

The chapter then models a hypothetical future PQ-signature soft fork layered on top of this P2MR-style commitment pattern. That future layer is not part of BIP-360. The ML-DSA / SLH-DSA output type and the (public_key, signature) P2WPKH-style commit-then-reveal witness it would define are this chapter’s model, not any current proposal. BIP-360 P2MR by itself does NOT add a post-quantum signature scheme; it only changes how Bitcoin outputs commit to spending conditions.

On Ethereum the wallet ships into a smart-contract wallet’s deployment bytecode under ERC-4337 (Buterin et al., 2021), with the ML-DSA verifier living in account validateUserOp logic rather than in native EOA signature checks. The lifecycle (sign locally, broadcast, wait for inclusion) is the same as the Ch 37 transaction-surface migration. The new ingredient in this chapter is the wallet-side derivation chain end to end: master seed, BIP-32-like derivation path, child key, public-key hash (Bitcoin) or wallet-contract address (Ethereum), and finally the spend.

BIP-32 derivation, hardened versus non-hardened, watch-only

Section titled “BIP-32 derivation, hardened versus non-hardened, watch-only”

BIP-32 (Wuille, 2012) specifies a child key derivation function that takes three inputs: the parent key (secret in hardened mode, public in non-hardened mode), a 32-byte chain code, and a 32-bit index. The function applies HMAC-SHA-512 with the chain code as the key and the (parent key, index) pair as the data. The 64-byte HMAC output splits into a 32-byte child key offset and a 32-byte child chain code. The hardened mode prepends a 0x00 byte before the parent secret; the non-hardened mode uses the serialized parent public key. The hardened bit is the high bit of the index: indices below 2312^{31} are non-hardened; indices at or above 2312^{31} are hardened. The convention marks hardened branches with a trailing apostrophe: m/44'/0'/0'/0/0.

The hardened mode is feasible for any signature primitive because the derivation operates on bytes. The wallet runs the scheme’s deterministic seed expansion from the child seed to produce a keypair under whatever primitive the operator picks. ML-DSA-65, SLH-DSA-128s, the Ed25519+ML-DSA-65 composite, XMSS-MT, and LMS all admit hardened derivation under a BIP-32-like hardened seed schedule. The HMAC-SHA-512 chain-code propagation and the path syntax (m/44'/0'/0'/0'/0') carry over, but the child key is produced by the PQ scheme’s own deterministic key-expansion layer run from the derived seed, not by BIP-32’s secp256k1 scalar-addition rule. The PQ wallet therefore inherits BIP-32 vocabulary and path interoperability without inheriting BIP-32’s curve-specific public-key derivation.

A production PQ-HD profile should also domain-separate the derived seed by primitive, network, account role, and key purpose (for example a profile-versioned label like "Strand-PQHD/v1/ML-DSA-65/signing") rather than treating the BIP-32 byte stream as globally reusable entropy. Same path + same seed across ML-DSA, SLH-DSA, XMSS, and LMS without domain separation would couple unrelated key materials and undercut the scheme-independence the PQ-HD construction otherwise provides.

The non-hardened mode requires that the primitive supports a public-key-only derivation map. Under ECDSA over secp256k1, the parent public key is a curve point PP; the child public key is P+tGP + tG where tt is the 32-byte HMAC offset and GG is the curve generator. A watcher with the parent public key and the chain code (no parent secret) computes the child public key without ever holding a secret key. The two together form an extended public key (xpub: serialized parent public key concatenated with the chain code). This is the BIP-32 watch-only-wallet property: the custody operator publishes the xpub to a watcher. The watcher derives every child address and monitors balances; the watcher cannot spend.

ML-DSA-65 does not admit a public-key-only derivation. Per FIPS 204, the ML-DSA public key encodes ρ\rho (a 32-byte seed from which AA is expanded) and t1\mathbf{t}_1 (the high-order half of t=As1+s2\mathbf{t} = A\mathbf{s}_1 + \mathbf{s}_2). The private key encodes ρ\rho, KK, trtr, s1\mathbf{s}_1, s2\mathbf{s}_2, and t0\mathbf{t}_0. There is no scalar-offset analogue that produces a valid child (ρc,t1,c)(\rho_c, \mathbf{t}_{1,c}) from the parent public key alone. Producing a valid child requires running ML-DSA.KeyGen on fresh seed material to draw new s1,s2\mathbf{s}_1, \mathbf{s}_2 and re-derive everything.

Figure 38.1 below compares the BIP-32 derivation tree under legacy ECDSA-secp256k1 against the redesigned tree under ML-DSA-65. The figure marks which derivation edges survive on each side and which require a redesign.

BIP-32 derivation under ECDSA versus ML-DSA-65 Two columns side by side. Left column titled ECDSA-secp256k1 shows a six-level derivation tree, spanning five derivation steps, from master at the top to address-index at the bottom. Each node has a green "supported" badge. Right column titled ML-DSA-65 shows the same six levels. The top four nodes (master, purpose-44 hardened, coin-type-0 hardened, account-0 hardened) carry green "supported" badges; the bottom two non-hardened nodes (change-0 and address-index-0) carry red "NOT supported" badges with a dashed outline indicating the watch-only property does not survive at those levels. ECDSA-secp256k1 ML-DSA-65 master purpose 44' coin_type 0' account 0' change 0 address_index 0 [supported] [supported] [supported] [supported] [supported] [supported] master purpose 44' coin_type 0' account 0' change 0 address_index 0 [supported] [supported] [supported] [supported] [NOT supported] [NOT supported] derivation edge survives non-hardened branch breaks under candidate
Figure 38.1. BIP-32 derivation survival under ECDSA versus ML-DSA-65. Left: every BIP-44 step survives under ECDSA because the curve admits a public-key-only derivation. Right: the master expansion and the three hardened nodes (purpose, coin_type, account) survive under ML-DSA-65; the two non-hardened steps (change, address_index) do not, marking the watch-only property as a casualty of the migration.

The other four post-quantum candidates fail the same test, each for its own structural reason. Only the legacy baseline passes it.

CandidatePublic-key-only mapStructural reason
ECDSA-secp256k1yesthe child point is P+tGP + tG
ML-DSA-65nono scalar-offset analogue for (ρ,t1)(\rho, \mathbf{t}_1)
SLH-DSA-128snofresh seed material feeds the hypertree
Ed25519+ML-DSA-65nothe ML-DSA half forces hardened-only
XMSS-MTnoper-layer fresh subtree seeds
LMSnoper-layer fresh subtree seeds

The SLH-DSA-128s private key is SK.seed, SK.prf, PK.seed, PK.root, and the public key is PK.seed, PK.root. Each signing key is generated from fresh seed material feeding the FORS plus WOTS+ hypertree construction (Ch 17) (National Institute of Standards and Technology, 2024b). The composite is hardened-only at the composite level because both halves must derive consistently. The stateful hash-based schemes (XMSS-MT (Hülsing et al., 2018), LMS (McGrew et al., 2019)) derive subtree keys from per-layer fresh seeds rather than from a shared public-key derivation map (Cooper et al., 2020).

The watch-only property is therefore a casualty of the migration unless the wallet operator runs a redesigned hierarchy. One redesign is a bounded watch-only architecture: the wallet pre-derives a finite batch of addresses for the next monitoring interval and publishes the batch (or a Merkle commitment to it) as a signed view descriptor. The watcher monitors that bounded horizon. New addresses past the horizon require a fresh publication from the wallet operator.

The term “xpub” is misleading under this redesign. In BIP-32 an xpub authorizes unbounded non-hardened child-public-key derivation; under hardened-only PQ derivation, publishing a public key plus chain code gives the watcher no derivation capability past what the wallet operator has explicitly pre-derived and published. The redesign drops the open-ended watch-only property in exchange for a periodic-publication property: a watcher who refreshes the view descriptor on a stated cadence and accepts that derivation capability now lives on the wallet operator side.

The Strand operator picks one primitive per custody shape based on state-management tolerance and signature-size economics. Each shape constrains those two axes differently, and each has a short key the fit matrix uses as its column heading.

Custody shapeMatrix keyState constraint
Single-device hothot1needs tamper-resistant storage and an atomic counter
Multi-device hothot+real-time state sync across devices is unreliable
Hardware-only coldcold-hwsigns rarely; one secure element suffices
Multisig coldmsigMM of NN devices; each cosigner keeps its own key and its own index

The single-device bar survives every supported reset, factory-restore, and backup-replay route only on a hardware-wallet-equivalent device, not on a typical consumer-phone software hot wallet. Multi-device hot wallets share the seed across phone, desktop, and a browser plug-in. Coordinating a monotonically advancing one-time-key index across independent devices introduces race conditions. The consequence under a stateful scheme is index reuse and a forgery-enabling collapse on the next signature at the reused index.

The first inline block walks the 6 by 4 fit matrix. The function below is a pedagogical slice of wallet_rotation.custody_fit over the six primitives and the four custody shapes.

# Block 1: pedagogical slice of wallet_rotation.custody_fit (stdlib only).
SHAPES = ("hot1", "hot+", "cold-hw", "msig")
PRIMS = {
"ECDSA-secp256k1": {"legacy": True, "state": (1, 1, 1, 1), "marginal": (0, 0, 0, 0)},
"ML-DSA-65": {"legacy": False, "state": (1, 1, 1, 1), "marginal": (0, 0, 0, 0)},
"SLH-DSA-128s": {"legacy": False, "state": (1, 1, 1, 1), "marginal": (1, 1, 0, 0)},
"Ed25519+ML-DSA-65": {"legacy": False, "state": (1, 1, 1, 1), "marginal": (0, 0, 0, 0)},
"XMSS-MT": {"legacy": False, "state": (1, 0, 1, 1), "marginal": (1, 0, 0, 0)},
"LMS": {"legacy": False, "state": (1, 0, 1, 1), "marginal": (1, 0, 0, 0)},
}
def fit(primitive, idx):
p = PRIMS[primitive]
if p["legacy"]:
return "legacy"
if not p["state"][idx]:
return "unfit"
return "marginal" if p["marginal"][idx] else "fit"
print(f"{'primitive':<19} {'hot1':<10} {'hot+':<10} {'cold-hw':<10} msig")
for primitive in PRIMS:
a = fit(primitive, 0)
b = fit(primitive, 1)
c = fit(primitive, 2)
d = fit(primitive, 3)
print(f"{primitive:<19} {a:<10} {b:<10} {c:<10} {d}")
# ==> primitive hot1 hot+ cold-hw msig
# ==> ECDSA-secp256k1 legacy legacy legacy legacy
# ==> ML-DSA-65 fit fit fit fit
# ==> SLH-DSA-128s marginal marginal fit fit
# ==> Ed25519+ML-DSA-65 fit fit fit fit
# ==> XMSS-MT marginal unfit fit fit
# ==> LMS marginal unfit fit fit

Every Python block this chapter prints is also a standalone file in the companion repository, under chapter-code/ch38/, one file per block. Appendix C covers the clone and the environment they run on.

ECDSA-secp256k1 is legacy everywhere: it is the pre-migration baseline, not a migration target. ML-DSA-65 passes every shape. The 3,309-byte signature and fast verification on commodity hardware clear both hot and cold path constraints (specific cycle counts are benchmark-dependent and not standardized in FIPS 204). SLH-DSA-128s is marginal on the per-spend hot paths because the 7,856-byte signature and millisecond-scale signing on constrained hardware press against the byte budget; it is fit on cold paths where the operator signs rarely. The composite is fit everywhere at a 64-byte and one-Ed25519-verify overhead.

XMSS-MT and LMS are fit on the cold shapes, where strict monotonic counter state is feasible inside a single secure element. They are marginal on single-device hot wallets. NIST SP 800-208 §8.1 requires signing inside a validated hardware module that cannot export private keying material and that writes the incremented leaf index to nonvolatile storage before releasing the signature (Cooper et al., 2020). A single-device hot wallet is a conforming signer only where its signing module meets those requirements. They are unfit on multi-device hot (hot+) because that shape replicates one seed across devices, and a monotonically advancing one-time-key index cannot be coordinated across independent copies of the same key without race-condition exposure. Multisig cold (msig) is the opposite arrangement: each cosigner holds an independent key with its own index, so each device is the cold-hw single-signer state machine and no index is shared. SP 800-208 permits exactly this multi-key design for spreading signing across modules (Cooper et al., 2020). The residual risks are per-cosigner (cloning, backup replay, or stale restoration of any one cosigner key), and the operational bill is the cold-hw state discipline multiplied by NN.

Figure 38.2 below is the custody-shape by primitive fit matrix. The cells encode the fit decision (legacy, fit, marginal, unfit) with a redundant non-color encoding (label text plus a corner marker per category) so the figure remains readable on a monochrome render.

Custody shape by primitive fit matrix A six by four matrix. Rows: ECDSA-secp256k1, ML-DSA-65, SLH-DSA-128s, Ed25519+ML-DSA-65, XMSS-MT, LMS. Columns: hot1 (single-device hot), hot+ (multi-device hot), cold-hw (hardware-only cold), msig (multisig cold). Each cell carries one of four labels (legacy, fit, marginal, unfit) with a corner marker, in the legend's left-to-right order: circle for legacy, square for fit, triangle for marginal, X for unfit. The ECDSA row is uniformly legacy. The ML-DSA-65 and Ed25519+ML-DSA-65 rows are uniformly fit. The SLH-DSA-128s row is marginal on hot1 and hot+, fit on cold-hw and msig. The XMSS-MT and LMS rows are marginal on hot1, unfit on hot+, and fit on cold-hw and msig. Custody shape by primitive fit hot1 hot+ cold-hw msig ECDSA-secp256k1 legacy legacy legacy legacy ML-DSA-65 fit fit fit fit SLH-DSA-128s marginal marginal fit fit Ed25519+ML-DSA-65 fit fit fit fit XMSS-MT marginal unfit fit fit LMS marginal unfit fit fit legacy fit marginal unfit
Figure 38.2. Custody shape by primitive fit matrix. Columns: hot1 (single-device hot), hot+ (multi-device hot), cold-hw (hardware-only cold), msig (multisig cold). ML-DSA-65 and Ed25519+ML-DSA-65 are fit for all four shapes. SLH-DSA-128s is marginal on the two hot paths and fit on cold. XMSS-MT and LMS are fit on hardware-only cold storage and on independent-key multisig cold, where each cosigner device keeps its own key and index, marginal on single-device hot, where conforming signing needs a hardware module meeting SP 800-208 §8.1's key-confinement and durable-state requirements, and unfit on any shape that shares one seed across devices.

The conservative-assumption preference moves SLH-DSA-128s to the top of any cold-storage shape. The hash-only security argument depends on the pseudorandomness of the underlying PRF and on preimage resistance and related properties of the underlying hash function, SHA-2 or SHAKE, with the hypothesis set named in Chapter 18. It does not depend on a structured-lattice assumption. The hardware-fit preference moves XMSS-MT or LMS to the top of any single-device shape on constrained hardware where signing time and signature size matter. NIST SP 800-208 §§4 and 5 fix the approved LMS and XMSS-MT profiles (Cooper et al., 2020), and several of them produce signatures well under SLH-DSA-128s’s 7,856 bytes (National Institute of Standards and Technology, 2024b). By the length formulas in RFC 8391 §4.2.3 and RFC 8554 §5.4, XMSSMT-SHA2_20/2_256 produces a 4,963-byte signature and LMS_SHA256_M32_H10 over LMOTS_SHA256_N32_W8 a 1,452-byte one (Hülsing et al., 2018; McGrew et al., 2019). Signing time is a separate question: neither standard publishes a benchmark, and the cost depends on the parameter set, the implementation, and the platform. The cost is that the index counter must live in non-volatile memory and advance atomically before each signature. Restoring the same key with lost or rewound state is a key-reuse event.

The derivation tree under the chosen candidate keeps the BIP-32 hardened branches and reworks the non-hardened branches per the survival map. The second inline block walks the BIP-44-style path m/44'/0'/0'/0/0 under both ECDSA and ML-DSA-65 and prints which steps survive under each.

# Block 2: pedagogical slice of wallet_rotation.derivation_tree (stdlib only).
import hmac
from hashlib import sha512
# ECDSA-secp256k1 admits a public-key-only derivation (scalar offset on the
# curve), so non-hardened branches survive. Lattice and hash primitives have
# no such map; only hardened branches survive.
NON_HARDENED_OK = {
"ECDSA-secp256k1": True,
"ML-DSA-65": False,
"SLH-DSA-128s": False,
"Ed25519+ML-DSA-65": False,
"XMSS-MT": False,
"LMS": False,
}
def derive_step(parent_secret, chain_code, index, hardened):
if hardened:
idx_bytes = (index | 0x80000000).to_bytes(4, "big")
data = b"\x00" + parent_secret + idx_bytes
else:
# Pedagogical placeholder: real BIP-32 non-hardened mode hashes
# the serialized parent public key plus the index. This walker
# substitutes parent_secret so the chain-code propagation continues
# past non-hardened steps; the survival flag is the actual signal.
data = parent_secret + index.to_bytes(4, "big")
out = hmac.new(chain_code, data, sha512).digest()
return out[:32], out[32:]
def walk(primitive, master_seed, path):
master = hmac.new(b"Bitcoin seed", master_seed, sha512).digest()
secret, chain_code = master[:32], master[32:]
rows = [f" {'master':<8} supported"]
for raw in [p for p in path.split("/")[1:] if p]:
hardened = raw.endswith("'")
idx = int(raw[:-1] if hardened else raw)
secret, chain_code = derive_step(secret, chain_code, idx, hardened)
label = f"m/{idx}{'h' if hardened else ' '}"
flag = "supported" if (hardened or NON_HARDENED_OK[primitive]) else "NOT supported"
rows.append(f" {label:<8} {flag}")
return rows
SEED = bytes.fromhex(
"000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
)
PATH = "m/44'/0'/0'/0/0"
for primitive in ("ECDSA-secp256k1", "ML-DSA-65"):
print(f"-- {primitive} on {PATH} --")
for row in walk(primitive, SEED, PATH):
print(row)
# ==> -- ECDSA-secp256k1 on m/44'/0'/0'/0/0 --
# ==> master supported
# ==> m/44h supported
# ==> m/0h supported
# ==> m/0h supported
# ==> m/0 supported
# ==> m/0 supported
# ==> -- ML-DSA-65 on m/44'/0'/0'/0/0 --
# ==> master supported
# ==> m/44h supported
# ==> m/0h supported
# ==> m/0h supported
# ==> m/0 NOT supported
# ==> m/0 NOT supported

Under ECDSA every step survives because non-hardened derivation works on the curve. Under ML-DSA-65 the three hardened steps survive but the two trailing non-hardened steps (change and address_index in the BIP-44 convention) do not. A wallet under ML-DSA-65 has two redesign options. Option A: replace the change/address_index non-hardened tail with hardened branches at every level. The wallet keeps the parent secret to derive child addresses on demand, accepting the loss of the watch-only property. Option B: run the bounded watch-only architecture from the “BIP-32 derivation, hardened versus non-hardened, watch-only” section above. Each child account derives on a hardened branch. The wallet operator publishes a signed view descriptor covering the next monitoring interval. The watcher refreshes on a stated cadence and monitors a bounded set of pre-derived addresses between refreshes.

Both redesigns ship through the Ch 37 two-layer Bitcoin path (BIP-360 P2MR as the long-exposure key-path mitigation, plus a future PQ-signature soft fork that defines the ML-DSA / SLH-DSA output type at spend). On Ethereum they ship through CREATE2-style smart-account addresses under ERC-4337 with the ML-DSA verifier in account validateUserOp logic. The address-derivation chain end to end is: master seed, BIP-32-like hardened derivation path under the candidate, child key, public-key hash (Bitcoin) or wallet-contract address (Ethereum), spend.

The rotation cadence is the operator’s response to the Mosca window for the wallet surface. The wallet surface from Ch 36 has X=10X = 10 and Y=4Y = 4. Three Z scenarios bound the operator’s planning. The aggressive scenario is Z=4Z = 4, which is Z=YZ = Y exactly: the CRQC arrives at the moment the migration window would have to start, leaving no safe interval for a fixed-interval rotation. The NCSC-2035-aligned scenario is Z=9Z = 9, anchored to the NCSC migration-completion milestone of 2035 (UK National Cyber Security Centre, 2025). That is a migration deadline the operator can plan against, not an NCSC prediction that a cryptographically relevant quantum computer arrives in 2035. The mid-2040 scenario is Z=14Z = 14, which is Z=X+YZ = X + Y exactly: the CRQC arrives at the end of the wallet’s seed lifetime plus migration, so the breach window closes at zero rather than going negative. The third inline block runs the recommendation function over all three.

# Block 3: pedagogical slice of wallet_rotation.mosca_wallet (stdlib only).
def breach_years(X, Y, Z):
return X + Y - Z
def recommend(X, Y, Z):
breach = breach_years(X, Y, Z)
safe_window = max(0, Z - Y)
if breach <= 0:
return ("calendar", Y)
if safe_window >= 1:
return ("every-N-years", safe_window)
return ("external-trigger", 0)
# Strand wallet surface from Ch 36: X=10, Y=4.
X, Y = 10, 4
for label, Z in (("aggressive Z=4", 4), ("ncsc-2035 Z=9", 9), ("mid-2040 Z=14", 14)):
cadence, N = recommend(X, Y, Z)
breach = breach_years(X, Y, Z)
print(f"{label}: breach={breach:>3}y -> {cadence}, N={N}y")
# ==> aggressive Z=4: breach= 10y -> external-trigger, N=0y
# ==> ncsc-2035 Z=9: breach= 5y -> every-N-years, N=5y
# ==> mid-2040 Z=14: breach= 0y -> calendar, N=4y

Under the aggressive scenario, Z=YZ = Y leaves zero years of safe window for a fixed-interval rotation. No fixed-interval cadence clears the Mosca inequality. The recommendation falls back to external-trigger. That is only a meaningful mitigation if the trigger fires early enough to leave migration time. In practice the feasible response under Z=YZ = Y is immediate emergency migration, an explicit reduction of wallet exposure (freeze or move at-risk balances), or explicit risk acceptance. Under the NCSC-2035-aligned scenario, ZY=5Z - Y = 5 years of safe window admit a five-year rotation interval, bringing effective seed lifetime to 5 and clearing the breach. Under mid-2040, no breach exists. The operator rotates on the same calendar cadence the address book already turns over (every Y=4Y = 4 years).

One cadence option is never selected across the three scenarios above. The every-N-transactions cadence caps per-key exposure by spend count rather than calendar time. It is feasible whenever every-N-years is feasible, at higher operational cost (rotation triggers on every transaction count), and nothing in these three scenarios chooses it. The external-trigger cadence is the aggressive scenario’s fallback above. It is the one option that is always definable without always being risk-clearing. It works only if the trigger fires early enough to leave migration time, and it carries the highest operational cost, since the operator must scramble on the trigger event. The calendar cadence is feasible only when no breach exists. It is the cheapest option when feasible.

The cutover from legacy ECDSA to the chosen post-quantum primitive uses the Ed25519+ML-DSA-65 composite framing from Ch 27 and Ch 37. The composite is a wallet- and account-abstraction-level design pattern rather than a drop-in replacement for either chain’s native signature primitives. Bitcoin does not today have Ed25519 or ML-DSA consensus opcodes. Adopting a native composite there would require new script support. That script support would also need to address Tapscript’s 520-byte stack-element limit for large public keys and signatures, either by lifting it for new opcodes or by defining an encoding strategy that avoids single oversized stack elements (Ch 37). Ethereum EOAs are secp256k1-based. The composite fits more naturally inside an ERC-4337 smart-account validateUserOp body than into native EOA signature checks.

Within those custody-side constructions, each spending transaction during the migration window carries two signatures. The first is a classical signature over Ed25519, replacing ECDSA at the cutover for primitive uniformity with the composite. The second is the chosen post-quantum signature: ML-DSA-65 by default for hot wallets, SLH-DSA-128s for cold. The wallet operator runs the composite verifier on every spend. The chain (or the account contract) accepts the transaction only if both halves verify.

The composite buys defense in depth during the cutover. The Ch 27 framing gives the EUF-CMA bound on the composite as the bound on the stronger component alone, subject to the standard AND-composition engineering conditions from Ch 37: unambiguous encoding, domain separation, independent keys, and both-verify semantics. If a cryptanalytic break or proof flaw later surfaces against MLWE / MSIS, the classical Ed25519 half retains the security guarantee against a non-quantum adversary. If a CRQC arrives, the lattice ML-DSA-65 half retains the guarantee against the quantum adversary.

The cost is the composite byte overhead. The raw Ed25519 signature is 64 bytes. A real transaction encoding also carries an Ed25519 public key and the composite’s algorithm identifiers, putting the lower-bound size at 3309 + 64 = 3373 bytes against ML-DSA-65’s 3309. The runtime cost is one additional Ed25519 verification per spend. The byte cost is small relative to the lattice or hash component. The verification cost is not (Ch 37).

The rollback path keeps the legacy ECDSA-secp256k1 derivation chain operational behind the composite cutover until the operator commits to the post-quantum-only stage. A wallet that detects a runtime failure in the lattice verifier (a buggy ML-DSA implementation on a major full-node release, for example) can revert to legacy spending against ECDSA addresses that still hold balance. After the deprecation block (the latest spend-of-legacy-ECDSA block under the operator’s schedule), the rollback path closes. The wallet is post-quantum-only from that point.

Cryptanalysis: three wallet-surface attack categories

Section titled “Cryptanalysis: three wallet-surface attack categories”

The wallet surface inherits three attack categories specific to the custody setting beyond the generic Shor break covered in Ch 04 and the per-output-type mempool framing covered in Ch 37.

The first is state exposure. A stateful hash-based scheme tracks a one-time-key index. Signing at any unused index produces a valid signature. An attacker who steals the state file (the index plus the seed) does not need to forge anything: they can sign as the legitimate key holder at any not-yet-consumed index along the path. The mitigation is hardware-wallet-only single-device custody: the secure element holds both the seed and the current index, and the index advances atomically inside the device before any signature is exported (Cooper et al., 2020).

The Ch 15 multi-device dismissal is sharper here. A multi-device hot wallet that replicates the state file across phone and desktop produces a race: any device can advance the index, and a sync failure produces an index reuse on the lagging device. If the same one-time key is used to sign two different messages, the scheme’s security can collapse and forgeries become computationally feasible against the resulting one-time-key surface. The relevant RFC and NIST guidance is explicit that the private-key state must be updated in non-volatile storage before the signature is exported (Cooper et al., 2020; Hülsing et al., 2018; McGrew et al., 2019). The mitigation is to drop XMSS-MT and LMS from any shape that replicates one key’s state file across devices. The chapter’s custody-fit matrix marks both unfit on multi-device-hot for this reason. Multisig cold replicates nothing: each cosigner advances only its own key’s index, so the race does not exist there.

The second is address-derivation forgery via Shor (more precisely: public keys observed before migration become exploitable once a CRQC can solve the relevant discrete-log problem). A pre-rotation ECDSA address admits a Shor attack once its public key is observable to the attacker, on-chain or off it. On-chain, the reveal happens at the P2TR output, at the first spend for P2WPKH or P2PKH, or in the mempool while a spend is in flight. Off-chain, a shared xpub is the wide door: it yields every non-hardened descendant’s public key, and Shor on the parent public key recovers the parent private key and with it every non-hardened descendant. Any such address that still holds funds at ZZ-day admits a CRQC running Shor against the revealed point to recover the private scalar. The exposure vector depends on the output type per the Ch 37 mempool framing.

Output typeWhen the public key is exposed
Bitcoin P2WPKH, P2PKHat the first spend, in the in-flight mempool window
Bitcoin P2TR key pathat UTXO creation, in the scriptPubKey
Future PQ type over BIP-360 P2MRat the spend, in the revealing witness
Ethereum EOAat the spend, recoverable from the signature

Taproot’s commitment at UTXO creation makes its threat horizon funding-to-spend rather than the in-flight mempool window. The future PQ-signature output type on top of BIP-360 P2MR (Ch 37) is modeled as P2WPKH-style commit-then-reveal: the output commits to a hash of the post-quantum public key, and the spending witness reveals the public key alongside the signature. Ethereum EOAs do not serialize the raw public key as a separate transaction field. It is recoverable from the recoverable-ECDSA signature plus the signed transaction hash, by the same mechanism a full node uses to derive the sender, so EOA spend activity still effectively exposes it.

The wallet-side mitigation is the rotation cadence: rotate every NN years to bring effective seed lifetime to ZYZ - Y before ZZ, or rotate on an external trigger if no fixed interval clears the breach. The chapter’s mosca_wallet.recommend_cadence produces the per-Z recommendation. Exercise 4 walks a worked example.

The third is multi-device synchronization attack. A wallet that synchronizes the seed across phone, desktop, and a browser plug-in admits an attacker who watches the synchronization channel. Under stateless schemes (ML-DSA-65, SLH-DSA-128s) the synchronization channel is a confidentiality risk: an attacker who copies the seed gets the signing keypair. The mitigation is the same as the legacy ECDSA case: encrypt the seed in transit and at rest, audit the synchronization endpoints. Under stateful schemes (XMSS-MT, LMS) the synchronization channel is a signing-key integrity risk: the attacker who copies the seed also gets the live one-time-key index, and any signature produced at a not-yet-consumed index is valid. The mitigation is to drop stateful schemes from any shape that synchronizes one seed across devices. The chapter’s matrix marks both unfit on multi-device-hot accordingly. An independent-key multisig has no seed-synchronization channel to attack, which is why multisig-cold survives.

The six candidates trade off on four axes.

CandidateState managementHardware-wallet fitMulti-device viabilityRotation cadence interaction
ECDSA-secp256k1stateless (legacy)fits todayfits todayrotation source, not target
ML-DSA-65statelessfits all shapesfits all shapesindependent of cadence
SLH-DSA-128sstatelessmarginal on hot pathmarginal on hot pathindependent of cadence
Ed25519+ML-DSA-65statelessfits all shapesfits all shapesclassical fallback during cutover
XMSS-MTstateful (per-leaf index)fits single-device hardware-only; marginal on consumer single-device hotunfit as shared-seed; fit as independent-key multisigper-spend index advances; cadence triggers fresh subtree
LMSstateful (per-leaf index)fits single-device hardware-only; marginal on consumer single-device hotunfit as shared-seed; fit as independent-key multisigper-spend index advances; cadence triggers fresh subtree

An operator who weighs the prospect of a structural attack on the lattice problems takes the conservative-assumption preference (covered above): accept the byte-budget marginalization on hot paths against ML-DSA-65 and run SLH-DSA-128s on cold instead.

The hardware-fit preference for XMSS-MT or LMS on single-device hardware-only shapes carries one operational gotcha. The index counter must live in non-volatile memory (NVRAM) and advance atomically inside the device. A vendor recall or factory reset that destroys the key outright is a key-loss event, not a forgery event. The forge-the-next-signature event is a reset or recovery path that restores the same key at a stale index, and a seed-derived backup makes that restoration easy to do by accident. NVRAM cells also have a finite write-cycle budget. A single-device wallet that signs frequently can erode the counter cell over the device’s lifetime. This is part of why these schemes are properly homed in low-frequency cold storage rather than high-frequency hot wallets even on hardware. A hardware vendor targeting XMSS-MT or LMS needs a counter that monotonically advances and survives every supported reset path, with wear-leveling across the secure element’s NVRAM bank. The wallet operator validates the counter discipline before deploying.

The composite preference applies during the cutover window only. Ed25519+ML-DSA-65 carries 64 bytes more than ML-DSA-65 alone (a 2 percent overhead) and one Ed25519 verification on top of one ML-DSA-65 verification. The composite is the right cutover candidate for an operator who wants both hedges at once and is willing to pay the marginal byte and verification overhead during the cutover. Under the AND rule the ML-DSA-65 leg carries the protection against an early CRQC, which breaks the Ed25519 leg outright, and the Ed25519 leg keeps the classical guarantee standing if the younger lattice scheme turns out to have a defect while classical signatures are still secure.

The deployment-shape axis is the wallet-vendor question rather than a chain question. Bitcoin’s two-layer path (BIP-360 P2MR plus a future PQ-signature soft fork, Ch 37) puts the address-format change on-chain. The per-spend candidate decision lands in the wallet vendor’s hands once that soft fork addresses the BIP-342 stack-element limit and defines an ML-DSA / SLH-DSA output type. Ethereum’s ERC-4337 path puts the candidate decision in the smart-contract wallet’s deployment bytecode. Either way the wallet vendor is the work-stream owner per the Ch 30 framework. The operator-side timeline is the wallet vendor’s release schedule rather than the chain’s hard-fork schedule.

  1. Wallet Mosca-window breach under three Z scenarios. The Strand wallet surface has X=10X = 10 and Y=4Y = 4. Compute the breach window under three Z scenarios: Z=4Z = 4 (aggressive), Z=9Z = 9 (NCSC working assumption), and Z=14Z = 14 (mid-2040). Rank the four cadence options (calendar, every-N-years, every-N-transactions, external-trigger) for each scenario by operational cost, and pick the cheapest feasible cadence under each.

  2. Redesigned BIP-32-like derivation tree under ML-DSA-65. Sketch a redesigned BIP-32-like derivation tree under ML-DSA-65 that preserves a bounded watch-only property. State which branches are hardened and which (if any) are non-hardened. Justify the cadence at which the wallet operator publishes a fresh signed view descriptor to the watcher. Compute the bound on the watcher’s address-derivation horizon between publications.

  3. Open-ended: custody-shape application. Apply the chapter’s four-shape custody taxonomy to three custody arrangements. (a) A custodial exchange holding user balances under a single corporate hot wallet. (b) A self-custody hot wallet running on a phone and a desktop with a shared seed. (c) A 2-of-3 hardware-wallet multisig held by three independent trustees. For each, name the custody shape, pick a candidate from the chapter’s matrix, and state one constraint the candidate imposes that the legacy ECDSA setup did not.

  4. Strand rotation cadence under Z=9Z = 9. Strand has 200 thousand active wallet addresses. Each rotation per address costs the operator 12 minutes of operational work (rotate the seed, reissue the public key, update the address book). The operator runs rotations 24 hours a day with parallelism 100. Justify a rotation cadence under Z=9Z = 9. Compute the total wall-clock time to rotate the full address book once, and the steady-state cadence the operator can sustain. Compare against the chapter’s recommended every-N-years cadence.

  5. Open-ended: map to the Ch 30 four-phase program. Map the wallet migration to the Ch 30 four-phase program (discovery, first-wave, broad-rollout, end-of-migration). For each phase, name the wallet-vendor work-stream owner per the same Ch 30 framework (wallet vendor, hardware-vendor, custody platform, end user). Justify each mapping in one sentence.

The chapter package at solutions/ch38-wallets-addresses-key-rotation and the test suite at tests/ch38 ground the custody-fit and cadence computations the exercises reuse. Solutions to Exercises 1, 2, and 4 are in Appendix D, Chapter 38; Exercises 3 and 5 are open-ended and have editorial-note treatments rather than worked answers.

A separate track, for rebuilding rather than reading: the package exercises/ch38-wallets-addresses-key-rotation has every function the chapter teaches replaced by a stub. Run PQC_IMPL=exercises pytest tests/ch38 to grade your version against the suite that proves the reference one.

Beast, H., Heilman, E., & Foxen Duke, I. (2024). BIP-360: Pay-to-Merkle-Root (P2MR). Bitcoin Improvement Proposal (Draft). https://github.com/bitcoin/bips/blob/master/bip-0360.mediawiki
Buterin, V., Weiss, Y., Tirosh, D., Nacson, S., Forshtat, A., Gazso, K., & Hess, T. (2021). ERC-4337: Account Abstraction Using Alt Mempool. Ethereum Request for Comments / EIP. https://eips.ethereum.org/EIPS/eip-4337
Cooper, D., Apon, D., Dang, Q., Davidson, M., Dworkin, M., & Miller, C. (2020). Recommendation for Stateful Hash-Based Signature Schemes. NIST Special Publication 800-208. https://doi.org/10.6028/nist.sp.800-208
Hülsing, A., Butin, D., Gazdag, S., Rijneveld, J., & Mohaisen, A. (2018). XMSS: eXtended Merkle Signature Scheme. IETF RFC 8391. https://doi.org/10.17487/rfc8391
McGrew, D., Curcio, M., & Fluhrer, S. (2019). Leighton-Micali Hash-Based Signatures. IRTF RFC 8554 (Informational). https://www.rfc-editor.org/rfc/rfc8554.html
National Institute of Standards and Technology. (2024a). FIPS 204: Module-Lattice-Based Digital Signature Standard. Federal Information Processing Standards Publication. https://doi.org/10.6028/NIST.FIPS.204
National Institute of Standards and Technology. (2024b). FIPS 205: Stateless Hash-Based Digital Signature Standard. Federal Information Processing Standards Publication. https://doi.org/10.6028/NIST.FIPS.205
Palatinus, M., & Rusnak, P. (2014). BIP-44: Multi-Account Hierarchy for Deterministic Wallets. Bitcoin Improvement Proposal (Specification). https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
UK National Cyber Security Centre. (2025). Timelines for migration to post-quantum cryptography. NCSC guidance. https://www.ncsc.gov.uk/guidance/pqc-migration-timelines
Wuille, P. (2012). BIP-32: Hierarchical Deterministic Wallets. Bitcoin Improvement Proposal (Informational). https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki

Last updated: