Skip to content

Latest commit

 

History

History
201 lines (161 loc) · 8.1 KB

File metadata and controls

201 lines (161 loc) · 8.1 KB

Substrate library

The Substrate library allows deriving keys for coins of the Polkadot ecosystem, since they don't follow BIP44.
The module generates the same keys and addresses as Polkadot-JS and uses sr25519 curve for keys derivation.
With respect to BIP-0032, Substrate paths can be also strings (in addition to numbers) and they are identified by a prefix:

  • / for not-hardened (soft) derivation (e.g. "/soft")
  • // for hardened derivation (e.g. "//hard")

Coin types

Supported coins enumerative:

Coin Enum
Acala SubstrateCoins.ACALA
Bifrost SubstrateCoins.BIFROST
Chainx SubstrateCoins.CHAINX
Edgeware SubstrateCoins.EDGEWARE
Generic SubstrateCoins.GENERIC
Karura SubstrateCoins.KARURA
Kusama SubstrateCoins.KUSAMA
Moonbeam SubstrateCoins.MOONBEAM
Moonriver SubstrateCoins.MOONRIVER
Phala Network SubstrateCoins.PHALA
Plasm Network SubstrateCoins.PLASM
Polkadot SubstrateCoins.POLKADOT
Sora SubstrateCoins.SORA
Stafi SubstrateCoins.STAFI

The code is structured so that it can be easily extended with other coins if needed.

Construction from seed

The class can be constructed from a seed, like Bip32. The seed can be specified manually or generated by SubstrateBip39SeedGenerator.

NOTE: If Bip39SeedGenerator is used instead, the wrong addresses will be generated

Code example

import binascii
from bip_utils import SubstrateBip39SeedGenerator, SubstrateCoins, Substrate

# Generate from mnemonic
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
seed_bytes = SubstrateBip39SeedGenerator(mnemonic).Generate()
# Specify seed manually. The seed is required to be 32-byte long. If longer, only the first 32 bytes will be considered.
seed_bytes = binascii.unhexlify(b"5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc1")

# Construction from seed
substrate_ctx = Substrate.FromSeed(seed_bytes, SubstrateCoins.POLKADOT)
# Construction from seed by specifying the path
substrate_ctx = Substrate.FromSeedAndPath(seed_bytes, "//hard/soft", SubstrateCoins.POLKADOT)

Construction from private/public key

The class can be constructed from a private or a public key.

Code example

import binascii
from bip_utils import SubstrateCoins, Substrate, Sr25519PublicKey, Sr25519PrivateKey

# Construction from private key bytes
priv_key_bytes = binascii.unhexlify(b"2ec306fc1c5bc2f0e3a2c7a6ec6014ca4a0823a7d7d42ad5e9d7f376a1c36c0d14a2ddb1ef1df4adba49f3a4d8c0f6205117907265f09a53ccf07a4e8616dfd8")
substrate_ctx = Substrate.FromPrivateKey(priv_key_bytes, SubstrateCoins.POLKADOT)
# Or key object directly
substrate_ctx = Substrate.FromPrivateKey(Sr25519PrivateKey.FromBytes(priv_key_bytes), SubstrateCoins.POLKADOT)
# Return false
print(substrate_ctx.IsPublicOnly())

# Construction from public key bytes
# The object will be public-only and support only public derivation
pub_key_bytes = binascii.unhexlify(b"66933bd1f37070ef87bd1198af3dacceb095237f803f3d32b173e6b425ed7972")
substrate_ctx = Substrate.FromPublicKey(pub_key_bytes, SubstrateCoins.POLKADOT)
# Or key object directly
substrate_ctx = Substrate.FromPublicKey(Sr25519PublicKey.FromBytes(pub_key_bytes), SubstrateCoins.POLKADOT)
# Return true
print(substrate_ctx.IsPublicOnly())

Keys derivation

Like Bip32, each time a key is derived a new instance of the Substrate class is returned.
The usage is similar to Bip32/Bip44 module.

Code example

import binascii
from bip_utils import SubstrateCoins, Substrate

# Seed bytes
seed_bytes = binascii.unhexlify(b"5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc1")
# Construction from seed
substrate_ctx = Substrate.FromSeed(seed_bytes, SubstrateCoins.POLKADOT)
# Print master keys and address
print(substrate_ctx.PrivateKey().Raw().ToBytes())
print(bytes(substrate_ctx.PrivateKey().Raw()))
print(substrate_ctx.PrivateKey().Raw().ToHex())
print(substrate_ctx.PublicKey().RawCompressed().ToBytes())
print(bytes(substrate_ctx.PublicKey().RawCompressed()))
print(substrate_ctx.PublicKey().RawCompressed().ToHex())
print(substrate_ctx.PublicKey().ToAddress())

# Derive a child key
substrate_ctx = substrate_ctx.ChildKey("//hard")
# Print derived keys and address
print(substrate_ctx.PrivateKey().Raw().ToHex())
print(substrate_ctx.PublicKey().RawCompressed().ToHex())
print(substrate_ctx.PublicKey().ToAddress())
# Print path
print(substrate_ctx.Path().ToStr())

# Derive a path
substrate_ctx = substrate_ctx.DerivePath("//hard/soft") # Path: //hard/soft
substrate_ctx = substrate_ctx.DerivePath("//0/1")       # Path: //hard/soft//0/1
# Print derived keys and address
print(substrate_ctx.PrivateKey().Raw().ToHex())
print(substrate_ctx.PublicKey().RawCompressed().ToHex())
print(substrate_ctx.PublicKey().ToAddress())
# Print path
print(substrate_ctx.Path().ToStr())

It's also possible to use public derivation (i.e. "watch-only" addresses) by:

  • converting a private object to a public-only using ConvertToPublic method
  • constructing a public-only object from a public key

In case of a public-only object, only public derivation will be supported (only "soft" path elements), otherwise a SubstrateKeyError exception will be raised.

Code example

import binascii
from bip_utils import SubstrateKeyError, SubstrateCoins, Substrate

# Construction from public key
pub_key_bytes = b"66933bd1f37070ef87bd1198af3dacceb095237f803f3d32b173e6b425ed7972"
substrate_ctx = Substrate.FromPublicKey(binascii.unhexlify(pub_key_bytes), SubstrateCoins.POLKADOT)
# Return true
print(substrate_ctx.IsPublicOnly())
# Print key and address
print(substrate_ctx.PublicKey().RawCompressed().ToHex())
print(substrate_ctx.PublicKey().ToAddress())

# Public derivation is used to derive a child key
substrate_ctx = substrate_ctx.ChildKey("/soft")
# Print key and address
print(substrate_ctx.PublicKey().RawCompressed().ToHex())
print(substrate_ctx.PublicKey().ToAddress())
# Print path
print(substrate_ctx.Path().ToStr())
# Public derivation is used to derive a path
substrate_ctx = substrate_ctx.DerivePath("/0/1")

# Getting the private key will raise a SubstrateKeyError
try:
    print(substrate_ctx.PrivateKey().Raw().ToHex())
except SubstrateKeyError as ex:
    print(ex)

# Deriving a hard path will raise a SubstrateKeyError
try:
    substrate_ctx = substrate_ctx.ChildKey("//hard")
    substrate_ctx = substrate_ctx.DerivePath("//0/1")
except SubstrateKeyError as ex:
    print(ex)

# Construction from private key
priv_key_bytes = b"2ec306fc1c5bc2f0e3a2c7a6ec6014ca4a0823a7d7d42ad5e9d7f376a1c36c0d14a2ddb1ef1df4adba49f3a4d8c0f6205117907265f09a53ccf07a4e8616dfd8"
substrate_ctx = Substrate.FromPrivateKey(binascii.unhexlify(priv_key_bytes), SubstrateCoins.POLKADOT)
# Convert to public object
substrate_ctx.ConvertToPublic()
# Same as before...

Parse path

The Substrate module allows also to parse derivation paths.
Please note that, if a path contains only numbers (e.g. "//123"), it'll be considered as an integer and not as a string of ASCII characters.

Code example

from bip_utils import SubstratePath, SubstratePathParser

# Parse path, SubstratePathError is raised in case of errors
path = SubstratePathParser.Parse("//hard/soft")
# Or construct directly from a list of indexes
path = SubstratePath(["//hard", "/soft"])

# Get length
print(path.Length())
# Get as string
print(path.ToStr())
print(str(path))
# Print elements info and value
for elem in path:
    print(elem.IsHard())
    print(elem.IsSoft())
    print(elem.ToStr())
    print(str(elem))
    print(elem.ChainCode())
# Get as list of strings
path_list = path.ToList()
for elem in path_list:
    print(elem)