How are Sui addresses derived from keys?
November 1, 2026
A Sui address is the 32-byte BLAKE2b-256 hash of one signature-scheme flag byte followed by the public key bytes: address = BLAKE2b-256(flag || pubkey). The flag is 0x00 for Ed25519, 0x01 for secp256k1, 0x02 for secp256r1 and 0x03 for multisig. Wallets derive the key pair itself from a BIP-39 mnemonic along a BIP-44 path with coin type 784, which is m/44'/784'/0'/0'/0' for the first Ed25519 account.
From mnemonic to address
Mnemonic → seed. Sui accepts BIP-39 phrases of 12, 15, 18, 21 or 24 words (128 to 256 bits of entropy).
Seed → private key. The path's purpose field depends on the scheme:
Scheme Path Standard Ed25519 m/44'/784'/{account}'/{change}'/{address}'SLIP-0010, all levels hardened secp256k1 m/54'/784'/{account}'/{change}/{address}BIP-32, first 3 levels hardened secp256r1 m/74'/784'/{account}'/{change}/{address}BIP-32, first 3 levels hardened Public key → address. Prepend the flag byte, hash with BLAKE2b with 32-byte output, and hex-encode with
0x.
Because the flag goes into the hash, the same raw key bytes would give different addresses under different schemes. Wallets increment the last path level (…/0'/0'/1', …/0'/0'/2') to create more accounts from one phrase.
Reproduce it in TypeScript
This derives a key with @mysten/sui v2 and checks the SDK's address against a hand-computed hash:
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { toHex } from '@mysten/sui/utils';
import { blake2b } from '@noble/hashes/blake2.js';
const mnemonic = 'test test test test test test test test test test test junk';
const kp = Ed25519Keypair.deriveKeypair(mnemonic, "m/44'/784'/0'/0'/0'");
const pk = kp.getPublicKey();
const bytes = new Uint8Array([pk.flag(), ...pk.toRawBytes()]);
const manual = '0x' + toHex(blake2b(bytes, { dkLen: 32 }));
console.log(pk.toSuiAddress()); // 0xc88ef07b…0636026e
console.log(manual === pk.toSuiAddress()); // trueNever use a well-known test mnemonic like this one for real funds.
The CLI does the same thing when you run sui client new-address ed25519 or sui keytool import "<mnemonic>" ed25519. See importing and exporting keys with the CLI.
Addresses that don't come from a single key
The same "flag plus data, then hash" pattern covers other account types:
- Multisig (
0x03): the hash covers the threshold and each member's flag, public key and weight. The address changes if any of those change. See Sui multisig. - zkLogin (
0x05): the hash covers the OAuth issuer (iss) and an address seed. The seed is a Poseidon hash of the user's key claim (usuallysub), the app's client ID (aud) and a user salt. The user signs with a short-lived ephemeral key, so there is no long-term private key to store. See what is zkLogin. - Passkeys (
0x06): a secp256r1 key held by a WebAuthn authenticator. See Sui passkeys.
Gotchas
- Wrong scheme, wrong address. Importing a secp256k1 phrase as Ed25519 gives a different, empty address. If an imported wallet shows no balance, check the scheme and the account index.
- Ethereum keys don't map over. The same secp256k1 key gives a different address on Sui than on Ethereum, because the hash function and the address length are different.
- Object IDs aren't derived this way. They come from the creating transaction's digest and a counter, even though they look the same as addresses.
Sources
- Transaction authentication, Sui documentation
- Wallet cryptography specifications, Sui blog
- base_types.rs (address, multisig and object ID derivation), MystenLabs/sui
- zkLogin, Sui documentation