Skip to content

Commit

Permalink
Merge #549: psbt: Implement BIP 370 PSBTv2
Browse files Browse the repository at this point in the history
b11d0c3 jade: Do not use psbt.tx (Andrew Chow)
0c4052b coldcard: Convert PSBT to v0 before sending to Coldcard (Andrew Chow)
e24c963 ci: Include psbt2 in bitcoind patch (Andrew Chow)
521b30d psbt: key type is a compact sized uint (Andrew Chow)
b47bee0 psbt: Add version conversion function (Andrew Chow)
e03d309 tx: Change CTransaction.hash to be non-reversed bytes (Andrew Chow)
f38bd7f trezor: do not use psbt.tx (Andrew Chow)
5dbcb3e ledger: avoid using psbt.tx (Andrew Chow)
47c8180 bitbox01: avoid using psbt.tx directly (Andrew Chow)
4817d83 psbt: add get_unsigned_tx (Andrew Chow)
82923e3 bitbox02: Use PSBTv2 fields (Andrew Chow)
5d8a5de psbt: Add compute_lock_time() function (Andrew Chow)
39d8cff psbt: Add get_txout function to PSBTOutput (Andrew Chow)
ec981a8 psbt: Use PSBTv2 fields with PSBTv0 (Andrew Chow)
ed87c3a psbt: Implement PSBTv2 fields (Andrew Chow)

Pull request description:

  Implements serialization and usage of PSBTv2 fields.

Top commit has no ACKs.

Tree-SHA512: 42815ccd6cde878aeb112a109b799488b38756a42e578cb59d39c0e4eee693eb0db982ea656167d6660d9808ee60b8c321b752d5bc9aa9b38e979e27cdde7e7c
  • Loading branch information
achow101 committed Feb 3, 2022
2 parents 3d9ebd3 + b11d0c3 commit 0d846a6
Show file tree
Hide file tree
Showing 11 changed files with 4,836 additions and 1,392 deletions.
32 changes: 19 additions & 13 deletions hwilib/devices/bitbox02.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,11 @@ def script_config_from_utxo(
# must be exactly one pubkey per input that belongs to the BitBox02.
found_pubkeys: List[bytes] = []

for input_index, (psbt_in, tx_in) in builtins.enumerate(
zip(psbt.inputs, psbt.tx.vin)
):
for input_index, psbt_in in builtins.enumerate(psbt.inputs):
assert psbt_in.prev_txid is not None
assert psbt_in.prev_out is not None
assert psbt_in.sequence is not None

if psbt_in.sighash and psbt_in.sighash != 1:
raise BadArgumentError(
"The BitBox02 only supports SIGHASH_ALL. Found sighash: {}".format(
Expand All @@ -650,13 +652,15 @@ def script_config_from_utxo(
# The BitBox02 for now requires the prevtx, at least until Taproot activates.

if psbt_in.non_witness_utxo:
if tx_in.prevout.hash != psbt_in.non_witness_utxo.sha256:
assert psbt_in.non_witness_utxo.sha256 is not None
if psbt_in.prev_txid != ser_uint256(psbt_in.non_witness_utxo.sha256):
raise BadArgumentError(
"Input {} has a non_witness_utxo with the wrong hash".format(
input_index
)
)
utxo = psbt_in.non_witness_utxo.vout[tx_in.prevout.n]
assert psbt_in.prev_out is not None
utxo = psbt_in.non_witness_utxo.vout[psbt_in.prev_out]
prevtx = psbt_in.non_witness_utxo
elif psbt_in.witness_utxo:
utxo = psbt_in.witness_utxo
Expand Down Expand Up @@ -687,10 +691,10 @@ def script_config_from_utxo(
)
inputs.append(
{
"prev_out_hash": ser_uint256(tx_in.prevout.hash),
"prev_out_index": tx_in.prevout.n,
"prev_out_hash": psbt_in.prev_txid,
"prev_out_index": psbt_in.prev_out,
"prev_out_value": utxo.nValue,
"sequence": tx_in.nSequence,
"sequence": psbt_in.sequence,
"keypath": keypath,
"script_config_index": script_config_index,
"prev_tx": {
Expand All @@ -717,9 +721,10 @@ def script_config_from_utxo(
)

outputs: List[bitbox02.BTCOutputType] = []
for output_index, (psbt_out, tx_out) in builtins.enumerate(
zip(psbt.outputs, psbt.tx.vout)
):

for output_index, psbt_out in builtins.enumerate(psbt.outputs):
tx_out = psbt_out.get_txout()

_, keypath = find_our_key(psbt_out.hd_keypaths)
is_change = keypath and keypath[-2] == 1
if is_change:
Expand Down Expand Up @@ -771,13 +776,14 @@ def script_config_from_utxo(
script_configs[0].script_config, script_configs[0].keypath
)

assert psbt.tx_version is not None
sigs = self.init().btc_sign(
self._get_coin(),
script_configs,
inputs=inputs,
outputs=outputs,
locktime=psbt.tx.nLockTime,
version=psbt.tx.nVersion,
locktime=psbt.compute_lock_time(),
version=psbt.tx_version,
)

for (_, sig), pubkey, psbt_in in zip(sigs, found_pubkeys, psbt.inputs):
Expand Down
1 change: 1 addition & 0 deletions hwilib/devices/coldcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def sign_tx(self, tx: PSBT) -> PSBT:

for _ in range(passes):
# Get psbt in hex and then make binary
tx.convert_to_v0()
fd = io.BytesIO(base64.b64decode(tx.serialize()))

# learn size (portable way)
Expand Down
9 changes: 3 additions & 6 deletions hwilib/devices/digitalbitbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@
is_witness,
)
from ..psbt import PSBT
from ..tx import (
CTransaction,
)
from .._serialize import (
ser_sig_der,
ser_sig_compact,
Expand Down Expand Up @@ -391,8 +388,8 @@ def get_pubkey_at_path(self, path: str) -> ExtendedKey:
@digitalbitbox_exception
def sign_tx(self, tx: PSBT) -> PSBT:

# Create a transaction with all scriptsigs blanekd out
blank_tx = CTransaction(tx.tx)
# Create a transaction with all scriptsigs blanked out
blank_tx = tx.get_unsigned_tx()

# Get the master key fingerprint
master_fp = self.get_master_fingerprint()
Expand Down Expand Up @@ -477,7 +474,7 @@ def sign_tx(self, tx: PSBT) -> PSBT:
preimage += struct.pack("<q", psbt_in.witness_utxo.nValue)
preimage += struct.pack("<I", txin.nSequence)
preimage += hashOutputs
preimage += struct.pack("<I", tx.tx.nLockTime)
preimage += struct.pack("<I", blank_tx.nLockTime)
preimage += b"\x01\x00\x00\x00"

# hash it
Expand Down
12 changes: 5 additions & 7 deletions hwilib/devices/jade.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@
parse_path
)
from ..psbt import PSBT
from ..tx import (
CTransaction
)
from .._script import (
is_p2sh,
is_p2wpkh,
Expand Down Expand Up @@ -192,7 +189,7 @@ def _split_at_last_hardened_element(path: Sequence[int]) -> Tuple[Sequence[int],
paths.append(suffix)
return signers, paths

c_txn = CTransaction(tx.tx)
c_txn = tx.get_unsigned_tx()
master_fp = self.get_master_fingerprint()
signing_singlesigs = False
signing_multisigs = {}
Expand All @@ -204,7 +201,7 @@ def _split_at_last_hardened_element(path: Sequence[int]) -> Tuple[Sequence[int],

# Signing input details
jade_inputs = []
for n_vin, (txin, psbtin) in py_enumerate(zip(c_txn.vin, tx.inputs)):
for n_vin, psbtin in py_enumerate(tx.inputs):
# Get bip32 path to use to sign, if required for this input
path = None
multisig_input = len(psbtin.hd_keypaths) > 1
Expand All @@ -230,9 +227,10 @@ def _split_at_last_hardened_element(path: Sequence[int]) -> Tuple[Sequence[int],
if psbtin.witness_utxo:
utxo = psbtin.witness_utxo
if psbtin.non_witness_utxo:
if txin.prevout.hash != psbtin.non_witness_utxo.sha256:
if psbtin.prev_txid != psbtin.non_witness_utxo.hash:
raise BadArgumentError(f'Input {n_vin} has a non_witness_utxo with the wrong hash')
utxo = psbtin.non_witness_utxo.vout[txin.prevout.n]
assert psbtin.prev_out is not None
utxo = psbtin.non_witness_utxo.vout[psbtin.prev_out]
input_txn_bytes = psbtin.non_witness_utxo.serialize_without_witness()
if utxo is None:
raise Exception('PSBT is missing input utxo information, cannot sign')
Expand Down
5 changes: 1 addition & 4 deletions hwilib/devices/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@
is_witness,
)
from ..psbt import PSBT
from ..tx import (
CTransaction,
)
import logging
import re

Expand Down Expand Up @@ -184,7 +181,7 @@ def sign_tx(self, tx: PSBT) -> PSBT:
- Transactions containing both segwit and non-segwit inputs are not entirely supported; only the segwit inputs wil lbe signed in this case.
"""
c_tx = CTransaction(tx.tx)
c_tx = tx.get_unsigned_tx()
tx_bytes = c_tx.serialize_with_witness()

# Master key fingerprint
Expand Down
29 changes: 18 additions & 11 deletions hwilib/devices/trezor.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
from types import MethodType

import base64
import builtins
import getpass
import logging
import sys
Expand Down Expand Up @@ -362,11 +363,15 @@ def sign_tx(self, tx: PSBT) -> PSBT:
inputs = []
to_ignore = [] # Note down which inputs whose signatures we're going to ignore
has_tr = False
for input_num, (psbt_in, txin) in py_enumerate(list(zip(tx.inputs, tx.tx.vin))):
for input_num, psbt_in in builtins.enumerate(tx.inputs):
assert psbt_in.prev_txid is not None
assert psbt_in.prev_out is not None
assert psbt_in.sequence is not None

txinputtype = messages.TxInputType(
prev_hash=ser_uint256(txin.prevout.hash)[::-1],
prev_index=txin.prevout.n,
sequence=txin.nSequence,
prev_hash=psbt_in.prev_txid[::-1],
prev_index=psbt_in.prev_out,
sequence=psbt_in.sequence,
)

# Detrermine spend type
Expand All @@ -375,9 +380,9 @@ def sign_tx(self, tx: PSBT) -> PSBT:
if psbt_in.witness_utxo:
utxo = psbt_in.witness_utxo
if psbt_in.non_witness_utxo:
if txin.prevout.hash != psbt_in.non_witness_utxo.sha256:
if psbt_in.prev_txid != psbt_in.non_witness_utxo.hash:
raise BadArgumentError('Input {} has a non_witness_utxo with the wrong hash'.format(input_num))
utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n]
utxo = psbt_in.non_witness_utxo.vout[psbt_in.prev_out]
if utxo is None:
continue
scriptcode = utxo.scriptPubKey
Expand Down Expand Up @@ -502,7 +507,8 @@ def ignore_input() -> None:

# prepare outputs
outputs = []
for i, out in py_enumerate(tx.tx.vout):
for psbt_out in tx.outputs:
out = psbt_out.get_txout()
txoutput = messages.TxOutputType(amount=out.nValue)
txoutput.script_type = messages.OutputScriptType.PAYTOADDRESS
if out.is_p2pkh():
Expand All @@ -520,7 +526,6 @@ def ignore_input() -> None:
raise BadArgumentError("Output is not an address")

# Add the derivation path for change
psbt_out = tx.outputs[i]
for _, keypath in psbt_out.hd_keypaths.items():
if keypath.fingerprint != master_fp:
continue
Expand Down Expand Up @@ -575,19 +580,21 @@ def ignore_input() -> None:
script_pubkey=vout.scriptPubKey,
)
t.bin_outputs.append(o)
logging.debug(psbt_in.non_witness_utxo.hash)
assert(psbt_in.non_witness_utxo.hash is not None)
logging.debug(psbt_in.non_witness_utxo.hash.hex())
assert psbt_in.non_witness_utxo.sha256 is not None
prevtxs[ser_uint256(psbt_in.non_witness_utxo.sha256)[::-1]] = t

# Sign the transaction
assert tx.tx_version is not None
signed_tx = btc.sign_tx(
client=self.client,
coin_name=self.coin_name,
inputs=inputs,
outputs=outputs,
prev_txes=prevtxs,
version=tx.tx.nVersion,
lock_time=tx.tx.nLockTime,
version=tx.tx_version,
lock_time=tx.compute_lock_time(),
)

# Each input has one signature
Expand Down

0 comments on commit 0d846a6

Please sign in to comment.