1
0
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:
slush0 2014-01-14 14:29:18 +01:00
parent 999194fa59
commit 91b2b637b4
6 changed files with 233 additions and 31 deletions

View File

@ -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):
@ -36,6 +38,29 @@ class TestAddresses(common.TrezorTest):
language='english') language='english')
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()

View File

@ -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):

View File

@ -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

View File

@ -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
View 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

View File

@ -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."""