From b9925432cd4ab33e67861f2ffda3379a9e40bf0c Mon Sep 17 00:00:00 2001 From: slush0 Date: Mon, 13 Jan 2014 04:44:57 +0100 Subject: [PATCH] Basic blockchain.info API SimpleSignTx basic unit test --- cmd.py | 6 +++-- tests/test_signtx.py | 36 +++++++++++++++++++----------- trezorlib/api_blockchain.py | 41 ++++++++++++++++++++++++++++++++++ trezorlib/ckd_public.py | 2 ++ trezorlib/client.py | 44 +++++++++++++++++++++++++++++++------ 5 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 trezorlib/api_blockchain.py diff --git a/cmd.py b/cmd.py index e5d571a570..21dde28e01 100755 --- a/cmd.py +++ b/cmd.py @@ -113,10 +113,12 @@ class Commands(object): if args.mnemonic: mnemonic = ' '.join(args.mnemonic) - return self.client.load_device_by_mnemonic(mnemonic, args.pin, args.passphrase_protection, args.label) + return self.client.load_device_by_mnemonic(mnemonic, args.pin, + args.passphrase_protection, args.label, 'english') else: - return self.client.load_device_by_xprv(args.xprv, args.pin, args.passphrase_protection, args.label) + return self.client.load_device_by_xprv(args.xprv, args.pin, + args.passphrase_protection, args.label, 'english') def reset_device(self, args): return self.client.reset_device(True, args.strength, args.passphrase, args.pin, args.label) diff --git a/tests/test_signtx.py b/tests/test_signtx.py index 1974fe7bf0..832e0a7470 100644 --- a/tests/test_signtx.py +++ b/tests/test_signtx.py @@ -3,20 +3,30 @@ import common import binascii import trezorlib.messages_pb2 as proto +import trezorlib.types_pb2 as proto_types -''' -./electrum -w ~/.electrum-bitkey mktx 1FQVPnjrbkPWeA8poUoEnX9U3n9DyhAVtv 0.001 ---fromaddr 13jqMdrZAzMYkaFqHGv5GbJavdZpoKDkSL --changeaddr 17GpAFnkHRjWKePkX4kxHaHy49V8EHTr7i --fee 0.0005 - -01000000012de70f7d6ffed0db70f8882f3fca90db9bb09f0e99bce27468c23d3c994fcd56 -010000008b4830450221009b985e14d53cfeed3496846db6ddaa77a0206138d0df4c2ccd3b -759e91bae3e1022004c76e10f99ccac8ced761719181a96bae25a74829eab3ecb8f29eb07f -e18f7e01410436ae8595f03a7324d1d1482ede8560a4508c767fbc662559482d5759b32209 -a62964699995f6e018cfbeb7a71a66d4c64fa38875d79ead0a9ac66f59c1c8b3a3ffffffff -0250c30000000000001976a91444ce5c6789b0bb0e8a9ab9a4769fe181cb274c4688aca086 -0100000000001976a9149e03078026388661b197129a43f0f64f88379ce688ac00000000 -''' class TestSignTx(common.TrezorTest): + + def test_simplesigntx(self): + # tx: d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882 + # input 0: 0.0039 BTC + + inp1 = proto_types.TxInputType(address_n=[0], # 14LmW5k4ssUrtbAB4255zdqv3b4w1TuX9e + # amount=390000, + prev_hash=binascii.unhexlify('d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882'), + prev_index=0, + ) + + out1 = proto_types.TxOutputType(address='1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1', + amount=380000, + script_type=proto_types.PAYTOADDRESS, + ) + + tx = self.client.simple_sign_tx('Bitcoin', [inp1, ], [out1, ]) + print binascii.hexlify(tx.serialized_tx) + self.assertEqual(binascii.hexlify(tx.serialized_tx), '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b4830450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede7810121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0160cc0500000000001976a914de9b2a8da088824e8fe51debea566617d851537888ac00000000') + + ''' def test_signtx(self): expected_tx = '01000000012de70f7d6ffed0db70f8882f3fca90db9bb09f0e99bce27468c23d3c994fcd56' \ '010000008b4830450221009b985e14d53cfeed3496846db6ddaa77a0206138d0df4c2ccd3b' \ @@ -51,7 +61,7 @@ class TestSignTx(common.TrezorTest): ) print binascii.hexlify(self.client.sign_tx([inp1], [out1, out2])[1]) - + ''' ''' def test_workflow(self): inp1 = proto.TxInput(index=0, diff --git a/trezorlib/api_blockchain.py b/trezorlib/api_blockchain.py new file mode 100644 index 0000000000..9ceaae2269 --- /dev/null +++ b/trezorlib/api_blockchain.py @@ -0,0 +1,41 @@ +''' + Blockchain API implementing Blockchain.info interface +''' +import binascii +import urllib2 +import json + +import types_pb2 as proto_types + +class BlockchainApi(object): + def _raw_tx(self, txhash): + # Download tx data from blockchain.info + f = urllib2.urlopen('http://blockchain.info/rawtx/%s?scripts=true' % txhash) + print 'got', txhash + return json.load(f) + + def submit(self, tx): + raise Exception("Not implemented yet") + + def get_tx(self, txhash): + # Build protobuf transaction structure from blockchain.info + d = self._raw_tx(txhash) + t = proto_types.TransactionType() + + for inp in d['inputs']: + di = self._raw_tx(inp['prev_out']['tx_index']) + i = t.inputs.add() + i.prev_hash = binascii.unhexlify(di['hash']) + i.prev_index = inp['prev_out']['n'] + i.script_sig = binascii.unhexlify(inp['script']) + + for output in d['out']: + o = t.outputs.add() + o.amount = output['value'] + o.script_pubkey = binascii.unhexlify(output['script']) + + return t + +if __name__ == '__main__': + api = BlockchainApi() + print api.get_tx('b9f382b8dfc34accc05491712a1ad8f7f075a02056dc4821d1f60702fb3fdb2f') diff --git a/trezorlib/ckd_public.py b/trezorlib/ckd_public.py index f3cc43e1c1..ec5886085f 100644 --- a/trezorlib/ckd_public.py +++ b/trezorlib/ckd_public.py @@ -6,6 +6,8 @@ from ecdsa.curves import SECP256k1 import messages_pb2 as proto +# FIXME, this isn't finished yet + PRIME_DERIVATION_FLAG = 0x80000000 def is_prime(n): diff --git a/trezorlib/client.py b/trezorlib/client.py index 82bf907b85..c37c1a18c2 100644 --- a/trezorlib/client.py +++ b/trezorlib/client.py @@ -7,6 +7,7 @@ import ckd_public import tools import messages_pb2 as proto import types_pb2 as types +from api_blockchain import BlockchainApi def show_message(message): print "MESSAGE FROM DEVICE:", message @@ -33,7 +34,9 @@ PRIME_DERIVATION_FLAG = 0x80000000 class TrezorClient(object): def __init__(self, transport, debuglink=None, - message_func=show_message, input_func=show_input, pin_func=pin_func, passphrase_func=passphrase_func, debug=False): + message_func=show_message, input_func=show_input, + pin_func=pin_func, passphrase_func=passphrase_func, + blockchain_api=None, debug=False): self.transport = transport self.debuglink = debuglink @@ -41,8 +44,14 @@ class TrezorClient(object): self.input_func = input_func self.pin_func = pin_func self.passphrase_func = passphrase_func + self.debug = debug + if blockchain_api: + self.blockchain = blockchain_api + else: + self.blockchain = BlockchainApi() + self.setup_debuglink() self.init_device() @@ -90,6 +99,7 @@ class TrezorClient(object): return self.call(proto.GetPublicKey(address_n=n)).node def get_address(self, coin_name, n): + n = self._convert_prime(n) return self.call(proto.GetAddress(address_n=n, coin_name=coin_name)).address def get_entropy(self, size): @@ -168,7 +178,8 @@ class TrezorClient(object): if resp.code == types.Failure_ActionCancelled: raise CallException("Action cancelled by user") - elif resp.code == types.Failure_PinInvalid: + elif resp.code in (types.Failure_PinInvalid, + types.Failure_PinCancelled, types.Failure_PinExpected): raise PinException("PIN is invalid") raise CallException(resp.code, resp.message) @@ -192,7 +203,24 @@ class TrezorClient(object): return False - def sign_tx(self, inputs, outputs): + def simple_sign_tx(self, coin_name, inputs, outputs): + msg = proto.SimpleSignTx() + msg.coin_name = coin_name + msg.inputs.extend(inputs) + msg.outputs.extend(outputs) + + known_hashes = [] + for inp in inputs: + if inp.prev_hash in known_hashes: + continue + + tx = msg.transactions.add() + tx.CopyFrom(self.blockchain.get_tx(binascii.hexlify(inp.prev_hash))) + known_hashes.append(inp.prev_hash) + + return self.call(msg) + + def sign_tx(self, coin_name, inputs, outputs): ''' inputs: list of TxInput outputs: list of TxOutput @@ -300,11 +328,11 @@ class TrezorClient(object): return s_inputs ''' - def reset_device(self, display_random, strength, passphrase_protection, pin_protection, label): + def reset_device(self, display_random, strength, passphrase_protection, pin_protection, label, language): # Begin with device reset workflow msg = proto.ResetDevice(display_random=display_random, strength=strength, - language='english', + language=language, passphrase_protection=bool(passphrase_protection), pin_protection=bool(pin_protection), label=label @@ -320,10 +348,10 @@ class TrezorClient(object): return isinstance(resp, proto.Success) - def load_device_by_mnemonic(self, mnemonic, pin, passphrase_protection, label): + def load_device_by_mnemonic(self, mnemonic, pin, passphrase_protection, label, language): resp = self.call(proto.LoadDevice(mnemonic=mnemonic, pin=pin, passphrase_protection=passphrase_protection, - language='english', + language=language, label=label)) self.init_device() return isinstance(resp, proto.Success) @@ -369,6 +397,8 @@ class TrezorClient(object): return isinstance(resp, proto.Success) def bip32_ckd(self, public_node, n): + raise Exception("Unfinished code") + if not isinstance(n, list): raise Exception('Parameter must be a list')