Skip to content

feat: add secp256r1 to the ECDSA curves in the interface spec - #394

Open
MRmarioruci wants to merge 3 commits into
mainfrom
feat/ecdsa-secp256r1-variant
Open

MRmarioruci wants to merge 3 commits into
mainfrom
feat/ecdsa-secp256r1-variant

Conversation

@MRmarioruci

@MRmarioruci MRmarioruci commented Sep 15, 2026

Copy link
Copy Markdown

New variant secp256r1 of ecdsa_curve, selecting threshold ECDSA on the NIST P-256 curve in ecdsa_public_key and sign_with_ecdsa. P-256 is the curve the web platform standardised on: WebAuthn, TLS, VAPID web push (ES256) and OAuth DPoP. A canister cannot produce any of those signatures today, so each one needs a key held off-chain.

Changes

Key derivation for the new curve is the same generalization of BIP32 instantiated over P-256, which is SLIP-10. signing/key_derivation.rs:210 dispatches Ed25519 => eddsa_ckd, _ => bip32_ckdpub, so K256 and P256 run the same code, and bip32_ckdpub documents itself as following SLIP-0010 with BIP32 identical for K256. The derivation paragraph is split into one bullet per curve, matching how schnorr_public_key already presents its two algorithms. Signatures are encoded as for secp256k1, because EccCurveType::scalar_bytes is 32 on both curves, so r and s are 32 bytes each. The ecdsa_curve argument of ic0.cost_sign_with_ecdsa accepts 1, and the changelog bullet goes under the current 0.67.0 heading.

Two things deliberately left out. The u32 value 1 is normative, since it is the System API ABI, so it has to agree with dfinity/ic: please confirm it before this merges. And this describes the interface, not availability. Whether a given key_id exists depends on the implementation, so the pages that list the keys deployed on mainnet are unchanged, docs/references/management-canister.md included. Those should change when a key is generated.

Paired with dfinity/ic#11573, which adds the variant to the implementation.

Tests

didc check public/references/ic.did passes, and node scripts/validate.js passes on the three changed markdown files.

🤖 Generated with Claude Code

New variant `secp256r1` of `ecdsa_curve`, selecting threshold ECDSA on the
NIST P-256 curve in `ecdsa_public_key` and `sign_with_ecdsa`.

Key derivation for the new curve is the same generalization of BIP32
instantiated over P-256, which is SLIP-10. The derivation paragraph is split
into one bullet per curve, matching how `schnorr_public_key` already presents
its two algorithms. Signatures are encoded as for `secp256k1`: P-256 has a
256-bit group order, so r and s are 32 bytes each. The `ecdsa_curve` argument
of `ic0.cost_sign_with_ecdsa` accepts 1.

This describes the interface, not availability. As for any curve, whether a
given `key_id` exists depends on the implementation, so the pages listing the
keys deployed on mainnet are unchanged.
@github-actions github-actions Bot added the interface-spec Changes to the IC interface specification label Sep 15, 2026
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown

🤖 Here's your preview: https://kwnd6-zaaaa-aaaam-ai7va-cai.icp0.io

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No unresolved review comments were identified, and the interface and documentation changes are consistent.

Pull request overview

Adds secp256r1 support to the IC interface specification for threshold ECDSA.

Changes:

  • Adds the P-256 Candid curve variant.
  • Documents SLIP-10 derivation and 64-byte signatures.
  • Maps System API curve value 1 to P-256 and updates the changelog.
File summaries
File Description
public/references/ic.did Adds the secp256r1 variant.
docs/references/ic-interface-spec/management-canister.md Documents P-256 derivation and signatures.
docs/references/ic-interface-spec/changelog.md Records the interface addition.
docs/references/ic-interface-spec/canister-interface.md Documents the numeric curve mapping.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@MRmarioruci
MRmarioruci marked this pull request as ready for review September 15, 2026 12:54
@MRmarioruci
MRmarioruci requested review from a team as code owners September 15, 2026 12:54
@marc0olo

Copy link
Copy Markdown
Member

Review: secp256r1 in the ECDSA curves

Must fix

  • Hold until feat(crypto): add secp256r1 variant to EcdsaCurve ic#11573 merges: the spec leads, but publishing it before the protocol accepts the variant would describe a curve the network rejects. Nothing to change for it, just the merge timing.
  • The u32 value is decided here: the body asks for 1 to be confirmed against dfinity/ic, and feat(crypto): add secp256r1 variant to EcdsaCurve ic#11573 points back here. This repo is the source of truth, so the value is normative once published. Drop the caveat and flip the cross-reference in feat(crypto): add secp256r1 variant to EcdsaCurve ic#11573.
  • The changelog bullet is in the wrong section: main now has ### 0.68.0 (2026-09-14), and a test merge drops the bullet at the end of ### 0.67.0 (2026-08-31), with no conflict to flag it. Give it a new ### 0.69.0 {$0_69_0} at the top. The sections run on biweekly Mondays, so date it at the first one after feat(crypto): add secp256r1 variant to EcdsaCurve ic#11573 merges.
  • docs/references/management-canister.md: the availability pages are rightly untouched, but three lines there state the interface, not availability. Line 356 currently supports secp256k1 now covers both curves. Line 361 needs the P-256 / SLIP-10 sentence. Line 373 64 bytes for secp256k1 applies to both.

Suggestions

  • The secp256r1 bullet omits the non-conforming path case: the secp256k1 bullet says such a path still returns a key, just not BIP32-compatible. Worth stating for SLIP-10 too.
  • Bullet placement: move the curve bullets below the return value paragraph, as in schnorr_public_key.

The entry was written when 0.67.0 was the open section. 0.68.0 has since
been cut, so the entry sat under a version released three weeks before the
change existed. Git merged it cleanly because the insertion point stayed
unambiguous, which is why nothing flagged it.
pull Bot pushed a commit to mikeyhodl/ic that referenced this pull request Sep 22, 2026
### Why

* The threshold crypto layer already implements ECDSA on NIST P-256.
[`EccCurveType`](https://github.com/dfinity/ic/blob/master/rs/crypto/internal/crypto_lib/threshold_sig/canister_threshold_sig/src/utils/group.rs#L25)
has a `P256` variant, `AlgorithmId` has `ThresholdEcdsaSecp256r1`, and
the IDkg code paths are curve-generic, with no `todo!` or
`unimplemented!` left on a P256 arm.
* The public
[`EcdsaCurve`](https://github.com/dfinity/ic/blob/master/rs/types/management_canister_types/src/lib.rs#L2892)
enum has a single variant, so a `secp256r1` key id cannot be named in
the management canister API or represented in the registry. The curve is
unreachable from outside the crypto layer.
* P-256 is the curve the web platform standardised on: WebAuthn, TLS,
VAPID web push (ES256) and OAuth DPoP. A canister cannot produce any of
those signatures today, so each one needs a key held off-chain. For
Internet Identity's web push work that key is the blocker.

### What

`EcdsaCurve` gains `Secp256r1` in `rs/types/management_canister_types`
and in the published `packages/ic-management-canister-types`, together
with the Candid doc comment, the `u32` conversions that decode the
`ic0.cost_sign_with_ecdsa` argument, and `FromStr`, which is what NNS
proposals parse. [`From<EcdsaCurve> for
AlgorithmId`](https://github.com/dfinity/ic/blob/master/rs/types/types/src/crypto.rs#L277)
maps the new variant to `AlgorithmId::ThresholdEcdsaSecp256r1`; that is
the single conversion handing a key id to the crypto layer.
`types.proto` gains `ECDSA_CURVE_SECP256R1 = 2` so an `EcdsaKeyId`
round-trips through the registry, plus the regenerated files. Both
`tests/ic.did` fixtures gain the variant, which is what the Candid
equality tests compare the Rust enum against.

Two values need confirming before this merges: `u32` = 1 and protobuf =
2. Each is the next free value in its own scheme, but the `u32` is
normative because it is the System API ABI in the interface spec. The
matching spec change is dfinity/developer-docs#394.

The variant has to land in one commit. Adding it to either the proto or
a types crate on its own breaks the other's exhaustive match, so no
split compiles.

This generates no key and makes no key id available. Enabling a key is a
separate registry and governance step.

`rs/tests/consensus/tecdsa` is deliberately not extended.
`cast_ecdsa_key_id` casts into the published
`ic-management-canister-types` through cdk-rs, so r1 system tests need
that crate released first, and `verify_signature` needs a P-256
verifier. Happy to follow up with that once the crate is out.

### Tests

`cargo check --tests`, the 39 unit tests and both
`candid_equality_test`s pass on `ic-management-canister-types`,
`ic-management-canister-types-private`, `ic-types` and `ic-protobuf`.
`cargo run -p ic-protobuf-generator` reproduces the five generated files
exactly.

Two gaps CI needs to cover, since neither is reachable on macOS:
`rs/orchestrator` does not build there (`libc::gettid`, `SYS_futex`,
`libcryptsetup-rs`), so its test arm is unverified, and a match in any
other Linux-only crate would not have shown up locally.

Unrelated, but worth flagging: running the protobuf generator also
rewrites doc-comment escaping in six generated files it does not
otherwise touch (`\<=` becomes `<=`, list renumbering). That looks like
prost-build 0.14.3 disagreeing with whatever version produced the
committed files. Reverted here, but the next regen will hit it.
alin-at-dfinity pushed a commit to dfinity/ic that referenced this pull request Sep 22, 2026
### Why

* The threshold crypto layer already implements ECDSA on NIST P-256.
[`EccCurveType`](https://github.com/dfinity/ic/blob/master/rs/crypto/internal/crypto_lib/threshold_sig/canister_threshold_sig/src/utils/group.rs#L25)
has a `P256` variant, `AlgorithmId` has `ThresholdEcdsaSecp256r1`, and
the IDkg code paths are curve-generic, with no `todo!` or
`unimplemented!` left on a P256 arm.
* The public
[`EcdsaCurve`](https://github.com/dfinity/ic/blob/master/rs/types/management_canister_types/src/lib.rs#L2892)
enum has a single variant, so a `secp256r1` key id cannot be named in
the management canister API or represented in the registry. The curve is
unreachable from outside the crypto layer.
* P-256 is the curve the web platform standardised on: WebAuthn, TLS,
VAPID web push (ES256) and OAuth DPoP. A canister cannot produce any of
those signatures today, so each one needs a key held off-chain. For
Internet Identity's web push work that key is the blocker.

### What

`EcdsaCurve` gains `Secp256r1` in `rs/types/management_canister_types`
and in the published `packages/ic-management-canister-types`, together
with the Candid doc comment, the `u32` conversions that decode the
`ic0.cost_sign_with_ecdsa` argument, and `FromStr`, which is what NNS
proposals parse. [`From<EcdsaCurve> for
AlgorithmId`](https://github.com/dfinity/ic/blob/master/rs/types/types/src/crypto.rs#L277)
maps the new variant to `AlgorithmId::ThresholdEcdsaSecp256r1`; that is
the single conversion handing a key id to the crypto layer.
`types.proto` gains `ECDSA_CURVE_SECP256R1 = 2` so an `EcdsaKeyId`
round-trips through the registry, plus the regenerated files. Both
`tests/ic.did` fixtures gain the variant, which is what the Candid
equality tests compare the Rust enum against.

Two values need confirming before this merges: `u32` = 1 and protobuf =
2. Each is the next free value in its own scheme, but the `u32` is
normative because it is the System API ABI in the interface spec. The
matching spec change is dfinity/developer-docs#394.

The variant has to land in one commit. Adding it to either the proto or
a types crate on its own breaks the other's exhaustive match, so no
split compiles.

This generates no key and makes no key id available. Enabling a key is a
separate registry and governance step.

`rs/tests/consensus/tecdsa` is deliberately not extended.
`cast_ecdsa_key_id` casts into the published
`ic-management-canister-types` through cdk-rs, so r1 system tests need
that crate released first, and `verify_signature` needs a P-256
verifier. Happy to follow up with that once the crate is out.

### Tests

`cargo check --tests`, the 39 unit tests and both
`candid_equality_test`s pass on `ic-management-canister-types`,
`ic-management-canister-types-private`, `ic-types` and `ic-protobuf`.
`cargo run -p ic-protobuf-generator` reproduces the five generated files
exactly.

Two gaps CI needs to cover, since neither is reachable on macOS:
`rs/orchestrator` does not build there (`libc::gettid`, `SYS_futex`,
`libcryptsetup-rs`), so its test arm is unverified, and a match in any
other Linux-only crate would not have shown up locally.

Unrelated, but worth flagging: running the protobuf generator also
rewrites doc-comment escaping in six generated files it does not
otherwise touch (`\<=` becomes `<=`, list renumbering). That looks like
prost-build 0.14.3 disagreeing with whatever version produced the
committed files. Reverted here, but the next regen will hit it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

interface-spec Changes to the IC interface specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants