docs: Restore the qr steps for offline signing - #93
BenWestgate wants to merge 5 commits into
Conversation
Commit 36d903d replaced the guide's offline-signing steps with a link to Bitcoin Core's tutorial, leaving "the QR tools below" and the QR troubleshooting section with no step that uses qr. Follow the tutorial's two wallets and use qr to carry the public descriptors to the online watch-only wallet, the PSBT to the offline signer, and the signed transaction back. The tutorial's exportwatchonlywallet file is too large for a QR, so the descriptors are imported into a blank watch-only wallet instead. Mention qrencode as the fallback. Closes #92 Claude-Session: https://claude.ai/code/session_01T233rKgZqE5wzDm3EVTHL1
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
BenWestgate
left a comment
There was a problem hiding this comment.
AI-generated release-gate review, posted at the maintainer’s request.
ACK d4641c4. I independently exercised the documented Bitcoin Core 32.0rc2 RPC sequence on regtest: exporting listdescriptors, importing the filtered descriptor array into a blank private-key-disabled wallet, comparing the next receiving address, creating a watch-only send PSBT, signing it with walletprocesspsbt, and broadcasting the resulting hex with sendrawtransaction. The descriptor QR payload was 1,976 bytes; address comparison succeeded; the watch-only send was incomplete with a PSBT; offline processing completed with hex; broadcast succeeded.
The shell boundaries are also appropriate: descriptor JSON is passed as an RPC stdin argument rather than shell-evaluated, and PSBT/raw transaction values are quoted. The existing rc1 documentation URL is a release-documentation follow-up, not a correctness blocker while stable v32.0 does not yet exist and the release monitor is active.
No correctness or security blocker found. This commit is Claude-authored, so it still needs responsible human review/rewrite as required by the project’s authorship policy before integration.
|
Is the exportwatchonlywallet too large for qr even after it is compressed? |
The watch-only wallet file from exportwatchonlywallet does not fit in a QR even compressed (12,288 bytes; 2,365 gzipped against qr's 2,331-byte limit), so the guide sends the public descriptors. gzip shrinks them from 1,975 to 619 bytes, which takes qr's code from version 37 to 19, about half the width, and makes the scan more reliable. The online side reads the QR with zbarcam -Sbinary and pipes it through gunzip. Without -Sbinary, ZBar rewrites the bytes as text and gunzip fails. qrencode needs -8 for the same data; without it the input stops at the first zero byte. Validation: on two separate Bitcoin Core 32.0rc2 regtest nodes with the BIP-93 test vector, the gzipped listdescriptors output went through qr and back through zbarimg with the guide's flags and gunzip, all 8 descriptors imported with success, and both wallets gave the same first address. qrencode -8 round-tripped the same bytes. Docs-only change. Refs #92 Claude-Session: https://claude.ai/code/session_01T233rKgZqE5wzDm3EVTHL1
A compromised online computer can show an address it controls, and a deposit to it is lost even though the keys never left the offline signer. Bitcoin Core's offline-signing tutorial gets receiving addresses from the watch-only wallet without checking them. The new step keeps addresses and labels in watch_only_wallet, as the tutorial does, then scans each address into the offline computer and looks it up with getaddressinfo. The address is given out only when the result shows "ismine": true and matches what is sent. It needs only bitcoin-cli, qr and zbarcam, which Tails ships, and works while offline_wallet is locked. The spend step becomes step 4. Validation: on two separate Bitcoin Core 32.0rc2 regtest nodes with the BIP-93 test vector, an address from getnewaddress "LABEL" went through qr and zbarimg into a locked offline_wallet, which reported "ismine": true and the same address; the online wallet kept the label. A foreign address reported false. Address 1,002 reported false until keypoolrefill 2000 on the unlocked offline wallet, then true. Docs-only change. Refs #92 Claude-Session: https://claude.ai/code/session_01T233rKgZqE5wzDm3EVTHL1
Tails doesn't ship jq, and Bails can't install packages because it doesn't run as an administrator. The offline-signing steps now pull the descriptors, the PSBT and the signed transaction out of Core's JSON with python3, which Tails includes. Importing the full listdescriptors entries works, so the descriptor step no longer filters fields; the gzipped QR grows from about 620 to 640 bytes. Drop the qrencode fallback, which also needs an install; Tails always has qr. Validated on Core 32.0rc2 regtest: 8 descriptors imported from a gzipped QR image read by zbarimg -Sbinary, addresses matched, a PSBT signed offline and broadcast; signing with a wallet that can't sign exits with "The PSBT is not fully signed." and shows no QR. Fixes #111 Refs #92 Claude-Session: https://claude.ai/code/session_01T233rKgZqE5wzDm3EVTHL1
Malware on the online computer can swap an address after the offline getaddressinfo check. When the payer is your own phone wallet or is with you, showing the checked address as a QR on the offline screen lets them scan it without it passing through the online computer, so nothing needs comparing. An exchange withdrawal or a payer over the internet needs the address on a networked device, so the guide says to compare the last screen before submitting, such as the exchange's confirmation page, with the address shown offline. Refs #92 Claude-Session: https://claude.ai/code/session_01T233rKgZqE5wzDm3EVTHL1
BenWestgate
left a comment
There was a problem hiding this comment.
Codex current-head re-review: ACK 6f6c86f.
I re-reviewed the four follow-ups added after the earlier d4641c4 ACK. The descriptor payload is compressed well under the QR limit; the receive path now verifies each online-generated address against the locked offline signer and gives out the checked address directly from the offline screen where possible; Tails/Bails-compatible python3 replaces the unavailable jq; and the signed-transaction path fails closed when the PSBT is incomplete. The guide keeps only public descriptors, PSBTs, checked addresses and signed transactions crossing the QR boundary. There are no inline review threads, and exact-head Python-package run 694 succeeded.
No correctness or security blocker found. These Claude-authored documentation commits still require responsible-human rewrite/squash before integration.
Requested by Ben · project thread
Before: the user guide's "watch-only wallet and offline signer" section only linked to Bitcoin Core's offline-signing tutorial. The guide still referred to "the QR tools below" and had a QR troubleshooting section, but no step used
qr.After: the section follows the tutorial's
offline_walletandwatch_only_walletin four steps: restore the signer withms32 wallet; send the gzipped public descriptors to a blank watch-only wallet by QR; get receiving addresses and labels from the watch-only wallet and check each one on the offline signer before giving it out; then carry the PSBT to the signer and the signed transaction back by QR. Every command uses only what Tails includes (python3,gzip,qr,zbarcam) besides Bitcoin Core and codex32, so it works in Bails, which can't install packages.The tutorial moves an
exportwatchonlywalletfile, which doesn't fit in a QR even compressed (12,288 bytes on regtest; 2,365 gzipped, overqr's 2,331-byte default limit). The guide sends thelistdescriptorsentries instead and imports them withimportdescriptors. gzip shrinks the 8 default descriptors to about 640 bytes. The online side reads it withzbarcam -Sbinary(ZBar 0.23.1 or newer) andgunzip. A shortpython3command takes the descriptors, the PSBT and the signed hex out of Core's JSON in place ofjq, which Tails lacks.The receive check exists because a compromised online computer could show an address it controls. The address is scanned into the offline computer and looked up with
getaddressinfo; it is given out only when the result shows"ismine": true. For your own phone wallet or a payer who is present, the offline screen shows the checked address as a QR, so nothing needs comparing. For an exchange withdrawal or a payer over the internet, the address passes through a networked device, so the guide says to compare the last screen before submitting with the address shown offline. The check works whileoffline_walletis locked. Which receive flow the guide should use is still open in #112, which compares this one with making addresses on the offline wallet.How: the commands were run end to end against Bitcoin Core 32.0rc2 on two separate regtest nodes, with
zbarimgreading the generated QR images in place of a camera. All 8 descriptors imported from the gzipped QR, both wallets gave the same first address, a labelled online address checked"ismine": trueon the locked signer while a foreign one checkedfalse, the PSBT and the signed hex survived the QR round trip, andsendrawtransactionaccepted the result. Signing with a wallet that can't sign stops with "The PSBT is not fully signed." and shows no QR. Without-Sbinary, ZBar rewrites the gzip bytes as text andgunzipfails. Past the signer's first 1,000 addresses a real address checksfalseuntilkeypoolrefill, as the guide says. Docs-only.Closes #92
Closes #111
🤖 Generated with Claude Code
https://claude.ai/code/session_01T233rKgZqE5wzDm3EVTHL1
Generated by Claude Code