diff --git a/docs/delegation.md b/docs/delegation.md new file mode 100644 index 00000000..de7a1efa --- /dev/null +++ b/docs/delegation.md @@ -0,0 +1,351 @@ +# Delegatable Credentials + +Delegatable credentials enable a credential issuer (delegator) to delegate their authority to issue specific types of credentials to other entities (delegees). This creates a chain of trust where delegees can issue credentials on behalf of the original issuer, within the bounds of their delegated authority. + +## Overview + +A delegatable credential allows an issuer to: +- Grant specific issuance authorities to delegates +- Control which claims delegates can issue +- Establish delegation chains with multiple levels +- Enforce policies using Cedar policy language +- Verify the entire delegation chain during presentation + +## Key Concepts + +### Delegation Chain + +A delegation chain consists of: +- **Root Credential**: The original delegation issued by the authoritative issuer +- **Delegated Credentials**: Credentials issued by delegates using their delegated authority +- **Chain Verification**: Each credential in the chain references its predecessor via `previousCredentialId` and the root via `rootCredentialId` + +### May Claim + +The `mayClaim` property (IRI: `https://www.dock.io/rdf2020#mayClaim`) specifies which claims a delegate is authorized to issue. For example: + +```javascript +credentialSubject: { + id: 'did:example:delegate', + creditScore: 760, + [MAY_CLAIM_IRI]: ['creditScore'], // Delegate can only issue creditScore claims +} +``` + +### Cedar Policies + +[Cedar](https://docs.cedarpolicy.com/) is a language for defining permissions as policies. Cedar policies provide fine-grained authorization control over delegation chains. They can enforce: +- Maximum delegation depth +- Required root issuer +- Minimum claim values +- Specific claim values + +## API Reference + +### Core Functions + +#### `issueDelegationCredential(keyDoc, options)` + +Issues a root delegation credential that grants authority to a delegate. + +**Parameters:** +- `keyDoc`: Signing key document of the issuer +- `options`: + - `id`: Unique credential identifier + - `issuer`: DID of the issuer + - `@context`: JSON-LD context including delegation terms + - `issuanceDate`: ISO timestamp + - `type`: Array including 'DelegationCredential' + - `credentialSubject`: Object containing: + - `id`: DID of the delegate + - `[MAY_CLAIM_IRI]`: Array of authorized claim names + - Additional claims + - `previousCredentialId`: Should be `null` for root credentials + - `rootCredentialId`: Should be `null` for root credentials + +**Returns:** Signed delegation credential + +**Example:** +```javascript +const rootCredential = await issueDelegationCredential(issuerKey, { + id: 'urn:uuid:12345678', + issuer: issuerDid, + '@context': CREDIT_SCORE_DELEGATION_CONTEXT, + issuanceDate: new Date().toISOString(), + type: [ + 'VerifiableCredential', + 'CreditScoreCredential', + 'DelegationCredential', + ], + credentialSubject: { + id: holderDid, + creditScore: 760, + [MAY_CLAIM_IRI]: ['creditScore'], + }, + previousCredentialId: null, + rootCredentialId: null, +}); +``` + +#### `issueDelegatedCredential(keyDoc, options)` + +Issues a credential using delegated authority. + +**Parameters:** +- `keyDoc`: Signing key document of the delegate +- `options`: + - `id`: Unique credential identifier + - `issuer`: DID of the delegate issuer + - `@context`: JSON-LD context + - `issuanceDate`: ISO timestamp + - `type`: Array of credential types + - `credentialSubject`: Object containing: + - `id`: DID of the credential subject + - Claims being issued + - `[MAY_CLAIM_IRI]` (optional): For further delegation + - `previousCredentialId`: ID of the delegating credential + - `rootCredentialId`: ID of the root credential + +**Returns:** Signed delegated credential + +**Example:** +```javascript +const delegatedCredential = await issueDelegatedCredential(holderKey, { + id: 'urn:uuid:87654321', + issuer: holderDid, + '@context': CREDIT_SCORE_CREDENTIAL_CONTEXT, + issuanceDate: new Date().toISOString(), + type: [ + 'VerifiableCredential', + 'CreditScoreCredential', + 'DelegationCredential', + ], + credentialSubject: { + id: agentDid, + creditScore: 400, + [MAY_CLAIM_IRI]: ['creditScore'], // For further delegation + }, + previousCredentialId: rootCredential.id, + rootCredentialId: rootCredential.id, +}); +``` + +#### `createSignedPresentation(keyDoc, options)` + +Creates a signed verifiable presentation containing delegated credentials. + +**Parameters:** +- `keyDoc`: Signing key document of the holder +- `options`: + - `credentials`: Array of credentials (must include full delegation chain) + - `holderDid`: DID of the presentation holder + - `challenge`: Challenge string for replay protection + - `domain`: Domain string for binding + +**Returns:** Signed verifiable presentation + +**Example:** +```javascript +const presentation = await createSignedPresentation(agentKey, { + credentials: [rootCredential, credDelegatedToAgent], + holderDid: agentDid, + challenge: 'test-challenge-123', + domain: 'test.example.com', +}); +``` + +#### `verifyDelegatablePresentation(presentation, options)` + +Verifies a presentation containing delegated credentials. + +**Parameters:** +- `presentation`: The verifiable presentation to verify +- `options`: + - `challenge`: Expected challenge string + - `domain`: Expected domain string + - `policies` (optional): Cedar policies object + +**Returns:** Verification result object containing: +- `verified`: Boolean indicating overall verification status +- `delegationResult`: Delegation verification result with: + - `decision`: 'allow' or 'deny' + - `summaries`: Array of delegation chain summaries + - `failures`: Array of failure objects (if denied) +- `credentialResults`: Array of individual credential verification results + +**Example:** +```javascript +const result = await verifyDelegatablePresentation(presentation, { + challenge: CHALLENGE, + domain: DOMAIN, + policies, +}); + +console.log('Verified:', result.verified); +console.log('Decision:', result.delegationResult?.decision); +``` + +#### `createCedarPolicy(options)` + +Creates Cedar policy rules for delegation authorization. + +**Parameters:** +- `options`: + - `maxDepth`: Maximum allowed delegation chain depth + - `rootIssuer`: Required DID of the root issuer + - `requiredClaims`: Object mapping claim names to required values: + - Number values: minimum required value (e.g., `creditScore: 500`) + - String values: exact required value (e.g., `role: "admin"`) + - Any value: description of the claim requirement + +**Returns:** Policy object with `staticPolicies` string + +**Example:** +```javascript +const policies = createCedarPolicy({ + maxDepth: 2, + rootIssuer: 'did:example:root-issuer', + requiredClaims: { + creditScore: 500, // Minimum value + role: 'admin', // Exact match + body: 'Issuer of Credits', // Description + }, +}); +``` + +### Context Constants + +#### `MAY_CLAIM_IRI` +The IRI used for the `mayClaim` property in credential subjects. +```javascript +const MAY_CLAIM_IRI = 'https://www.dock.io/rdf2020#mayClaim'; +``` + +#### `DELEGATION_CONTEXT_TERMS` +Base JSON-LD terms for delegation credentials. + +#### `W3C_CREDENTIALS_V1` +Standard W3C Verifiable Credentials context URL. + + +## Integration with Presentation Exchange (PEX) + +Delegatable credentials can be used with Presentation Exchange to request specific delegation chains: + +```javascript +const presentationDefinition = { + id: 'delegation_test', + input_descriptors: [ + { + id: 'root-credential', + name: 'Root Credential', + purpose: 'Must be the root credential issued by the required issuer', + group: ['1'], + constraints: { + fields: [ + { + path: ['$.type'], + filter: { + type: 'array', + contains: { const: 'CreditScoreCredential' }, + }, + }, + { + path: ['$.issuer', '$.iss'], + filter: { + type: 'string', + const: issuerDid, + }, + }, + ], + }, + }, + { + id: 'other-credentials', + name: 'Additional Credentials', + purpose: 'Any number of additional credentials of the specified type', + group: ['2'], + constraints: { + fields: [ + { + path: ['$.type'], + filter: { + type: 'array', + contains: { const: 'CreditScoreCredential' }, + }, + }, + ], + }, + }, + ], + submission_requirements: [ + { + from: '1', + name: 'Root Credential', + rule: 'pick', + count: 1, + }, + { + from: '2', + name: 'Additional Credentials', + rule: 'pick', + min: 0, + }, + ], +}; + +// Filter credentials that match the definition +const filterResult = await credentialService.filterCredentials({ + credentials: [rootCredential, credDelegatedToAgent], + presentationDefinition, + holderDid: agentDid, +}); + +// Create presentation +const presentation = await createSignedPresentation(agentKey, { + credentials: [rootCredential, credDelegatedToAgent], + holderDid: agentDid, + challenge: CHALLENGE, + domain: DOMAIN, +}); + +// Evaluate presentation against definition +const validationResults = await pexService.evaluatePresentation({ + presentation, + presentationDefinition, +}); +``` + +## Best Practices + +1. **Always Include Full Chain**: When creating presentations with delegated credentials, include the complete delegation chain from root to leaf. + +2. **Use Specific mayClaim Values**: Be explicit about which claims a delegate can issue. Avoid overly broad delegations. + +3. **Implement Cedar Policies**: Use Cedar policies to enforce business rules like maximum delegation depth and claim requirements. + +4. **Handle Verification Failures**: Check both the overall `verified` status and the `delegationResult.decision` to understand why verification failed. + + +## Testing + +See `integration-tests/delegatable-credentials.test.ts` for comprehensive test examples including: + +- Issuing valid delegation credentials +- Issuing delegated credentials with proper chain references +- Creating and verifying presentations with delegation chains +- Enforcing authorization with Cedar policies +- Handling unauthorized delegations +- Multi-level delegation chains +- Integration with Presentation Exchange + +Run the tests with: +```bash +npm test integration-tests/delegatable-credentials.test.ts +``` + +## References + +- [W3C Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) +- [Cedar Policy Language](https://www.cedarpolicy.com/) +- [Presentation Exchange](https://identity.foundation/presentation-exchange/) diff --git a/integration-tests/delegatable-credentials.test.ts b/integration-tests/delegatable-credentials.test.ts index 7d3e0408..9c4a7b30 100644 --- a/integration-tests/delegatable-credentials.test.ts +++ b/integration-tests/delegatable-credentials.test.ts @@ -8,8 +8,10 @@ import { W3C_CREDENTIALS_V1, DELEGATION_CONTEXT_TERMS, } from '@docknetwork/wallet-sdk-wasm/src/services/credential/delegatable-credentials'; -import { didService } from '@docknetwork/wallet-sdk-wasm/src/services/dids/service'; - +import {credentialService} from '@docknetwork/wallet-sdk-wasm/src/services/credential/service'; +import {didService} from '@docknetwork/wallet-sdk-wasm/src/services/dids/service'; +import {pexService} from '@docknetwork/wallet-sdk-wasm/src/services/pex'; +import {v4 as uuidv4} from 'uuid'; // ============================================================================ // TEST-SPECIFIC CONTEXTS (Credit Score Use Case) // ============================================================================ @@ -23,8 +25,9 @@ const CREDIT_SCORE_DELEGATION_CONTEXT = [ { ...DELEGATION_CONTEXT_TERMS, ex: 'https://example.org/credentials#', + xsd: 'http://www.w3.org/2001/XMLSchema#', CreditScoreDelegation: 'ex:CreditScoreDelegation', - body: 'ex:body', + creditScore: {'@id': 'ex:creditScore', '@type': 'xsd:integer'}, }, ]; @@ -38,111 +41,154 @@ const CREDIT_SCORE_CREDENTIAL_CONTEXT = [ ex: 'https://example.org/credentials#', xsd: 'http://www.w3.org/2001/XMLSchema#', CreditScoreCredential: 'ex:CreditScoreCredential', - creditScore: { '@id': 'ex:creditScore', '@type': 'xsd:integer' }, + creditScore: {'@id': 'ex:creditScore', '@type': 'xsd:integer'}, }, ]; -// ============================================================================ -// TEST CONFIGURATION -// ============================================================================ - -const DELEGATION_ROOT_ID = 'urn:cred:delegation-root'; -const CREDIT_SCORE_CRED_ID = 'urn:cred:credit-score-alice'; -const SUBJECT_DID = 'did:example:alice'; - const CHALLENGE = 'test-challenge-123'; const DOMAIN = 'test.example.com'; +function generateCredentialId(namespace: string) { + return `urn:${namespace}:${uuidv4()}`; +} + describe('Delegatable Credentials', () => { - let rootIssuerKey: any; - let delegateKey: any; - let rootIssuerDid: string; - let delegateDid: string; - let delegationCredential: any; - let creditScoreCredential: any; + let issuerKey: any; + let holderKey: any; + let agentKey: any; + let subAgentKey: any; + let issuerDid: string; + let holderDid: string; + let agentDid: string; + let subAgentDid: string; + let rootCredential: any; + let credDelegatedToAgent: any; + let credDelegatedToSubAgent: any; let unauthorizedDelegationCredential: any; beforeAll(async () => { // Generate key pairs for root issuer and delegate - rootIssuerKey = await didService.generateKeyDoc({ + issuerKey = await didService.generateKeyDoc({ type: 'ed25519', }); - delegateKey = await didService.generateKeyDoc({ + holderKey = await didService.generateKeyDoc({ + type: 'ed25519', + }); + agentKey = await didService.generateKeyDoc({ + type: 'ed25519', + }); + subAgentKey = await didService.generateKeyDoc({ type: 'ed25519', }); // Extract DIDs from the key documents - rootIssuerDid = rootIssuerKey.controller; - delegateDid = delegateKey.controller; - - console.log('Root Issuer DID:', rootIssuerDid); - console.log('Delegate DID:', delegateDid); + issuerDid = issuerKey.controller; + holderDid = holderKey.controller; + agentDid = agentKey.controller; + subAgentDid = subAgentKey.controller; // Issue the root delegation credential // This grants the delegate authority to issue creditScore claims - delegationCredential = await issueDelegationCredential(rootIssuerKey, { - id: DELEGATION_ROOT_ID, - issuerDid: rootIssuerDid, - delegateDid: delegateDid, - mayClaim: ['creditScore'], - context: CREDIT_SCORE_DELEGATION_CONTEXT, - types: ['VerifiableCredential', 'CreditScoreDelegation', 'DelegationCredential'], - additionalSubjectProperties: { - body: 'Issuer of Credit Scores', + const rootCredentialId = generateCredentialId('root-issuer'); + rootCredential = await issueDelegationCredential(issuerKey, { + id: rootCredentialId, + issuer: issuerDid, + '@context': CREDIT_SCORE_DELEGATION_CONTEXT, + issuanceDate: new Date().toISOString(), + type: [ + 'VerifiableCredential', + 'CreditScoreCredential', + 'DelegationCredential', + ], + credentialSubject: { + id: holderDid, + creditScore: 760, + [MAY_CLAIM_IRI]: ['creditScore'], }, + previousCredentialId: null, + rootCredentialId: rootCredentialId, }); - // Issue a credit score credential as the delegate - creditScoreCredential = await issueDelegatedCredential(delegateKey, { - id: CREDIT_SCORE_CRED_ID, - issuerDid: delegateDid, - subjectDid: SUBJECT_DID, - claims: { - creditScore: 760, + // Issue a credit score credential as the agent + credDelegatedToAgent = await issueDelegatedCredential(holderKey, { + id: generateCredentialId('root-issuer'), + '@context': CREDIT_SCORE_CREDENTIAL_CONTEXT, + issuer: holderDid, + issuanceDate: new Date().toISOString(), + type: [ + 'VerifiableCredential', + 'CreditScoreCredential', + 'DelegationCredential', + ], + credentialSubject: { + id: agentDid, + creditScore: 400, + [MAY_CLAIM_IRI]: ['creditScore'], + }, + previousCredentialId: rootCredential.id, + rootCredentialId: rootCredential.id, + }); + + credDelegatedToSubAgent = await issueDelegatedCredential(agentKey, { + id: generateCredentialId('agent'), + issuanceDate: new Date().toISOString(), + issuer: agentDid, + credentialSubject: { + id: subAgentDid, + creditScore: 200, + [MAY_CLAIM_IRI]: ['creditScore'], }, - rootCredentialId: DELEGATION_ROOT_ID, - previousCredentialId: DELEGATION_ROOT_ID, - context: CREDIT_SCORE_CREDENTIAL_CONTEXT, - types: ['VerifiableCredential', 'CreditScoreCredential'], + rootCredentialId: rootCredential.id, + previousCredentialId: credDelegatedToAgent.id, + '@context': CREDIT_SCORE_CREDENTIAL_CONTEXT, + type: ['VerifiableCredential', 'CreditScoreCredential'], }); // Issue an unauthorized delegation (no creditScore in mayClaim) - unauthorizedDelegationCredential = await issueDelegationCredential(rootIssuerKey, { - id: 'urn:cred:unauthorized-delegation', - issuerDid: rootIssuerDid, - delegateDid: delegateDid, - mayClaim: ['someOtherClaim'], // Does NOT include creditScore - context: CREDIT_SCORE_DELEGATION_CONTEXT, - types: ['VerifiableCredential', 'CreditScoreDelegation', 'DelegationCredential'], + const unauthorizedCredentialId = 'urn:cred:unauthorized-delegation'; + unauthorizedDelegationCredential = await issueDelegationCredential(issuerKey, { + id: unauthorizedCredentialId, + issuanceDate: new Date().toISOString(), + issuer: issuerDid, + credentialSubject: { + id: holderDid, + creditScore: 760, + [MAY_CLAIM_IRI]: ['someOtherClaim'], + }, + previousCredentialId: null, + rootCredentialId: unauthorizedCredentialId, + '@context': CREDIT_SCORE_DELEGATION_CONTEXT, + type: ['VerifiableCredential', 'CreditScoreDelegation', 'DelegationCredential'], }); }); it('should issue a valid delegation credential', () => { - expect(delegationCredential).toBeDefined(); - expect(delegationCredential.id).toBe(DELEGATION_ROOT_ID); - expect(delegationCredential.issuer).toBe(rootIssuerDid); - expect(delegationCredential.credentialSubject.id).toBe(delegateDid); - expect(delegationCredential.credentialSubject[MAY_CLAIM_IRI]).toContain('creditScore'); - expect(delegationCredential.proof).toBeDefined(); - expect(delegationCredential.rootCredentialId).toBeUndefined(); - expect(delegationCredential.previousCredentialId).toBeNull(); + expect(rootCredential).toBeDefined(); + expect(rootCredential.issuer).toBe(issuerDid); + expect(rootCredential.credentialSubject.id).toBe(holderDid); + expect(rootCredential.credentialSubject[MAY_CLAIM_IRI]).toContain( + 'creditScore', + ); + expect(rootCredential.proof).toBeDefined(); + expect(rootCredential.rootCredentialId).toBe(rootCredential.id); + expect(rootCredential.previousCredentialId).toBeNull(); }); it('should issue a valid delegated credential', () => { - expect(creditScoreCredential).toBeDefined(); - expect(creditScoreCredential.id).toBe(CREDIT_SCORE_CRED_ID); - expect(creditScoreCredential.issuer).toBe(delegateDid); - expect(creditScoreCredential.credentialSubject.id).toBe(SUBJECT_DID); - expect(creditScoreCredential.credentialSubject.creditScore).toBe(760); - expect(creditScoreCredential.proof).toBeDefined(); - expect(creditScoreCredential.rootCredentialId).toBe(DELEGATION_ROOT_ID); - expect(creditScoreCredential.previousCredentialId).toBe(DELEGATION_ROOT_ID); + expect(credDelegatedToAgent).toBeDefined(); + expect(credDelegatedToAgent.issuer).toBe(holderDid); + expect(credDelegatedToAgent.credentialSubject.id).toBe(agentDid); + expect(credDelegatedToAgent.credentialSubject.creditScore).toBe(400); + expect(credDelegatedToAgent.proof).toBeDefined(); + expect(credDelegatedToAgent.rootCredentialId).toBe(rootCredential.id); + expect(credDelegatedToAgent.previousCredentialId).toBe(rootCredential.id); + expect(credDelegatedToAgent.type).toContain('VerifiableCredential'); }); it('should create a valid presentation with a delegated credential', async () => { - const presentation = await createSignedPresentation(delegateKey, { - credentials: [delegationCredential], - holderDid: delegateDid, + const presentation = await createSignedPresentation(agentKey, { + credentials: [rootCredential, credDelegatedToAgent], + holderDid: agentDid, challenge: CHALLENGE, domain: DOMAIN, }); @@ -155,11 +201,103 @@ describe('Delegatable Credentials', () => { expect(result.verified).toBe(true); }); + it('should handle pex request with delegated credential', async () => { + + const presentationDefinition = { + id: 'delegation_test', + input_descriptors: [ + { + id: 'root-credential', + name: 'Root Credential', + purpose: 'Must be the root credential issued by the required issuer', + group: ['1'], + constraints: { + fields: [ + { + path: ['$.type'], + filter: { + type: 'array', + contains: { + const: 'CreditScoreCredential', + }, + }, + }, + { + path: ['$.issuer', '$.iss'], + filter: { + type: 'string', + const: issuerDid, + }, + }, + ], + }, + }, + { + id: 'other-credentials', + name: 'Additional Credentials', + purpose: + 'Any number of additional credentials of the specified type from any issuer', + group: ['2'], + constraints: { + fields: [ + { + path: ['$.type'], + filter: { + type: 'array', + contains: { + const: 'CreditScoreCredential', + }, + }, + } + ], + }, + }, + ], + submission_requirements: [ + { + from: '1', + name: 'Root Credential', + rule: 'pick', + count: 1, + }, + { + from: '2', + name: 'Additional Credentials', + rule: 'pick', + min: 0, + }, + ], + }; + + const filterResult = await credentialService.filterCredentials({ + credentials: [rootCredential, credDelegatedToAgent], + presentationDefinition, + holderDid: agentDid, + }); + + const presentation = await createSignedPresentation(agentKey, { + credentials: [rootCredential, credDelegatedToAgent], + holderDid: agentDid, + challenge: CHALLENGE, + domain: DOMAIN, + }); + + const validationResults = await pexService.evaluatePresentation({ + presentation, + presentationDefinition, + }); + + expect(filterResult.verifiableCredential?.length).toBe(2); + expect(filterResult.errors?.length).toBe(0); + expect(validationResults.errors?.length).toBe(0); + }); + + it('should verify authorized delegation with Cedar policies', async () => { // Create a signed presentation with both credentials - const presentation = await createSignedPresentation(delegateKey, { - credentials: [delegationCredential, creditScoreCredential], - holderDid: delegateDid, + const presentation = await createSignedPresentation(agentKey, { + credentials: [rootCredential, credDelegatedToAgent], + holderDid: agentDid, challenge: CHALLENGE, domain: DOMAIN, }); @@ -167,10 +305,9 @@ describe('Delegatable Credentials', () => { // Create Cedar policy that allows this delegation const policies = createCedarPolicy({ maxDepth: 2, - rootIssuer: rootIssuerDid, + rootIssuer: issuerDid, requiredClaims: { creditScore: 0, - body: 'Issuer of Credit Scores', }, }); @@ -192,7 +329,10 @@ describe('Delegatable Credentials', () => { expect(result.delegationResult?.decision).toBe('allow'); // Log delegation summary for debugging - if (result.delegationResult?.summaries?.length > 0) { + if ( + result.delegationResult?.summaries?.length && + result.delegationResult.summaries.length > 0 + ) { const summary = result.delegationResult.summaries[0]; console.log('Delegation summary:', { rootIssuer: summary.rootIssuer, @@ -206,23 +346,25 @@ describe('Delegatable Credentials', () => { it('should deny unauthorized delegation (wrong mayClaim)', async () => { // Create a credential that uses an unauthorized delegation // The unauthorizedDelegationCredential only grants 'someOtherClaim', not 'creditScore' - const unauthorizedCreditScore = await issueDelegatedCredential(delegateKey, { + const unauthorizedCreditScore = await issueDelegatedCredential(holderKey, { id: 'urn:cred:unauthorized-credit-score', - issuerDid: delegateDid, - subjectDid: SUBJECT_DID, - claims: { + issuer: holderDid, + issuanceDate: new Date().toISOString(), + credentialSubject: { + id: agentDid, creditScore: 500, + [MAY_CLAIM_IRI]: ['creditScore'], }, - rootCredentialId: 'urn:cred:unauthorized-delegation', - previousCredentialId: 'urn:cred:unauthorized-delegation', - context: CREDIT_SCORE_CREDENTIAL_CONTEXT, - types: ['VerifiableCredential', 'CreditScoreCredential'], + rootCredentialId: unauthorizedDelegationCredential.id, + previousCredentialId: unauthorizedDelegationCredential.id, + '@context': CREDIT_SCORE_CREDENTIAL_CONTEXT, + type: ['VerifiableCredential', 'CreditScoreCredential'], }); // Create presentation with unauthorized delegation - const presentation = await createSignedPresentation(delegateKey, { + const presentation = await createSignedPresentation(agentKey, { credentials: [unauthorizedDelegationCredential, unauthorizedCreditScore], - holderDid: delegateDid, + holderDid: holderDid, challenge: CHALLENGE, domain: DOMAIN, }); @@ -230,7 +372,7 @@ describe('Delegatable Credentials', () => { // Create Cedar policy that requires creditScore claim authorization const policies = createCedarPolicy({ maxDepth: 2, - rootIssuer: rootIssuerDid, + rootIssuer: issuerDid, requiredClaims: { creditScore: 0, }, @@ -258,9 +400,9 @@ describe('Delegatable Credentials', () => { it('should verify delegation without Cedar policies', async () => { // Create a signed presentation - const presentation = await createSignedPresentation(delegateKey, { - credentials: [delegationCredential, creditScoreCredential], - holderDid: delegateDid, + const presentation = await createSignedPresentation(agentKey, { + credentials: [rootCredential, credDelegatedToAgent], + holderDid: agentDid, challenge: CHALLENGE, domain: DOMAIN, }); @@ -292,7 +434,11 @@ describe('Delegatable Credentials', () => { expect(policy.staticPolicies).toContain('context.tailDepth <= 3'); expect(policy.staticPolicies).toContain('did:example:root'); - expect(policy.staticPolicies).toContain('context.authorizedClaims.level >= 5'); - expect(policy.staticPolicies).toContain('context.authorizedClaims.role == "admin"'); + expect(policy.staticPolicies).toContain( + 'context.authorizedClaims.level >= 5', + ); + expect(policy.staticPolicies).toContain( + 'context.authorizedClaims.role == "admin"', + ); }); -}); +}); \ No newline at end of file diff --git a/packages/wasm/src/services/credential/delegatable-credentials.ts b/packages/wasm/src/services/credential/delegatable-credentials.ts index 113b56bc..6e4923f4 100644 --- a/packages/wasm/src/services/credential/delegatable-credentials.ts +++ b/packages/wasm/src/services/credential/delegatable-credentials.ts @@ -139,45 +139,8 @@ export const PRESENTATION_CONTEXT = [W3C_CREDENTIALS_V1]; */ export async function issueDelegationCredential( keyPair: KeyPair, - params: { - id: string; - issuerDid: string; - delegateDid: string; - mayClaim: string[]; - context: any[]; - types: string[]; - additionalSubjectProperties?: Record; - previousCredentialId?: string | null; - rootCredentialId?: string; - } + credential: any ): Promise { - const { - id, - issuerDid, - delegateDid, - mayClaim, - context, - types, - additionalSubjectProperties = {}, - previousCredentialId = null, - rootCredentialId, - } = params; - - const credential = { - '@context': context, - id, - type: types, - issuer: issuerDid, - issuanceDate: new Date().toISOString(), - credentialSubject: { - id: delegateDid, - [MAY_CLAIM_IRI]: mayClaim, - ...additionalSubjectProperties, - }, - rootCredentialId: undefined, - previousCredentialId, - }; - const preparedKey = prepareKeyForSigning(keyPair); return issueCredential(preparedKey, credential); } @@ -190,42 +153,8 @@ export async function issueDelegationCredential( */ export async function issueDelegatedCredential( keyPair: KeyPair, - params: { - id: string; - issuerDid: string; - subjectDid: string; - claims: Record; - rootCredentialId: string; - previousCredentialId: string; - context: any[]; - types: string[]; - } + credential: any ): Promise { - const { - id, - issuerDid, - subjectDid, - claims, - rootCredentialId, - previousCredentialId, - context, - types, - } = params; - - const credential = { - '@context': context, - id, - type: types, - issuer: issuerDid, - issuanceDate: new Date().toISOString(), - credentialSubject: { - id: subjectDid, - ...claims, - }, - rootCredentialId, - previousCredentialId, - }; - const preparedKey = prepareKeyForSigning(keyPair); return issueCredential(preparedKey, credential); } @@ -477,4 +406,4 @@ class DelegatableCredentialsService { } } -export const delegatableCredentialsService = new DelegatableCredentialsService(); +export const delegatableCredentialsService = new DelegatableCredentialsService(); \ No newline at end of file