diff --git a/package-lock.json b/package-lock.json index a07cad9..df8f360 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8031,21 +8031,21 @@ } }, "node_modules/@ucanto/core": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@ucanto/core/-/core-10.0.1.tgz", - "integrity": "sha512-1BfUaJu0/c9Rl/WdZSDbScJJLsPsPe1g4ynl5kubUj3xDD/lyp/Q12PQVQ2X7hDiWwkpwmxCkRMkOxwc70iNKQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@ucanto/core/-/core-10.1.1.tgz", + "integrity": "sha512-Dypn1hlWvP4kFLuM98U02c8anFuT8hrR3uVi7YZ3wTyBeKOhl/0ggGBR06eyXLw6EL15Yp7dR3Mlce9jTEPwlA==", "dependencies": { "@ipld/car": "^5.1.0", "@ipld/dag-cbor": "^9.0.0", "@ipld/dag-ucan": "^3.4.0", - "@ucanto/interface": "^10.0.1", + "@ucanto/interface": "^10.0.2", "multiformats": "^11.0.2" } }, "node_modules/@ucanto/interface": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@ucanto/interface/-/interface-10.0.1.tgz", - "integrity": "sha512-+Vr/N4mLsdynV9/bqtdFiq7WsUf3265/Qx2aHJmPtXo9/QvWKthJtpe0g8U4NWkWpVfqIFvyAO2db6D9zWQfQw==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@ucanto/interface/-/interface-10.0.2.tgz", + "integrity": "sha512-0n1H6ChvC1moQl2lnGMdSN/ThfCiJ99VdyTtfiu/380vcf3U3Sb8soIrAWE9mM9KysNZUWfJBB0ahj5vganeoA==", "dependencies": { "@ipld/dag-ucan": "^3.4.0", "multiformats": "^11.0.2" @@ -8066,14 +8066,14 @@ } }, "node_modules/@ucanto/server": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@ucanto/server/-/server-10.0.0.tgz", - "integrity": "sha512-JMDMT3tFRE0S1cdtx/Hhh7v9FizV6IS0fPrh6pcli7AzKvXVy8Xu6EQ/66Fax4AQM2tkGxNNxjj2wHM7P4CqAg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@ucanto/server/-/server-10.0.2.tgz", + "integrity": "sha512-UNO4MAVXnMFP13JgcO3bZcfbW6FS4agZJJGozlo9FxNcUorfrRNTJ+uSmUStszjO+uHYzpIi0dPNRge2Fmm38Q==", "dependencies": { - "@ucanto/core": "^10.0.0", - "@ucanto/interface": "^10.0.0", - "@ucanto/principal": "^9.0.0", - "@ucanto/validator": "^9.0.1" + "@ucanto/core": "^10.1.1", + "@ucanto/interface": "^10.0.2", + "@ucanto/principal": "^9.0.1", + "@ucanto/validator": "^9.0.2" } }, "node_modules/@ucanto/transport": { @@ -18580,8 +18580,9 @@ "license": "Apache-2.0 OR MIT", "dependencies": { "@ucanto/client": "^9.0.1", + "@ucanto/core": "^10.1.1", "@ucanto/interface": "^10.0.0", - "@ucanto/server": "^10.0.0", + "@ucanto/server": "^10.0.2", "@ucanto/transport": "^9.1.1", "carstream": "^2.0.0", "multiformats": "^13.1.0" diff --git a/packages/core/package.json b/packages/core/package.json index 2497ba5..8d923d6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -111,8 +111,9 @@ }, "dependencies": { "@ucanto/client": "^9.0.1", + "@ucanto/core": "^10.1.1", "@ucanto/interface": "^10.0.0", - "@ucanto/server": "^10.0.0", + "@ucanto/server": "^10.0.2", "@ucanto/transport": "^9.1.1", "carstream": "^2.0.0", "multiformats": "^13.1.0" diff --git a/packages/core/src/capability/assert.js b/packages/core/src/capability/assert.js index 4d09e62..0f8acd7 100644 --- a/packages/core/src/capability/assert.js +++ b/packages/core/src/capability/assert.js @@ -21,7 +21,8 @@ export const location = capability({ range: Schema.struct({ offset: Schema.integer(), length: Schema.integer().optional() - }).optional() + }).optional(), + space: Schema.didBytes().optional() }), derives: (claimed, delegated) => ( and(equalWith(claimed, delegated)) || @@ -29,6 +30,7 @@ export const location = capability({ and(equal(claimed.nb.location, delegated.nb.location, 'location')) || and(equal(claimed.nb.range?.offset, delegated.nb.range?.offset, 'offset')) || and(equal(claimed.nb.range?.length, delegated.nb.range?.length, 'length')) || + and(equal(claimed.nb.space, delegated.nb.space, 'space')) || ok({}) ) }) @@ -57,7 +59,7 @@ export const index = capability({ with: URI.match({ protocol: 'did:' }), nb: Schema.struct({ /** DAG root CID */ - content: Schema.link(), + content: linkOrDigest(), /** * Link to a Content Archive that contains the index. * e.g. `index/sharded/dag@0.1` diff --git a/packages/core/src/client/api.ts b/packages/core/src/client/api.ts index 4485b90..a30d602 100644 --- a/packages/core/src/client/api.ts +++ b/packages/core/src/client/api.ts @@ -1,88 +1,51 @@ -import { Link, URI, UnknownLink, Block, MultihashDigest } from '@ucanto/client' +import { Delegation, Capability, Ability, Resource, Caveats } from '@ucanto/client' import * as Assert from '../capability/assert.js' +import { AssertEquals, AssertInclusion, AssertIndex, AssertLocation, AssertPartition, AssertRelation } from '../capability/api.js' + +type InferCaveats = C extends Capability ? NB : never + +type InferContent = C extends { content: infer T} ? T : never /** A verifiable claim about data. */ export interface ContentClaim { /** Subject of the claim e.g. CAR, DAG root etc. */ - readonly content: MultihashDigest + readonly content: InferContent> /** Discriminator for different types of claims. */ readonly type: T /** - * Returns an iterable of all IPLD blocks that are included in this claim. - */ - export (): IterableIterator - /** - * Writes the UCAN `Delegation` chain for this claim into a content addressed - * archive (CAR) buffer and returns it. + * Returns the underlying delegation this is based on */ - archive (): Promise + delegation() : Delegation } /** A claim not known to this library. */ export interface UnknownClaim extends ContentClaim<'unknown'> {} /** A claim that a CID is available at a URL. */ -export interface LocationClaim extends ContentClaim { - readonly location: URI[] - readonly range?: ByteRange +export interface LocationClaim extends ContentClaim, Readonly> { } /** A claim that a CID's graph can be read from the blocks found in parts. */ -export interface PartitionClaim extends ContentClaim { - /** CIDs CID - the hash of the binary sorted links in the set. */ - readonly blocks?: Link - /** List of archives (CAR CIDs) containing the blocks. */ - readonly parts: Link[] +export interface PartitionClaim extends ContentClaim, Readonly> { } /** A claim that a CID includes the contents claimed in another CID. */ -export interface InclusionClaim extends ContentClaim { - /** e.g. CARv2 Index CID or Sub-Deal CID (CommP) */ - readonly includes: Link - /** Zero-knowledge proof */ - readonly proof?: Link +export interface InclusionClaim extends ContentClaim, Readonly> { } /** * A claim that a content graph can be found in blob(s) that are identified and * indexed in the given index CID. */ -export interface IndexClaim extends ContentClaim { - /** - * Link to a Content Archive that contains the index. - * e.g. `index/sharded/dag@0.1` - * @see https://github.com/storacha/specs/blob/main/w3-index.md - */ - readonly index: Link +export interface IndexClaim extends ContentClaim, Readonly> { } /** A claim that a CID links to other CIDs. */ -export interface RelationClaim extends ContentClaim { - /** CIDs of blocks this content directly links to. */ - readonly children: UnknownLink[] - /** List of archives (CAR CIDs) containing the blocks. */ - readonly parts: RelationPart[] -} - -/** Part this content and it's children can be read from. */ -export interface RelationPart { - /** Part CID. */ - content: Link - /** CID of contents (CARv2 index) included in this part. */ - includes?: RelationPartInclusion -} - -export interface RelationPartInclusion { - /** Inclusion CID (CARv2 index) */ - content: Link - /** CIDs of parts this index may be found in. */ - parts?: Link[] +export interface RelationClaim extends ContentClaim, Readonly> { } /** A claim that the same data is referred to by another CID and/or multihash */ -export interface EqualsClaim extends ContentClaim { - /** A CID that is equivalent to the content CID e.g the Piece CID for that CAR CID */ - readonly equals: UnknownLink +export interface EqualsClaim extends ContentClaim, Readonly> { } /** Types of claim that are known to this library. */ @@ -103,8 +66,3 @@ export type Claim = | RelationClaim | EqualsClaim | UnknownClaim - -export interface ByteRange { - readonly offset: number - readonly length?: number -} diff --git a/packages/core/src/client/index.js b/packages/core/src/client/index.js index fb7b5b4..d28da48 100644 --- a/packages/core/src/client/index.js +++ b/packages/core/src/client/index.js @@ -3,11 +3,11 @@ import { extract as extractDelegation } from '@ucanto/core/delegation' import { connect, invoke, delegate } from '@ucanto/client' import { CAR, HTTP } from '@ucanto/transport' import { sha256 } from 'multiformats/hashes/sha2' -import { decode as decodeDigest } from 'multiformats/hashes/digest' import { equals } from 'multiformats/bytes' import { base58btc } from 'multiformats/bases/base58' import { CARReaderStream } from 'carstream/reader' import * as Assert from '../capability/assert.js' +import { decode as decodeDigest } from 'multiformats/hashes/digest' export const serviceURL = new URL('https://claims.web3.storage') @@ -23,22 +23,21 @@ export const connection = connect({ export { connect, invoke, delegate, CAR, HTTP } -const assertCapNames = [ - Assert.location.can, - Assert.partition.can, - Assert.inclusion.can, - Assert.index.can, - Assert.relation.can, - Assert.equals.can -] +const assertCapMap = { + [Assert.location.can]: Assert.location, + [Assert.partition.can]: Assert.partition, + [Assert.inclusion.can]: Assert.inclusion, + [Assert.index.can]: Assert.index, + [Assert.relation.can]: Assert.relation, + [Assert.equals.can]: Assert.equals +} /** * @param {import('@ucanto/interface').Capability} cap * @returns {cap is import('../server/api.js').AnyAssertCap} */ const isAssertCap = cap => - // @ts-expect-error - assertCapNames.includes(cap.can) && + Object.keys(assertCapMap).includes(cap.can) && 'nb' in cap && typeof cap.nb === 'object' && 'content' in cap.nb @@ -52,17 +51,25 @@ export const decode = async bytes => { if (delegation.error) { throw new Error('failed to decode claim', { cause: delegation.error }) } - const cap = delegation.ok.capabilities[0] + return decodeDelegation(delegation.ok) +} + +/** + * @param {import('@ucanto/interface').Delegation} delegation + * @returns {Promise} + */ +export const decodeDelegation = async delegation => { + const cap = delegation.capabilities[0] if (!isAssertCap(cap)) { throw new Error('invalid claim') } // @ts-expect-error + const parsedCap = assertCapMap[cap.can].create({ with: cap.with, nb: cap.nb }) + // @ts-expect-error return { - ...cap.nb, - content: 'digest' in cap.nb.content ? decodeDigest(cap.nb.content.digest) : cap.nb.content.multihash, - type: cap.can, - export: () => delegation.ok.export(), - archive: async () => bytes + ...parsedCap.nb, + type: parsedCap.can, + delegation: () => delegation } } @@ -120,3 +127,12 @@ export const read = async (content, options) => { return claims } + +/** + * + * @param {import('./api.js').Claim} claim + * @returns + */ +export const contentMultihash = (claim) => { + return 'digest' in claim.content ? decodeDigest(claim.content.digest) : claim.content.multihash +}