BIP32 public CKD

pull/1/head
slush0 11 years ago
parent 999194fa59
commit 91b2b637b4

@ -1,5 +1,7 @@
import unittest
import common
import trezorlib.ckd_public as bip32
from trezorlib import tools
class TestAddresses(common.TrezorTest):
def test_btc(self):
@ -36,6 +38,29 @@ class TestAddresses(common.TrezorTest):
language='english')
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__':
unittest.main()

@ -7,6 +7,7 @@ import trezorlib.types_pb2 as proto_types
class TestSignTx(common.TrezorTest):
'''
def test_simplesigntx(self):
# tx: d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882
# input 0: 0.0039 BTC
@ -25,6 +26,31 @@ class TestSignTx(common.TrezorTest):
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_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):

@ -1,25 +1,63 @@
import struct
import hmac
import hashlib
from ecdsa.util import string_to_number
from ecdsa.curves import SECP256k1
import messages_pb2 as proto
import ecdsa
from ecdsa.util import string_to_number, number_to_string
from ecdsa.curves import SECP256k1
from ecdsa.ellipticcurve import Point, INFINITY
# FIXME, this isn't finished yet
import msqrt
import tools
import types_pb2 as proto_types
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):
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):
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):
# 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()
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.depth = node.depth + 1
node_out.child_num = i
node_out.chain_code = I64[32:]
node_out.fingerprint = fingerprint(node.public_key)
# FIXME
# node_out.public_key = cls._get_pubkey(node_out.private_key)
# x, y = self.public_pair
# the_point = I_left_as_exponent * ecdsa.generator_secp256k1 + ecdsa.Point(ecdsa.generator_secp256k1.curve(), x, y, SECP256k1.generator.order())
# BIP32 magic converts old public key to new public point
x, y = sec_to_public_pair(node.public_key)
point = I_left_as_exponent * SECP256k1.generator + \
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

@ -396,20 +396,6 @@ class TrezorClient(object):
self.init_device()
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):
if self.features.bootloader_mode == False:
raise Exception("Device must be in bootloader mode")

@ -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'
__b58base = len(__b58chars)
def b58encode(v):
""" encode v, which is a string of bytes, to base58."""

Loading…
Cancel
Save