From dcf0224175bf656c34abf3107e4530e3e72a17ba Mon Sep 17 00:00:00 2001 From: Dominic Bosco Date: Wed, 13 Aug 2025 09:26:02 -0400 Subject: [PATCH] add domain/challenge to vp signing; only return jwt from signing --- .changeset/cuddly-rocks-tell.md | 8 ++ demos/e2e/src/credential-issuer.ts | 5 +- demos/e2e/src/receipt-issuer.ts | 5 +- demos/identity-a2a/src/issuer.ts | 8 +- demos/identity/src/credential-issuer.ts | 8 +- demos/payments/src/receipt-service.ts | 8 +- .../credentials/build-signed-credential.ts | 16 +-- examples/issuer/src/routes/status.ts | 14 ++- .../src/verify-payment-receipt.test.ts | 14 ++- packages/vc/src/index.ts | 4 + .../vc/src/signing/sign-credential.test.ts | 7 +- packages/vc/src/signing/sign-credential.ts | 17 ++- .../vc/src/signing/sign-presentation.test.ts | 107 +++++++++++------- packages/vc/src/signing/sign-presentation.ts | 39 +++---- packages/vc/src/signing/types.ts | 7 +- .../verification/parse-jwt-credential.test.ts | 5 +- .../src/verification/parse-jwt-credential.ts | 6 +- 17 files changed, 153 insertions(+), 125 deletions(-) create mode 100644 .changeset/cuddly-rocks-tell.md diff --git a/.changeset/cuddly-rocks-tell.md b/.changeset/cuddly-rocks-tell.md new file mode 100644 index 0000000..6884c55 --- /dev/null +++ b/.changeset/cuddly-rocks-tell.md @@ -0,0 +1,8 @@ +--- +"@agentcommercekit/ack-pay": minor +"@agentcommercekit/vc": minor +"@agentcommercekit/ack-id": minor +"agentcommercekit": minor +--- + +Update credential signing to return only jwt; add domain and challenge to verifiable presentation signing. diff --git a/demos/e2e/src/credential-issuer.ts b/demos/e2e/src/credential-issuer.ts index d0c69dc..6434f36 100644 --- a/demos/e2e/src/credential-issuer.ts +++ b/demos/e2e/src/credential-issuer.ts @@ -90,11 +90,10 @@ export class CredentialIssuer { issuer: this.did }) - const { jwt } = await signCredential(credential, { + const jwt = await signCredential(credential, { did: this.did, signer: this.signer, - alg: "ES256K", - resolver: this.resolver + alg: "ES256K" }) return jwt diff --git a/demos/e2e/src/receipt-issuer.ts b/demos/e2e/src/receipt-issuer.ts index 8198659..e69cf8d 100644 --- a/demos/e2e/src/receipt-issuer.ts +++ b/demos/e2e/src/receipt-issuer.ts @@ -92,11 +92,10 @@ export class ReceiptIssuer { payerDid }) - const { jwt } = await signCredential(credential, { + const jwt = await signCredential(credential, { did: this.did, signer: this.signer, - alg: "ES256K", - resolver: this.resolver + alg: "ES256K" }) return jwt diff --git a/demos/identity-a2a/src/issuer.ts b/demos/identity-a2a/src/issuer.ts index f24ea07..5c403a8 100644 --- a/demos/identity-a2a/src/issuer.ts +++ b/demos/identity-a2a/src/issuer.ts @@ -5,6 +5,7 @@ import { createJwtSigner, generateKeypair, getDidResolver, + parseJwtCredential, signCredential } from "agentcommercekit" import type { DidUri } from "agentcommercekit" @@ -37,12 +38,13 @@ export async function issueCredential({ issuer: issuerDid }) - const { verifiableCredential } = await signCredential(credential, { + const jwt = await signCredential(credential, { did: issuerDid, signer, - alg: "EdDSA", - resolver + alg: "EdDSA" }) + const verifiableCredential = await parseJwtCredential(jwt, resolver) + return verifiableCredential } diff --git a/demos/identity/src/credential-issuer.ts b/demos/identity/src/credential-issuer.ts index 6f953ab..39f497a 100644 --- a/demos/identity/src/credential-issuer.ts +++ b/demos/identity/src/credential-issuer.ts @@ -3,6 +3,7 @@ import { createDidWebDocumentFromKeypair, createJwtSigner, generateKeypair, + parseJwtCredential, signCredential, verifyJwt } from "agentcommercekit" @@ -88,13 +89,14 @@ export class CredentialIssuer { issuer: this.did }) - const { verifiableCredential } = await signCredential(credential, { + const jwt = await signCredential(credential, { did: this.did, signer: this.signer, - alg: "ES256K", - resolver: this.resolver + alg: "ES256K" }) + const verifiableCredential = await parseJwtCredential(jwt, this.resolver) + return verifiableCredential } } diff --git a/demos/payments/src/receipt-service.ts b/demos/payments/src/receipt-service.ts index f221be1..ab388ad 100644 --- a/demos/payments/src/receipt-service.ts +++ b/demos/payments/src/receipt-service.ts @@ -11,6 +11,7 @@ import { createPaymentReceipt, getDidResolver, isDidPkhUri, + parseJwtCredential, signCredential, verifyJwt, verifyPaymentToken @@ -125,17 +126,16 @@ app.post("/", async (c) => { payerDid: parsed.issuer }) - const { jwt, verifiableCredential } = await signCredential(receipt, { + const jwt = await signCredential(receipt, { did: serverIdentity.did, signer: serverIdentity.jwtSigner, - alg: "ES256K", - resolver: didResolver + alg: "ES256K" }) log(successMessage("Receipt created successfully")) return c.json({ receipt: jwt, - details: verifiableCredential + details: await parseJwtCredential(jwt, didResolver) }) }) diff --git a/examples/issuer/src/lib/credentials/build-signed-credential.ts b/examples/issuer/src/lib/credentials/build-signed-credential.ts index a5a925f..e963287 100644 --- a/examples/issuer/src/lib/credentials/build-signed-credential.ts +++ b/examples/issuer/src/lib/credentials/build-signed-credential.ts @@ -1,4 +1,8 @@ -import { makeRevocable, signCredential } from "agentcommercekit" +import { + makeRevocable, + parseJwtCredential, + signCredential +} from "agentcommercekit" import { getStatusListPosition } from "@/db/utils/get-status-list-position" import type { CredentialResponse, Issuer } from "../types" import type { DatabaseCredential } from "@/db/schema" @@ -34,13 +38,9 @@ export async function buildSignedCredential({ statusListUrl: `${baseUrl}/status/${statusListId}` }) - const { verifiableCredential, jwt } = await signCredential( - unsignedCredential, - { - ...issuer, - resolver - } - ) + const jwt = await signCredential(unsignedCredential, issuer) + + const verifiableCredential = await parseJwtCredential(jwt, resolver) return { credential: verifiableCredential, diff --git a/examples/issuer/src/routes/status.ts b/examples/issuer/src/routes/status.ts index 175fb14..eb08644 100644 --- a/examples/issuer/src/routes/status.ts +++ b/examples/issuer/src/routes/status.ts @@ -1,6 +1,10 @@ import { apiSuccessResponse } from "@repo/api-utils/api-response" import { notFound } from "@repo/api-utils/exceptions" -import { createStatusListCredential, signCredential } from "agentcommercekit" +import { + createStatusListCredential, + parseJwtCredential, + signCredential +} from "agentcommercekit" import { Hono } from "hono" import { env } from "hono/adapter" import { getStatusList } from "@/db/queries/status-lists" @@ -56,10 +60,10 @@ app.get( issuer: issuer.did }) - const { verifiableCredential } = await signCredential(credential, { - ...issuer, - resolver - }) + const jwt = await signCredential(credential, issuer) + + const verifiableCredential = + await parseJwtCredential(jwt, resolver) return c.json(apiSuccessResponse(verifiableCredential)) } diff --git a/packages/ack-pay/src/verify-payment-receipt.test.ts b/packages/ack-pay/src/verify-payment-receipt.test.ts index ddbbf66..f3acae0 100644 --- a/packages/ack-pay/src/verify-payment-receipt.test.ts +++ b/packages/ack-pay/src/verify-payment-receipt.test.ts @@ -5,7 +5,11 @@ import { } from "@agentcommercekit/did" import { createJwtSigner, curveToJwtAlgorithm } from "@agentcommercekit/jwt" import { generateKeypair } from "@agentcommercekit/keys" -import { InvalidCredentialError, signCredential } from "@agentcommercekit/vc" +import { + InvalidCredentialError, + parseJwtCredential, + signCredential +} from "@agentcommercekit/vc" import { beforeEach, describe, expect, it } from "vitest" import { createPaymentReceipt } from "./create-payment-receipt" import { createPaymentRequestBody } from "./create-payment-request-body" @@ -65,14 +69,12 @@ describe("verifyPaymentReceipt()", () => { ) }) - const signed = await signCredential(unsignedReceipt, { + signedReceiptJwt = await signCredential(unsignedReceipt, { did: receiptIssuerDid, - signer: createJwtSigner(receiptIssuerKeypair), - resolver + signer: createJwtSigner(receiptIssuerKeypair) }) - signedReceipt = signed.verifiableCredential - signedReceiptJwt = signed.jwt + signedReceipt = await parseJwtCredential(signedReceiptJwt, resolver) }) it("validates a JWT receipt string", async () => { diff --git a/packages/vc/src/index.ts b/packages/vc/src/index.ts index 63314fc..b124606 100644 --- a/packages/vc/src/index.ts +++ b/packages/vc/src/index.ts @@ -1,3 +1,5 @@ +import { verifyPresentation } from "did-jwt-vc" + export * from "./create-credential" export * from "./is-credential" export * from "./signing/sign-credential" @@ -13,3 +15,5 @@ export * from "./verification/types" export * from "./verification/parse-jwt-credential" export * from "./verification/verify-parsed-credential" export * from "./verification/verify-proof" + +export { verifyPresentation } diff --git a/packages/vc/src/signing/sign-credential.test.ts b/packages/vc/src/signing/sign-credential.test.ts index b1b62a5..5524296 100644 --- a/packages/vc/src/signing/sign-credential.test.ts +++ b/packages/vc/src/signing/sign-credential.test.ts @@ -39,11 +39,10 @@ test("signCredential creates a valid JWT and verifiable credential", async () => }) // Sign the credential - const { jwt, verifiableCredential } = await signCredential(credential, { + const jwt = await signCredential(credential, { did: issuerDid, signer: createJwtSigner(issuerKeypair), - alg: "ES256K", - resolver + alg: "ES256K" }) // Verify the JWT using did-jwt verifier @@ -58,5 +57,5 @@ test("signCredential creates a valid JWT and verifiable credential", async () => const payload = result.payload as JwtCredentialPayload // Verify VC-specific payload elements - expect(verifiableCredential).toMatchObject(payload.vc) + expect(credential).toMatchObject(payload.vc) }) diff --git a/packages/vc/src/signing/sign-credential.ts b/packages/vc/src/signing/sign-credential.ts index 4c78180..c36cf1c 100644 --- a/packages/vc/src/signing/sign-credential.ts +++ b/packages/vc/src/signing/sign-credential.ts @@ -1,7 +1,8 @@ import { isJwtString } from "@agentcommercekit/jwt" import { createVerifiableCredentialJwt, verifyCredential } from "did-jwt-vc" -import type { SignOptions } from "./types" +import type { Signer } from "./types" import type { Verifiable, W3CCredential } from "../types" +import type { Resolvable } from "@agentcommercekit/did" import type { JwtString } from "@agentcommercekit/jwt" type SignedCredential = { @@ -22,18 +23,16 @@ type SignedCredential = { * @param options - The {@link SignCredentialOptions} to use * @returns A {@link SignedCredential} */ -export async function signCredential( - credential: T, - options: SignOptions -): Promise> { +export async function signCredential( + credential: W3CCredential, + signer: Signer +): Promise { // options.alg is already a JwtAlgorithm, no conversion needed - const jwt = await createVerifiableCredentialJwt(credential, options) + const jwt = await createVerifiableCredentialJwt(credential, signer) if (!isJwtString(jwt)) { throw new Error("Failed to sign credential") } - const { verifiableCredential } = await verifyCredential(jwt, options.resolver) - - return { jwt, verifiableCredential: verifiableCredential as Verifiable } + return jwt } diff --git a/packages/vc/src/signing/sign-presentation.test.ts b/packages/vc/src/signing/sign-presentation.test.ts index 91215b2..d7c25ca 100644 --- a/packages/vc/src/signing/sign-presentation.test.ts +++ b/packages/vc/src/signing/sign-presentation.test.ts @@ -5,57 +5,80 @@ import { } from "@agentcommercekit/did" import { createJwtSigner, verifyJwt } from "@agentcommercekit/jwt" import { generateKeypair } from "@agentcommercekit/keys" -import { expect, test } from "vitest" +import { describe, expect, it } from "vitest" import { createPresentation } from "../create-presentation" import { signPresentation } from "./sign-presentation" +import type { Signer } from "./types" import type { Verifiable, W3CCredential } from "../types" -test("signPresentation creates a valid JWT and verifiable presentation", async () => { - const resolver = getDidResolver() - const holderKeypair = await generateKeypair("secp256k1") - const holderDid = createDidWebUri("https://holder.example.com") +const resolver = getDidResolver() +const holderKeypair = await generateKeypair("secp256k1") +const holderDid = createDidWebUri("https://holder.example.com") - resolver.addToCache( - holderDid, - createDidDocumentFromKeypair({ - did: holderDid, - keypair: holderKeypair - }) - ) - - // Create a mock credential for the presentation - const mockCredential: Verifiable = { - "@context": ["https://www.w3.org/2018/credentials/v1"], - type: ["VerifiableCredential"], - issuer: { id: "did:example:issuer" }, - credentialSubject: { id: "did:example:subject" }, - issuanceDate: new Date().toISOString(), - proof: { - type: "Ed25519Signature2018" - } +resolver.addToCache( + holderDid, + createDidDocumentFromKeypair({ + did: holderDid, + keypair: holderKeypair + }) +) + +const signer: Signer = { + did: holderDid, + signer: createJwtSigner(holderKeypair), + alg: "ES256K" +} + +// Create a mock credential for the presentation +const mockCredential: Verifiable = { + "@context": ["https://www.w3.org/2018/credentials/v1"], + type: ["VerifiableCredential"], + issuer: { id: "did:example:issuer" }, + credentialSubject: { id: "did:example:subject" }, + issuanceDate: new Date().toISOString(), + proof: { + type: "Ed25519Signature2018" } +} - // Generate an unsigned presentation - const presentation = createPresentation({ - credentials: [mockCredential], - holder: holderDid, - id: "test-presentation", - type: "TestPresentation" - }) +// Generate an unsigned presentation +const presentation = createPresentation({ + credentials: [mockCredential], + holder: holderDid, + id: "test-presentation", + type: "TestPresentation" +}) - // Sign the presentation - const { jwt, verifiablePresentation } = await signPresentation(presentation, { - did: holderDid, - signer: createJwtSigner(holderKeypair), - alg: "ES256K", - resolver - }) +describe("signPresentation", () => { + it("creates a valid JWT and verifiable presentation", async () => { + // Sign the presentation + const jwt = await signPresentation(presentation, signer) + + // Verify the JWT using did-jwt verifier + const result = await verifyJwt(jwt, { + resolver + }) - // Verify the JWT using did-jwt verifier - const result = await verifyJwt(jwt, { - resolver + expect(result.payload.iss).toBe(holderDid) + expect(result.payload.nonce).toBeUndefined() + expect(result.payload.aud).toBeUndefined() + expect(presentation).toMatchObject(result.payload.vp) }) - expect(result.payload.iss).toBe(holderDid) - expect(verifiablePresentation).toMatchObject(result.payload.vp) + it("includes a challenge and domain", async () => { + const jwt = await signPresentation(presentation, signer, { + challenge: "test-challenge", + domain: "https://example.com" + }) + + const result = await verifyJwt(jwt, { + resolver, + policies: { + aud: false + } + }) + + expect(result.payload.nonce).toBe("test-challenge") + expect(result.payload.aud).toEqual(["https://example.com"]) + }) }) diff --git a/packages/vc/src/signing/sign-presentation.ts b/packages/vc/src/signing/sign-presentation.ts index 460bd9d..1d87a12 100644 --- a/packages/vc/src/signing/sign-presentation.ts +++ b/packages/vc/src/signing/sign-presentation.ts @@ -1,18 +1,12 @@ import { isJwtString } from "@agentcommercekit/jwt" -import { createVerifiablePresentationJwt, verifyPresentation } from "did-jwt-vc" -import type { SignOptions } from "./types" -import type { Verifiable, W3CPresentation } from "../types" +import { createVerifiablePresentationJwt } from "did-jwt-vc" +import type { Signer } from "./types" +import type { W3CPresentation } from "../types" import type { JwtString } from "@agentcommercekit/jwt" -type SignedPresentation = { - /** - * The signed {@link Verifiable} presentation - */ - verifiablePresentation: Verifiable - /** - * The JWT string representation of the signed presentation - */ - jwt: JwtString +type SignPresentationOptions = { + challenge?: string + domain?: string } /** @@ -20,23 +14,22 @@ type SignedPresentation = { * * @param presentation - The {@link W3CPresentation} to sign * @param options - The {@link SignCredentialOptions} to use - * @returns A {@link SignedPresentation} + * @returns A JWT encoded verifiable presentation */ export async function signPresentation( presentation: W3CPresentation, - options: SignOptions -): Promise { - // options.alg is already a JwtAlgorithm, no conversion needed - const jwt = await createVerifiablePresentationJwt(presentation, options) + signer: Signer, + options: SignPresentationOptions = {} +): Promise { + const jwt = await createVerifiablePresentationJwt( + presentation, + signer, + options + ) if (!isJwtString(jwt)) { throw new Error("Failed to sign presentation") } - const { verifiablePresentation } = await verifyPresentation( - jwt, - options.resolver - ) - - return { jwt, verifiablePresentation } + return jwt } diff --git a/packages/vc/src/signing/types.ts b/packages/vc/src/signing/types.ts index 944e5c4..850f95d 100644 --- a/packages/vc/src/signing/types.ts +++ b/packages/vc/src/signing/types.ts @@ -1,7 +1,6 @@ -import type { Resolvable } from "@agentcommercekit/did" import type { JwtAlgorithm, JwtSigner } from "@agentcommercekit/jwt" -export interface SignOptions { +export interface Signer { /** * The algorithm to use for the JWT */ @@ -14,8 +13,4 @@ export interface SignOptions { * The signer to use for the JWT */ signer: JwtSigner - /** - * A resolver to use for parsing the signed credential - */ - resolver: Resolvable } diff --git a/packages/vc/src/verification/parse-jwt-credential.test.ts b/packages/vc/src/verification/parse-jwt-credential.test.ts index d3b8c34..0402c6f 100644 --- a/packages/vc/src/verification/parse-jwt-credential.test.ts +++ b/packages/vc/src/verification/parse-jwt-credential.test.ts @@ -37,11 +37,10 @@ test("parseJwtCredential should parse a valid credential", async () => { } }) - const { jwt } = await signCredential(credential, { + const jwt = await signCredential(credential, { did: issuerDid, signer: createJwtSigner(issuerKeypair), - alg: "ES256K", - resolver + alg: "ES256K" }) const vc = await parseJwtCredential(jwt, resolver) diff --git a/packages/vc/src/verification/parse-jwt-credential.ts b/packages/vc/src/verification/parse-jwt-credential.ts index c122da4..ed06c1c 100644 --- a/packages/vc/src/verification/parse-jwt-credential.ts +++ b/packages/vc/src/verification/parse-jwt-credential.ts @@ -9,11 +9,11 @@ import type { Resolvable } from "@agentcommercekit/did" * @param resolver - The resolver to use for did resolution * @returns A {@link Verifiable} */ -export async function parseJwtCredential( +export async function parseJwtCredential( jwt: string, resolver: Resolvable -): Promise> { +): Promise> { const result = await verifyCredential(jwt, resolver) - return result.verifiableCredential + return result.verifiableCredential as Verifiable }