Skip to content

fix(did,keys): emit a real Multikey for publicKeyMultibase - #166

Open
Dusk1e wants to merge 1 commit into
agentcommercekit:mainfrom
Dusk1e:fix/multikey-publickey-multibase
Open

fix(did,keys): emit a real Multikey for publicKeyMultibase#166
Dusk1e wants to merge 1 commit into
agentcommercekit:mainfrom
Dusk1e:fix/multikey-publickey-multibase

Conversation

@Dusk1e

@Dusk1e Dusk1e commented Aug 16, 2026

Copy link
Copy Markdown

Fixes #165.

createDidDocument builds verification methods with type: "Multikey", but the publicKeyMultibase on them was a multibase encoding of the raw public key bytes. A Multikey is multibase(base58-btc, varint(multicodec code) ‖ key-bytes), so the value carried no algorithm identifier — a relying party reading it as a Multikey cannot tell an Ed25519 key from a P-256 one. createDidKeyUri already built the prefixed form, so the same key came out two different ways depending on which function produced it.

Using the two keypairs fixtured in packages/did/src/methods/did-key.test.ts:

curve before after (= createDidKeyUri)
Ed25519 z8myPWEuZj3T3WzBQu281AeLzE3pmU9h7em1YEGiD6ick z6MknEES6VA14awWdV27ab5r1jtz3d6ct2wULmvU4YgE1wQ8
secp256k1 zMi3oP598f15BeBYp3tdwtNEE3omWSogmoT8Q5ckb1N5AKKJj59KU61vMYYncCBDzGDaDkn7zib2ACobFPXB8TjGT zQ3shNCcRrVT3tm43o6JNjSjQaiBXvSb8kHtFhoNGR8eimFZs

Change

publicKeyToMultikey(publicKey, curve) in @agentcommercekit/keys prepends the curve's multicodec code as a varint and multibase-encodes the result. encodePublicKeyMultibase uses it, and convertLegacyPublicKeyToMultibase in @agentcommercekit/did routes hex and base58 through the same encoder rather than calling bytesToMultibase directly — those two are converted to a Multikey verification method as well, so they carried the same defect.

The codes live in a new keyCurveMulticodecs map in keys, next to keyCurves. That is the placement decision I flagged in the issue: keys already owns curve metadata, key generation and every other encoding, and it is the package the encoder lives in. KEY_CONFIG in did/methods/did-key.ts is untouched, so nothing about did:key changes — instead the new test asserts the two agree, which is what keeps them from drifting apart again.

secp256k1 and secp256r1 keys are compressed to the 33-byte form their codes identify, via a new compressPublicKey on each curve module (Point.fromBytes(...).toBytes(true), the same @noble/curves API isValidPublicKey already uses). generateKeypair stores these uncompressed, which is why the secp256k1 row above is so long. Ed25519 has a single 32-byte encoding and is passed through.

Behaviour changes

  • The publicKeyMultibase emitted by createDidDocument, createDidDocumentFromKeypair, createDidWebDocument and encodePublicKey("multibase", ...) changes for the multibase, hex and base58 encodings. The default jwk encoding is unaffected. An already-published document keeps whatever it was published with; re-generating it produces the corrected value. Marked minor on both packages.
  • encodePublicKey("multibase", bytes, curve) now throws for secp256k1/secp256r1 when bytes is not a point on that curve, because compressing it means decoding it. This surfaced in did-resolvers/did-resolver.test.ts, which passed a 20-byte Ethereum address as a secp256k1 public key; that fixture is now a real key from did-key.test.ts. The test exercises the resolver cache, so the key material is incidental to it. Happy to make the compression lenient instead if you would rather this path keep accepting arbitrary bytes, though that would put non-keys back into Multikey values.

Tests

  • keys: the multibase encoding decodes to the curve's multicodec code followed by a key of the expected length (32 for Ed25519, 33 for the EC curves).
  • did: for every curve, publicKeyMultibase equals the did:key identifier for the same key, across the multibase, hex and base58 encodings — asserted against createDidKeyUri, which is independent of the code being changed.
  • Two existing expectations that encoded the old value (create-did-document.test.ts, methods/did-web.test.ts) now build theirs with publicKeyToMultikey.

Both new tests fail on main. keys (102), did (73), vc (109), ack-id (39) and ack-pay (34) pass; oxlint and oxfmt --check are clean on the changed files.

One note on my local run: packages/did/src/did-resolvers/pkh-did-resolver.test.ts cannot run on Windows because the did-pkh fixture filenames contain :, which is #145. Unrelated to this change and left alone.

AI assistance disclosure

Per the repository AI policy: this contribution was AI-assisted using Claude Code (Claude Opus). AI assistance was used to find the inconsistency, write the change and the tests, and run verification locally. I reviewed the final diff, can explain what it does and where it changes behaviour, and take responsibility for what is submitted here.

Summary by CodeRabbit

  • New Features
    • Public keys now use Multikey encoding compatible with did:key identifiers.
    • Added support for compressed secp256k1 and secp256r1 public keys.
    • Added curve-specific public-key compression and Multicodec mapping APIs.
  • Bug Fixes
    • Invalid secp256k1 and secp256r1 points are now rejected during multibase encoding.
    • Legacy hex and Base58 keys are converted using the correct Multikey format.
    • JWK output remains unchanged.

`createDidDocument` builds verification methods with `type: "Multikey"`,
but `publicKeyMultibase` held a multibase encoding of the raw public key
bytes. A Multikey is `multibase(base58-btc, varint(multicodec) ‖ key)`, so
the value carried no algorithm identifier and a relying party could not
tell one curve's key from another's. `createDidKeyUri` already built the
prefixed form, so the same key came out two different ways depending on
which function produced it.

Encode the multicodec code for the curve and, for secp256k1 and
secp256r1, the 33-byte compressed point that code identifies. The
`hex` and `base58` inputs route through the same encoder, since they are
converted to a Multikey verification method too.

`publicKeyMultibase` now equals the `did:key` method-specific identifier
for the same key, which is asserted directly against `createDidKeyUri`.
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0349bee6-4b68-4dfa-bc99-a543e31cb317

📥 Commits

Reviewing files that changed from the base of the PR and between 0b8fdaa and 5ab3c68.

📒 Files selected for processing (10)
  • .changeset/multikey-public-key-multibase.md
  • packages/did/src/create-did-document.test.ts
  • packages/did/src/create-did-document.ts
  • packages/did/src/did-resolvers/did-resolver.test.ts
  • packages/did/src/methods/did-web.test.ts
  • packages/keys/src/curves/secp256k1.ts
  • packages/keys/src/curves/secp256r1.ts
  • packages/keys/src/key-curves.ts
  • packages/keys/src/public-key.test.ts
  • packages/keys/src/public-key.ts

Included review availability: Your plan includes up to 2 reviews per rolling hour; 0 remain after this review.


Walkthrough

@agentcommercekit/keys now generates Multikey-form public key encodings with curve Multicodec prefixes and compressed elliptic-curve keys. @agentcommercekit/did uses these encodings for document verification methods and adds matching did:key coverage.

Changes

Multikey public key encoding

Layer / File(s) Summary
Curve metadata and compression
packages/keys/src/key-curves.ts, packages/keys/src/curves/secp256k1.ts, packages/keys/src/curves/secp256r1.ts
Added curve-specific Multicodec identifiers and public key compression APIs.
Public key Multikey encoding
packages/keys/src/public-key.ts, packages/keys/src/public-key.test.ts
Added publicKeyToMultikey. Multibase encoding now includes the curve Multicodec prefix and compressed key bytes.
DID document integration and validation
packages/did/src/create-did-document.ts, packages/did/src/create-did-document.test.ts, packages/did/src/methods/did-web.test.ts, packages/did/src/did-resolvers/did-resolver.test.ts, .changeset/multikey-public-key-multibase.md
DID document conversion uses the shared encoder. Tests verify matching Multikey values across supported curves and input encodings.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 5ab3c

The change corrects Multikey encoding and includes focused coverage for the affected key and DID paths; no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

Suggested reviewers: venables

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The changes satisfy the Multikey requirements in #165, but they do not address the directly linked Windows fixture issue in #145. Remove #145 from the linked issues or fix the Windows-incompatible fixture filenames before merging.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: emitting valid Multikey values for DID and key packages.
Out of Scope Changes check ✅ Passed The code changes support the Multikey encoding fix in #165 and contain no unrelated implementation changes.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

bug(did): publicKeyMultibase on Multikey verification methods is not a Multikey

1 participant