From 8fb60da96c14357c90ae49fb0a4ab3a48d97bb8b Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili <contact@gozala.io> Date: Wed, 14 Dec 2022 11:52:20 -0800 Subject: [PATCH 1/2] fix: toJSON behavior on the ucan.data --- packages/core/package.json | 2 +- packages/core/test/delegation.spec.js | 134 ++++++++++++++++++++++++++ packages/interface/package.json | 2 +- packages/interface/src/lib.ts | 4 +- packages/principal/package.json | 2 +- pnpm-lock.yaml | 16 +-- 6 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 packages/core/test/delegation.spec.js diff --git a/packages/core/package.json b/packages/core/package.json index 552b5d32..b596906a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -31,7 +31,7 @@ "dependencies": { "@ipld/car": "^5.0.0", "@ipld/dag-cbor": "^8.0.0", - "@ipld/dag-ucan": "^3.0.1", + "@ipld/dag-ucan": "^3.1.1", "@ucanto/interface": "^4.0.3", "multiformats": "^10.0.2" }, diff --git a/packages/core/test/delegation.spec.js b/packages/core/test/delegation.spec.js new file mode 100644 index 00000000..5fa38308 --- /dev/null +++ b/packages/core/test/delegation.spec.js @@ -0,0 +1,134 @@ +import { assert, test } from './test.js' +import { Delegation, UCAN, isDelegation, parseLink } from '../src/lib.js' +import { alice, bob, mallory, service } from './fixtures.js' +import { base64 } from 'multiformats/bases/base64' +const utf8 = new TextEncoder() + +const link = parseLink( + 'bafybeid4cy7pj33wuead6zioxdtx3zwalhr6hd572tgqubgmy2ahrmi6vu' +) +/** + * @param {unknown} value + */ +const toJSON = value => JSON.parse(JSON.stringify(value)) + +test('delegation.data.toJSON', async () => { + const ucan = await Delegation.delegate({ + issuer: alice, + audience: bob, + capabilities: [ + { + can: 'store/add', + with: alice.did(), + }, + ], + }) + + assert.deepEqual(toJSON(ucan.data), { + v: UCAN.VERSION, + iss: alice.did(), + aud: bob.did(), + att: [ + { + can: 'store/add', + with: alice.did(), + }, + ], + exp: ucan.expiration, + prf: [], + s: { '/': { bytes: base64.baseEncode(ucan.signature) } }, + }) +}) + +test('delegation.data.toJSON with proofs', async () => { + const proof = await Delegation.delegate({ + issuer: alice, + audience: bob, + capabilities: [ + { + can: 'store/add', + with: alice.did(), + }, + ], + }) + + const ucan = await Delegation.delegate({ + issuer: bob, + audience: mallory, + capabilities: [ + { + can: 'store/add', + with: alice.did(), + root: link, + }, + ], + proofs: [proof], + }) + + assert.deepEqual(toJSON(ucan.data), { + v: UCAN.VERSION, + iss: bob.did(), + aud: mallory.did(), + att: [ + { + can: 'store/add', + with: alice.did(), + root: { '/': link.toString() }, + }, + ], + exp: ucan.expiration, + prf: [ + { + '/': proof.cid.toString(), + }, + ], + s: { '/': { bytes: base64.baseEncode(ucan.signature) } }, + }) +}) + +test('delegation.data.toJSON with bytes', async () => { + const content = utf8.encode('hello world') + const proof = await Delegation.delegate({ + issuer: alice, + audience: bob, + capabilities: [ + { + can: 'store/add', + with: alice.did(), + }, + ], + }) + + const ucan = await Delegation.delegate({ + issuer: bob, + audience: mallory, + capabilities: [ + { + can: 'store/add', + with: alice.did(), + root: content, + }, + ], + proofs: [proof], + }) + + assert.deepEqual(toJSON(ucan.data), { + v: UCAN.VERSION, + iss: bob.did(), + aud: mallory.did(), + att: [ + { + can: 'store/add', + with: alice.did(), + root: { '/': { bytes: base64.baseEncode(content) } }, + }, + ], + exp: ucan.expiration, + prf: [ + { + '/': proof.cid.toString(), + }, + ], + s: { '/': { bytes: base64.baseEncode(ucan.signature) } }, + }) +}) diff --git a/packages/interface/package.json b/packages/interface/package.json index 650040ec..4c9f6253 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -23,7 +23,7 @@ "build": "tsc --build" }, "dependencies": { - "@ipld/dag-ucan": "^3.0.1", + "@ipld/dag-ucan": "^3.1.1", "multiformats": "^10.0.2" }, "devDependencies": { diff --git a/packages/interface/src/lib.ts b/packages/interface/src/lib.ts index 0997d4cb..ad4ab2e7 100644 --- a/packages/interface/src/lib.ts +++ b/packages/interface/src/lib.ts @@ -151,7 +151,7 @@ export interface Delegation<C extends Capabilities = Capabilities> { issuer: UCAN.Principal audience: UCAN.Principal capabilities: C - expiration?: UCAN.UTCUnixTimestamp + expiration: UCAN.UTCUnixTimestamp notBefore?: UCAN.UTCUnixTimestamp nonce?: UCAN.Nonce @@ -159,6 +159,8 @@ export interface Delegation<C extends Capabilities = Capabilities> { facts: Fact[] proofs: Proof[] iterate(): IterableIterator<Delegation> + + signature: Signature } /** diff --git a/packages/principal/package.json b/packages/principal/package.json index 79f96b59..a81d4769 100644 --- a/packages/principal/package.json +++ b/packages/principal/package.json @@ -27,7 +27,7 @@ "build": "tsc --build" }, "dependencies": { - "@ipld/dag-ucan": "^3.0.1", + "@ipld/dag-ucan": "^3.1.1", "@noble/ed25519": "^1.7.1", "@ucanto/interface": "^4.0.3", "multiformats": "^10.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57d50563..8abaaed8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,7 +54,7 @@ importers: specifiers: '@ipld/car': ^5.0.0 '@ipld/dag-cbor': ^8.0.0 - '@ipld/dag-ucan': ^3.0.1 + '@ipld/dag-ucan': ^3.1.1 '@types/chai': ^4.3.3 '@types/mocha': ^9.1.0 '@ucanto/interface': ^4.0.3 @@ -69,7 +69,7 @@ importers: dependencies: '@ipld/car': 5.0.0 '@ipld/dag-cbor': 8.0.0 - '@ipld/dag-ucan': 3.0.1 + '@ipld/dag-ucan': 3.1.1 '@ucanto/interface': link:../interface multiformats: 10.0.2 devDependencies: @@ -85,18 +85,18 @@ importers: packages/interface: specifiers: - '@ipld/dag-ucan': ^3.0.1 + '@ipld/dag-ucan': ^3.1.1 multiformats: ^10.0.2 typescript: ^4.8.4 dependencies: - '@ipld/dag-ucan': 3.0.1 + '@ipld/dag-ucan': 3.1.1 multiformats: 10.0.2 devDependencies: typescript: 4.8.4 packages/principal: specifiers: - '@ipld/dag-ucan': ^3.0.1 + '@ipld/dag-ucan': ^3.1.1 '@noble/ed25519': ^1.7.1 '@types/chai': ^4.3.3 '@types/mocha': ^9.1.0 @@ -110,7 +110,7 @@ importers: playwright-test: ^8.1.1 typescript: ^4.8.4 dependencies: - '@ipld/dag-ucan': 3.0.1 + '@ipld/dag-ucan': 3.1.1 '@noble/ed25519': 1.7.1 '@ucanto/interface': link:../interface multiformats: 10.0.2 @@ -474,8 +474,8 @@ packages: multiformats: 10.0.2 dev: false - /@ipld/dag-ucan/3.0.1: - resolution: {integrity: sha512-71YwJeRHxwX3diPXfwiuzhJTjmJSqi8XW/x5Xglp82UqpM5xwtNojB07VhmDXTZXhKi42bZHyQIOLaca/t9IHw==} + /@ipld/dag-ucan/3.1.1: + resolution: {integrity: sha512-iTIME/QZNGMSXvM8aPDMR9zp6d5orCEAL0BqgAobm9jDt0RV0CLITLWgvxb5MSc0vjRw7GR51Bva1RdZw2qs9w==} dependencies: '@ipld/dag-cbor': 8.0.0 '@ipld/dag-json': 9.0.1 From ead73a9c0bd205d02f0c2f19a4e356a92b788f30 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili <contact@gozala.io> Date: Wed, 14 Dec 2022 13:34:25 -0800 Subject: [PATCH 2/2] feat: delegation to-json --- package.json | 2 +- packages/core/src/delegation.js | 13 +++ packages/core/test/delegation.spec.js | 121 +++++++++++++++++++++++++- packages/interface/src/lib.ts | 42 ++++++++- pnpm-lock.yaml | 28 +++--- 5 files changed, 185 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 6c72138a..9e4ffdc8 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "mocha": "^10.1.0", "prettier": "2.7.1", - "typescript": "4.8.3" + "typescript": "^4.8.4" }, "prettier": { "trailingComma": "es5", diff --git a/packages/core/src/delegation.js b/packages/core/src/delegation.js index 0564afe4..07398831 100644 --- a/packages/core/src/delegation.js +++ b/packages/core/src/delegation.js @@ -130,6 +130,19 @@ export class Delegation { iterate() { return it(this) } + + /** + * @returns {API.DelegationJSON<this>} + */ + toJSON() { + return /** @type {any} */ ({ + ...this.data.toJSON(), + '/': this.cid.toString(), + prf: this.proofs.map(proof => + isDelegation(proof) ? proof : { '/': proof.toString() } + ), + }) + } } /** diff --git a/packages/core/test/delegation.spec.js b/packages/core/test/delegation.spec.js index 5fa38308..da13091e 100644 --- a/packages/core/test/delegation.spec.js +++ b/packages/core/test/delegation.spec.js @@ -1,6 +1,6 @@ import { assert, test } from './test.js' -import { Delegation, UCAN, isDelegation, parseLink } from '../src/lib.js' -import { alice, bob, mallory, service } from './fixtures.js' +import { Delegation, UCAN, delegate, parseLink } from '../src/lib.js' +import { alice, bob, mallory, service as w3 } from './fixtures.js' import { base64 } from 'multiformats/bases/base64' const utf8 = new TextEncoder() @@ -132,3 +132,120 @@ test('delegation.data.toJSON with bytes', async () => { s: { '/': { bytes: base64.baseEncode(ucan.signature) } }, }) }) + +test('toJSON delegation', async () => { + const ucan = await delegate({ + issuer: alice, + audience: w3, + capabilities: [ + { + with: alice.did(), + can: 'test/echo', + nb: { + message: 'data:1', + }, + }, + ], + expiration: Infinity, + }) + + assert.deepEqual(toJSON(ucan), { + '/': ucan.cid.toString(), + v: ucan.version, + iss: alice.did(), + aud: w3.did(), + att: [ + { + nb: { + message: 'data:1', + }, + can: 'test/echo', + with: alice.did(), + }, + ], + exp: null, + prf: [], + s: { + '/': { bytes: base64.baseEncode(ucan.signature) }, + }, + }) +}) + +test('toJSON delegation chain', async () => { + const proof = await delegate({ + issuer: bob, + audience: alice, + capabilities: [ + { + with: bob.did(), + can: 'test/echo', + }, + ], + }) + + const proof2 = await delegate({ + issuer: mallory, + audience: alice, + capabilities: [ + { + with: mallory.did(), + can: 'test/echo', + }, + ], + }) + + const ucan = await delegate({ + issuer: alice, + audience: w3, + capabilities: [ + { + with: bob.did(), + can: 'test/echo', + nb: { + message: 'data:hi', + }, + }, + ], + proofs: [proof, proof2.cid], + }) + + assert.deepEqual(toJSON(ucan), { + '/': ucan.cid.toString(), + v: ucan.version, + iss: alice.did(), + aud: w3.did(), + att: [ + { + with: bob.did(), + can: 'test/echo', + nb: { + message: 'data:hi', + }, + }, + ], + exp: ucan.expiration, + prf: [ + { + '/': proof.cid.toString(), + iss: bob.did(), + aud: alice.did(), + att: [ + { + with: bob.did(), + can: 'test/echo', + }, + ], + exp: proof.expiration, + v: proof.version, + s: { '/': { bytes: base64.baseEncode(proof.signature) } }, + prf: [], + }, + { + '/': proof2.cid.toString(), + }, + ], + s: { + '/': { bytes: base64.baseEncode(ucan.signature) }, + }, + }) +}) diff --git a/packages/interface/src/lib.ts b/packages/interface/src/lib.ts index ad4ab2e7..f97d2b07 100644 --- a/packages/interface/src/lib.ts +++ b/packages/interface/src/lib.ts @@ -17,8 +17,13 @@ import { Principal, MulticodecCode, SigAlg, + ToJSON, + SignatureJSON, + JSONUnknown, + IntoJSON, + JSONObject, } from '@ipld/dag-ucan' -import { Link, Block as IPLDBlock } from 'multiformats' +import { Link, UnknownLink, Block as IPLDBlock, ToString } from 'multiformats' import * as UCAN from '@ipld/dag-ucan' import { CanIssue, @@ -59,6 +64,10 @@ export type { MultibaseEncoder, MulticodecCode, Principal, + ToJSON, + ToString, + UnknownLink, + JSONUnknown, } export * as UCAN from '@ipld/dag-ucan' @@ -161,8 +170,39 @@ export interface Delegation<C extends Capabilities = Capabilities> { iterate(): IterableIterator<Delegation> signature: Signature + version: UCAN.Version + + toJSON(): DelegationJSON<this> } +export type DelegationJSON<T extends Delegation = Delegation> = ToJSON< + T, + { + '/': ToString<T['cid']> + v: T['version'] + iss: DID + aud: DID + att: ToJSON< + T['capabilities'], + T['capabilities'] & + UCAN.Tuple<{ with: UCAN.Resource; can: UCAN.Ability; nb?: JSONObject }> + > + exp: T['expiration'] + nbf?: T['notBefore'] & {} + nnc?: T['nonce'] & {} + fct: ToJSON<T['facts']> + prf: ProofJSON[] & JSONUnknown[] + s: SignatureJSON<T['signature']> + } +> + +export type ProofJSON = DelegationJSON | LinkJSON<UCANLink> + +export type LinkJSON<T extends UnknownLink = UnknownLink> = ToJSON< + T, + { '/': ToString<T> } +> + /** * An Invocation represents a UCAN that can be presented to a service provider to * invoke or "exercise" a {@link Capability}. You can think of invocations as a diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8abaaed8..28ad5ae3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: specifiers: mocha: ^10.1.0 prettier: 2.7.1 - typescript: 4.8.3 + typescript: ^4.8.4 devDependencies: mocha: 10.1.0 prettier: 2.7.1 - typescript: 4.8.3 + typescript: 4.9.4 packages/client: specifiers: @@ -48,7 +48,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/core: specifiers: @@ -81,7 +81,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/interface: specifiers: @@ -92,7 +92,7 @@ importers: '@ipld/dag-ucan': 3.1.1 multiformats: 10.0.2 devDependencies: - typescript: 4.8.4 + typescript: 4.9.4 packages/principal: specifiers: @@ -123,7 +123,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/server: specifiers: @@ -166,7 +166,7 @@ importers: multiformats: 10.0.2 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/transport: specifiers: @@ -201,7 +201,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/validator: specifiers: @@ -240,7 +240,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages: @@ -2969,14 +2969,8 @@ packages: is-typedarray: 1.0.0 dev: true - /typescript/4.8.3: - resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true - - /typescript/4.8.4: - resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} + /typescript/4.9.4: + resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} engines: {node: '>=4.2.0'} hasBin: true dev: true