diff --git a/docs/concepts/chain-key-cryptography.md b/docs/concepts/chain-key-cryptography.md index 211dbb77..045bba66 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 the [offline key derivation guide](../guides/chain-fusion/offline-key-derivation.md) for TypeScript and Rust libraries. + + + ### 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/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 1746fd10..97717787 100644 --- a/docs/reference/management-canister.md +++ b/docs/reference/management-canister.md @@ -392,6 +392,10 @@ 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 + +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) ### `vetkd_public_key`