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")
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.
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)
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())
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
ConvertToPublicmethod - 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...
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)