Skip to content

Releases

Trezor unified opt-in sighash .2 - 1.14.2 (Model One), 2.12.5 (Model T, Safe 3, Safe 5, Safe 7)

Choose a tag to compare

@kwsantiago kwsantiago released this 04 Sep 03:48
unified-sighash.2
9d406f1

Trezor firmware signing with the unified opt-in signature hash as Bitcoin Knots v29.4.1.knots20260508 defines it, with Bitcoin-only builds for all five models.

Current for v29.4.1.knots20260508. The message is unchanged since rc3: doc/unified-sighash.md and the 166 vectors are byte-identical across the release candidates and the final tag, and the vectors here match them at sha256 5c5e95fc1ab8ef9ce6b3cb6e76b8c74a987d182ebd01d8e2b98b6d0fbb26f630. Nothing in this firmware carries an activation height, so the schedule needs no change here.

This is an unofficial fork. Not published, reviewed or endorsed by SatoshiLabs or the Trezor project, and not audited. It is not signed by the vendor, and the consequences of that are permanent and described under "Installing it".

Capability_UnifiedSigHash is 29. A host reads it to tell whether the firmware implements the opt-in at all. 28 would collide with upstream's Capability_Ethereum_EIP7702 and 27 is reserved, so this takes the next free number. A host looking for 29 does not recognise a device reporting anything else and refuses to ask it for the opt-in, so a mismatch fails closed rather than producing an unprotected signature.

What it changes

TxInput.unified_sighash asks for hash type 0x21. A signature made with it commits to every spent amount and scriptPubKey rather than only the input being signed, which closes CVE-2020-14199 at the consensus layer, and it does not verify under the pre-fork rules, so the transaction cannot be replayed onto a chain that has not adopted the change.

Signing is byte-for-byte unchanged unless the host asks for it.

Covers bare and P2SH (script type 0), segwit v0 including P2SH-wrapped (1), and taproot key path (2). Only SIGHASH_ALL|SIGHASH_UNIFIED is implemented, because this firmware never signs SIGHASH_NONE, SIGHASH_SINGLE or SIGHASH_ANYONECANPAY at all and so has no legacy counterpart to give a unified form to. Tapscript, script type 3, is unreachable because there is no script-path taproot here.

The device names the hash type on the approval screen before the amount and the fee. Labelling only the opt-in would make its absence indistinguishable from the screen never having said anything, so ordinary signing is unchanged and shows no such screen.

Two rules the device enforces that are not in the message itself:

  • The opt-in is bound to what the confirmation screen reported. Both algorithms produce a valid signature, so nothing else in the protocol would catch a host that gets the opt-in approved and then quietly drops it at signing time.
  • A replacement may add the opt-in but must not remove it. A replacement that changes nothing else is confirmed with the TXID screen alone, so without this a host could take a transaction you approved with replay protection and obtain a pre-fork-valid signature over the identical transaction for one tap.

The version strings stay at upstream's, 1.14.2 and 2.12.5. Features carries only major, minor and patch, so a suffix would either claim to be a newer official release or trip downgrade protection. A device identifies itself instead by the unofficial-firmware warning and by revision, which is 9d406f1af3d6a8fbe1ae9571e594e7d42f099bde.

Files

file model version
firmware-T1B1-bitcoinonly-1.14.2-9d406f1.bin Model One 1.14.2
firmware-T2T1-btconly-2.12.5-9d406f1a.bin Model T 2.12.5
firmware-T3B1-btconly-2.12.5-9d406f1a.bin Safe 3 2.12.5
firmware-T3T1-btconly-2.12.5-9d406f1a.bin Safe 5 2.12.5
firmware-T3W1-btconly-2.12.5-9d406f1a.bin Safe 7 2.12.5

All Bitcoin-only. SHA256SUMS covers every file, SHA256SUMS.asc signs it, and privkeyio-signing-key.asc is the key.

Verified

Per model. Only the Model One has been on hardware. Everything else is the emulator, which runs the same sources but not the bootloader, the buttons or the display.

model verified
Model One 501 device tests, node end to end, a 798-signature randomised cross-check, and a mainnet transaction signed on a real device
Model T 51 tests: the unified suite, CoinJoin and replacement
Safe 3 18 unified tests
Safe 5 18 unified tests
Safe 7 18 unified tests

Every model reports Capability_UnifiedSigHash at 29.

The digest. All 166 cross-implementation vectors from Knots' unified_sighash.json are committed. The firmware's own digest runs the 27 that are hash type 0x21 and a script type it can spend and matches all 27; the rest are skipped with the count printed and the total asserted, so a silently shrinking vector set cannot pass as a clean run. A second implementation written from the specification covers all six hash types and reproduces 154 of 166, the 12 skipped being tapscript, for which the vectors carry no tapleaf hash.

Against a node. Every supported script type signed and mined by a Knots v29.4.1.knots20260508 regtest node past activation, plus a transaction mixing opted-in and legacy inputs, replacements of both a segwit and a bare original, a pre-fork input spent after activation, and a replacement that drops the opt-in, which must be refused. The control is the same transaction signed with the legacy digest and stamped with the opt-in byte, which the node rejects; the same signature honestly labelled is accepted, so what is enforced is the byte and not the transaction shape.

Randomised. 400 transactions of random shape, random input and output counts, versions, locktimes, sequences, amounts and script types, driven through the device over the wire, every digest recomputed with the Knots tree's own implementation: 798 signatures, zero disagreements.

CVE-2020-14199. Changing only a sibling input's amount changes the digest for input 0, so the device's signature no longer verifies.

Reproducible. Every model built twice from the same commit in the same container, byte identical.

Reviewed. Three independent read-only passes, which found the two defects that mattered, both fixed and covered by tests.

Installing it

Installing this erases the device, and so does going back to official firmware. You will need your recovery seed both times.

An unsigned build shows a warning and its fingerprint on every boot, permanently. Anyone can produce an image that gets the same warning, so the warning tells you the firmware is unofficial, not that it is this firmware. The fingerprint is what tells you that.

gpg --import privkeyio-signing-key.asc
gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum -c SHA256SUMS

Those two checks prove the file matches what was published here, under the key above. They run entirely on your computer, so they do not protect you from a computer that is already compromised: anything able to substitute the firmware is equally able to print the output you were expecting. Run them, but they are not the check that carries the weight.

There is no vendor signature for the device to verify, and that cannot be fixed. The bootloader checks firmware against SatoshiLabs' key, and only SatoshiLabs can sign with it, so no third-party build satisfies it. Verifying somebody else's signature on your own PC is not a substitute for the device verifying the vendor's.

The check that does not rely on your computer is the fingerprint. Build the firmware yourself from the tag and compute its fingerprint:

./build-docker.sh --init-only --models T1B1 unified-sighash.2
./reproducible-build-legacy.sh . unified-sighash.2 ./out
trezorctl firmware verify ./out/firmware.bin

reproducible-build-core.sh does the same for the other four models. Compare what that prints against the four hex lines the device shows on its own screen under Compare fingerprints, which it does on every boot and again before it installs anything. Both numbers are sha256 over the firmware header with the signature fields zeroed, so they are one value computed in two places. Building is what establishes which fingerprint is correct; the device is what reports which one it is actually running. A compromised computer can lie about either number on its own, but it cannot change what the device puts on its display.

Then, with the device in bootloader mode:

trezorctl firmware update -f <the file for your model>

Reaching a device over USB needs libusb. If trezorctl reports Failed to enumerate WebUsbTransport, run it from an environment that provides one, or it will not find your device.

Confirm what is running before trusting anything it signs. sign_unified.py check prints the model, version and revision, and exits non-zero unless the device reports Capability_UnifiedSigHash.

Using it

Trezor Suite does not know about the field, so opting in needs a host that sets it. sign_unified.py in this release does. run-hardware-test.sh is the guided test: it starts its own Knots regtest node past activation, funds addresses the device controls, and signs one transaction per script type, so the only thing you do is press the buttons.

No warranty

This is unofficial, unaudited firmware provided as is, with no warranty and no liability, under the same LGPL terms as the source it inherits. It is not a Trezor release and is neither affiliated with nor supported by SatoshiLabs. Flashing it is entirely at your own risk, and you alone bear any loss of funds or loss of access to your device.

Built from the tag unified-sighash.2, which is 9d406f1af3d6a8fbe1ae9571e594e7d42f099bde. Build from the tag rather than the branch: the firmware embeds the commit it was built from, so a branch that has moved since produces a different binary.