Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/did-key-uri-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@agentcommercekit/did": patch
---

`isDidKeyUri` no longer accepts `did:key:z`, the multibase prefix carrying no
key material. The check sliced from index 8 — the length of `"did:key:"`, not of
`"did:key:z"` — so the `z` stayed in the string it tested and satisfied the `+`
in `z[a-km-zA-HJ-NP-Z1-9]+` by itself. The guard therefore vouched for a DID
that `getDidResolver().resolve()` reports as `invalidDid`. The check is now a
single pattern spanning the whole URI, so the quantifier applies to the
base58btc value as the documented grammar intends. Every other input is
unaffected: `z` is itself a base58btc character, so it was the only string the
old slice let through.
24 changes: 24 additions & 0 deletions packages/did/src/methods/did-key.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,28 @@ describe("isDidKeyUri", () => {
it("returns false for invalid did:key", () => {
expect(isDidKeyUri("invalid-did-key")).toBe(false)
})

it("returns false for a multibase prefix with no key material", () => {
expect(isDidKeyUri("did:key:z")).toBe(false)
expect(isDidKeyUri("did:key:")).toBe(false)
})

it("returns false for characters outside the base58btc alphabet", () => {
for (const char of ["0", "O", "I", "l"]) {
expect(isDidKeyUri(`did:key:z${char}`)).toBe(false)
}
})

it("returns false for a non-string", () => {
expect(isDidKeyUri(undefined)).toBe(false)
expect(isDidKeyUri(123)).toBe(false)
})

it("does not vouch for a did:key the resolver rejects", async () => {
const resolver = getDidResolver()
const resolved = await resolver.resolve("did:key:z")

expect(resolved.didResolutionMetadata.error).toBe("invalidDid")
expect(isDidKeyUri("did:key:z")).toBe(false)
})
})
14 changes: 8 additions & 6 deletions packages/did/src/methods/did-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ export const KEY_CONFIG = {
},
} as const

/**
* The `did:key` grammar documented on {@link isDidKeyUri}, as a single pattern.
* The `+` applies to the base58btc value that follows the `z`, so the multibase
* prefix on its own is not a `did:key` URI.
*/
const didKeyUriRegex = /^did:key:z[a-km-zA-HJ-NP-Z1-9]+$/

/**
* Checks if a given item is a valid did:key URI, according to the following format:
* did-key-format := did:key:<mb-value>
Expand All @@ -45,12 +52,7 @@ export const KEY_CONFIG = {
* @returns `true` if the value is a did:key URI, `false` otherwise
*/
export function isDidKeyUri(did: unknown): did is DidKeyUri {
if (typeof did !== "string" || !did.startsWith("did:key:z")) {
return false
}

const mbValue = did.slice(8) // Get everything after "did:key:z"
return /^[a-km-zA-HJ-NP-Z1-9]+$/.test(mbValue)
return typeof did === "string" && didKeyUriRegex.test(did)
}

/**
Expand Down