Repository navigation
feat(crypto): support the mainnet secp256r1 ECDSA keys in ic-pub-key - #11894
Merged
Merged
Conversation
The mainnet ECDSA keys `key_1` and `test_key_1` now also exist over secp256r1, but `ic-pub-key` derives keys offline only for secp256k1. * `ic-secp256r1` gains `PublicKey::mainnet_key` with the master public keys of `ecdsa:Secp256r1:key_1` and `ecdsa:Secp256r1:test_key_1`, read from the CUPs of subnets uzr34 and fuqsr. * `ic-pub-key` derives keys for both behind a new default `secp256r1` feature, and moves from the workspace's `ic-management-canister-types` 0.8.0 to the in-repo 0.11.0, the first version with `EcdsaCurve::Secp256r1`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Mainnet compatibility needs independent validation because the new expected keys were generated using the same derivation implementation.
1 open issue
What changed in this PR
Adds offline derivation for mainnet P-256 ECDSA keys, extending the curve support introduced in #11573.
Changes:
- Embeds both mainnet master keys in
ic-secp256r1. - Enables P-256 derivation by default in
ic-pub-key. - Updates dependencies, build targets, tests, and changelogs.
| File | Description |
|---|---|
| packages/ic-secp256r1/tests/tests.rs | Checks master-key round trips. |
| packages/ic-secp256r1/src/lib.rs | Adds mainnet master-key lookup. |
| packages/ic-secp256r1/CHANGELOG.md | Documents the new API. |
| packages/ic-secp256r1/Cargo.toml | Adds runtime hex-literal dependency. |
| packages/ic-secp256r1/BUILD.bazel | Mirrors the dependency addition. |
| packages/ic-pub-key/tests/tests.rs | Adds offline P-256 derivation vectors. |
| packages/ic-pub-key/src/lib.rs | Implements P-256 derivation and serialization. |
| packages/ic-pub-key/CHANGELOG.md | Documents support and re-export changes. |
| packages/ic-pub-key/Cargo.toml | Enables P-256 and updates management types. |
| packages/ic-pub-key/BUILD.bazel | Updates features and dependencies. |
| Cargo.lock | Records dependency changes. |
Review effort: Balanced
PocketIC holds secp256r1 keys for `key_1`, `test_key_1` and `dfx_test_key`, as it does for the other algorithms `ic-pub-key` supports. * `ic-secp256r1` gains `PublicKey::pocketic_key` with their master public keys, and a test that derives them the way PocketIC does. * `ic-pub-key` derives keys for `pocketic_key_1`, `pocketic_test_key_1` and `dfx_test_key` over secp256r1. * The expected keys of the secp256r1 tests in `ic-pub-key` are what the replica's `derive_threshold_public_key` returns for these master keys, which a one-off check confirmed for the mainnet ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ic-secp256k1` and `ic-ed25519` ship the mainnet and PocketIC master public keys of their algorithms, which `ic-pub-key` uses for offline key derivation. The mainnet ECDSA keys `key_1` and `test_key_1` now also exist over secp256r1, and PocketIC holds secp256r1 keys too, but `ic-secp256r1` has none of them. * `PublicKey::mainnet_key` returns the master public keys of `ecdsa:Secp256r1:key_1` and `ecdsa:Secp256r1:test_key_1`, read from the CUPs of subnets uzr34 and fuqsr. * `PublicKey::pocketic_key` returns the ones PocketIC uses for `key_1`, `test_key_1` and `dfx_test_key`, and a test derives them the way PocketIC does. * The version moves to 0.3.0, to be released before `ic-pub-key` can depend on these keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add `PublicKey::derive_mainnet_key` and `PublicKey::derive_pocketic_key`, and re-export `ic_principal::Principal` as `CanisterId` for them, as `ic-secp256k1` and `ic-ed25519` do. Their tests expect what the replica's `derive_threshold_public_key` returns for the same master keys, canisters and paths. * Test the SLIP-10 retry for nist256p1 (index 33941), the only branch of the derivation the tests didn't reach. It runs when iL >= n, about once in 2^32 steps on P-256. * Rename `mainnet_keys_are_the_ones_in_the_cups` to `mainnet_keys_round_trip`, which is what it checks. * Name the two new enums in the changelog, drop the duplicate `hex-literal` dev-dependency, and list `ic-secp256r1` among the crates with hardcoded master keys in the `cup_explorer` README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-key-secp256r1 ic-pub-key needs the ic-secp256r1 0.3.0 of #11895: take its version of ic-secp256r1 and raise the requirement to 0.3.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* `TryFrom<&EcdsaKeyId>` has one feature-gated block per curve, like `TryFrom<&SchnorrKeyId>`, instead of depending on the order of its checks. * `serialize` and `chain_code` of `CanisterMasterKey` and `DerivedPublicKey` share one implementation on `DerivedPublicKeyInner`. * The ECDSA tests also check the chain code, and that unknown key names are rejected. All expected values are what the replica's `derive_threshold_public_key` returns. The test helpers follow the Schnorr ones and are feature-gated, so single-feature builds have no unused code. * The crate docs and README describe the features and `default-features = false`, and `derive_ecdsa_key` names the feature each curve needs. * The changelog describes the dependency changes since the published 0.3.0, and `Cargo.toml` explains why `ic-management-canister-types` is the in-repo crate. * The BUILD file lists the features once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eichhorl
changed the base branch from
master
to
eichhorl/ic-secp256r1-master-keys
October 20, 2026 12:13
eichhorl
added this pull request to stack #11902
October 20, 2026 12:13
Resolve two conflicts with #11909: - CHANGELOG.md: keep #11909's line for ic-management-canister-types 0.5.0 -> 0.11.0 once, next to this PR's entries. - Cargo.lock: master's, with cargo re-applying this PR's change to the ic-pub-key entry. The ic-secp256r1 changes this branch carried from #11895 are identical to master's squash commit, so they drop out of the diff. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#11909 moved the workspace to ic-management-canister-types 0.11.0, which has EcdsaCurve::Secp256r1, so ic-pub-key no longer needs the in-repo crate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
basvandijk
pushed a commit
to Ocheretovich/ic
that referenced
this pull request
Oct 23, 2026
dfinity#11895) ### Why `ic-secp256k1` and `ic-ed25519` ship the mainnet and PocketIC master public keys of their algorithms, and `ic-pub-key` uses them to derive keys offline. The mainnet ECDSA keys `key_1` and `test_key_1` now also exist over secp256r1 (P-256), and PocketIC holds secp256r1 keys too (dfinity#11741). But `ic-secp256r1` has none of them. This PR adds the keys and bumps the version, so the crate can be released. `ic-pub-key` can only use the keys once that release is out (dfinity#11894). That's the same order as dfinity#7148 before dfinity#7194 for the secp256k1 and Ed25519 PocketIC keys. ### What * `PublicKey::mainnet_key(MasterPublicKeyId)` returns `key_1` and `test_key_1`. * `PublicKey::pocketic_key(PocketIcMasterPublicKeyId)` returns the keys PocketIC uses for `key_1`, `test_key_1` and `dfx_test_key`. * `PublicKey::derive_mainnet_key` and `PublicKey::derive_pocketic_key` derive a canister's public key from those master keys. They are offline equivalents of `ecdsa_public_key`. For their `&CanisterId` argument, the crate re-exports `ic_principal::Principal` as `CanisterId`, as `ic-secp256k1` and `ic-ed25519` do. * `hex-literal` becomes a regular dependency, to embed the keys, and `ic_principal` a new one. * The version goes from 0.2.0 to 0.3.0, with a dated changelog entry, for a release after merging. In-repo dependents use the crate by path without a version, so nothing else changes. * The `cup_explorer` README now lists `ic-secp256r1` among the crates with hardcoded master keys. Where the keys come from: * **Mainnet:** the CUPs of subnets uzr34 (`key_1`) and fuqsr (`test_key_1`). In that same output, the secp256k1, BIP340, Ed25519 and VetKD master keys match the values `ic-secp256k1`, `ic-ed25519` and `ic-vetkeys` already ship. * **PocketIC:** derived the way the StateMachine does it, from `PrivateKey::generate_insecure_key_for_testing(42)` and the key name. ### Tests * `mainnet_keys_round_trip`: the mainnet keys round-trip. * A test derives the PocketIC keys the StateMachine's way and compares them with the hardcoded ones. * `derive_mainnet_key` and `derive_pocketic_key` have known-answer tests for all five keys. The expected keys are what the replica's `derive_threshold_public_key` (the function that answers `ecdsa_public_key`) returns for the same master key, canister and path. * The SLIP-10 retry for nist256p1 (index 33941) is now tested. It's the only branch of the derivation the existing tests didn't reach: it runs when iL >= n, about once in 2^32 steps on P-256. The vector is the one the replica's own key derivation test uses. * Locally, in the dev container, all of these pass: `cargo check`, `cargo fmt`, `rust-lint.sh`, `bazel build //... --nobuild`, `//bazel:buildifier.check`, and the 37 tests from the depth-2 rdeps query (this package, `ic-pub-key`, `rs/crypto`, the P-256 basic-sig crate, `standalone-sig-verifier`, `rs/validator`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
basvandijk
pushed a commit
to Ocheretovich/ic
that referenced
this pull request
Oct 23, 2026
Bump the workspace dependency `ic-management-canister-types` from 0.8.0 to 0.11.0. This is needed for dfinity#11894 (offline derivation of secp256r1 keys in `ic-pub-key`). Between 0.8.0 and 0.11.0 nothing was removed or renamed. The additions still break Rust code in places, because none of these types is `#[non_exhaustive]`: - `CanisterSettings` and `DefiniteCanisterSettings` gained the fields `minimum_incoming_canister_call_cycles` and `status_visibility`, along with the new `StatusVisibility` type. - `HttpRequestArgs` gained `pricing_version`, and `HttpMethod` gained `PATCH`. - New types for flexible HTTP outcalls and subnet metrics. - `EcdsaCurve` gained `Secp256r1`. Changes that follow from the bump: - `canister_test`: one struct literal lists every `CanisterSettings` field, so it now sets the two new fields as well. - CMC: `cmc.did` declares the two new `CanisterSettings` fields, because `test_candid_interface_compatibility` requires it. The CMC now forwards these two settings in `create_canister` and `notify_create_canister`. Before, it dropped them. `unreleased_changelog.md` records this. dfinity#10756 did the same for `snapshot_visibility`. - ckDOGE minter: `ckdoge_minter.did` declares the two new `DefiniteCanisterSettings` fields, which `get_canister_status` now returns. The replica returns both fields unconditionally since dfinity#10398 and dfinity#10667, and all mainnet GuestOS versions in `mainnet-icos-revisions.json` contain both PRs. So decoding `canister_status` with the new types also works on mainnet. - Changelogs: `pocket-ic` and `ic-pub-key` re-export these types, so both record the bump. The older copies left in `Cargo.lock` come from `ic-utils` and `evm_rpc_types`, which have no newer release, and from the cdk, which a follow-up PR bumps. This does not yet remove the secp256r1 workarounds in the system tests (`cast_ecdsa_key_id`, the signer canister) and in the PocketIC test canister. Those are cdk canisters, and the workspace's `ic-cdk-management-canister` 0.1.1 re-exports the 0.7.1 types. Its 0.3.0 requires `ic-management-canister-types` ^0.11, so this bump comes first. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
mraszyk
approved these changes
Oct 23, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
The mainnet ECDSA keys
key_1andtest_key_1now also exist over secp256r1 (P-256), the curve #11573 added, and PocketIC holds secp256r1 keys too (#11741).ic-pub-keyderives canister public keys offline from hardcoded master keys, but for ECDSA only over secp256k1: any other curve returnsAlgorithmNotSupported.ic-secp256r10.3.0 (#11895) has the secp256r1 master keys, and the workspace'sic-management-canister-types0.11.0 (#11909) hasEcdsaCurve::Secp256r1. Both are published.What
secp256r1feature, on by default, derives keys forkey_1,test_key_1,pocketic_key_1,pocketic_test_key_1anddfx_test_key, following the secp256k1 code paths. It requiresic-secp256r10.3.0.TryFrom<&EcdsaKeyId>checks each curve in its own feature-gated block, likeTryFrom<&SchnorrKeyId>, instead of depending on the order of its checks.serializeandchain_codeshare one implementation onDerivedPublicKeyInner, instead of one each inCanisterMasterKeyandDerivedPublicKey.default-features = false, andderive_ecdsa_keynames the feature each curve needs.ic-vetkeys0.6.0 → 0.7.0 and theic-cdk-management-canisterdependency of thevetkeysfeature. chore: bump ic-management-canister-types to 0.11.0 #11909 already recordedic-management-canister-types0.5.0 → 0.11.0, which makes the next release a breaking one.Tests
derive_threshold_public_key, the function that answersecdsa_public_key, returns for these master keys. A one-off check, not committed, confirmed this for all 11 ECDSA vectors:ic-pub-keymatches the replica on both public key and chain code.ecdsa_public_keyreturned exactly the public keysic-pub-keyderives offline, forkey_1andtest_key_1on both curves, each with an empty and a 7-element path. Mainnet doesn't expose chain codes, so those rest on the replica check.vetkeys: the two warnings master already has inCanisterMasterKey::derive_keyremain. This PR removes a third, inTryFrom<&SchnorrKeyId>.cargo check(default and each single feature),rust-lint.sh,bazel build //... --nobuild,//bazel:buildifier.check, and the 3 affected tests.🤖 Generated with Claude Code