Concept
Addresses and keys
How a Tenrai wallet turns 24 words into ML-DSA-65 keys, how a key becomes a spend lock and an address, and what fingerprints and verification words are.
On this page
From words to keys
| Step | Result |
|---|---|
| 24 English BIP-39 words | 256 bits of entropy |
| Optional passphrase, stretched with Argon2id | 32 bytes mixed into the root |
| HMAC-SHA512 over entropy and stretched passphrase | The wallet root |
| One hardened step each for purpose, coin type, account, branch, index | A 32-byte seed |
| ML-DSA-65 key generation from that seed | One key pair |
The scheme is called TENRAI-HD-v1. BIP-39 serves only as a way to write down 256 bits. The derivation is neither BIP-32 nor the BIP-39 seed derivation, so wallets built for other chains do not reproduce these keys. TCC-0001 has the exact rules.
- 24 words only. The key-generation seed is 32 bytes; a 12-word phrase would supply half the entropy and is refused.
- Every step is hardened. There is no public derivation: nobody can compute your addresses from one public key.
- Paths are account, then branch (
0receive,1change), then index. - The passphrase is normalized (NFKD) before use, may be up to 256 bytes, and is never stored. It protects a stolen phrase. It does not add to the 256 bits the words already hold.
Argon2id runs once per unlock with 64 MiB of memory and 3 passes, not once per address.
The messenger key comes from the same root on its own hardened path, m/0x544D'/0x5452'/0', which shares no step with the address keys.
From key to address
A spend lock is 41 bytes: one profile byte and a 40-byte commitment.
| Profile | Commitment |
|---|---|
1, single key | SHAKE256("TRAI_LOCK_V1" ‖ 0x01 ‖ public key), 40 bytes |
2, threshold | The same over a 2-of-3 or 3-of-5 set of keys |
An address is a format byte and the lock, encoded as lower-case Bech32m with the network's prefix:
| Network | Prefix | Payment scheme |
|---|---|---|
| Mainnet | trai | trai: |
| Testnet, Testnet100 | ttrai | trai-test: |
| Development | dtrai | trai-dev: |
Parsers refuse mixed case, a bad checksum, a wrong prefix and unknown lock data.
The public key stays hidden behind the hash until the address first spends. After that it sits in the chain's key registry. That is safe: ML-DSA is post-quantum, so reusing an address does not weaken it, and it makes later spends smaller.
Burn address
The single-key lock whose commitment is 40 zero bytes is the burn lock. Spending from it would take a public key that hashes to zero, which nobody can find. The chain burns every transaction output paid to it, so the amount leaves the supply. Each network has one burn address:
Mainnet trai1qyqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhhus7s
Testnet, Testnet100 ttrai1qyqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfztafh
Development dtrai1qyqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpxq6mlRecovery fingerprint
A wrong passphrase cannot be detected; it opens a different, valid, empty wallet. The fingerprint makes the difference visible:
digest = SHA-384("TENRAI-HD-v1/WALLET-FINGERPRINT" ‖ first receive public key)
fingerprint = "TRAI-" ‖ 4 hex digits ‖ "-" ‖ 4 hex digits for example TRAI-9C41-0B7EWallets show it when a wallet is created and whenever one is restored. It is part of TCC-0001.
Verification words
Six public words stand for an address, so two people can compare it by eye or by voice:
digest = SHA-384("TRAI_ADDRESS_WORDS_V1", [prefix, address version, lock])
words = six 11-bit indexes into a fixed list of 2,048 words- The list shares no word with the BIP-39 list, so the words are never mistaken for a recovery phrase.
- The prefix is part of the hash: one key has different words on mainnet and on a testnet.
- Finding a key whose address shows the same six words as a chosen address takes about 2^66 key generations.
They are a comparison aid. Anyone who knows the address can compute them, so they prove nothing about ownership. For that there is the ownership proof. The word list and the derivation are TCC-0003.
Wallet file
The wallet file stores only the 32 bytes of entropy, encrypted. It is 103 bytes long:
| Bytes | Field |
|---|---|
| 9 | Magic TRAISEED1 |
| 2 | Version |
| 16 | Argon2id salt |
| 24 | XChaCha20-Poly1305 nonce |
| 4 | Ciphertext length |
| 48 | Encrypted entropy and tag |
The key comes from the password through Argon2id. A wrong password and a changed byte fail the same check. The recovery passphrase is not in the file. TCC-0002 has the offsets, the encryption and a test vector.
