Skip to content

Instantly share code, notes, and snippets.

@Sajjon
Last active January 22, 2025 10:04
Show Gist options
  • Save Sajjon/060c5747c6ffead12f78645b623a8164 to your computer and use it in GitHub Desktop.
Save Sajjon/060c5747c6ffead12f78645b623a8164 to your computer and use it in GitHub Desktop.
SLIP10 reference Python script modified for Radix CAP26 (SLIP10 application)
#!/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'
)
@Sajjon
Copy link
Author

Sajjon commented Jan 22, 2025

Output when USE_RADIX_CUSTOM_SECURIFIED_HALF_NOTATION = False:

### 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/1073741824H
  * 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/1073741824H
  * private: b0b9180f7c96778cffba7af2ef1ddf4705fca21b965e8a722ccf2ec403c35950
  * public: e0293d4979bc303ea4fe361a62baf9c060c7d90267972b05c61eead9ef3eed3e

### Index: 1073741825 ([index_base] 1 + 1073741824 [offset]) for ed25519
  * path: m/44H/1022H/1H/525H/1460H/1073741825H
  * private: c1880587c727f2f01dfdf61d19b44283d311b31c12e8898b774b73e8067d25b1
  * public: c6aaee6fa60d73a17989ce2a2a5db5a88cd696aef61d2f298262fae189dff04e

### Index: 1073741826 ([index_base] 2 + 1073741824 [offset]) for ed25519
  * path: m/44H/1022H/1H/525H/1460H/1073741826H
  * private: 837bc77bb29e4702be39c69fbade7d350bc23f6daddf68a64474984e899a97a3
  * public: 6a92b3338dc74a50e8b3fff896a7e0f43c42742544af52de20353675d8bc7907

### Index: 1073741827 ([index_base] 3 + 1073741824 [offset]) for ed25519
  * path: m/44H/1022H/1H/525H/1460H/1073741827H
  * private: 86151d58d11a2b18aaa3c81d7947b3a6c332e4fa38686aebf624853c5cbc7f3c
  * public: f2cb47d7fd2287ddddee8ee6fb11ff010901facfadc7983311ba4e2cff3d538a

### Index: 2147483647 ([index_base] 0 + 2147483647 [offset]) for ed25519
  * path: m/44H/1022H/1H/525H/1460H/2147483647H
  * private: 7eae6f235206329561b09fc2235d35e017c3f28b54fd3b4f6525e601257c4ce7
  * public: 87a2f84f826da0c62052fbe7b385ab78883c02d1fa5472c55a06aa529a0701e9

### Index: 2147483646 ([index_base] -1 + 2147483647 [offset]) for ed25519
  * path: m/44H/1022H/1H/525H/1460H/2147483646H
  * private: 5a8b6327942ca8fc5b30fb5b0c1fa53e97362d514ff4f2c281060b9d51f7fc88
  * public: 932123e6c46af8ebde7a96bee4563e09bbf41b28eae9d6ba1c667a2f490a1fcf

### Index: 2147483645 ([index_base] -2 + 2147483647 [offset]) for ed25519
  * path: m/44H/1022H/1H/525H/1460H/2147483645H
  * private: f63fe429c5723448dfb8d1f3eda88a659473b4c38960a09bb20efe546fac95ee
  * public: b2819057da648f36eadb59f60b732d4ae7fb22a207acf214e0271d3c587afd54

### Index: 2147483644 ([index_base] -3 + 2147483647 [offset]) for ed25519
  * path: m/44H/1022H/1H/525H/1460H/2147483644H
  * private: 361126bd7947254c49b83c23bbb557219cfa2ac5e5a4551501f18236ffa4eb17
  * public: 481b737f5baaf52520612e70858ffa72a3624d5a050da5748844ac14036c8b17

@Sajjon
Copy link
Author

Sajjon commented Jan 22, 2025

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