Création de dérivation ED25519 en Python

Le client Python py-substrate-interface ne gère pas la création de dérivation en ED25519.

C’est donc à nous de le faire si on utilise ED25519 au lieu de SR25519 pour les nouveaux comptes V2.

Pour l’instant, j’échoue à obtenir l’équivalent des outils Rust/Javascript comme subkey.

subkey inspect --scheme ed25519
URI: 
Secret Key URI `bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice` is account:
  Network ID:        substrate
  Secret seed:       0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115
  Public key (hex):  0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee
  Account ID:        0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee
  Public key (SS58): 5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu
  SS58 Address:      5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu

Avec le mnémonique de dev :

bottom drive obey lake curtain smoke basket hold race lonely fit walk

et la dérivation “//Alice”,

J’ai le mini-secret suivant :

b'\xfa\xc7\x95\x9d\xbf\xe7/\x05.Z\x0c<\x8de0\xf2\x02\xb0/\xd8\xf9\xf5\xca5\x80\xec\x8d\xebw\x97G\x9e'

Le client py-substrate-interface calcul ce chaincode pour “//Alice” :

b'\x14Alice\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

Et une fois le mini-secret dérivé avec ce chaincode, je dois obtenir ce mini-secret :

derived_mini_secret = derive_ed25519_hard(mini_secret, cc)
assert derived_mini_secret.hex() == "abf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115"

Mais j’échoue car le code proposé par Vector n’est pas bon.

Ce code n’est pas bon :

def derive_ed25519_hard(mini_secret: bytes, cc: bytes) -> bytes:
    assert len(mini_secret) == 32
    assert len(cc) == 32
    # Encoder le tuple complet comme en Rust
    data = b'Ed25519HDKD' + b'\x00' + mini_secret + b'\x00' + cc
    return hashlib.blake2b(data, digest_size=32).digest()

En s’appuyant sur le code Rust disponible ici :

/// Derive a single hard junction.
fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed {
	use codec::Encode;
	("Ed25519HDKD", secret_seed, cc).using_encoded(sp_crypto_hashing::blake2_256)
}

Pouvez-vous m’aider à corriger le code Python pour obtenir le bon mini-secret dérivé ?

1 Like

Blake2 et Blake2b sont deux algos différents !

1 Like

Heuréka ! :tada:

Je viens de redemander à chatGPT et contrairement à Vector, il a vu qu’il fallait encoder en SCALE avant de faire le hash en blake2b (ça fonctionne avec blake2b… mais merci quand mếme :wink: ).

La solution :

import hashlib
import base58
from nacl.bindings import crypto_sign_seed_keypair


def ss58_encode(pubkey: bytes, prefix=42) -> str:
    data = bytes([prefix]) + pubkey
    checksum = hashlib.blake2b(b'SS58PRE' + data, digest_size=64).digest()[:2]
    return base58.b58encode(data + checksum).decode()


def scale_encode_string(s: str) -> bytes:
    """Encode une chaîne de caractères en SCALE (Compact<u32> + UTF-8 string)"""
    s_bytes = s.encode("utf-8")
    length = len(s_bytes)

    # Compact encoding pour longueur < 64 (mode single-byte)
    if length < 64:
        encoded_len = (length << 2).to_bytes(1, "little")
    else:
        raise NotImplementedError("SCALE compact encoding only implemented for len < 64")

    return encoded_len + s_bytes


def derive_ed25519_hard_junction(seed: bytes, cc: bytes) -> bytes:
    """
    Équivalent de la fonction Rust:
    ("Ed25519HDKD", seed, cc).using_encoded(blake2_256)
    """
    assert len(seed) == 32
    assert len(cc) == 32

    encoded = scale_encode_string("Ed25519HDKD") + seed + cc
    return hashlib.blake2b(encoded, digest_size=32).digest()

# === Exemple avec "//Alice" ===

mnemonic = "bottom drive obey lake curtain smoke basket hold race lonely fit walk"
mini_secret = b'\xfa\xc7\x95\x9d\xbf\xe7/\x05.Z\x0c<\x8de0\xf2\x02\xb0/\xd8\xf9\xf5\xca5\x80\xec\x8d\xebw\x97G\x9e'

cc = b'\x14Alice\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
print("Derivation: bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice")
# Puis faire la dérivation hard
derived_mini_secret = derive_ed25519_hard_junction(mini_secret, cc)

# Créer keypair à partir du mini-secret
pubkey, _ = crypto_sign_seed_keypair(derived_mini_secret)

# 0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115
print("Derived mini-secret:", derived_mini_secret.hex())
assert derived_mini_secret.hex() == "abf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115"

# 0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee
print("Public Key:", pubkey.hex())
assert pubkey.hex() == "88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee"

# 5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu"
print("SS58:", ss58_encode(pubkey, 42))
assert ss58_encode(pubkey, 42) == "5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu"
1 Like