Skip to main content

Wallets, key files, and transactions

Create or import a wallet

KeyStore uses English BIP39 mnemonics and SLIP-0010 Ed25519 derivation at m/44'/73404'/{account}'.

from znn.wallet.keystore import KeyStore

store = KeyStore.new_random()
first = store.get_key_pair(0)
second = store.get_key_pair(1)

print(store.mnemonic) # back this up securely; never log it in production
print(first.address)

Import an existing mnemonic or the stable 16/32-byte entropy profile:

store = KeyStore.from_mnemonic(mnemonic, passphrase="optional BIP39 passphrase")
store = KeyStore.from_entropy(entropy_hex)

Account indices must be non-negative integers. Call clear() on key stores and key pairs when you no longer need their in-memory secret material.

Keys and signatures

from znn.wallet.keypair import KeyPair, verify_signature

keypair = KeyPair(private_key_hex)

# Legacy text signing returns a base64-encoded signature.
signature = keypair.sign("Hello, aliens").decode()
verify_signature(keypair.public_key, signature, "Hello, aliens")

# Protocol signing uses the raw 32-byte hash and returns 64 raw signature bytes.
block_signature = keypair.sign_hash(block_hash)

KeyPair accepts only 32-byte Ed25519 private keys. Account-block signing does not base64-wrap or transform the canonical hash before signing.

Encrypted key files

Version-one key files use Argon2id and AES-256-GCM with authenticated data zenon.

import json

from znn.wallet.keyfile import decrypt, encrypt, needs_upgrade

document = encrypt(store, password, timestamp=1_700_000_000)
serialized = json.dumps(document)

restored = decrypt(json.loads(serialized), password)
assert restored.get_key_pair(0).address == store.get_key_pair(0).address

if needs_upgrade(document):
document = encrypt(restored, password)

Customize Argon2 work factors when required by your application:

document = encrypt(
store,
password,
kdf_config={
"timeCost": 2,
"memoryCost": 131072,
"hashLength": 32,
"parallelism": 4,
},
)

Malformed documents, unsupported algorithms, invalid parameters, bad passwords, and corrupted ciphertext raise KeyFileError. Keep the full document, including baseAddress, timestamp, version, and every declared crypto field.

Prepare before publishing

Transaction preparation and publication are separate operations. Preparation:

  1. reads the account and momentum frontiers;
  2. validates receive blocks when applicable;
  3. requests the required plasma/PoW values;
  4. applies fused plasma or generates and verifies PoW;
  5. hashes the canonical account-block preimage; and
  6. signs the raw hash with Ed25519.
from znn.api.embedded.plasma import PlasmaApi
from znn.api.ledger import LedgerApi
from znn.model.nom.account_block import AccountBlock
from znn.model.primitives.address import Address
from znn.model.primitives.token_standard import ZNN_ZTS
from znn.wallet.transact import Transact

block = AccountBlock.send(
Address.parse("z1qzpcwr6lk0zhejzpt04j2jcqqadtu046ffr8nr"),
ZNN_ZTS,
100_000_000,
)

tx = Transact(
private_key_hex,
ledger_api=LedgerApi(client),
plasma_api=PlasmaApi(client),
)

prepared = await tx.prepare_block(block)
# Inspect or transfer `prepared` to another policy layer here.
published = await tx.publish_block(prepared)

publish_block() requires the canonical null success response and returns the prepared block. fast_forward_block() combines preparation and publication. Convenience methods send(), receive(), and call_contract() build and fast-forward their respective blocks.

High-level SDK transaction flow

from znn import Zenon
from znn.model.nom.account_block import AccountBlock

sdk = Zenon()
await sdk.initialize("wss://your-node.example:35998")
try:
block = AccountBlock.send(to_address, token_standard, amount)
published = await sdk.send(block, keypair)
finally:
await sdk.disconnect()

Zenon.prepare_block() is also available when you need only a prepared block. Use the explicit Transact.prepare_block() / publish_block() split when that prepared block must later be published unchanged.

Custom PoW providers

A provider receives (data_hash_hex, difficulty) and returns a 16-character lowercase nonce hex string. It may be synchronous or asynchronous.

async def remote_pow(data_hash, difficulty):
return await trusted_pow_service(data_hash, difficulty)

sdk.set_pow_provider(remote_pow)

Returned nonces are length-checked and verified locally. A preselected nonce and difficulty are also accepted when they satisfy at least the node-required difficulty.

The example cookbook includes complete environment-driven wallet, key-file, embedded builder, prepare/publish, high-level SDK, ABI, and PoW programs.