Conformance — v1 test vectors¶
This document points at the canonical fixtures a v1 implementation
SHOULD validate itself against. The fixtures live in
tests/fixtures/
on GitHub and are committed to source so external implementations
can pull just that directory without cloning the full repository.
The reference implementations in piwallet/ (Python) and
companion/ (TypeScript) both regenerate and check against these
files on every run of their respective test suites, so they cannot
silently drift.
1. Canonical wallet¶
File: tests/fixtures/addresses_canonical.json
Anchors the seed → addresses pipeline. The fixture is a JSON object:
{
"mnemonic": "abandon abandon ... about",
"path": "m/44'/236'/0'",
"fingerprint": "<8 lowercase hex chars>",
"xpub": "<Base58Check xpub at m/44'/236'/0'>",
"addresses": {
"receive": [ { "index": 0, "address": "<base58>" }, ... ],
"change": [ { "index": 0, "address": "<base58>" }, ... ]
}
}
A conformant implementation MUST be able to:
- Run BIP39
mnemonic→ 64-byte seed with empty passphrase producing the seed that derivesxpubbyte-for-byte atm/44'/236'/0'. - Compute the 4-byte self-fingerprint of
xpub(seederivation.md§4) and produce the same value asfingerprint. - Derive
m/0/iandm/1/ifromxpubfor every listedindexand produce the same Base58Check P2PKH addresses.
The reference implementations cross-check this in both directions —
Python's tests/test_derivation.py and TypeScript's
companion/tests/derive.test.ts both load this file and assert
their output equals every entry.
2. Canonical proposal¶
Files:
tests/fixtures/proposal_01.cbor— a v1unsigned_proposalenvelope, gzip-compressed, 511 bytes.tests/fixtures/proposal_01.json— companion metadata (mnemonic, funding tx, addresses, amounts, block height, Merkle root).tests/fixtures/proposal_01_decoded.json— a field-by-field decoded form of the envelope, suitable as a structural reference for a third-party decoder. Generated byscripts/dump_decoded_envelope.py.
The proposal pays 30 000 sats from m/44'/236'/0'/0/0 to an
external address, with 19 500 sats of change back to
m/44'/236'/0'/1/0 and 500 sats of implicit fee. The funding tx
sits inside a synthetic 2-leaf Merkle tree whose root is anchored at
the (synthetic) block height 812 345. None of this is on the live
chain — it is a self-consistent synthetic vector for offline
testing.
2.1 Bytes-level check¶
A v1 envelope decoder fed the 511 bytes of proposal_01.cbor MUST:
- gunzip the bytes to ~650 bytes of CBOR;
- decode the CBOR to a single map with
v == 1andkind == "tx"; - expose the same fields, in the same types, that
proposal_01_decoded.jsonlists.
A simple way to verify your implementation is to print your own
structural dump and compare against proposal_01_decoded.json.
The Python reference dump is regenerated by:
python scripts/dump_decoded_envelope.py \
tests/fixtures/proposal_01.cbor \
tests/fixtures/proposal_01_decoded.json
proposal_01_decoded.json is regenerated as part of the repo's
test suite, so the file in git always matches the current CBOR
encoding.
2.2 Cryptographic check¶
The bytes are not just structurally valid — they pass every check
in spv.md §1. A v1 signer fed this envelope (with the
mnemonic from §1 as its loaded wallet) MUST:
- Parse the envelope without errors.
- Recover the same funding txid as
proposal_01.json.funding_txidfrom the BEEF. - Compute the same Merkle root as
proposal_01.json.merkle_root_hexfrom the path attached to the funding tx. - Re-derive the input's P2PKH script from
m/44'/236'/0'/0/0and match it against the funding tx's output atvout=0. - Re-derive the change output's P2PKH script from
m/44'/236'/0'/1/0and match it against the proposal'soutputs[1].script. - Compute a
fee_satsof500.
Implementations that get this far on the fixture have a viable v1 signer; implementations that fail any of the above are not conformant and SHOULD NOT advertise compatibility.
3. Cross-implementation decode¶
This is optional but useful as a sanity check. The reference
TypeScript decoder reads the Python-produced proposal_01.cbor
and asserts every logical field matches the metadata
(tests/test_decoded_envelope_fixture.py and
companion/tests/envelope.test.ts).
A third-party encoder is free to produce different bytes than the
Python reference (e.g., a different gzip compression level,
canonical CBOR map ordering, or mtime value in the gzip header)
as long as the bytes round-trip through both reference decoders
to the same logical envelope. Use the structural dump from §2.1 as
the comparison target rather than a raw byte-level diff.
4. PW1 multipart round-trip¶
There is no dedicated PW1 fixture file. Instead, build one on-the-fly:
- Take any envelope blob (e.g.,
proposal_01.cbor). - Run your encoder to produce a list of
PW1|...lines. - Feed them into your assembler in arbitrary order.
- Compare the assembled bytes against the original.
The reference implementations exercise this in both directions:
- Python —
tests/test_qr_multipart.py. - TypeScript —
companion/tests/pw1.test.ts.
If you can do this for one envelope, you can do it for all of them — the transport is content-agnostic.
5. Versioning the fixtures¶
When v2 of the protocol arrives, this directory will be cloned to
docs/protocol/v2/ with a new tests/fixtures/v2/ directory.
v1 fixtures will be kept in their current location forever so
older implementations can keep validating themselves.
A change that would alter proposal_01.cbor bytes is, by
definition, a protocol change and a new fixture file SHOULD be
added rather than mutating an existing one.