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 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
|
||||
|
||||
import ecdsa
|
||||
from ecdsa.util import string_to_number, number_to_string
|
||||
from ecdsa.curves import SECP256k1
|
||||
from ecdsa.ellipticcurve import Point, INFINITY
|
||||
|
||||
import messages_pb2 as proto
|
||||
|
||||
# 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")
|
||||
|
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'
|
||||
__b58base = len(__b58chars)
|
||||
|
||||
|
||||
def b58encode(v):
|
||||
""" encode v, which is a string of bytes, to base58."""
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user