2018-11-08 17:15:49 +00:00
|
|
|
# This file is part of the Trezor project.
|
|
|
|
#
|
2019-05-29 16:44:09 +00:00
|
|
|
# Copyright (C) 2012-2019 SatoshiLabs and contributors
|
2018-11-08 17:15:49 +00:00
|
|
|
#
|
|
|
|
# This library is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Lesser General Public License version 3
|
|
|
|
# as published by the Free Software Foundation.
|
|
|
|
#
|
|
|
|
# This library is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Lesser General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the License along with this library.
|
|
|
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
|
|
|
|
2020-03-04 12:38:06 +00:00
|
|
|
from decimal import Decimal
|
|
|
|
|
2020-03-24 13:18:08 +00:00
|
|
|
from . import exceptions, messages
|
|
|
|
from .tools import expect, normalize_nfc, session
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
|
2020-03-04 12:38:06 +00:00
|
|
|
def from_json(json_dict):
|
|
|
|
def make_input(vin):
|
|
|
|
i = messages.TxInputType()
|
|
|
|
if "coinbase" in vin:
|
|
|
|
i.prev_hash = b"\0" * 32
|
|
|
|
i.prev_index = 0xFFFFFFFF # signed int -1
|
|
|
|
i.script_sig = bytes.fromhex(vin["coinbase"])
|
|
|
|
i.sequence = vin["sequence"]
|
|
|
|
|
|
|
|
else:
|
|
|
|
i.prev_hash = bytes.fromhex(vin["txid"])
|
|
|
|
i.prev_index = vin["vout"]
|
|
|
|
i.script_sig = bytes.fromhex(vin["scriptSig"]["hex"])
|
|
|
|
i.sequence = vin["sequence"]
|
|
|
|
|
|
|
|
return i
|
|
|
|
|
|
|
|
def make_bin_output(vout):
|
|
|
|
o = messages.TxOutputBinType()
|
|
|
|
o.amount = int(Decimal(vout["value"]) * (10 ** 8))
|
|
|
|
o.script_pubkey = bytes.fromhex(vout["scriptPubKey"]["hex"])
|
|
|
|
return o
|
|
|
|
|
|
|
|
t = messages.TransactionType()
|
|
|
|
t.version = json_dict["version"]
|
|
|
|
t.lock_time = json_dict.get("locktime")
|
|
|
|
t.inputs = [make_input(vin) for vin in json_dict["vin"]]
|
|
|
|
t.bin_outputs = [make_bin_output(vout) for vout in json_dict["vout"]]
|
|
|
|
return t
|
|
|
|
|
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
@expect(messages.PublicKey)
|
2018-08-13 16:21:24 +00:00
|
|
|
def get_public_node(
|
2018-09-05 13:21:11 +00:00
|
|
|
client,
|
|
|
|
n,
|
|
|
|
ecdsa_curve_name=None,
|
|
|
|
show_display=False,
|
|
|
|
coin_name=None,
|
2018-10-30 13:56:57 +00:00
|
|
|
script_type=messages.InputScriptType.SPENDADDRESS,
|
2018-08-13 16:21:24 +00:00
|
|
|
):
|
|
|
|
return client.call(
|
2018-10-30 13:56:57 +00:00
|
|
|
messages.GetPublicKey(
|
2018-08-13 16:21:24 +00:00
|
|
|
address_n=n,
|
|
|
|
ecdsa_curve_name=ecdsa_curve_name,
|
|
|
|
show_display=show_display,
|
|
|
|
coin_name=coin_name,
|
2018-09-05 13:21:11 +00:00
|
|
|
script_type=script_type,
|
2018-08-13 16:21:24 +00:00
|
|
|
)
|
|
|
|
)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
@expect(messages.Address, field="address")
|
2018-08-13 16:21:24 +00:00
|
|
|
def get_address(
|
|
|
|
client,
|
|
|
|
coin_name,
|
|
|
|
n,
|
|
|
|
show_display=False,
|
|
|
|
multisig=None,
|
2018-10-30 13:56:57 +00:00
|
|
|
script_type=messages.InputScriptType.SPENDADDRESS,
|
2018-08-13 16:21:24 +00:00
|
|
|
):
|
2018-10-30 13:56:57 +00:00
|
|
|
return client.call(
|
|
|
|
messages.GetAddress(
|
|
|
|
address_n=n,
|
|
|
|
coin_name=coin_name,
|
|
|
|
show_display=show_display,
|
|
|
|
multisig=multisig,
|
|
|
|
script_type=script_type,
|
2018-08-13 16:21:24 +00:00
|
|
|
)
|
2018-10-30 13:56:57 +00:00
|
|
|
)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
|
2020-06-16 19:04:43 +00:00
|
|
|
@expect(messages.OwnershipId, field="ownership_id")
|
|
|
|
def get_ownership_id(
|
|
|
|
client,
|
|
|
|
coin_name,
|
|
|
|
n,
|
|
|
|
multisig=None,
|
|
|
|
script_type=messages.InputScriptType.SPENDADDRESS,
|
|
|
|
):
|
|
|
|
return client.call(
|
|
|
|
messages.GetOwnershipId(
|
|
|
|
address_n=n,
|
|
|
|
coin_name=coin_name,
|
|
|
|
multisig=multisig,
|
|
|
|
script_type=script_type,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def get_ownership_proof(
|
|
|
|
client,
|
|
|
|
coin_name,
|
|
|
|
n,
|
|
|
|
multisig=None,
|
|
|
|
script_type=messages.InputScriptType.SPENDADDRESS,
|
|
|
|
user_confirmation=False,
|
|
|
|
ownership_ids=None,
|
|
|
|
commitment_data=None,
|
|
|
|
):
|
|
|
|
res = client.call(
|
|
|
|
messages.GetOwnershipProof(
|
|
|
|
address_n=n,
|
|
|
|
coin_name=coin_name,
|
|
|
|
script_type=script_type,
|
|
|
|
multisig=multisig,
|
|
|
|
user_confirmation=user_confirmation,
|
|
|
|
ownership_ids=ownership_ids,
|
|
|
|
commitment_data=commitment_data,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if not isinstance(res, messages.OwnershipProof):
|
|
|
|
raise exceptions.TrezorException("Unexpected message")
|
|
|
|
|
|
|
|
return res.ownership_proof, res.signature
|
|
|
|
|
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
@expect(messages.MessageSignature)
|
2018-08-13 16:21:24 +00:00
|
|
|
def sign_message(
|
2018-10-30 13:56:57 +00:00
|
|
|
client, coin_name, n, message, script_type=messages.InputScriptType.SPENDADDRESS
|
2018-08-13 16:21:24 +00:00
|
|
|
):
|
2018-06-13 17:04:18 +00:00
|
|
|
message = normalize_nfc(message)
|
2018-08-13 16:21:24 +00:00
|
|
|
return client.call(
|
2018-10-30 13:56:57 +00:00
|
|
|
messages.SignMessage(
|
2018-08-13 16:21:24 +00:00
|
|
|
coin_name=coin_name, address_n=n, message=message, script_type=script_type
|
|
|
|
)
|
|
|
|
)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
def verify_message(client, coin_name, address, signature, message):
|
|
|
|
message = normalize_nfc(message)
|
|
|
|
try:
|
2018-08-13 16:21:24 +00:00
|
|
|
resp = client.call(
|
2018-10-30 13:56:57 +00:00
|
|
|
messages.VerifyMessage(
|
2018-08-13 16:21:24 +00:00
|
|
|
address=address,
|
|
|
|
signature=signature,
|
|
|
|
message=message,
|
|
|
|
coin_name=coin_name,
|
|
|
|
)
|
|
|
|
)
|
2020-03-24 15:02:48 +00:00
|
|
|
except exceptions.TrezorFailure:
|
2020-03-24 13:18:08 +00:00
|
|
|
return False
|
2018-10-30 13:56:57 +00:00
|
|
|
return isinstance(resp, messages.Success)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
@session
|
2018-10-30 13:56:57 +00:00
|
|
|
def sign_tx(client, coin_name, inputs, outputs, details=None, prev_txes=None):
|
2020-01-24 16:51:43 +00:00
|
|
|
this_tx = messages.TransactionType(inputs=inputs, outputs=outputs)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
if details is None:
|
|
|
|
signtx = messages.SignTx()
|
|
|
|
else:
|
|
|
|
signtx = details
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
signtx.coin_name = coin_name
|
|
|
|
signtx.inputs_count = len(inputs)
|
|
|
|
signtx.outputs_count = len(outputs)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
res = client.call(signtx)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
# Prepare structure for signatures
|
2020-05-25 22:24:41 +00:00
|
|
|
signatures = [
|
|
|
|
None if i.script_type != messages.InputScriptType.EXTERNAL else ""
|
|
|
|
for i in inputs
|
|
|
|
]
|
2018-10-30 13:56:57 +00:00
|
|
|
serialized_tx = b""
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
def copy_tx_meta(tx):
|
2019-03-05 15:28:17 +00:00
|
|
|
tx_copy = messages.TransactionType(**tx)
|
2018-10-30 13:56:57 +00:00
|
|
|
# clear fields
|
|
|
|
tx_copy.inputs_cnt = len(tx.inputs)
|
|
|
|
tx_copy.inputs = []
|
|
|
|
tx_copy.outputs_cnt = len(tx.bin_outputs or tx.outputs)
|
|
|
|
tx_copy.outputs = []
|
|
|
|
tx_copy.bin_outputs = []
|
|
|
|
tx_copy.extra_data_len = len(tx.extra_data or b"")
|
|
|
|
tx_copy.extra_data = None
|
|
|
|
return tx_copy
|
|
|
|
|
|
|
|
R = messages.RequestType
|
|
|
|
while isinstance(res, messages.TxRequest):
|
2018-06-13 17:04:18 +00:00
|
|
|
# If there's some part of signed transaction, let's add it
|
2018-10-30 13:56:57 +00:00
|
|
|
if res.serialized:
|
|
|
|
if res.serialized.serialized_tx:
|
|
|
|
serialized_tx += res.serialized.serialized_tx
|
|
|
|
|
|
|
|
if res.serialized.signature_index is not None:
|
|
|
|
idx = res.serialized.signature_index
|
|
|
|
sig = res.serialized.signature
|
|
|
|
if signatures[idx] is not None:
|
|
|
|
raise ValueError("Signature for index %d already filled" % idx)
|
|
|
|
signatures[idx] = sig
|
|
|
|
|
|
|
|
if res.request_type == R.TXFINISHED:
|
2018-06-13 17:04:18 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
# Device asked for one more information, let's process it.
|
2020-01-24 16:51:43 +00:00
|
|
|
if res.details.tx_hash is not None:
|
|
|
|
current_tx = prev_txes[res.details.tx_hash]
|
|
|
|
else:
|
|
|
|
current_tx = this_tx
|
2018-10-30 13:56:57 +00:00
|
|
|
|
|
|
|
if res.request_type == R.TXMETA:
|
|
|
|
msg = copy_tx_meta(current_tx)
|
|
|
|
res = client.call(messages.TxAck(tx=msg))
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
elif res.request_type == R.TXINPUT:
|
|
|
|
msg = messages.TransactionType()
|
2018-06-13 17:04:18 +00:00
|
|
|
msg.inputs = [current_tx.inputs[res.details.request_index]]
|
2018-10-30 13:56:57 +00:00
|
|
|
res = client.call(messages.TxAck(tx=msg))
|
|
|
|
|
|
|
|
elif res.request_type == R.TXOUTPUT:
|
|
|
|
msg = messages.TransactionType()
|
2018-06-13 17:04:18 +00:00
|
|
|
if res.details.tx_hash:
|
|
|
|
msg.bin_outputs = [current_tx.bin_outputs[res.details.request_index]]
|
|
|
|
else:
|
|
|
|
msg.outputs = [current_tx.outputs[res.details.request_index]]
|
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
res = client.call(messages.TxAck(tx=msg))
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
elif res.request_type == R.TXEXTRADATA:
|
2018-06-13 17:04:18 +00:00
|
|
|
o, l = res.details.extra_data_offset, res.details.extra_data_len
|
2018-10-30 13:56:57 +00:00
|
|
|
msg = messages.TransactionType()
|
2018-08-13 16:21:24 +00:00
|
|
|
msg.extra_data = current_tx.extra_data[o : o + l]
|
2018-10-30 13:56:57 +00:00
|
|
|
res = client.call(messages.TxAck(tx=msg))
|
|
|
|
|
|
|
|
if not isinstance(res, messages.TxRequest):
|
2020-03-24 13:18:08 +00:00
|
|
|
raise exceptions.TrezorException("Unexpected message")
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
if None in signatures:
|
2020-03-24 13:18:08 +00:00
|
|
|
raise exceptions.TrezorException("Some signatures are missing!")
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-10-30 13:56:57 +00:00
|
|
|
return signatures, serialized_tx
|
2020-07-03 14:52:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
@expect(messages.Success, field="message")
|
|
|
|
def authorize_coinjoin(
|
|
|
|
client,
|
|
|
|
coordinator,
|
|
|
|
max_total_fee,
|
|
|
|
n,
|
|
|
|
coin_name,
|
|
|
|
fee_per_anonymity=None,
|
|
|
|
script_type=messages.InputScriptType.SPENDADDRESS,
|
|
|
|
):
|
|
|
|
return client.call(
|
|
|
|
messages.AuthorizeCoinJoin(
|
|
|
|
coordinator=coordinator,
|
|
|
|
max_total_fee=max_total_fee,
|
|
|
|
address_n=n,
|
|
|
|
coin_name=coin_name,
|
|
|
|
fee_per_anonymity=fee_per_anonymity,
|
|
|
|
script_type=script_type,
|
|
|
|
)
|
|
|
|
)
|