suideveloper
All questions
Accounts and wallets

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

  1. Mnemonic → seed. Sui accepts BIP-39 phrases of 12, 15, 18, 21 or 24 words (128 to 256 bits of entropy).

  2. 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
  3. 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:

TypeScript
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()); // true

Never 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 (usually sub), 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

More on accounts and wallets