mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 06:48:16 +00:00
BIP32 public CKD
This commit is contained in:
parent
999194fa59
commit
91b2b637b4
@ -1,5 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import common
|
import common
|
||||||
|
import trezorlib.ckd_public as bip32
|
||||||
|
from trezorlib import tools
|
||||||
|
|
||||||
class TestAddresses(common.TrezorTest):
|
class TestAddresses(common.TrezorTest):
|
||||||
def test_btc(self):
|
def test_btc(self):
|
||||||
@ -37,5 +39,28 @@ class TestAddresses(common.TrezorTest):
|
|||||||
|
|
||||||
self.assertEqual(self.client.get_address('Testnet', [111, 42]), 'moN6aN6NP1KWgnPSqzrrRPvx2x1UtZJssa')
|
self.assertEqual(self.client.get_address('Testnet', [111, 42]), 'moN6aN6NP1KWgnPSqzrrRPvx2x1UtZJssa')
|
||||||
|
|
||||||
|
def test_public_ckd(self):
|
||||||
|
self.client.load_device_by_mnemonic(mnemonic=self.mnemonic1,
|
||||||
|
pin='',
|
||||||
|
passphrase_protection=False,
|
||||||
|
label='test',
|
||||||
|
language='english')
|
||||||
|
|
||||||
|
node = self.client.get_public_node([])
|
||||||
|
node_sub1 = self.client.get_public_node([1])
|
||||||
|
node_sub2 = bip32.public_ckd(node, [1])
|
||||||
|
|
||||||
|
print node_sub1
|
||||||
|
print node_sub2
|
||||||
|
|
||||||
|
self.assertEqual(node_sub1.chain_code, node_sub2.chain_code)
|
||||||
|
self.assertEqual(node_sub1.public_key, node_sub2.public_key)
|
||||||
|
|
||||||
|
address1 = self.client.get_address('Bitcoin', [1])
|
||||||
|
address2 = bip32.get_address(node_sub2, 0)
|
||||||
|
|
||||||
|
self.assertEqual(address2, '1CK7SJdcb8z9HuvVft3D91HLpLC6KSsGb')
|
||||||
|
self.assertEqual(address1, address2)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -7,6 +7,7 @@ import trezorlib.types_pb2 as proto_types
|
|||||||
|
|
||||||
class TestSignTx(common.TrezorTest):
|
class TestSignTx(common.TrezorTest):
|
||||||
|
|
||||||
|
'''
|
||||||
def test_simplesigntx(self):
|
def test_simplesigntx(self):
|
||||||
# tx: d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882
|
# tx: d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882
|
||||||
# input 0: 0.0039 BTC
|
# input 0: 0.0039 BTC
|
||||||
@ -25,6 +26,31 @@ class TestSignTx(common.TrezorTest):
|
|||||||
tx = self.client.simple_sign_tx('Bitcoin', [inp1, ], [out1, ])
|
tx = self.client.simple_sign_tx('Bitcoin', [inp1, ], [out1, ])
|
||||||
print binascii.hexlify(tx.serialized_tx)
|
print binascii.hexlify(tx.serialized_tx)
|
||||||
self.assertEqual(binascii.hexlify(tx.serialized_tx), '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b4830450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede7810121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0160cc0500000000001976a914de9b2a8da088824e8fe51debea566617d851537888ac00000000')
|
self.assertEqual(binascii.hexlify(tx.serialized_tx), '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b4830450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede7810121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0160cc0500000000001976a914de9b2a8da088824e8fe51debea566617d851537888ac00000000')
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_simplesigntx_testnet(self):
|
||||||
|
# tx: d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882
|
||||||
|
# input 0: 0.0039 BTC
|
||||||
|
self.client.load_device_by_xprv('xprv9s21ZrQH143K3zttRjiQmYwyugvd13pnd2VzefWrfSouRfnj5oSkJgBQXxtn18E9mqrDop7fQ8Xnb9JCLPE4vghzhpU4dT33ZJ7frjzTEW8',
|
||||||
|
'', False, 'testnet')
|
||||||
|
|
||||||
|
inp1 = proto_types.TxInputType(address_n=[6], # mo8uUSFJULCMA4neRS9aS9jiXZ1N72FSLK
|
||||||
|
# amount=390000,
|
||||||
|
prev_hash=binascii.unhexlify('d83b27f16ce5069e0c8e4a02813f252500e257744d5b00c9b6128be7189117b1'),
|
||||||
|
prev_index=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
out1 = proto_types.TxOutputType(address='mjKKH3Dk95VMbdNnDQYHZXoQ9QwuCZocwb',
|
||||||
|
amount=80085000,
|
||||||
|
script_type=proto_types.PAYTOADDRESS,
|
||||||
|
)
|
||||||
|
|
||||||
|
rawtx = {'d83b27f16ce5069e0c8e4a02813f252500e257744d5b00c9b6128be7189117b1': '01000000013b21cc65080c57793d0e47045a24d8e92262dc47efdc425fd5cad9a25e928f6c000000006b483045022100bde591f2c997bafa8388916663b148f4093914851a33a9903da69ad97afa6f470220138c6ff11321339974bac9c0992d7b9d72aef0c2d098f26267ec9f05d532c859012103edcc8dc5cac7dca6ed191d812621fb300863fea0dd5d14180b482b917a35acc4ffffffff020800c604000000001976a91453958011070469e2ef5e1115f34f509717d6884288acf8c99502000000001976a9141e2ba9407a6920246d0f345beecb89ed47c99a7788ac00000000'}
|
||||||
|
tx = self.client.simple_sign_tx('Testnet', [inp1, ], [out1, ])
|
||||||
|
print binascii.hexlify(tx.serialized_tx)
|
||||||
|
# self.assertEqual(binascii.hexlify(tx.serialized_tx), '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b4830450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede7810121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0160cc0500000000001976a914de9b2a8da088824e8fe51debea566617d851537888ac00000000')
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
def test_signtx(self):
|
def test_signtx(self):
|
||||||
|
@ -1,25 +1,63 @@
|
|||||||
import struct
|
import struct
|
||||||
import hmac
|
import hmac
|
||||||
import hashlib
|
import hashlib
|
||||||
from ecdsa.util import string_to_number
|
|
||||||
|
import ecdsa
|
||||||
|
from ecdsa.util import string_to_number, number_to_string
|
||||||
from ecdsa.curves import SECP256k1
|
from ecdsa.curves import SECP256k1
|
||||||
|
from ecdsa.ellipticcurve import Point, INFINITY
|
||||||
|
|
||||||
import messages_pb2 as proto
|
import msqrt
|
||||||
|
import tools
|
||||||
# FIXME, this isn't finished yet
|
import types_pb2 as proto_types
|
||||||
|
|
||||||
PRIME_DERIVATION_FLAG = 0x80000000
|
PRIME_DERIVATION_FLAG = 0x80000000
|
||||||
|
|
||||||
|
def point_to_pubkey(point):
|
||||||
|
order = SECP256k1.order
|
||||||
|
x_str = number_to_string(point.x(), order)
|
||||||
|
y_str = number_to_string(point.y(), order)
|
||||||
|
vk = x_str + y_str
|
||||||
|
return chr((ord(vk[63]) & 1) + 2) + vk[0:32] # To compressed key
|
||||||
|
|
||||||
|
def sec_to_public_pair(pubkey):
|
||||||
|
"""Convert a public key in sec binary format to a public pair."""
|
||||||
|
x = string_to_number(pubkey[1:33])
|
||||||
|
sec0 = pubkey[:1]
|
||||||
|
if sec0 not in (b'\2', b'\3'):
|
||||||
|
raise Exception("Compressed pubkey expected")
|
||||||
|
|
||||||
|
def public_pair_for_x(generator, x, is_even):
|
||||||
|
curve = generator.curve()
|
||||||
|
p = curve.p()
|
||||||
|
alpha = (pow(x, 3, p) + curve.a() * x + curve.b()) % p
|
||||||
|
beta = msqrt.modular_sqrt(alpha, p)
|
||||||
|
if is_even == bool(beta & 1):
|
||||||
|
return (x, p - beta)
|
||||||
|
return (x, beta)
|
||||||
|
|
||||||
|
return public_pair_for_x(ecdsa.ecdsa.generator_secp256k1, x, is_even=(sec0 == b'\2'))
|
||||||
|
|
||||||
def is_prime(n):
|
def is_prime(n):
|
||||||
return (bool)(n & PRIME_DERIVATION_FLAG)
|
return (bool)(n & PRIME_DERIVATION_FLAG)
|
||||||
|
|
||||||
def hash_160(public_key):
|
|
||||||
md = hashlib.new('ripemd160')
|
|
||||||
md.update(hashlib.sha256(public_key).digest())
|
|
||||||
return md.digest()
|
|
||||||
|
|
||||||
def fingerprint(pubkey):
|
def fingerprint(pubkey):
|
||||||
return string_to_number(hash_160(pubkey)[:4])
|
return string_to_number(tools.hash_160(pubkey)[:4])
|
||||||
|
|
||||||
|
def get_address(public_node, address_type):
|
||||||
|
return tools.public_key_to_bc_address(public_node.public_key, address_type)
|
||||||
|
|
||||||
|
def public_ckd(public_node, n):
|
||||||
|
if not isinstance(n, list):
|
||||||
|
raise Exception('Parameter must be a list')
|
||||||
|
|
||||||
|
node = proto_types.HDNodeType()
|
||||||
|
node.CopyFrom(public_node)
|
||||||
|
|
||||||
|
for i in n:
|
||||||
|
node.CopyFrom(get_subnode(node, i))
|
||||||
|
|
||||||
|
return node
|
||||||
|
|
||||||
def get_subnode(node, i):
|
def get_subnode(node, i):
|
||||||
# Public Child key derivation (CKD) algorithm of BIP32
|
# Public Child key derivation (CKD) algorithm of BIP32
|
||||||
@ -34,16 +72,22 @@ def get_subnode(node, i):
|
|||||||
I64 = hmac.HMAC(key=node.chain_code, msg=data, digestmod=hashlib.sha512).digest()
|
I64 = hmac.HMAC(key=node.chain_code, msg=data, digestmod=hashlib.sha512).digest()
|
||||||
I_left_as_exponent = string_to_number(I64[:32])
|
I_left_as_exponent = string_to_number(I64[:32])
|
||||||
|
|
||||||
node_out = proto.HDNodeType()
|
node_out = proto_types.HDNodeType()
|
||||||
node_out.version = node.version
|
node_out.version = node.version
|
||||||
node_out.depth = node.depth + 1
|
node_out.depth = node.depth + 1
|
||||||
node_out.child_num = i
|
node_out.child_num = i
|
||||||
node_out.chain_code = I64[32:]
|
node_out.chain_code = I64[32:]
|
||||||
node_out.fingerprint = fingerprint(node.public_key)
|
node_out.fingerprint = fingerprint(node.public_key)
|
||||||
|
|
||||||
# FIXME
|
# BIP32 magic converts old public key to new public point
|
||||||
# node_out.public_key = cls._get_pubkey(node_out.private_key)
|
x, y = sec_to_public_pair(node.public_key)
|
||||||
# x, y = self.public_pair
|
point = I_left_as_exponent * SECP256k1.generator + \
|
||||||
# the_point = I_left_as_exponent * ecdsa.generator_secp256k1 + ecdsa.Point(ecdsa.generator_secp256k1.curve(), x, y, SECP256k1.generator.order())
|
Point(SECP256k1.curve, x, y, SECP256k1.order)
|
||||||
|
|
||||||
|
if point == INFINITY:
|
||||||
|
raise Exception("Point cannot be INFINITY")
|
||||||
|
|
||||||
|
# Convert public point to compressed public key
|
||||||
|
node_out.public_key = point_to_pubkey(point)
|
||||||
|
|
||||||
return node_out
|
return node_out
|
||||||
|
@ -396,20 +396,6 @@ class TrezorClient(object):
|
|||||||
self.init_device()
|
self.init_device()
|
||||||
return isinstance(resp, proto.Success)
|
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')
|
|
||||||
|
|
||||||
node = types.HDNodeType()
|
|
||||||
node.CopyFrom(public_node)
|
|
||||||
|
|
||||||
for i in n:
|
|
||||||
node.CopyFrom(ckd_public.get_subnode(node, i))
|
|
||||||
|
|
||||||
return node
|
|
||||||
|
|
||||||
def firmware_update(self, fp):
|
def firmware_update(self, fp):
|
||||||
if self.features.bootloader_mode == False:
|
if self.features.bootloader_mode == False:
|
||||||
raise Exception("Device must be in bootloader mode")
|
raise Exception("Device must be in bootloader mode")
|
||||||
|
94
trezorlib/msqrt.py
Normal file
94
trezorlib/msqrt.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/
|
||||||
|
|
||||||
|
def modular_sqrt(a, p):
|
||||||
|
""" Find a quadratic residue (mod p) of 'a'. p
|
||||||
|
must be an odd prime.
|
||||||
|
|
||||||
|
Solve the congruence of the form:
|
||||||
|
x^2 = a (mod p)
|
||||||
|
And returns x. Note that p - x is also a root.
|
||||||
|
|
||||||
|
0 is returned is no square root exists for
|
||||||
|
these a and p.
|
||||||
|
|
||||||
|
The Tonelli-Shanks algorithm is used (except
|
||||||
|
for some simple cases in which the solution
|
||||||
|
is known from an identity). This algorithm
|
||||||
|
runs in polynomial time (unless the
|
||||||
|
generalized Riemann hypothesis is false).
|
||||||
|
"""
|
||||||
|
# Simple cases
|
||||||
|
#
|
||||||
|
if legendre_symbol(a, p) != 1:
|
||||||
|
return 0
|
||||||
|
elif a == 0:
|
||||||
|
return 0
|
||||||
|
elif p == 2:
|
||||||
|
return p
|
||||||
|
elif p % 4 == 3:
|
||||||
|
return pow(a, (p + 1) / 4, p)
|
||||||
|
|
||||||
|
# Partition p-1 to s * 2^e for an odd s (i.e.
|
||||||
|
# reduce all the powers of 2 from p-1)
|
||||||
|
#
|
||||||
|
s = p - 1
|
||||||
|
e = 0
|
||||||
|
while s % 2 == 0:
|
||||||
|
s /= 2
|
||||||
|
e += 1
|
||||||
|
|
||||||
|
# Find some 'n' with a legendre symbol n|p = -1.
|
||||||
|
# Shouldn't take long.
|
||||||
|
#
|
||||||
|
n = 2
|
||||||
|
while legendre_symbol(n, p) != -1:
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
# Here be dragons!
|
||||||
|
# Read the paper "Square roots from 1; 24, 51,
|
||||||
|
# 10 to Dan Shanks" by Ezra Brown for more
|
||||||
|
# information
|
||||||
|
#
|
||||||
|
|
||||||
|
# x is a guess of the square root that gets better
|
||||||
|
# with each iteration.
|
||||||
|
# b is the "fudge factor" - by how much we're off
|
||||||
|
# with the guess. The invariant x^2 = ab (mod p)
|
||||||
|
# is maintained throughout the loop.
|
||||||
|
# g is used for successive powers of n to update
|
||||||
|
# both a and b
|
||||||
|
# r is the exponent - decreases with each update
|
||||||
|
#
|
||||||
|
x = pow(a, (s + 1) / 2, p)
|
||||||
|
b = pow(a, s, p)
|
||||||
|
g = pow(n, s, p)
|
||||||
|
r = e
|
||||||
|
|
||||||
|
while True:
|
||||||
|
t = b
|
||||||
|
m = 0
|
||||||
|
for m in xrange(r):
|
||||||
|
if t == 1:
|
||||||
|
break
|
||||||
|
t = pow(t, 2, p)
|
||||||
|
|
||||||
|
if m == 0:
|
||||||
|
return x
|
||||||
|
|
||||||
|
gs = pow(g, 2 ** (r - m - 1), p)
|
||||||
|
g = (gs * gs) % p
|
||||||
|
x = (x * gs) % p
|
||||||
|
b = (b * g) % p
|
||||||
|
r = m
|
||||||
|
|
||||||
|
def legendre_symbol(a, p):
|
||||||
|
""" Compute the Legendre symbol a|p using
|
||||||
|
Euler's criterion. p is a prime, a is
|
||||||
|
relatively prime to p (if p divides
|
||||||
|
a, then a|p = 0)
|
||||||
|
|
||||||
|
Returns 1 if a has a square root modulo
|
||||||
|
p, -1 otherwise.
|
||||||
|
"""
|
||||||
|
ls = pow(a, (p - 1) / 2, p)
|
||||||
|
return -1 if ls == p - 1 else ls
|
@ -1,7 +1,34 @@
|
|||||||
|
import hashlib
|
||||||
|
|
||||||
|
Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest()
|
||||||
|
|
||||||
|
def hash_160(public_key):
|
||||||
|
md = hashlib.new('ripemd160')
|
||||||
|
md.update(hashlib.sha256(public_key).digest())
|
||||||
|
return md.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def hash_160_to_bc_address(h160, address_type):
|
||||||
|
vh160 = chr(address_type) + h160
|
||||||
|
h = Hash(vh160)
|
||||||
|
addr = vh160 + h[0:4]
|
||||||
|
return b58encode(addr)
|
||||||
|
|
||||||
|
def compress_pubkey(public_key):
|
||||||
|
if public_key[0] == '\x04':
|
||||||
|
return chr((ord(public_key[64]) & 1) + 2) + public_key[1:33]
|
||||||
|
raise Exception("Pubkey is already compressed")
|
||||||
|
|
||||||
|
def public_key_to_bc_address(public_key, address_type, compress=True):
|
||||||
|
if public_key[0] == '\x04' and compress:
|
||||||
|
public_key = compress_pubkey(public_key)
|
||||||
|
|
||||||
|
h160 = hash_160(public_key)
|
||||||
|
return hash_160_to_bc_address(h160, address_type)
|
||||||
|
|
||||||
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||||
__b58base = len(__b58chars)
|
__b58base = len(__b58chars)
|
||||||
|
|
||||||
|
|
||||||
def b58encode(v):
|
def b58encode(v):
|
||||||
""" encode v, which is a string of bytes, to base58."""
|
""" encode v, which is a string of bytes, to base58."""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user