For AI agents: Documentation index at /llms.txt

Skip to content

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.

Install the library and its peer dependency:

Terminal window
npm install @dfinity/ic-pub-key @dfinity/principal

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 key

Use forMainnetKey("test_key_1") for the development key, or forPocketIcKey("key_1") for PocketIC tests.

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
Cargo.toml
# 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:

Terminal window
# ECDSA secp256k1
npx @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.