Skip to content

Add did:key node IDs and certificate-free member/user identities - #8602

Draft
Amaury Chamayou (achamayou) wants to merge 9 commits into
microsoft:mainfrom
achamayou:achamayou-issue-2419-support-more-digest-algorithms-dc20ad
Draft

Amaury Chamayou (achamayou) wants to merge 9 commits into
microsoft:mainfrom
achamayou:achamayou-issue-2419-support-more-digest-algorithms-dc20ad

Conversation

@achamayou

@achamayou Amaury Chamayou (achamayou) commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Motivation

Remove the certificate dependency from member/user enrollment and COSE authentication, and use IDs containing public keys rather than hard-coded SHA-256 fingerprints.
Related to #2419; existing signature, receipt, and attestation digest algorithms are unchanged.

Implementation summary

  • Enroll members and users directly with did_key, without cert or id_format, and store DER public keys in separate governance tables instead of identity certificates.
  • Authenticate COSE requests against enrolled public keys; support certificate-free genesis members, Python SDK/CLI signing, and recovery-share submission.
  • Support atomic migration through ordered remove_member/set_member or remove_user/set_user actions in one proposal, while retaining application-time duplicate checks and member activation/recovery constraints.
  • Preserve opt-in did:key node IDs, legacy certificate enrollment, and optional TLS client-certificate compatibility; update caller types and API/configuration schemas.
  • Document the certificate-free workflow and atomic migration in the RST migration guide.

Safety and compatibility

Legacy certificate identities retain their SHA-256 IDs and existing behavior.
Canonical DIDs embed supported public keys, have no certificate validity period, and are authorized and revoked explicitly through governance.
Certificate-free COSE retains the existing supported ECDSA algorithms.
DIDs are enrolled identities, not aliases or authorization grants; same-role cross-format duplicate keys are rejected.
Migration actions apply in one transaction, with rollback if any action fails.
Migrated members start Accepted and must acknowledge again; recovery thresholds cannot be bypassed by atomic replacement.
There is no backfill or automatic transfer of historical metadata or recovery state.
Upgrade every node before opting in to DID identities.
The legacy constitution path retains compatibility with older nodes during rolling upgrades.

Validation

Current certificate-free changes pass:

  • Four affected native unit-test executables and builds of the governance/logging runtime variants.
  • The complete Python SDK suite: 631 tests, including certificate-free CLI signing.
  • JavaScript build and 98 tests, including ordered migration and duplicate-key regressions.
  • Both focused DID governance scenarios: legacy migration and certificate-free DID genesis, including failed-proposal rollback, activation, revocation, snapshot reconstruction, and recovery-owner signing.
  • Focused schema, host-configuration, and COSE-mode workflow scenarios.
  • Live upgrade from released CCF 7.0.23 to the current build.
  • Full warning-as-error Sphinx build and scripts/ci-checks.sh without auto-fix.

Full extended compatibility is not passing.
With -DLONG_TESTS=ON, cd build && ./tests.sh --output-on-failure -R '^lts_compatibility$' --no-tests=error reproduces the existing failure while released CCF 6.0.28 initiates private recovery (HTTP 500), before that ledger-recovery sequence reaches the current build.
Remaining extended ledger-compatibility cases and required GitHub CI checks must pass before merge.

Custom instructions used

Applied .github/copilot-instructions.md, .github/instructions/reviewing.instructions.md, .github/instructions/changelog.instructions.md, and .github/CONTRIBUTING.md.

Keep SHA-256 defaults and digest behavior unchanged while supporting canonical public-key IDs throughout enrollment, authentication, node lifecycle, and SDK tooling. Preserve legacy enrollment during CCF 7 rolling upgrades and cover mixed-format lifecycle and recovery.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@achamayou
Amaury Chamayou (achamayou) requested a review from a team as a code owner October 19, 2026 17:20
Copilot AI balanced review requested due to automatic review settings October 19, 2026 17:20
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

🟡 Changes recommended

Disk-based DID recovery and encoded node requests remain broken, and legacy enrollment adds quadratic table scanning.

3 open issues
What changed in this PR

Adds opt-in public-key identities across CCF while retaining SHA-256 defaults. This supports same-key certificate replacement, not the digest agility requested in #2419.

Changes:

  • Adds canonical did:key derivation, enrollment, and authentication.
  • Integrates identity formats into joining, recovery, signing tools, and governance paths.
  • Updates schemas, migration documentation, and regression coverage.

Custom instructions used

  • .github/instructions/reviewing.instructions.md
File Description
tests/​lts_compatibility.py Checks legacy enrollment during upgrades.
tests/​infra/​remote.py Propagates node identity format.
tests/​infra/​openapi.py Decodes schema-validation path parameters.
tests/​infra/​node.py Supports format-aware node identities.
tests/​infra/​network.py Configures member and user formats.
tests/​infra/​member.py Supports DID signing and recovery paths.
tests/​infra/​crypto.py Adds identity derivation helpers.
tests/​infra/​consortium.py Passes enrollment formats.
tests/​infra/​clients.py Supports DID COSE identities.
tests/​governance.py Adds DID lifecycle and recovery scenarios.
tests/​config.jinja Emits node identity configuration.
src/​node/​startup_inputs.h Passes genesis member formats.
src/​node/​rpc/​test/​node_frontend_test.cpp Tests DID configuration and joining.
src/​node/​rpc/​test/​internal_tables_access_test.cpp Tests enrollment conflict handling.
src/​node/​rpc/​test/​frontend_test.cpp Tests DID certificate authentication.
src/​node/​rpc/​serialization.h Serializes join identity format.
src/​node/​rpc/​self_cert_auth.h Matches the local node's format.
src/​node/​rpc/​node_frontend.h Handles DID joining and duplicates.
src/​node/​rpc/​node_call_types.h Adds join format selection.
src/​node/​node_state.h Derives and confirms node IDs.
src/​node/​internal_tables_access.h Enforces enrollment format constraints.
src/​node/​gov/​handlers/​helpers.h Decodes and validates identity paths.
src/​node/​gov/​handlers/​acks.h Decodes member action paths.
src/​node/​gov/​gov_endpoint_registry.h Updates identity schemas.
src/​js/​extensions/​ccf/​converters.cpp Exposes native identity converters.
src/​endpoints/​test/​endpoint_registry.cpp Tests encoded DID action paths.
src/​endpoints/​authentication/​cose_auth.cpp Checks enrolled DID key binding.
src/​endpoints/​authentication/​cert_auth.cpp Adds DID certificate lookup.
src/​enclave/​enclave.h Passes configured node format.
src/​crypto/​test/​crypto.cpp Tests DID encoding and key equality.
src/​crypto/​did_key.cpp Implements canonical DID conversion.
samples/​constitutions/​default/​actions.js Adds DID enrollment safeguards.
python/​utils/​submit_recovery_share.sh Supports DID recovery signing.
python/​tests/​test_identity.py Tests SDK identity derivation.
python/​tests/​test_cose_sign.py Tests both signing formats.
python/​src/​ccf/​identity.py Adds identity helpers and CLI.
python/​src/​ccf/​cose.py Adds signing format selection.
js/​ccf-app/​test/​polyfill.test.ts Tests identity polyfills.
js/​ccf-app/​src/​polyfill.ts Implements local identity conversion.
js/​ccf-app/​src/​global.ts Declares identity APIs.
include/​ccf/​service/​tables/​users.h Adds user enrollment format.
include/​ccf/​service/​tables/​nodes.h Adds format-aware node derivation.
include/​ccf/​service/​tables/​members.h Adds member enrollment format.
include/​ccf/​node/​configuration.h Adds startup format options.
include/​ccf/​identity_id_format.h Defines supported formats.
include/​ccf/​entity_id.h Validates and describes DID IDs.
include/​ccf/​crypto/​did_key.h Declares DID conversion APIs.
include/​ccf/​crypto/​cert_id.h Adds certificate identity derivation.
doc/​use_apps/​issue_commands.rst Documents DID signing options.
doc/​schemas/​node_openapi.json Allows DID node IDs.
doc/​schemas/​gov/​2024-07-01/​gov.json Updates stable governance schemas.
doc/​schemas/​gov/​2023-06-01-preview/​gov.json Updates preview governance schemas.
doc/​schemas/​gov_openapi.json Updates latest governance schemas.
doc/​operations/​configuration.rst Documents startup identity settings.
doc/​host_config_schema/​host_config.json Describes format configuration fields.
doc/​governance/​open_network.rst Documents DID user enrollment.
doc/​governance/​adding_member.rst Documents DID member workflows.
doc/​build_apps/​auth/​cert.rst Explains replacement-certificate authentication.
doc/​architecture/​cryptography.rst Defines identity and migration semantics.
cmake/​crypto.cmake Includes DID implementation in builds.
CHANGELOG.md Announces opt-in identity support.

Review effort: Balanced

Comment thread tests/infra/member.py Resolved
Comment thread src/node/rpc/node_frontend.h Resolved
Use a one-byte identity format enum and explicit QuickJS boolean conversions to satisfy Virtual A's clang-tidy checks.

Normalize DER-imported RSA public keys through JWK before canonical PKCS#1 re-encoding. This preserves DID encoding while fixing the Node 24 failure in modules_test and programmability_and_jwt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve enrolled member formats and recovery roles during disk-based recovery. Avoid scanning legacy certificate rosters and decode node identity path parameters, with native and end-to-end regression coverage.

Allow certificate DID results to move into optional returns, fixing the required clang-tidy CI failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…errors

- set_member no longer no-ops for an already-enrolled did:key member: it
  replaces the stored certificate, encryption key and member data and
  resets the member to Accepted, exactly like legacy members.
- Join requests whose caller key cannot be identified in the requested
  ID format now fail with 400 instead of 500; DID derivation failures in
  cert/node/self authentication policies fall through to generic reasons
  rather than leaking parser error text.
- Clarify in docs that enrolled-certificate validity does not bound
  access for DID identities.
- Add JS unit, native and e2e regression coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refresh against main at 9cdc86c without rewriting published history. Adapt the unsupported-key join regression to the updated certificate helper's separate validity arguments, and move the DID release note and SDK version to the next patch, 7.0.24.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Store public keys without identity certificates, authenticate DID COSE requests directly, and migrate legacy identities atomically in ordered governance proposals. Update genesis, SDK/CLI, recovery tooling, schemas, regression coverage, and the RST migration guide.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 264fbdd6871f4086ab3af9b5484907c3
@achamayou Amaury Chamayou (achamayou) changed the title Add opt-in did:key identities for nodes, members, and users Add did:key node IDs and certificate-free member/user identities Nov 8, 2026

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants