Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

elements: support explicit transaction signing #473

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions common/protob/messages-bitcoin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ message TxAck {
optional uint32 timestamp = 13; // only for Capricoin, transaction timestamp
optional uint32 branch_id = 14; // only for Zcash, BRANCH_ID when overwintered is set
/**
* Structure representing confidential asset amount
*/
message TxConfidentialValue {
required bytes value = 1;
required bytes asset = 2;
optional bytes nonce = 3;
}
/**
* Structure representing transaction input
*/
message TxInputType {
Expand All @@ -216,6 +224,8 @@ message TxAck {
optional uint32 decred_script_version = 10; // only for Decred
optional bytes prev_block_hash_bip115 = 11; // block hash of previous transaction output (for bip115 implementation)
optional uint32 prev_block_height_bip115 = 12; // block height of previous transaction output (for bip115 implementation)
optional TxConfidentialValue confidential_value = 13;
optional bytes issuance = 14;
}
/**
* Structure representing compiled transaction output
Expand All @@ -224,6 +234,7 @@ message TxAck {
required uint64 amount = 1;
required bytes script_pubkey = 2;
optional uint32 decred_script_version = 3; // only for Decred
optional TxConfidentialValue confidential_value = 4;
}
/**
* Structure representing transaction output
Expand All @@ -238,6 +249,7 @@ message TxAck {
optional uint32 decred_script_version = 7; // only for Decred
optional bytes block_hash_bip115 = 8; // block hash of existing block (recommended current_block - 300) (for bip115 implementation)
optional uint32 block_height_bip115 = 9; // block height of existing block (recommended current_block - 300) (for bip115 implementation)
optional TxConfidentialValue confidential_value = 10;
enum OutputScriptType {
PAYTOADDRESS = 0; // used for all addresses (bitcoin, p2sh, witness)
PAYTOSCRIPTHASH = 1; // p2sh address (deprecated; use PAYTOADDRESS)
Expand Down
3 changes: 3 additions & 0 deletions core/src/apps/wallet/sign_tx/decred.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def add_prevouts(self, txi: TxInputType):
def add_sequence(self, txi: TxInputType):
pass

def add_issuance(self, txi: TxInputType):
pass

def add_output_count(self, tx: SignTx):
write_varint(self.h_prefix, tx.outputs_count)

Expand Down
21 changes: 20 additions & 1 deletion core/src/apps/wallet/sign_tx/segwit_bip143.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Bip143:
def __init__(self):
self.h_prevouts = HashWriter(sha256())
self.h_sequence = HashWriter(sha256())
self.h_issuance = HashWriter(sha256())
self.h_outputs = HashWriter(sha256())

def add_prevouts(self, txi: TxInputType):
Expand All @@ -36,6 +37,12 @@ def add_prevouts(self, txi: TxInputType):
def add_sequence(self, txi: TxInputType):
write_uint32(self.h_sequence, txi.sequence)

def add_issuance(self, txi: TxInputType):
# TODO: handle issuance properly, ignore for now
issuance = txi.issuance or b""
write_varint(self.h_issuance, len(issuance))
write_bytes(self.h_issuance, issuance)

def add_output(self, txo_bin: TxOutputBinType):
write_tx_output(self.h_outputs, txo_bin)

Expand All @@ -45,6 +52,9 @@ def get_prevouts_hash(self, coin: CoinInfo) -> bytes:
def get_sequence_hash(self, coin: CoinInfo) -> bytes:
return get_tx_hash(self.h_sequence, double=coin.sign_hash_double)

def get_issuance_hash(self, coin: CoinInfo) -> bytes:
return get_tx_hash(self.h_issuance, double=coin.sign_hash_double)

def get_outputs_hash(self, coin: CoinInfo) -> bytes:
return get_tx_hash(self.h_outputs, double=coin.sign_hash_double)

Expand All @@ -63,6 +73,8 @@ def preimage_hash(
write_uint32(h_preimage, tx.version) # nVersion
write_bytes(h_preimage, self.get_prevouts_hash(coin)) # hashPrevouts
write_bytes(h_preimage, self.get_sequence_hash(coin)) # hashSequence
if coin.confidential_assets:
write_bytes(h_preimage, self.get_issuance_hash(coin)) # hashIssuance

write_bytes_reversed(h_preimage, txi.prev_hash) # outpoint
write_uint32(h_preimage, txi.prev_index) # outpoint
Expand All @@ -71,7 +83,14 @@ def preimage_hash(
write_varint(h_preimage, len(script_code))
write_bytes(h_preimage, script_code)

write_uint64(h_preimage, txi.amount) # amount
if coin.confidential_assets:
ensure(
txi.confidential_value is not None,
"txi.confidential_value must be set for " + coin.coin_name,
)
write_bytes(h_preimage, txi.confidential_value.value)
else:
write_uint64(h_preimage, txi.amount)
write_uint32(h_preimage, txi.sequence) # nSequence
write_bytes(h_preimage, self.get_outputs_hash(coin)) # hashOutputs
write_uint32(h_preimage, tx.lock_time) # nLockTime
Expand Down
46 changes: 39 additions & 7 deletions core/src/apps/wallet/sign_tx/signing.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,18 @@ async def check_tx_fee(tx: SignTx, keychain: seed.Keychain):
weight.add_input(txi)
hash143.add_prevouts(txi) # all inputs are included (non-segwit as well)
hash143.add_sequence(txi)
hash143.add_issuance(txi) # TODO: support also non-Elements hashing

if not addresses.validate_full_path(txi.address_n, coin, txi.script_type):
await helpers.confirm_foreign_address(txi.address_n)

if txi.multisig:
multifp.add(txi.multisig)

if txi.script_type in (
InputScriptType.SPENDWITNESS,
InputScriptType.SPENDP2SHWITNESS,
):
if (
txi.script_type
in (InputScriptType.SPENDWITNESS, InputScriptType.SPENDP2SHWITNESS)
) or coin.confidential_assets:
if not coin.segwit:
raise SigningError(
FailureType.DataError, "Segwit not enabled on this coin"
Expand Down Expand Up @@ -163,6 +164,8 @@ async def check_tx_fee(tx: SignTx, keychain: seed.Keychain):
txo = await helpers.request_tx_output(tx_req, o)
txo_bin.amount = txo.amount
txo_bin.script_pubkey = output_derive_script(txo, coin, keychain)
if coin.confidential_assets:
txo_bin.confidential_value = txo.confidential_value
weight.add_output(txo_bin.script_pubkey)

if change_out == 0 and output_is_change(txo, wallet_path, segwit_in, multifp):
Expand Down Expand Up @@ -501,6 +504,8 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):
txo = await helpers.request_tx_output(tx_req, o)
txo_bin.amount = txo.amount
txo_bin.script_pubkey = output_derive_script(txo, coin, keychain)
if coin.confidential_assets:
txo_bin.confidential_value = txo.confidential_value

# serialize output
w_txo_bin = writers.empty_bytearray(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4)
Expand All @@ -514,6 +519,10 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):

tx_req.serialized = tx_ser

if coin.confidential_assets:
# In Elements, nLockTime is serialized before the witness
writers.write_uint32(tx_ser.serialized_tx, tx.lock_time)

any_segwit = True in segwit.values()

for i in range(tx.inputs_count):
Expand Down Expand Up @@ -553,7 +562,19 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):
signature, key_sign_pub, get_hash_type(coin)
)

tx_ser.serialized_tx = witness
if coin.confidential_assets:
# In Elements, there are more input-related witnesses
issuance_amount_rangeproof = bytearray([0])
inflation_keys_rangeproof = bytearray([0])
pegin_witness = bytearray([0])
tx_ser.serialized_tx = (
issuance_amount_rangeproof
+ inflation_keys_rangeproof
+ witness
+ pegin_witness
)
else:
tx_ser.serialized_tx = witness
tx_ser.signature_index = i
tx_ser.signature = signature
elif any_segwit:
Expand All @@ -563,7 +584,13 @@ async def sign_tx(tx: SignTx, keychain: seed.Keychain):

tx_req.serialized = tx_ser

writers.write_uint32(tx_ser.serialized_tx, tx.lock_time)
if coin.confidential_assets:
# In Elements, each blinded output should have range and surjection proofs.
for o in range(tx.outputs_count):
writers.write_varint(tx_ser.serialized_tx, 0) # surjection proof
writers.write_varint(tx_ser.serialized_tx, 0) # output amount rangeproof
else:
writers.write_uint32(tx_ser.serialized_tx, tx.lock_time)

if not utils.BITCOIN_ONLY and tx.overwintered:
if tx.version == 3:
Expand Down Expand Up @@ -684,7 +711,9 @@ def get_tx_header(coin: coininfo.CoinInfo, tx: SignTx, segwit: bool = False):
if tx.timestamp:
writers.write_uint32(w_txi, tx.timestamp)
if segwit:
writers.write_varint(w_txi, 0x00) # segwit witness marker
if not coin.confidential_assets:
# Elements doesn't use witness marker, only flag
writers.write_varint(w_txi, 0x00) # segwit witness marker
writers.write_varint(w_txi, 0x01) # segwit witness flag
writers.write_varint(w_txi, tx.inputs_count)
return w_txi
Expand Down Expand Up @@ -713,6 +742,9 @@ def output_derive_script(
o.address = get_address_for_change(o, coin, keychain)
else:
if not o.address:
if coin.confidential_assets:
# An empty address marks explicit fee output in Elements
return b""
raise SigningError(FailureType.DataError, "Missing address")

if coin.bech32_prefix and o.address.startswith(coin.bech32_prefix):
Expand Down
9 changes: 8 additions & 1 deletion core/src/apps/wallet/sign_tx/writers.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ def write_tx_input_decred_witness(w, i: TxInputType):


def write_tx_output(w, o: TxOutputBinType):
write_uint64(w, o.amount)
if o.confidential_value is not None:
# serialize Elements output:
write_bytes(w, o.confidential_value.asset)
write_bytes(w, o.confidential_value.value)
write_bytes(w, o.confidential_value.nonce)
else:
write_uint64(w, o.amount)

if o.decred_script_version is not None:
write_uint16(w, o.decred_script_version)
write_varint(w, len(o.script_pubkey))
Expand Down
3 changes: 3 additions & 0 deletions core/src/apps/wallet/sign_tx/zcash.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ def add_prevouts(self, txi: TxInputType):
def add_sequence(self, txi: TxInputType):
write_uint32(self.h_sequence, txi.sequence)

def add_issuance(self, txi: TxInputType):
pass

def add_output(self, txo_bin: TxOutputBinType):
write_tx_output(self.h_outputs, txo_bin)

Expand Down
31 changes: 31 additions & 0 deletions core/src/trezor/messages/TxConfidentialValue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Automatically generated by pb2py
# fmt: off
import protobuf as p

if __debug__:
try:
from typing import Dict, List, Optional
from typing_extensions import Literal # noqa: F401
except ImportError:
Dict, List, Optional = None, None, None # type: ignore


class TxConfidentialValue(p.MessageType):

def __init__(
self,
value: bytes = None,
asset: bytes = None,
nonce: bytes = None,
) -> None:
self.value = value
self.asset = asset
self.nonce = nonce

@classmethod
def get_fields(cls) -> Dict:
return {
1: ('value', p.BytesType, 0), # required
2: ('asset', p.BytesType, 0), # required
3: ('nonce', p.BytesType, 0),
}
7 changes: 7 additions & 0 deletions core/src/trezor/messages/TxInputType.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import protobuf as p

from .MultisigRedeemScriptType import MultisigRedeemScriptType
from .TxConfidentialValue import TxConfidentialValue

if __debug__:
try:
Expand Down Expand Up @@ -30,6 +31,8 @@ def __init__(
decred_script_version: int = None,
prev_block_hash_bip115: bytes = None,
prev_block_height_bip115: int = None,
confidential_value: TxConfidentialValue = None,
issuance: bytes = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.prev_hash = prev_hash
Expand All @@ -43,6 +46,8 @@ def __init__(
self.decred_script_version = decred_script_version
self.prev_block_hash_bip115 = prev_block_hash_bip115
self.prev_block_height_bip115 = prev_block_height_bip115
self.confidential_value = confidential_value
self.issuance = issuance

@classmethod
def get_fields(cls) -> Dict:
Expand All @@ -59,4 +64,6 @@ def get_fields(cls) -> Dict:
10: ('decred_script_version', p.UVarintType, 0),
11: ('prev_block_hash_bip115', p.BytesType, 0),
12: ('prev_block_height_bip115', p.UVarintType, 0),
13: ('confidential_value', TxConfidentialValue, 0),
14: ('issuance', p.BytesType, 0),
}
5 changes: 5 additions & 0 deletions core/src/trezor/messages/TxOutputBinType.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# fmt: off
import protobuf as p

from .TxConfidentialValue import TxConfidentialValue

if __debug__:
try:
from typing import Dict, List, Optional
Expand All @@ -17,15 +19,18 @@ def __init__(
amount: int = None,
script_pubkey: bytes = None,
decred_script_version: int = None,
confidential_value: TxConfidentialValue = None,
) -> None:
self.amount = amount
self.script_pubkey = script_pubkey
self.decred_script_version = decred_script_version
self.confidential_value = confidential_value

@classmethod
def get_fields(cls) -> Dict:
return {
1: ('amount', p.UVarintType, 0), # required
2: ('script_pubkey', p.BytesType, 0), # required
3: ('decred_script_version', p.UVarintType, 0),
4: ('confidential_value', TxConfidentialValue, 0),
}
4 changes: 4 additions & 0 deletions core/src/trezor/messages/TxOutputType.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import protobuf as p

from .MultisigRedeemScriptType import MultisigRedeemScriptType
from .TxConfidentialValue import TxConfidentialValue

if __debug__:
try:
Expand All @@ -27,6 +28,7 @@ def __init__(
decred_script_version: int = None,
block_hash_bip115: bytes = None,
block_height_bip115: int = None,
confidential_value: TxConfidentialValue = None,
) -> None:
self.address = address
self.address_n = address_n if address_n is not None else []
Expand All @@ -37,6 +39,7 @@ def __init__(
self.decred_script_version = decred_script_version
self.block_hash_bip115 = block_hash_bip115
self.block_height_bip115 = block_height_bip115
self.confidential_value = confidential_value

@classmethod
def get_fields(cls) -> Dict:
Expand All @@ -50,4 +53,5 @@ def get_fields(cls) -> Dict:
7: ('decred_script_version', p.UVarintType, 0),
8: ('block_hash_bip115', p.BytesType, 0),
9: ('block_height_bip115', p.UVarintType, 0),
10: ('confidential_value', TxConfidentialValue, 0),
}
31 changes: 31 additions & 0 deletions python/src/trezorlib/messages/TxConfidentialValue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Automatically generated by pb2py
# fmt: off
from .. import protobuf as p

if __debug__:
try:
from typing import Dict, List, Optional
from typing_extensions import Literal # noqa: F401
except ImportError:
Dict, List, Optional = None, None, None # type: ignore


class TxConfidentialValue(p.MessageType):

def __init__(
self,
value: bytes = None,
asset: bytes = None,
nonce: bytes = None,
) -> None:
self.value = value
self.asset = asset
self.nonce = nonce

@classmethod
def get_fields(cls) -> Dict:
return {
1: ('value', p.BytesType, 0), # required
2: ('asset', p.BytesType, 0), # required
3: ('nonce', p.BytesType, 0),
}
Loading