`inputPaths` names one HD key per entry, which is the whole story for a P2PKH
input: one input, one signature. A contract input is not like that. Its unlocking
bytecode may carry several sig/pubkey placeholders — an N-of-N agreement, or a
function taking (sig a, pubkey A, sig b, pubkey B) — and nothing in a 3-tuple can
say which placeholder a key fills.
So entries gain an optional fourth element, `slot`, and an input's index is listed
once per placeholder. `slot` defaults to 0, so every existing 3-tuple keeps
meaning exactly what it meant and no current dapp needs a capability check.
Sending `slot > 0`, or repeating an inputIndex, requires the wallet to advertise
`multislot` — an older wallet keeps one key per input and would return a
transaction missing signatures with nothing to say why.
This supersedes the `multislot` branch, which was cut before the
randomTradeSummary revert and still carries the reverted `txSummary` field. The
protocol design there is good and is kept: the placeholder format (65-byte
Schnorr sig push, 33-byte compressed pubkey push, spliced value-for-value so
offsets survive), the capability gate, and the three wallet rules — don't
deduplicate by inputIndex, compute each input's sighash once, reject rather than
under-fill. Two things are changed.
SLOTS ARE POSITIONAL, NOT BY VACANCY
The earlier definition numbered slots by scanning the template for zero-filled
pushes. That renumbers them as they fill, and a template is not always all
zeroes: in the N-of-N case the docs give as motivation, the dapp may have already
written the counterparty's signature into the first position. Verified against
that implementation's own reference fill, on a two-slot input with slot 0
pre-filled:
asked for slot 1 -> not filled at all (under-fill)
asked for slot 0 -> writes into the SECOND slot, silently
Here a push already holding a value still occupies its slot, so `slot` means the
same thing to the dapp that built the template and the wallet filling it,
whatever order things happen in. Overwriting a filled slot is an error rather
than a no-op, because discarding a counterparty's signature is not recoverable.
THE SCAN PARSES PUSHES INSTEAD OF SEARCHING FOR BYTES
A hex-substring search for the placeholder pattern can match bytes that merely
sit inside a larger push, splicing a signature into the middle of unrelated data.
findPlaceholders walks the script's push structure (direct pushes and
OP_PUSHDATA1/2/4) and throws on a truncated template rather than guessing at one
it cannot parse.
WHY THE HELPERS ARE IN THE LIBRARY
The placeholder layout is part of the wire contract — the dapp builds the
template, the wallet splices into it, and they must agree byte for byte or the
transaction is silently unspendable. Leaving every wallet to implement the scan
is how that goes wrong, and both failure modes above come from a reasonable
implementation of a reasonable-sounding rule.
fillPlaceholder throws on a missing slot, a wrong-length value, or an already
filled slot; unfilledPlaceholders is what "reject, don't under-fill" checks. That
makes the rule enforceable rather than something to remember.
Deliberately NOT included: the sighash-validation module from the earlier branch.
It is a separate concern — the library does no transaction signing today, so
adding a validator for it is new surface that deserves its own review, and its
signature-detection heuristic needs work (a 65-byte push is treated as a
signature, which an uncompressed public key also is).
23 core tests, including both misplacement cases above, the inside-a-larger-push
false positive, OP_PUSHDATA1 headers, truncated templates, and fill-order
independence.
Docs: protocol.md, extensions.md, wallet.md, dapp.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Splits ProtocolMessages exceeding NIP-44's 65,535-byte plaintext ceiling
across multiple gift-wrapped events. Symmetric (both directions),
fire-and-forget, backward-compatible via a new transport-level
\`extensions\` field on \`dapp_ready\` and \`wallet_ready\`. Resolves the
\"Failed to swap: invalid plaintext size\" error on aggregated swap
requests and enables signed-tx responses up to the 1 MB BCH consensus
limit (~2 MB hex).
Keep session persistance by default, such that when reloading a dapp,
the wizardconnect session is kept alive.
Improve signature request interface. Simplify the react API.
User may need some time to open their wallet to approve signature
request and miss it.
If we have a active request, re-send it if we see a
wallet_ready signal, suggesting the wallet was just opened.
Replace placeholder 'bip352' / 'bip47_rpa' with the finalized
extension names from the BCR post and BCH Stealth Protocol spec:
- bip352 → bch_stealth_bip352
- bip47_rpa → rpa_bip47
Add spend_path / scan_path fields to each extension handshake
object, pointing to the hardened gate paths where the wallet
exports xpubs. Dapps derive the non-hardened /0 child locally.
Add rpa_spend / rpa_scan path names alongside stealth_spend /
stealth_scan in all examples. Update known extensions table with
full path information and BCR reference links.
Spec: https://bitcoincashresearch.org/t/ecdh-stealth-addresses-on-bitcoin-cash-implementation-code/1773/5
I have read the CLA Document and I hereby sign the CLA
This backward compatible change allows wallets/dapps to add additional
features outside the basic transaction signature support to the
hdwalletv1 protocol.
The package nostr-dev-kit was using a dependency (tseep) which
downstream would flag as not CSP-safe.
Additionally nostr-tools footprint is much smaller.
Canonical encoding for BigInt (<bigint: Xn>) and Uint8Array (hex or
<Uint8Array: 0x...>) used in the relay protocol. Provides both
serialization (sourceOutputToRelay, transactionToHex) and deserialization
(parseExtendedJson, toUint8Array, toBigInt) so dapps and wallets
don't have to implement this independently.
The useWizardConnect hook now saves PathXpub[] from wallet_ready into
the localStorage session. On auto-reconnect, restoreSessionPaths() is
called so getPubkey() works immediately without waiting for the wallet.
Corrupt cached paths are cleared with a warning.
Allows dapps to cache the raw PathXpub[] from wallet_ready and restore
them on subsequent page loads. restoreSessionPaths decodes the xpub strings
and populates pubkeyState so getPubkey() works without waiting for the
wallet to reconnect. Throws on invalid xpub data.