Offline public key derivation
ICP’s threshold key derivation is deterministic: given the subnet’s master public key, a canister principal, and a derivation path, anyone can compute the same canister public key locally. No secrets are involved and no canister call is needed.
This is useful for computing Ethereum or Bitcoin addresses for a canister, building explorers or dashboards, and testing locally without a live ICP connection.
TypeScript
Section titled “TypeScript”Install the library and its peer dependency:
npm install @dfinity/ic-pub-key @dfinity/principalECDSA (secp256k1)
Section titled “ECDSA (secp256k1)”Used for Ethereum, EVM chains, and Bitcoin (legacy/SegWit).
import { ecdsa } from "@dfinity/ic-pub-key";import { Principal } from "@dfinity/principal";
const masterKey = ecdsa.secp256k1.PublicKeyWithChainCode.forMainnetKey("key_1");const path = ecdsa.secp256k1.DerivationPath.withCanisterPrefix( Principal.fromText("your-canister-id"), [] // additional sub-path components, if any);const derived = masterKey.deriveSubkeyWithChainCode(path);console.log(derived.public_key.toHex()); // SEC1-compressed hex public keyUse forMainnetKey("test_key_1") for the development key, or forPocketIcKey("key_1") for PocketIC tests.
Schnorr (Ed25519)
Section titled “Schnorr (Ed25519)”Used for Solana, TON, Polkadot, Cardano, and NEAR.
import { schnorr } from "@dfinity/ic-pub-key";import { Principal } from "@dfinity/principal";
const masterKey = schnorr.ed25519.PublicKeyWithChainCode.forMainnetKey("key_1");const path = schnorr.ed25519.DerivationPath.withCanisterPrefix( Principal.fromText("your-canister-id"), []);const derived = masterKey.deriveSubkeyWithChainCode(path);console.log(derived.public_key.toHex()); // 32-byte Ed25519 public key as hex# Disable the vetkeys feature to avoid pulling in heavy transitive dependencies# if VetKD support is not needed.ic-pub-key = { version = "0.3.0", default-features = false, features = ["secp256k1", "ed25519"] }See docs.rs/ic-pub-key for the full Rust API. The crate wraps ic-secp256k1 and ic-ed25519 from the ICP monorepo and exposes the same offline derivation logic.
The derive commands accept a parent public key and chain code and output the derived key as JSON. Pass the hex values from forMainnetKey() above or from a prior ecdsa_public_key / schnorr_public_key call:
# ECDSA secp256k1npx @dfinity/ic-pub-key derive ecdsa secp256k1 \ --pubkey <parent-public-key-hex> \ --chaincode <parent-chain-code-hex> \ --derivationpath <candid-blob-path>
# Schnorr Ed25519 (mainnet key_1 is the default: no flags needed for the master key)npx @dfinity/ic-pub-key derive schnorr ed25519 \ --derivationpath <candid-blob-path>For deriving Chain Fusion Signer addresses specifically (ETH/BTC for a given principal), use the signer commands instead: see the Chain Fusion Signer guide.
Next steps
Section titled “Next steps”- Chain Fusion Signer: sign transactions for Bitcoin and Ethereum from web apps and CLI
- Management canister reference: the
ecdsa_public_keyandschnorr_public_keymanagement canister methods - Chain-key cryptography: how threshold key derivation works