mirror of
https://github.com/bitcoin/bips.git
synced 2025-02-21 14:34:51 +01:00
Merge pull request #1762 from achow101/328-tests
328: test vectors, reference implementation, update to Proposed
This commit is contained in:
commit
2e71a7e758
8 changed files with 551 additions and 6 deletions
|
@ -1029,13 +1029,13 @@ Those proposing changes should consider that ultimately consent may rest with th
|
|||
| Jonas Nick, Tim Ruffing, Elliott Jin
|
||||
| Informational
|
||||
| Active
|
||||
|-
|
||||
|- style="background-color: #ffffcf"
|
||||
| [[bip-0328.mediawiki|328]]
|
||||
| Applications
|
||||
| Derivation Scheme for MuSig2 Aggregate Keys
|
||||
| Ava Chow
|
||||
| Informational
|
||||
| Draft
|
||||
| Proposed
|
||||
|-
|
||||
| [[bip-0329.mediawiki|329]]
|
||||
| Applications
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
Author: Ava Chow <me@achow101.com>
|
||||
Comments-Summary: No comments yet.
|
||||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0328
|
||||
Status: Draft
|
||||
Status: Proposed
|
||||
Type: Informational
|
||||
Created: 2024-01-15
|
||||
Created: 2024-06-04
|
||||
License: CC0-1.0
|
||||
</pre>
|
||||
|
||||
|
@ -59,7 +59,24 @@ partial signatures.
|
|||
|
||||
==Test Vectors==
|
||||
|
||||
TBD
|
||||
* Aggregate pubkey <tt>0354240c76b8f2999143301a99c7f721ee57eee0bce401df3afeaa9ae218c70f23</tt>
|
||||
** Synthetic xpub <tt>xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwXEKGEouzXE6QLLRxjatMcLLzJ5LV5Nib1BN7vJg6yp45yHHRbm</tt>
|
||||
** Keys:
|
||||
*** <tt>03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9</tt>
|
||||
*** <tt>02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9</tt>
|
||||
* Aggregate pubkey <tt>0290539eede565f5d054f32cc0c220126889ed1e5d193baf15aef344fe59d4610c</tt>
|
||||
** Synthetic xpub <tt>xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwVk5TFJk8Tw5WAdV3DhrGfbFA216sE9BsQQiSFTdudkETnKdg8k</tt>
|
||||
** Keys:
|
||||
*** <tt>02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9</tt>
|
||||
*** <tt>03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659</tt>
|
||||
*** <tt>023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66</tt>
|
||||
* Aggregate pubkey <tt>022479f134cdb266141dab1a023cbba30a870f8995b95a91fc8464e56a7d41f8ea</tt>
|
||||
** Synthetic xpub <tt>xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwUvaZYpysLX4wN59tjwU5pBuDjNrPEJbfxjLwn7ruzbXTcUTHkZ</tt>
|
||||
** Keys:
|
||||
*** <tt>02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659</tt>
|
||||
*** <tt>023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66</tt>
|
||||
*** <tt>02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9</tt>
|
||||
*** <tt>03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9</tt>
|
||||
|
||||
==Backwards Compatibility==
|
||||
|
||||
|
@ -72,7 +89,7 @@ derivation can be done, and the signers will be able to produce a signature for
|
|||
|
||||
==Reference Implementation==
|
||||
|
||||
TBD
|
||||
A Python reference implementation is available in this BIP's [[bip-0328|Auxiliary Files]].
|
||||
|
||||
==Acknowledgements==
|
||||
|
||||
|
|
176
bip-0328/_base58.py
Normal file
176
bip-0328/_base58.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
"""
|
||||
Base 58 conversion utilities
|
||||
****************************
|
||||
"""
|
||||
|
||||
#
|
||||
# base58.py
|
||||
# Original source: git://github.com/joric/brutus.git
|
||||
# which was forked from git://github.com/samrushing/caesure.git
|
||||
#
|
||||
# Distributed under the MIT/X11 software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#
|
||||
|
||||
from binascii import hexlify, unhexlify
|
||||
from typing import List
|
||||
|
||||
from _common import hash256
|
||||
|
||||
|
||||
b58_digits: str = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
|
||||
|
||||
def encode(b: bytes) -> str:
|
||||
"""
|
||||
Encode bytes to a base58-encoded string
|
||||
|
||||
:param b: Bytes to encode
|
||||
:return: Base58 encoded string of ``b``
|
||||
"""
|
||||
|
||||
# Convert big-endian bytes to integer
|
||||
n: int = int('0x0' + hexlify(b).decode('utf8'), 16)
|
||||
|
||||
# Divide that integer into base58
|
||||
temp: List[str] = []
|
||||
while n > 0:
|
||||
n, r = divmod(n, 58)
|
||||
temp.append(b58_digits[r])
|
||||
res: str = ''.join(temp[::-1])
|
||||
|
||||
# Encode leading zeros as base58 zeros
|
||||
czero: int = 0
|
||||
pad: int = 0
|
||||
for c in b:
|
||||
if c == czero:
|
||||
pad += 1
|
||||
else:
|
||||
break
|
||||
return b58_digits[0] * pad + res
|
||||
|
||||
def decode(s: str) -> bytes:
|
||||
"""
|
||||
Decode a base58-encoding string, returning bytes
|
||||
|
||||
:param s: Base48 string to decode
|
||||
:return: Bytes encoded by ``s``
|
||||
"""
|
||||
if not s:
|
||||
return b''
|
||||
|
||||
# Convert the string to an integer
|
||||
n: int = 0
|
||||
for c in s:
|
||||
n *= 58
|
||||
if c not in b58_digits:
|
||||
raise Exception('Character %r is not a valid base58 character' % c)
|
||||
digit = b58_digits.index(c)
|
||||
n += digit
|
||||
|
||||
# Convert the integer to bytes
|
||||
h: str = '%x' % n
|
||||
if len(h) % 2:
|
||||
h = '0' + h
|
||||
res = unhexlify(h.encode('utf8'))
|
||||
|
||||
# Add padding back.
|
||||
pad = 0
|
||||
for c in s[:-1]:
|
||||
if c == b58_digits[0]:
|
||||
pad += 1
|
||||
else:
|
||||
break
|
||||
return b'\x00' * pad + res
|
||||
|
||||
def decode_check(s: str) -> bytes:
|
||||
"""
|
||||
Decode a Base58Check encoded string, returning bytes
|
||||
|
||||
:param s: Base58 string to decode
|
||||
:return: Bytes encoded by ``s``
|
||||
"""
|
||||
data = decode(s)
|
||||
payload = data[:-4]
|
||||
checksum = data[-4:]
|
||||
calc_checksum = hash256(payload)
|
||||
if checksum != calc_checksum[:4]:
|
||||
raise ValueError("Invalid checksum")
|
||||
return payload
|
||||
|
||||
def encode_check(b: bytes) -> str:
|
||||
checksum = hash256(b)[0:4]
|
||||
data = b + checksum
|
||||
return encode(data)
|
||||
|
||||
def get_xpub_fingerprint(s: str) -> bytes:
|
||||
"""
|
||||
Get the parent fingerprint from an extended public key
|
||||
|
||||
:param s: The extended pubkey
|
||||
:return: The parent fingerprint bytes
|
||||
"""
|
||||
data = decode(s)
|
||||
fingerprint = data[5:9]
|
||||
return fingerprint
|
||||
|
||||
def get_xpub_fingerprint_hex(xpub: str) -> str:
|
||||
"""
|
||||
Get the parent fingerprint as a hex string from an extended public key
|
||||
|
||||
:param s: The extended pubkey
|
||||
:return: The parent fingerprint as a hex string
|
||||
"""
|
||||
data = decode(xpub)
|
||||
fingerprint = data[5:9]
|
||||
return hexlify(fingerprint).decode()
|
||||
|
||||
def to_address(b: bytes, version: bytes) -> str:
|
||||
"""
|
||||
Base58 Check Encode the data with the version number.
|
||||
Used to encode legacy style addresses.
|
||||
|
||||
:param b: The data to encode
|
||||
:param version: The version number to encode with
|
||||
:return: The Base58 Check Encoded string
|
||||
"""
|
||||
data = version + b
|
||||
checksum = hash256(data)[0:4]
|
||||
data += checksum
|
||||
return encode(data)
|
||||
|
||||
def xpub_to_pub_hex(xpub: str) -> str:
|
||||
"""
|
||||
Get the public key as a string from the extended public key.
|
||||
|
||||
:param xpub: The extended pubkey
|
||||
:return: The pubkey hex string
|
||||
"""
|
||||
data = decode(xpub)
|
||||
pubkey = data[-37:-4]
|
||||
return hexlify(pubkey).decode()
|
||||
|
||||
|
||||
def xpub_to_xonly_pub_hex(xpub: str) -> str:
|
||||
"""
|
||||
Get the public key as a string from the extended public key.
|
||||
|
||||
:param xpub: The extended pubkey
|
||||
:return: The pubkey hex string
|
||||
"""
|
||||
data = decode(xpub)
|
||||
pubkey = data[-36:-4]
|
||||
return hexlify(pubkey).decode()
|
||||
|
||||
|
||||
def xpub_main_2_test(xpub: str) -> str:
|
||||
"""
|
||||
Convert an extended pubkey from mainnet version to testnet version.
|
||||
|
||||
:param xpub: The extended pubkey
|
||||
:return: The extended pubkey re-encoded using testnet version bytes
|
||||
"""
|
||||
data = decode(xpub)
|
||||
test_data = b'\x04\x35\x87\xCF' + data[4:-4]
|
||||
checksum = hash256(test_data)[0:4]
|
||||
return encode(test_data + checksum)
|
1
bip-0328/_bip327.py
Symbolic link
1
bip-0328/_bip327.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
../bip-0327/reference.py
|
47
bip-0328/_common.py
Normal file
47
bip-0328/_common.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
"""
|
||||
Common Classes and Utilities
|
||||
****************************
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
|
||||
def sha256(s: bytes) -> bytes:
|
||||
"""
|
||||
Perform a single SHA256 hash.
|
||||
|
||||
:param s: Bytes to hash
|
||||
:return: The hash
|
||||
"""
|
||||
return hashlib.new('sha256', s).digest()
|
||||
|
||||
|
||||
def ripemd160(s: bytes) -> bytes:
|
||||
"""
|
||||
Perform a single RIPEMD160 hash.
|
||||
|
||||
:param s: Bytes to hash
|
||||
:return: The hash
|
||||
"""
|
||||
return hashlib.new('ripemd160', s).digest()
|
||||
|
||||
|
||||
def hash256(s: bytes) -> bytes:
|
||||
"""
|
||||
Perform a double SHA256 hash.
|
||||
A SHA256 is performed on the input, and then a second
|
||||
SHA256 is performed on the result of the first SHA256
|
||||
|
||||
:param s: Bytes to hash
|
||||
:return: The hash
|
||||
"""
|
||||
return sha256(sha256(s))
|
||||
|
||||
|
||||
def hash160(s: bytes) -> bytes:
|
||||
"""
|
||||
perform a single SHA256 hash followed by a single RIPEMD160 hash on the result of the SHA256 hash.
|
||||
|
||||
:param s: Bytes to hash
|
||||
:return: The hash
|
||||
"""
|
||||
return ripemd160(sha256(s))
|
244
bip-0328/_xpub.py
Normal file
244
bip-0328/_xpub.py
Normal file
|
@ -0,0 +1,244 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2020 The HWI developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
"""
|
||||
Key Classes and Utilities
|
||||
*************************
|
||||
|
||||
Classes and utilities for working with extended public keys, key origins, and other key related things.
|
||||
"""
|
||||
|
||||
import _base58 as base58
|
||||
from _common import (
|
||||
hash256,
|
||||
hash160,
|
||||
)
|
||||
import binascii
|
||||
import hmac
|
||||
import hashlib
|
||||
import struct
|
||||
from typing import (
|
||||
Dict,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
|
||||
HARDENED_FLAG = 1 << 31
|
||||
|
||||
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
|
||||
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||
G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)
|
||||
|
||||
Point = Optional[Tuple[int, int]]
|
||||
|
||||
def H_(x: int) -> int:
|
||||
"""
|
||||
Shortcut function that "hardens" a number in a BIP44 path.
|
||||
"""
|
||||
return x | HARDENED_FLAG
|
||||
|
||||
def is_hardened(i: int) -> bool:
|
||||
"""
|
||||
Returns whether an index is hardened
|
||||
"""
|
||||
return i & HARDENED_FLAG != 0
|
||||
|
||||
|
||||
def point_add(p1: Point, p2: Point) -> Point:
|
||||
if (p1 is None):
|
||||
return p2
|
||||
if (p2 is None):
|
||||
return p1
|
||||
if (p1[0] == p2[0] and p1[1] != p2[1]):
|
||||
return None
|
||||
if (p1 == p2):
|
||||
lam = (3 * p1[0] * p1[0] * pow(2 * p1[1], p - 2, p)) % p
|
||||
else:
|
||||
lam = ((p2[1] - p1[1]) * pow(p2[0] - p1[0], p - 2, p)) % p
|
||||
x3 = (lam * lam - p1[0] - p2[0]) % p
|
||||
return (x3, (lam * (p1[0] - x3) - p1[1]) % p)
|
||||
|
||||
|
||||
def point_mul(p: Point, n: int) -> Point:
|
||||
r = None
|
||||
for i in range(256):
|
||||
if ((n >> i) & 1):
|
||||
r = point_add(r, p)
|
||||
p = point_add(p, p)
|
||||
return r
|
||||
|
||||
|
||||
def deserialize_point(b: bytes) -> Point:
|
||||
x = int.from_bytes(b[1:], byteorder="big")
|
||||
y = pow((x * x * x + 7) % p, (p + 1) // 4, p)
|
||||
if (y & 1 != b[0] & 1):
|
||||
y = p - y
|
||||
return (x, y)
|
||||
|
||||
|
||||
def bytes_to_point(point_bytes: bytes) -> Point:
|
||||
header = point_bytes[0]
|
||||
if header == 4:
|
||||
x = point_bytes = point_bytes[1:33]
|
||||
y = point_bytes = point_bytes[33:65]
|
||||
return (int(binascii.hexlify(x), 16), int(binascii.hexlify(y), 16))
|
||||
return deserialize_point(point_bytes)
|
||||
|
||||
def point_to_bytes(p: Point) -> bytes:
|
||||
if p is None:
|
||||
raise ValueError("Cannot convert None to bytes")
|
||||
return (b'\x03' if p[1] & 1 else b'\x02') + p[0].to_bytes(32, byteorder="big")
|
||||
|
||||
|
||||
# An extended public key (xpub) or private key (xprv). Just a data container for now.
|
||||
# Only handles deserialization of extended keys into component data to be handled by something else
|
||||
class ExtendedKey(object):
|
||||
"""
|
||||
A BIP 32 extended public key.
|
||||
"""
|
||||
|
||||
MAINNET_PUBLIC = b'\x04\x88\xB2\x1E'
|
||||
MAINNET_PRIVATE = b'\x04\x88\xAD\xE4'
|
||||
TESTNET_PUBLIC = b'\x04\x35\x87\xCF'
|
||||
TESTNET_PRIVATE = b'\x04\x35\x83\x94'
|
||||
|
||||
def __init__(self, version: bytes, depth: int, parent_fingerprint: bytes, child_num: int, chaincode: bytes, privkey: Optional[bytes], pubkey: bytes) -> None:
|
||||
"""
|
||||
:param version: The version bytes for this xpub
|
||||
:param depth: The depth of this xpub as defined in BIP 32
|
||||
:param parent_fingerprint: The 4 byte fingerprint of the parent xpub as defined in BIP 32
|
||||
:param child_num: The number of this xpub as defined in BIP 32
|
||||
:param chaincode: The chaincode of this xpub as defined in BIP 32
|
||||
:param privkey: The private key for this xpub if available
|
||||
:param pubkey: The public key for this xpub
|
||||
"""
|
||||
self.version: bytes = version
|
||||
self.is_testnet: bool = version == ExtendedKey.TESTNET_PUBLIC or version == ExtendedKey.TESTNET_PRIVATE
|
||||
self.is_private: bool = version == ExtendedKey.MAINNET_PRIVATE or version == ExtendedKey.TESTNET_PRIVATE
|
||||
self.depth: int = depth
|
||||
self.parent_fingerprint: bytes = parent_fingerprint
|
||||
self.child_num: int = child_num
|
||||
self.chaincode: bytes = chaincode
|
||||
self.pubkey: bytes = pubkey
|
||||
self.privkey: Optional[bytes] = privkey
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, xpub: str) -> 'ExtendedKey':
|
||||
"""
|
||||
Create an :class:`~ExtendedKey` from a Base58 check encoded xpub
|
||||
|
||||
:param xpub: The Base58 check encoded xpub
|
||||
"""
|
||||
data = base58.decode(xpub)[:-4] # Decoded xpub without checksum
|
||||
return cls.from_bytes(data)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> 'ExtendedKey':
|
||||
"""
|
||||
Create an :class:`~ExtendedKey` from a serialized xpub
|
||||
|
||||
:param xpub: The serialized xpub
|
||||
"""
|
||||
|
||||
version = data[0:4]
|
||||
if version not in [ExtendedKey.MAINNET_PRIVATE, ExtendedKey.MAINNET_PUBLIC, ExtendedKey.TESTNET_PRIVATE, ExtendedKey.TESTNET_PUBLIC]:
|
||||
raise Exception(f"Extended key magic of {version.hex()} is invalid")
|
||||
is_private = version == ExtendedKey.MAINNET_PRIVATE or version == ExtendedKey.TESTNET_PRIVATE
|
||||
depth = data[4]
|
||||
parent_fingerprint = data[5:9]
|
||||
child_num = struct.unpack('>I', data[9:13])[0]
|
||||
chaincode = data[13:45]
|
||||
|
||||
if is_private:
|
||||
privkey = data[46:]
|
||||
pubkey = point_to_bytes(point_mul(G, int.from_bytes(privkey, byteorder="big")))
|
||||
return cls(version, depth, parent_fingerprint, child_num, chaincode, privkey, pubkey)
|
||||
else:
|
||||
pubkey = data[45:78]
|
||||
return cls(version, depth, parent_fingerprint, child_num, chaincode, None, pubkey)
|
||||
|
||||
def serialize(self) -> bytes:
|
||||
"""
|
||||
Serialize the ExtendedKey with the serialization format described in BIP 32.
|
||||
Does not create an xpub string, but the bytes serialized here can be Base58 check encoded into one.
|
||||
|
||||
:return: BIP 32 serialized extended key
|
||||
"""
|
||||
r = self.version + struct.pack('B', self.depth) + self.parent_fingerprint + struct.pack('>I', self.child_num) + self.chaincode
|
||||
if self.is_private:
|
||||
if self.privkey is None:
|
||||
raise ValueError("Somehow we are private but don't have a privkey")
|
||||
r += b"\x00" + self.privkey
|
||||
else:
|
||||
r += self.pubkey
|
||||
return r
|
||||
|
||||
def to_string(self) -> str:
|
||||
"""
|
||||
Serialize the ExtendedKey as a Base58 check encoded xpub string
|
||||
|
||||
:return: Base58 check encoded xpub
|
||||
"""
|
||||
data = self.serialize()
|
||||
checksum = hash256(data)[0:4]
|
||||
return base58.encode(data + checksum)
|
||||
|
||||
def get_printable_dict(self) -> Dict[str, object]:
|
||||
"""
|
||||
Get the attributes of this ExtendedKey as a dictionary that can be printed
|
||||
|
||||
:return: Dictionary containing ExtendedKey information that can be printed
|
||||
"""
|
||||
d: Dict[str, object] = {}
|
||||
d['testnet'] = self.is_testnet
|
||||
d['private'] = self.is_private
|
||||
d['depth'] = self.depth
|
||||
d['parent_fingerprint'] = binascii.hexlify(self.parent_fingerprint).decode()
|
||||
d['child_num'] = self.child_num
|
||||
d['chaincode'] = binascii.hexlify(self.chaincode).decode()
|
||||
if self.is_private and isinstance(self.privkey, bytes):
|
||||
d['privkey'] = binascii.hexlify(self.privkey).decode()
|
||||
d['pubkey'] = binascii.hexlify(self.pubkey).decode()
|
||||
return d
|
||||
|
||||
def derive_pub(self, i: int) -> 'ExtendedKey':
|
||||
"""
|
||||
Derive the public key at the given child index.
|
||||
|
||||
:param i: The child index of the pubkey to derive
|
||||
"""
|
||||
if is_hardened(i):
|
||||
raise ValueError("Index cannot be larger than 2^31")
|
||||
|
||||
# Data to HMAC. Same as CKDpriv() for public child key.
|
||||
data = self.pubkey + struct.pack(">L", i)
|
||||
|
||||
# Get HMAC of data
|
||||
Ihmac = hmac.new(self.chaincode, data, hashlib.sha512).digest()
|
||||
Il = Ihmac[:32]
|
||||
Ir = Ihmac[32:]
|
||||
|
||||
# Construct curve point Il*G+K
|
||||
Il_int = int(binascii.hexlify(Il), 16)
|
||||
child_pubkey = point_add(point_mul(G, Il_int), bytes_to_point(self.pubkey))
|
||||
|
||||
# Construct and return a new BIP32Key
|
||||
pubkey = point_to_bytes(child_pubkey)
|
||||
chaincode = Ir
|
||||
fingerprint = hash160(self.pubkey)[0:4]
|
||||
return ExtendedKey(ExtendedKey.TESTNET_PUBLIC if self.is_testnet else ExtendedKey.MAINNET_PUBLIC, self.depth + 1, fingerprint, i, chaincode, None, pubkey)
|
||||
|
||||
def derive_pub_path(self, path: Sequence[int]) -> 'ExtendedKey':
|
||||
"""
|
||||
Derive the public key at the given path
|
||||
|
||||
:param path: Sequence of integers for the path of the pubkey to derive
|
||||
"""
|
||||
key = self
|
||||
for i in path:
|
||||
key = key.derive_pub(i)
|
||||
return key
|
31
bip-0328/reference.py
Normal file
31
bip-0328/reference.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
#! /usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from _base58 import xpub_to_pub_hex
|
||||
from _bip327 import cbytes, key_agg
|
||||
from _xpub import ExtendedKey
|
||||
|
||||
CHAINCODE = bytes.fromhex("868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965")
|
||||
|
||||
def aggregate_to_xpub(aggregate: bytes) -> ExtendedKey:
|
||||
return ExtendedKey(ExtendedKey.MAINNET_PUBLIC, 0, b"\x00\x00\x00\x00", 0, CHAINCODE, None, aggregate)
|
||||
|
||||
def test_aggregate_to_xpub():
|
||||
with open(os.path.join(sys.path[0], "vectors.json"), "r") as f:
|
||||
test_data = json.load(f)
|
||||
|
||||
for test_case in test_data:
|
||||
keys = [bytes.fromhex(k) for k in test_case["keys"]]
|
||||
|
||||
agg_ctx = key_agg(keys)
|
||||
pub = cbytes(agg_ctx.Q)
|
||||
assert pub.hex() == test_case["aggregate_pubkey"]
|
||||
|
||||
xpub = aggregate_to_xpub(pub)
|
||||
assert xpub.to_string() == test_case["xpub"]
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_aggregate_to_xpub()
|
29
bip-0328/vectors.json
Normal file
29
bip-0328/vectors.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
[
|
||||
{
|
||||
"aggregate_pubkey": "0354240c76b8f2999143301a99c7f721ee57eee0bce401df3afeaa9ae218c70f23",
|
||||
"keys": [
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"
|
||||
],
|
||||
"xpub": "xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwXEKGEouzXE6QLLRxjatMcLLzJ5LV5Nib1BN7vJg6yp45yHHRbm"
|
||||
},
|
||||
{
|
||||
"aggregate_pubkey": "0290539eede565f5d054f32cc0c220126889ed1e5d193baf15aef344fe59d4610c",
|
||||
"keys": [
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66"
|
||||
],
|
||||
"xpub": "xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwVk5TFJk8Tw5WAdV3DhrGfbFA216sE9BsQQiSFTdudkETnKdg8k"
|
||||
},
|
||||
{
|
||||
"aggregate_pubkey": "022479f134cdb266141dab1a023cbba30a870f8995b95a91fc8464e56a7d41f8ea",
|
||||
"keys": [
|
||||
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
|
||||
],
|
||||
"xpub": "xpub661MyMwAqRbcFt6tk3uaczE1y6EvM1TqXvawXcYmFEWijEM4PDBnuCXwwUvaZYpysLX4wN59tjwU5pBuDjNrPEJbfxjLwn7ruzbXTcUTHkZ"
|
||||
}
|
||||
]
|
Loading…
Add table
Reference in a new issue