Last active
January 22, 2025 10:04
-
-
Save Sajjon/060c5747c6ffead12f78645b623a8164 to your computer and use it in GitHub Desktop.
SLIP10 reference Python script modified for Radix CAP26 (SLIP10 application)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# This is a simplified and modified version of | |
# https://github.com/satoshilabs/slips/blob/master/slip-0010/testvectors.py | |
# You need to install these dependencies: | |
# `python3 -m pip install cryptography` | |
# `python3 -m pip install Mnemonic` | |
import hashlib | |
import hmac | |
from mnemonic import Mnemonic | |
from cryptography.hazmat.primitives import serialization | |
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey | |
HARDENED_KEY_SPACE_OFFSET = 2**31 # same as `0x80000000` | |
SECURIFIED_KEY_SPACE_OFFSET = 2**30 # same as `0x40000000` | |
USE_RADIX_CUSTOM_SECURIFIED_HALF_NOTATION = False | |
# mode 0 - compatible with BIP32 private derivation | |
def seed2hdnode(seed, curve, modifier, curve_order): | |
k = seed | |
while True: | |
h = hmac.new(modifier, seed, hashlib.sha512).digest() | |
key, chaincode = h[:32], h[32:] | |
a = int.from_bytes(key, 'big') | |
if (curve in ('ed25519', 'curve25519')): | |
break | |
if (a < curve_order and a != 0): | |
break | |
seed = h | |
return (key, chaincode) | |
def publickey(private_key, curve): | |
if curve == 'ed25519': | |
sk = Ed25519PrivateKey.from_private_bytes(private_key) | |
key_encoding = serialization.Encoding.Raw | |
key_format = serialization.PublicFormat.Raw | |
prefix = b'\x00' | |
else: | |
raise BaseException('unsupported curve: '+curve) | |
return prefix + sk.public_key().public_bytes(key_encoding, key_format) | |
def derive(parent_key, parent_chaincode, i, curve, curve_order): | |
assert len(parent_key) == 32 | |
assert len(parent_chaincode) == 32 | |
k = parent_chaincode | |
if ((i & HARDENED_KEY_SPACE_OFFSET) != 0): | |
key = b'\x00' + parent_key | |
else: | |
key = publickey(parent_key, curve) | |
d = key + i.to_bytes(4, 'big') | |
while True: | |
h = hmac.new(k, d, hashlib.sha512).digest() | |
key, chaincode = h[:32], h[32:] | |
if curve in ('ed25519', 'curve25519'): | |
break | |
a = int.from_bytes(key, 'big') | |
key = (a + int.from_bytes(parent_key, 'big')) % curve_order | |
if (a < curve_order and key != 0): | |
key = key.to_bytes(32, 'big') | |
break | |
d = b'\x01' + h[32:] + i.to_bytes(4, 'big') | |
return (key, chaincode) | |
def get_curve_info(): | |
return ('ed25519', b'ed25519 seed', None) | |
def generate_by_hardening(name, seedhex, unhardened_path): | |
hardened_path = list(map(lambda x: x + HARDENED_KEY_SPACE_OFFSET, unhardened_path)) | |
curve, seedmodifier, curve_order = get_curve_info() | |
curvename = 'ed25519' | |
master_seed = bytes.fromhex(seedhex) | |
k,c = seed2hdnode(master_seed, curve, seedmodifier, curve_order) | |
p = publickey(k, curve) | |
path = 'm' | |
print("### "+name+" for "+curvename) | |
depth = 0 | |
for i in hardened_path: | |
if curve in ('ed25519', 'curve25519'): | |
# no public derivation for ed25519 and curve25519 | |
i = i | HARDENED_KEY_SPACE_OFFSET | |
depth = depth + 1 | |
if depth == 6 and ((i & SECURIFIED_KEY_SPACE_OFFSET) != 0) and USE_RADIX_CUSTOM_SECURIFIED_HALF_NOTATION: | |
path = path + "/" + str(i - HARDENED_KEY_SPACE_OFFSET - SECURIFIED_KEY_SPACE_OFFSET) | |
path = path + "S" | |
elif ((i & HARDENED_KEY_SPACE_OFFSET) != 0): | |
path = path + "/" + str(i - HARDENED_KEY_SPACE_OFFSET) | |
path = path + "H" | |
k,c = derive(k, c, i, curve, curve_order) | |
p = publickey(k, curve) | |
if depth != 6: | |
continue | |
print(' * path: ' + path) | |
print(' * private: ' + k.hex()) | |
print(' * public: ' + p[1:].hex()) | |
print() | |
def show_testvectors(mnemonic, expected_seedhex): | |
assert HARDENED_KEY_SPACE_OFFSET == 2**31 | |
assert HARDENED_KEY_SPACE_OFFSET == 0x80000000 | |
assert SECURIFIED_KEY_SPACE_OFFSET == 2**30 | |
assert SECURIFIED_KEY_SPACE_OFFSET == 0x40000000 | |
mnemo = Mnemonic("english") | |
seed = mnemo.to_seed(mnemonic, passphrase="") | |
seedhex = seed.hex() | |
assert seedhex == expected_seedhex | |
account_tx_signing_base_path = [44, 1022, 1, 525, 1460] | |
for (sign_of_i, offset) in [('+', 0), ('-', SECURIFIED_KEY_SPACE_OFFSET), ('+', SECURIFIED_KEY_SPACE_OFFSET), ('-', 2**31 - 1)]: | |
for i in [0, 1, 2, 3]: | |
entity_index_base = i if sign_of_i == '+' else -i | |
entity_index = entity_index_base + offset | |
unhardened_path = account_tx_signing_base_path.copy() | |
unhardened_path.append(entity_index) | |
description = f"Index: {entity_index} ([index_base] {entity_index_base} + {offset} [offset])" | |
generate_by_hardening(description, seedhex, unhardened_path) | |
show_testvectors( | |
"device phone sign source sample other device sample other device sample other device sample other device sample other device sample other device other paddle", | |
'ee4072e80ab4eb00706077f56ccc585ee715099dca0607d13ccf8aa94dce8ec013e85f5375046d5bf72f5aa0b85ebea364e16a4acfb89addf37b68200a8616d0' | |
) |
Output when USE_RADIX_CUSTOM_SECURIFIED_HALF_NOTATION = True
:
### Index: 0 ([index_base] 0 + 0 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/0H
* private: 5b82120ec4763f8bacff71c8e529894fea1e735a5698ff400364a913f7b20c00
* public: f3d0210f6c2cecbdc977b7aae19d468a6c363e73a055bc877248f8318f0122e8
### Index: 1 ([index_base] 1 + 0 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/1H
* private: da5e924c716a05b616940dd7828e3020de4dc09c371ab03966e00e95c68cb439
* public: df49129a10aa88c76837611a4ecda794ac5f650e4401037e1ff275e52bc784c5
### Index: 2 ([index_base] 2 + 0 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/2H
* private: 2efbe74c30b6a46c2add3d96af8b7146d98134c56cfdf4db024620b8c5246ea1
* public: c371389fc7667bb7ed19d9ea5d26b8a07efe22e4ed49374996beaab1fc21a239
### Index: 3 ([index_base] 3 + 0 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/3H
* private: f5ca1a82d9e9aa0ecd4d7692bac15389dc5359d91e3f591917e656911771e2a2
* public: ece08b0bc5c824f15823fe727bbe97fa9fdd058aec1e7dfc3ddbad4d31fc4b7f
### Index: 1073741824 ([index_base] 0 + 1073741824 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/0S
* private: b0b9180f7c96778cffba7af2ef1ddf4705fca21b965e8a722ccf2ec403c35950
* public: e0293d4979bc303ea4fe361a62baf9c060c7d90267972b05c61eead9ef3eed3e
### Index: 1073741823 ([index_base] -1 + 1073741824 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/1073741823H
* private: 5c5adfebe650684e3cc20e4dba49e1447d7ac12f63ae1bd8723554d0a95aaf38
* public: f4f43daaedc3603b3dc6b92a2014630a96ca2a20cc14d2dcaa71f49c30789689
### Index: 1073741822 ([index_base] -2 + 1073741824 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/1073741822H
* private: 5218159039d5c639ae4e0b6b351b821e3687aa44768230c4f06a13ae0c78715c
* public: 2155707a3cebd7788dc83113174d30e2c29abae34f399c27a6caa8c6f5ae543e
### Index: 1073741821 ([index_base] -3 + 1073741824 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/1073741821H
* private: d9e0394b67affb91b5acdc3ecf6786a6628892ffd605291c853568cbed498afa
* public: a484112bcd119488f13191a6ec57ff27606ea041537662730e60580cdb679616
### Index: 1073741824 ([index_base] 0 + 1073741824 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/0S
* private: b0b9180f7c96778cffba7af2ef1ddf4705fca21b965e8a722ccf2ec403c35950
* public: e0293d4979bc303ea4fe361a62baf9c060c7d90267972b05c61eead9ef3eed3e
### Index: 1073741825 ([index_base] 1 + 1073741824 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/1S
* private: c1880587c727f2f01dfdf61d19b44283d311b31c12e8898b774b73e8067d25b1
* public: c6aaee6fa60d73a17989ce2a2a5db5a88cd696aef61d2f298262fae189dff04e
### Index: 1073741826 ([index_base] 2 + 1073741824 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/2S
* private: 837bc77bb29e4702be39c69fbade7d350bc23f6daddf68a64474984e899a97a3
* public: 6a92b3338dc74a50e8b3fff896a7e0f43c42742544af52de20353675d8bc7907
### Index: 1073741827 ([index_base] 3 + 1073741824 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/3S
* private: 86151d58d11a2b18aaa3c81d7947b3a6c332e4fa38686aebf624853c5cbc7f3c
* public: f2cb47d7fd2287ddddee8ee6fb11ff010901facfadc7983311ba4e2cff3d538a
### Index: 2147483647 ([index_base] 0 + 2147483647 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/1073741823S
* private: 7eae6f235206329561b09fc2235d35e017c3f28b54fd3b4f6525e601257c4ce7
* public: 87a2f84f826da0c62052fbe7b385ab78883c02d1fa5472c55a06aa529a0701e9
### Index: 2147483646 ([index_base] -1 + 2147483647 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/1073741822S
* private: 5a8b6327942ca8fc5b30fb5b0c1fa53e97362d514ff4f2c281060b9d51f7fc88
* public: 932123e6c46af8ebde7a96bee4563e09bbf41b28eae9d6ba1c667a2f490a1fcf
### Index: 2147483645 ([index_base] -2 + 2147483647 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/1073741821S
* private: f63fe429c5723448dfb8d1f3eda88a659473b4c38960a09bb20efe546fac95ee
* public: b2819057da648f36eadb59f60b732d4ae7fb22a207acf214e0271d3c587afd54
### Index: 2147483644 ([index_base] -3 + 2147483647 [offset]) for ed25519
* path: m/44H/1022H/1H/525H/1460H/1073741820S
* private: 361126bd7947254c49b83c23bbb557219cfa2ac5e5a4551501f18236ffa4eb17
* public: 481b737f5baaf52520612e70858ffa72a3624d5a050da5748844ac14036c8b17
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output when
USE_RADIX_CUSTOM_SECURIFIED_HALF_NOTATION = False
: