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
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,38 @@ import type * as Domain from "@hyperledger/identus-domain";
import { base64url } from "multiformats/bases/base64";

/**
* Maps DID key purposes to W3C DID Document verification relationships.
* @see https://www.w3.org/TR/did-core/#verification-relationships
* DID Verification Relationships — Core Concepts for Self-Sovereign Identity (SSI)
*
* A Decentralized Identifier (DID) can prove ownership through cryptographic keys.
* Different operations require different keys — this is the concept of "verification relationships".
*
* See: https://www.w3.org/TR/did-core/#verification-relationships
*
* Mapping of SDK key purposes to DID Document verification relationships:
*
* | SDK Purpose | DID Verification Relationship | Use Case |
* |---------------------------|-------------------------------|-----------------------------------|
* | ISSUING_KEY | assertionMethod | Credential issuance (JWT/SD-JWT) |
* | AUTHENTICATION_KEY | authentication | Proving DID ownership |
* | KEY_AGREEMENT_KEY | keyAgreement | Encryption / key exchange |
* | CAPABILITY_INVOCATION_KEY | capabilityInvocation | Invoking delegated capabilities |
* | CAPABILITY_DELEGATION_KEY | capabilityDelegation | Delegating capabilities to others |
* | REVOCATION_KEY | revocation | Revoking credentials/keys |
*
* Why Multiple Keys?
* - Key rotation: If an issuing key is compromised, you can rotate it without affecting authentication
* - Security: Each key should only be used for its intended purpose
* - Scalability: The SDK can support additional key purposes as SSI evolves
*
* DID Document Structure (simplified):
* {
* "id": "did:prism:abc123...",
* "assertionMethod": ["#key-1"], // ISSUING_KEY
* "authentication": ["#key-2"], // AUTHENTICATION_KEY
* "keyAgreement": ["#key-3"], // KEY_AGREEMENT_KEY (future)
* "capabilityInvocation": ["#key-4"], // CAPABILITY_INVOCATION_KEY (future)
* "capabilityDelegation": ["#key-5"] // CAPABILITY_DELEGATION_KEY (future)
* }
*/
const PURPOSE_TO_VERIFICATION_RELATIONSHIP: Record<string, string> = {
AUTHENTICATION_KEY: "authentication",
Expand Down Expand Up @@ -36,20 +66,35 @@ interface FindSigningKeysArgs {
}

/**
* Search for the PrivateKeys that should be used for signing based on their key purpose.
* Maps DID key purposes to W3C DID Document verification relationships.
* Finds the private signing keys for a DID that match a specific verification relationship purpose.
*
* This task:
* 1. Resolves the DID document to get verification methods
* 2. Maps the given purpose to a W3C DID verification relationship
* 3. Matches those verification methods against available private keys
* 4. Returns the keys that can be used for the specified purpose
*
* Workflow:
* - ISSUING_KEY → looks in the DID's "assertionMethod" (for credential issuance)
* - AUTHENTICATION_KEY → looks in the DID's "authentication" (for DID ownership proofs)
* - Matches keys by multibase encoding or JWK representation
*
* @see https://www.w3.org/TR/did-core/#verification-relationships
*
* @param {Domain.DID} did subject of the search
* @param {Domain.PrivateKey} [privateKey] optional filter search to only this PrivateKey
* @param {string} [purpose] key purpose to search for:
* - "AUTHENTICATION_KEY" → "authentication" (proving ownership/control)
* - "ISSUING_KEY" → "assertionMethod" (issuing credentials)
* - "KEY_AGREEMENT_KEY" → "keyAgreement" (key agreement for encryption)
* - "CAPABILITY_INVOCATION_KEY" → "capabilityInvocation" (invoking capabilities)
* - "CAPABILITY_DELEGATION_KEY" → "capabilityDelegation" (delegating capabilities)
* - "REVOCATION_KEY" → "revocation" (revoking credentials/keys)
* @param {Domain.DID} did The DID subject — resolves its DID document
* @param {Domain.PrivateKey} [privateKey] Optional: filter to only check this specific key
* @param {string} purpose Key purpose to search for:
* - AUTHENTICATION_KEY → "authentication" (proving ownership/control)
* - ISSUING_KEY → "assertionMethod" (issuing credentials)
* - KEY_AGREEMENT_KEY → "keyAgreement" (key agreement for encryption)
* - CAPABILITY_INVOCATION_KEY → "capabilityInvocation" (invoking capabilities)
* - CAPABILITY_DELEGATION_KEY → "capabilityDelegation" (delegating capabilities)
* - REVOCATION_KEY → "revocation" (revoking credentials/keys)
* @returns {SigningKeyData[]} Array of matched signing keys with public key info and key IDs
*
* Example usage:
* - CreateJWT defaults to ISSUING_KEY for credential issuance

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.

SDJWT should too right? can you confirm that

* - Authentication flows use AUTHENTICATION_KEY for DID ownership proofs
*/
export class FindSigningKeys extends Task<SigningKeyData[], Args> {
async run(ctx: AgentContext) {
Expand All @@ -73,6 +118,23 @@ export class FindSigningKeys extends Task<SigningKeyData[], Args> {
return signingKeyData;
}

/**
* Matches verification methods from the DID document with available private keys.
*
* For each verification method in the DID document:
* 1. Encode the private key's public key in the same format (multibase or JWK)
* 2. Compare against the verification method's public key representation
* 3. If they match, include this key-pair in the results
*
* This ensures we only return keys that are:
* - Actually present in the DID document
* - Listed for the requested verification relationship
* - Have corresponding private keys available locally
*
* Why two encoding formats? DIDs can represent keys as either:
* - "publicKeyMultibase": compact encoding (base58, etc)
* - "publicKeyJwk": JSON Web Key format (for JWK-based cryptography)
*/
private matchKeys(
methods: Domain.DIDDocument.VerificationMethod[],
keyData: { privateKey: any; publicKey: any; encoded: string; encodedBase64Url: string }[]
Expand Down
38 changes: 32 additions & 6 deletions packages/lib/sdk/src/pollux/utils/jwt/CreateJwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,35 @@ import { base64url } from "multiformats/bases/base64";
import { FindSigningKeys } from "../../../edge-agent/didFunctions/FindDIDSigningKeys";

/**
* Asyncronously sign with a DID
* Creates a signed JWT (JSON Web Token) using a DID's private key.
*
*
* @param {DID} did
* @param payload
* @param header
* @returns {string}
* In Self-Sovereign Identity (SSI) systems, JWTs are commonly used for:
* - Issuing verifiable credentials (VCs)
* - Creating proof of DID ownership
* - Sharing signed claims about subjects
*
* Key Purpose Selection:
* - Default is "ISSUING_KEY" because most JWT creation is for credential issuance,
* which requires the "assertionMethod" capability (see DID spec)
* - Use "AUTHENTICATION_KEY" only when signing challenge-response proofs for DID ownership
*
* How it works:
* 1. Finds signing keys matching the specified purpose
* 2. Creates a JWT with the DID as the issuer
* 3. Signs with the found private key
* 4. Returns the signed JWT string (can be transmitted to other parties)
*
* The recipient can verify this JWT by:
* - Resolving your DID document
* - Getting your public key from the matching verification relationship
* - Using the public key to verify the JWT signature
*
* @param {DID} did - The DID that will be the issuer of this JWT
* @param {JWT.Payload} payload - The claims to include in the JWT
* @param {JWT.Header} [header] - Optional custom JWT header fields
* @param {PrivateKey} [privateKey] - Optional: specific key to use (otherwise searches all keys)
* @param {keyof PrismDIDKeys} [purpose] - Verification relationship to use (default: "ISSUING_KEY")
* @returns {string} The signed JWT
*/

interface Args {
Expand All @@ -27,6 +49,10 @@ interface Args {

export class CreateJWT extends Task<string, Args> {
async run(ctx: AgentContext) {
// Default to ISSUING_KEY because JWT creation is most commonly used for:
// - Credential issuance (requires "assertionMethod" in DID document)
// - Proof of claims about subjects
// Change to AUTHENTICATION_KEY only for challenge-response authentication flows
const signingKeys = await ctx.run(new FindSigningKeys({
did: this.args.did,
privateKey: this.args.privateKey,
Expand Down
37 changes: 31 additions & 6 deletions packages/lib/sdk/src/pollux/utils/jwt/CreateSDJWT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,33 @@ import { FindSigningKeys } from "../../../edge-agent/didFunctions/FindDIDSigning
import { expect } from "../../../utils";

/**
* Asyncronously sign with a DID
* Creates a signed SD-JWT (Selective Disclosure JWT) using a DID's private key.
*
*
* @param {DID} did
* @param payload
* @param header
* @returns {string}
* SD-JWT (RFC 9052) is an extension of JWT that allows:
* - Issuing credentials with selective disclosure capabilities
* - Holders to selectively reveal only necessary claims to verifiers
* - Privacy-preserving credential presentation
*
* Example use case:
* - An issuer creates an SD-JWT with claims: name, age, address, email
* - The holder can share a version that only discloses: name and age
* - Verifier can still verify the issuer's signature on the hidden claims
*
* Key Purpose Selection:
* - Default is "ISSUING_KEY" for credential issuance (requires "assertionMethod" in DID)
* - This is appropriate because SD-JWTs are typically issued as verifiable credentials
* - Note: Like CreateJWT, SD-JWT also defaults to ISSUING_KEY since both are used for
* credential issuance flows. Use AUTHENTICATION_KEY only for authentication challenges.
*
* See: https://datatracker.ietf.org/doc/html/rfc9052 (SD-JWT specification)
*
* @param {DID} did - The DID that will issue this SD-JWT credential
* @param {SdJwtVcPayload} payload - The credential claims to include
* @param {JWT.Header} [header] - Optional custom JWT header fields
* @param {DisclosureFrame} disclosureFrame - Specifies which claims are selectively disclosable
* @param {PrivateKey} [privateKey] - Optional: specific key to use (otherwise searches all keys)
* @param {keyof PrismDIDKeys} [purpose] - Verification relationship (default: "ISSUING_KEY")
* @returns {string} The signed SD-JWT
*/

interface Args {
Expand All @@ -29,6 +49,11 @@ interface Args {
export class CreateSDJWT extends Task<string, Args> {

async run(ctx: Plugins.Context) {
// Default to ISSUING_KEY because SD-JWTs are typically issued as verifiable credentials.
// The issuer signs the credential, allowing verifiers to:
// 1. Verify the issuer's signature
// 2. Selectively disclose only necessary claims
// The issuer's public key is found via the "assertionMethod" in their DID document.
const signingKeys = await ctx.run(
new FindSigningKeys({
did: this.args.did,
Expand Down