Skip to content

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

Open
Dusk1e wants to merge 2 commits into
agentcommercekit:mainfrom
Dusk1e:fix/multikey-publickey-multibase
Open

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

Conversation

@Dusk1e

@Dusk1e Dusk1e commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

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 public keys 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: Team

Run ID: 62e367f0-ee38-4b97-a14a-83b0fae19824

📥 Commits

Reviewing files that changed from the base of the PR and between 5ab3c68 and 964897a.

📒 Files selected for processing (3)
  • .changeset/multikey-public-key-multibase.md
  • packages/keys/src/public-key.test.ts
  • packages/keys/src/public-key.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/multikey-public-key-multibase.md

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains 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 with curve validation. 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. The changeset records the package releases and API changes.

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

Merge Risk: ⚪ Minimal · up to 96489

The PR corrects DID verification-method identifiers so Multikey values include the curve identifier and remain consistent with did:key output; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: venables

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: emitting a real Multikey for publicKeyMultibase values.
Linked Issues check ✅ Passed The changes satisfy issue #165 by adding curve-specific multicodec prefixes, compressing supported EC keys, applying the encoding to multibase, hex, and base58 inputs, and validating public keys.
Out of Scope Changes check ✅ Passed The changes remain within scope. The new compression, multicodec mapping, validation, implementation updates, and tests directly support the Multikey encoding correction.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 5 files. (1 skipped: 1 u…
Full details: Docstring Coverage

Explanation

Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 5 files. (1 skipped: 1 unsupported.)

✨ 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.

* @param curve - The curve the key belongs to
* @returns The Multikey string
*/
export function publicKeyToMultikey(

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.

It looks like Ed25519 input is not validated in this method but EC input is. We could likely call isValidPublicKey(publicKey, curve) at the top w/ errors thrown for each curve

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, fixed sir

…ikey

The secp256k1 and secp256r1 paths rejected bad input only as a side
effect of decoding the point to compress it, and Ed25519 has no
compression step, so any 32 bytes — and in fact any length — took the
`0xed` prefix and came back as a well-formed Multikey.

Check `isValidPublicKey(publicKey, curve)` up front instead, so every
curve fails the same way and for the stated reason.
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

2 participants