Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/lovely-rabbits-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@agentcommercekit/keys": patch
"@agentcommercekit/did": patch
---

Improve JWK methods, add did:jwks support
1 change: 1 addition & 0 deletions packages/did/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@agentcommercekit/caip": "workspace:*",
"@agentcommercekit/keys": "workspace:*",
"did-resolver": "^4.1.0",
"jwks-did-resolver": "^0.3.0",
"key-did-resolver": "^4.0.0",
"valibot": "^1.1.0",
"varint": "^6.0.0"
Expand Down
37 changes: 20 additions & 17 deletions packages/did/src/create-did-document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import {
keyCurves,
publicKeyEncodings
} from "@agentcommercekit/keys"
import {
bytesToMultibase,
publicKeyBytesToJwk
} from "@agentcommercekit/keys/encoding"
import { beforeEach, describe, expect, test } from "vitest"
import {
createDidDocument,
Expand Down Expand Up @@ -32,13 +36,6 @@ const contextMap = {
base58: "https://w3id.org/security/multikey/v1"
}

const encodingToPropertyMap = {
hex: "publicKeyMultibase",
jwk: "publicKeyJwk",
multibase: "publicKeyMultibase",
base58: "publicKeyMultibase"
} as const

describe("createDidDocument() and createDidDocumentFromKeypair()", () => {
const did = "did:web:example.com"
let secp256k1Keypair: Keypair
Expand Down Expand Up @@ -76,19 +73,25 @@ describe("createDidDocument() and createDidDocumentFromKeypair()", () => {
})

const keyId = `${did}#${encodingMap[encoding]}-1`
const expectedVerificationMethod =
encoding === "jwk"
? {
id: keyId,
type: keyTypeMap[encoding],
controller: did,
publicKeyJwk: publicKeyBytesToJwk(keypair.publicKey, curve)
}
: {
id: keyId,
type: keyTypeMap[encoding],
controller: did,
publicKeyMultibase: bytesToMultibase(keypair.publicKey)
}

const expectedDocument = {
"@context": ["https://www.w3.org/ns/did/v1", contextMap[encoding]],
id: did,
verificationMethod: [
{
id: keyId,
type: keyTypeMap[encoding],
controller: did,
[encodingToPropertyMap[encoding]]: expect.any(
encoding === "jwk" ? Object : String
) as unknown
}
],
verificationMethod: [expectedVerificationMethod],
authentication: [keyId],
assertionMethod: [keyId]
}
Expand Down
10 changes: 6 additions & 4 deletions packages/did/src/create-did-document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@
*/
export function createVerificationMethod({
did,
publicKey
publicKey: publicKeyWithEncoding
}: CreateVerificationMethodOptions): VerificationMethod {
const { encoding, value } = convertLegacyPublicKeyToMultibase(publicKey)
const { encoding, value: publicKey } = convertLegacyPublicKeyToMultibase(
publicKeyWithEncoding
)

const verificationMethod: VerificationMethod = {
id: `${did}#${encoding}-1`,
Expand All @@ -49,11 +51,11 @@
switch (encoding) {
case "jwk":
verificationMethod.type = "JsonWebKey2020"
verificationMethod.publicKeyJwk = value
verificationMethod.publicKeyJwk = publicKey
break
case "multibase":
verificationMethod.type = "Multikey"
verificationMethod.publicKeyMultibase = value
verificationMethod.publicKeyMultibase = publicKey
break
}

Expand Down Expand Up @@ -152,7 +154,7 @@
* value: {
* kty: "OKP",
* crv: "Ed25519",
* x: "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"

Check warning on line 157 in packages/did/src/create-did-document.ts

View workflow job for this annotation

GitHub Actions / check

Unknown word: "Mlrw"

Check warning on line 157 in packages/did/src/create-did-document.ts

View workflow job for this annotation

GitHub Actions / check

Unknown word: "Papi"
* }
* }
* })
Expand Down
3 changes: 3 additions & 0 deletions packages/did/src/did-resolvers/get-did-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getResolver as getJwksDidResolver } from "jwks-did-resolver"
import { getResolver as getKeyDidResolver } from "key-did-resolver"
import { DidResolver } from "./did-resolver"
import { getResolver as getPkhDidResolver } from "./pkh-did-resolver"
Expand Down Expand Up @@ -26,12 +27,14 @@ export function getDidResolver({
}: GetDidResolverOptions = {}): DidResolver {
const keyResolver = getKeyDidResolver()
const webResolver = getWebDidResolver(webOptions)
const jwksResolver = getJwksDidResolver(webOptions)
const pkhResolver = getPkhDidResolver()

const didResolver = new DidResolver(
{
...keyResolver,
...webResolver,
...jwksResolver,
...pkhResolver
},
options
Expand Down
157 changes: 126 additions & 31 deletions packages/keys/src/encoding/jwk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,79 @@ import { getPublicKeyFromPrivateKey } from "../public-key"
import type { KeyCurve } from "../key-curves"

/**
* JWK-encoding, specifically limited to public keys
* JWK-encoding
*/
export type PublicKeyJwkSecp256k1 = {
export type JwkSecp256k1 = {
kty: "EC"
crv: "secp256k1"
x: string // base64url encoded x-coordinate
y: string // base64url encoded y-coordinate
d?: string // base64url encoded private key
}

export type PublicKeyJwkSecp256r1 = {
export type PublicKeyJwkSecp256k1 = JwkSecp256k1 & {
d?: never
}

export type PrivateKeyJwkSecp256k1 = JwkSecp256k1 & {
d: string // base64url encoded private key
}

export type JwkSecp256r1 = {
kty: "EC"
crv: "secp256r1"
x: string // base64url encoded x-coordinate
y: string // base64url encoded y-coordinate
d?: string // base64url encoded private key
}

export type PublicKeyJwkEd25519 = {
export type PublicKeyJwkSecp256r1 = JwkSecp256r1 & {
d?: never
}

export type PrivateKeyJwkSecp256r1 = JwkSecp256r1 & {
d: string // base64url encoded private key
}

export type JwkEd25519 = {
kty: "OKP"
crv: "Ed25519"
x: string // base64url encoded x-coordinate
d?: string // base64url encoded private key
}

export type PublicKeyJwkEd25519 = JwkEd25519 & {
d?: never
}

export type PrivateKeyJwkEd25519 = JwkEd25519 & {
d: string // base64url encoded private key
}

export type Jwk = JwkSecp256k1 | JwkSecp256r1 | JwkEd25519

export type PublicKeyJwk =
| PublicKeyJwkSecp256k1
| PublicKeyJwkSecp256r1
| PublicKeyJwkEd25519

export type PrivateKeyJwk =
| PrivateKeyJwkSecp256k1
| PrivateKeyJwkSecp256r1
| PrivateKeyJwkEd25519

/**
* JWK-encoding for private keys
* Check if an object is a valid secp256k1 or secp256r1 public key (or private
* key) JWK
*
* @param jwk - The JWK to check
* @param crv - The curve to check
* @returns True if the JWK is a valid secp256k1 or secp256r1 public key JWK
*/
export type PrivateKeyJwk = PublicKeyJwk & {
d: string // base64url encoded private key
}

function isPublicKeyJwkSecp256(
function isJwkSecp256(
jwk: unknown,
crv: "secp256k1" | "secp256r1"
): jwk is PublicKeyJwkSecp256k1 | PublicKeyJwkSecp256r1 {
): jwk is JwkSecp256k1 | JwkSecp256r1 {
if (typeof jwk !== "object" || jwk === null) {
return false
}
Expand All @@ -62,21 +97,33 @@ function isPublicKeyJwkSecp256(
return true
}

export function isPublicKeyJwkSecp256k1(
jwk: unknown
): jwk is PublicKeyJwkSecp256k1 {
return isPublicKeyJwkSecp256(jwk, "secp256k1")
/**
* Check if an object is a valid secp256k1 public key (or private key) JWK
*
* @param jwk - The JWK to check
* @returns True if the JWK is a valid secp256k1 public key JWK
*/
export function isJwkSecp256k1(jwk: unknown): jwk is JwkSecp256k1 {
return isJwkSecp256(jwk, "secp256k1")
}

export function isPublicKeyJwkSecp256r1(
jwk: unknown
): jwk is PublicKeyJwkSecp256r1 {
return isPublicKeyJwkSecp256(jwk, "secp256r1")
/**
* Check if an object is a valid secp256r1 public key (or private key) JWK
*
* @param jwk - The JWK to check
* @returns True if the JWK is a valid secp256r1 public key JWK
*/
export function isJwkSecp256r1(jwk: unknown): jwk is JwkSecp256r1 {
return isJwkSecp256(jwk, "secp256r1")
}

export function isPublicKeyJwkEd25519(
jwk: unknown
): jwk is PublicKeyJwkEd25519 {
/**
* Check if an object is a valid Ed25519 public key (or private key) JWK
*
* @param jwk - The JWK to check
* @returns True if the JWK is a valid Ed25519 public key JWK
*/
export function isJwkEd25519(jwk: unknown): jwk is JwkEd25519 {
if (typeof jwk !== "object" || jwk === null) {
return false
}
Expand All @@ -98,27 +145,75 @@ export function isPublicKeyJwkEd25519(
return true
}

export function isJwk(jwk: unknown): jwk is Jwk {
return isJwkSecp256k1(jwk) || isJwkSecp256r1(jwk) || isJwkEd25519(jwk)
}

/**
* Check if an object is a valid public key JWK
*/
export function isPublicKeyJwk(jwk: unknown): jwk is PublicKeyJwk {
return (
isPublicKeyJwkSecp256k1(jwk) ||
isPublicKeyJwkSecp256r1(jwk) ||
isPublicKeyJwkEd25519(jwk)
)
return isJwk(jwk) && !("d" in jwk)
}

export function isPublicKeyJwkSecp256k1(
jwk: unknown
): jwk is PublicKeyJwkSecp256k1 {
return isJwkSecp256k1(jwk) && isPublicKeyJwk(jwk)
}

export function isPublicKeyJwkSecp256r1(
jwk: unknown
): jwk is PublicKeyJwkSecp256r1 {
return isJwkSecp256r1(jwk) && isPublicKeyJwk(jwk)
}

export function isPublicKeyJwkEd25519(
jwk: unknown
): jwk is PublicKeyJwkEd25519 {
return isJwkEd25519(jwk) && isPublicKeyJwk(jwk)
}

/**
* Check if an object is a valid private key JWK
*/
export function isPrivateKeyJwk(jwk: unknown): jwk is PrivateKeyJwk {
if (!isPublicKeyJwk(jwk)) {
return false
return isJwk(jwk) && !!jwk.d
}

export function isPrivateKeyJwkSecp256k1(
jwk: unknown
): jwk is PrivateKeyJwkSecp256k1 {
return isJwkSecp256k1(jwk) && isPrivateKeyJwk(jwk)
}

export function isPrivateKeyJwkSecp256r1(
jwk: unknown
): jwk is PrivateKeyJwkSecp256r1 {
return isJwkSecp256r1(jwk) && isPrivateKeyJwk(jwk)
}

export function isPrivateKeyJwkEd25519(
jwk: unknown
): jwk is PrivateKeyJwkEd25519 {
return isJwkEd25519(jwk) && isPrivateKeyJwk(jwk)
}

/**
* Get the public key JWK from a private key JWK
*
* @param jwk - The private key JWK
* @returns The public key JWK
*/
export function getPublicKeyJwk(
jwk: PrivateKeyJwk | PublicKeyJwk
): PublicKeyJwk {
if (isPrivateKeyJwk(jwk)) {
const { d: _d, ...publicKeyJwk } = jwk
return publicKeyJwk
}

const obj = jwk as Record<string, unknown>
return typeof obj.d === "string" && obj.d.length > 0
return jwk
}

/**
Expand Down
10 changes: 8 additions & 2 deletions packages/keys/src/keypair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import * as ed25519 from "./curves/ed25519"
import * as secp256k1 from "./curves/secp256k1"
import * as secp256r1 from "./curves/secp256r1"
import { base64urlToBytes } from "./encoding/base64"
import { privateKeyBytesToJwk, publicKeyJwkToBytes } from "./encoding/jwk"
import {
getPublicKeyJwk,
privateKeyBytesToJwk,
publicKeyJwkToBytes
} from "./encoding/jwk"
import type { PrivateKeyJwk } from "./encoding/jwk"
import type { KeyCurve } from "./key-curves"

Expand Down Expand Up @@ -63,8 +67,10 @@ export function keypairToJwk(keypair: Keypair): PrivateKeyJwk {
* @returns A Keypair
*/
export function jwkToKeypair(jwk: PrivateKeyJwk): Keypair {
const publicKeyJwk = getPublicKeyJwk(jwk)

return {
publicKey: publicKeyJwkToBytes(jwk),
publicKey: publicKeyJwkToBytes(publicKeyJwk),
privateKey: base64urlToBytes(jwk.d),
curve: jwk.crv
}
Expand Down
Loading
Loading