From e3f5cfbc69e254e019eb91b812a9fde6134ff4ee Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Apr 2026 15:45:28 +0200 Subject: [PATCH 1/2] docs: add offline public key derivation via ic-pub-key to concept and reference pages --- docs/concepts/chain-key-cryptography.md | 4 +++ docs/reference/management-canister.md | 44 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/docs/concepts/chain-key-cryptography.md b/docs/concepts/chain-key-cryptography.md index 211dbb77..cb11bbce 100644 --- a/docs/concepts/chain-key-cryptography.md +++ b/docs/concepts/chain-key-cryptography.md @@ -62,6 +62,10 @@ For ECDSA and BIP340, key derivation uses a generalized form of [BIP-32](https:/ Derivation is transparent — it happens inside the protocol as part of the signing and public-key-retrieval APIs. You provide a derivation path and the protocol handles the rest. +Because the derivation algorithm is deterministic and uses only public parameters (the master public key, the canister principal, and the derivation path), public key derivation can also be performed **offline** — no management canister call or network connection required. This is useful for building explorers, dashboards, or address-derivation tools that need a canister's public key or blockchain address without a live ICP connection. See [Offline public key derivation](../reference/management-canister.md#offline-public-key-derivation) for libraries and usage. + + + ### Pre-signatures Signing is split into two phases for performance. An expensive **pre-signature computation** runs asynchronously in the background, producing pre-computed values that are consumed by individual signing requests. This means the latency you experience when calling `sign_with_ecdsa` or `sign_with_schnorr` is dominated by a single consensus round, not the full multi-party computation. diff --git a/docs/reference/management-canister.md b/docs/reference/management-canister.md index 1746fd10..494e7c58 100644 --- a/docs/reference/management-canister.md +++ b/docs/reference/management-canister.md @@ -392,6 +392,50 @@ Signs a message using threshold Schnorr. The corresponding public key can be obt For practical usage of chain-key signing in Bitcoin and Ethereum workflows, see the [Bitcoin guide](../guides/chain-fusion/bitcoin.md) and [Ethereum guide](../guides/chain-fusion/ethereum.md). +### Offline public key derivation + +`ecdsa_public_key` and `schnorr_public_key` make on-chain calls and cost cycles. If you only need a public key — for example to derive a blockchain address or verify a signature — derivation can be done entirely offline. The algorithm is deterministic: given the master public key, the canister principal, and the derivation path, the result is always the same. + +Both a TypeScript library and a Rust crate are available. The mainnet master public keys for all key IDs are baked in, so no on-chain call is needed to get started. + +**TypeScript** (`@dfinity/ic-pub-key`): + +```typescript +import { ecdsa, schnorr } from "@dfinity/ic-pub-key"; +import { Principal } from "@dfinity/principal"; + +// ECDSA secp256k1 +const ecdsaMaster = ecdsa.secp256k1.PublicKeyWithChainCode.forMainnetKey("key_1"); +const ecdsaPath = ecdsa.secp256k1.DerivationPath.withCanisterPrefix( + Principal.fromText("your-canister-id"), + [] +); +const ecdsaKey = ecdsaMaster.deriveSubkeyWithChainCode(ecdsaPath); +console.log(ecdsaKey.public_key.toHex()); + +// Schnorr Ed25519 +const schnorrMaster = schnorr.ed25519.PublicKeyWithChainCode.forMainnetKey("key_1"); +const schnorrPath = schnorr.ed25519.DerivationPath.withCanisterPrefix( + Principal.fromText("your-canister-id"), + [] +); +const schnorrKey = schnorrMaster.deriveSubkeyWithChainCode(schnorrPath); +console.log(schnorrKey.public_key.toHex()); +``` + +**Rust** (`ic-pub-key`): + +```toml +# Cargo.toml — disable the vetkeys feature to avoid heavy transitive dependencies +ic-pub-key = { version = "0.3.0", default-features = false, features = ["secp256k1", "ed25519"] } +``` + +See [docs.rs/ic-pub-key](https://docs.rs/ic-pub-key) for the full Rust API. + +The CLI equivalent — `npx @dfinity/ic-pub-key derive ecdsa secp256k1` — accepts the same public key and chain code arguments and is useful for scripting and testing. + + + ## vetKD (Verifiable Encrypted Threshold Key Derivation) ### `vetkd_public_key` From ffe0aa0cf823d45bbffded7f01c189b9c55b92e2 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Apr 2026 15:55:21 +0200 Subject: [PATCH 2/2] docs: add offline key derivation guide, trim management canister reference --- docs/concepts/chain-key-cryptography.md | 2 +- .../chain-fusion/chain-fusion-signer.md | 1 + .../chain-fusion/offline-key-derivation.md | 93 +++++++++++++++++++ docs/reference/management-canister.md | 42 +-------- 4 files changed, 96 insertions(+), 42 deletions(-) create mode 100644 docs/guides/chain-fusion/offline-key-derivation.md diff --git a/docs/concepts/chain-key-cryptography.md b/docs/concepts/chain-key-cryptography.md index cb11bbce..045bba66 100644 --- a/docs/concepts/chain-key-cryptography.md +++ b/docs/concepts/chain-key-cryptography.md @@ -62,7 +62,7 @@ For ECDSA and BIP340, key derivation uses a generalized form of [BIP-32](https:/ Derivation is transparent — it happens inside the protocol as part of the signing and public-key-retrieval APIs. You provide a derivation path and the protocol handles the rest. -Because the derivation algorithm is deterministic and uses only public parameters (the master public key, the canister principal, and the derivation path), public key derivation can also be performed **offline** — no management canister call or network connection required. This is useful for building explorers, dashboards, or address-derivation tools that need a canister's public key or blockchain address without a live ICP connection. See [Offline public key derivation](../reference/management-canister.md#offline-public-key-derivation) for libraries and usage. +Because the derivation algorithm is deterministic and uses only public parameters (the master public key, the canister principal, and the derivation path), public key derivation can also be performed **offline** — no management canister call or network connection required. This is useful for building explorers, dashboards, or address-derivation tools that need a canister's public key or blockchain address without a live ICP connection. See the [offline key derivation guide](../guides/chain-fusion/offline-key-derivation.md) for TypeScript and Rust libraries. diff --git a/docs/guides/chain-fusion/chain-fusion-signer.md b/docs/guides/chain-fusion/chain-fusion-signer.md index 2194926c..0ee3b10b 100644 --- a/docs/guides/chain-fusion/chain-fusion-signer.md +++ b/docs/guides/chain-fusion/chain-fusion-signer.md @@ -270,6 +270,7 @@ These variants are defined by [papi](https://github.com/dfinity/papi), an open-s - [Bitcoin integration guide](bitcoin.md) — build a full Bitcoin dapp with your own signing backend - [Ethereum integration guide](ethereum.md) — EVM RPC canister for reading Ethereum state - [Cycles Ledger](../../reference/system-canisters.md#cycles-ledger) — fund your account with cycles +- [Offline key derivation](offline-key-derivation.md) — derive ETH/BTC addresses for any canister principal without a management canister call - [papi](https://github.com/dfinity/papi) — add the same `CallerPaysIcrc2Cycles` / `PatronPaysIcrc2Cycles` payment pattern to your own canister diff --git a/docs/guides/chain-fusion/offline-key-derivation.md b/docs/guides/chain-fusion/offline-key-derivation.md new file mode 100644 index 00000000..151b7e8d --- /dev/null +++ b/docs/guides/chain-fusion/offline-key-derivation.md @@ -0,0 +1,93 @@ +--- +title: "Offline public key derivation" +description: "Derive canister threshold public keys and blockchain addresses offline — no management canister call or cycles required." +sidebar: + order: 6 +--- + +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 on-chain 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 + +Install the library and its peer dependency: + +```bash +npm install @dfinity/ic-pub-key @dfinity/principal +``` + +### ECDSA (secp256k1) + +Used for Ethereum, EVM chains, and Bitcoin (legacy/SegWit). + +```typescript +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. + +### Schnorr (Ed25519) + +Used for Solana, TON, Polkadot, Cardano, and NEAR. + +```typescript +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 +``` + +## Rust + +```toml +# 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](https://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. + +## CLI + +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: + +```bash +# ECDSA secp256k1 +npx @dfinity/ic-pub-key derive ecdsa secp256k1 \ + --pubkey \ + --chaincode \ + --derivationpath + +# Schnorr Ed25519 (mainnet key_1 is the default — no flags needed for the master key) +npx @dfinity/ic-pub-key derive schnorr ed25519 \ + --derivationpath +``` + +For deriving Chain Fusion Signer addresses specifically (ETH/BTC for a given principal), use the `signer` commands instead — see the [Chain Fusion Signer guide](chain-fusion-signer.md#derive-offline-no-cycles). + +## Next steps + +- [Chain Fusion Signer](chain-fusion-signer.md) — sign transactions for Bitcoin and Ethereum from web apps and CLI +- [Management canister reference](../../reference/management-canister.md#chain-key-signing) — the on-chain `ecdsa_public_key` and `schnorr_public_key` methods +- [Chain-key cryptography](../../concepts/chain-key-cryptography.md) — how threshold key derivation works + + + + diff --git a/docs/reference/management-canister.md b/docs/reference/management-canister.md index 494e7c58..97717787 100644 --- a/docs/reference/management-canister.md +++ b/docs/reference/management-canister.md @@ -394,47 +394,7 @@ For practical usage of chain-key signing in Bitcoin and Ethereum workflows, see ### Offline public key derivation -`ecdsa_public_key` and `schnorr_public_key` make on-chain calls and cost cycles. If you only need a public key — for example to derive a blockchain address or verify a signature — derivation can be done entirely offline. The algorithm is deterministic: given the master public key, the canister principal, and the derivation path, the result is always the same. - -Both a TypeScript library and a Rust crate are available. The mainnet master public keys for all key IDs are baked in, so no on-chain call is needed to get started. - -**TypeScript** (`@dfinity/ic-pub-key`): - -```typescript -import { ecdsa, schnorr } from "@dfinity/ic-pub-key"; -import { Principal } from "@dfinity/principal"; - -// ECDSA secp256k1 -const ecdsaMaster = ecdsa.secp256k1.PublicKeyWithChainCode.forMainnetKey("key_1"); -const ecdsaPath = ecdsa.secp256k1.DerivationPath.withCanisterPrefix( - Principal.fromText("your-canister-id"), - [] -); -const ecdsaKey = ecdsaMaster.deriveSubkeyWithChainCode(ecdsaPath); -console.log(ecdsaKey.public_key.toHex()); - -// Schnorr Ed25519 -const schnorrMaster = schnorr.ed25519.PublicKeyWithChainCode.forMainnetKey("key_1"); -const schnorrPath = schnorr.ed25519.DerivationPath.withCanisterPrefix( - Principal.fromText("your-canister-id"), - [] -); -const schnorrKey = schnorrMaster.deriveSubkeyWithChainCode(schnorrPath); -console.log(schnorrKey.public_key.toHex()); -``` - -**Rust** (`ic-pub-key`): - -```toml -# Cargo.toml — disable the vetkeys feature to avoid heavy transitive dependencies -ic-pub-key = { version = "0.3.0", default-features = false, features = ["secp256k1", "ed25519"] } -``` - -See [docs.rs/ic-pub-key](https://docs.rs/ic-pub-key) for the full Rust API. - -The CLI equivalent — `npx @dfinity/ic-pub-key derive ecdsa secp256k1` — accepts the same public key and chain code arguments and is useful for scripting and testing. - - +If you only need a public key — to derive a blockchain address or verify a signature — the management canister call can be avoided entirely. ICP's key derivation algorithm is deterministic and uses only public parameters, so derivation can be performed offline without cycles or a network connection. See the [offline key derivation guide](../guides/chain-fusion/offline-key-derivation.md) for TypeScript and Rust libraries. ## vetKD (Verifiable Encrypted Threshold Key Derivation)