Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ipld/dag-ucan

Package Overview
Dependencies
Maintainers
8
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ipld/dag-ucan - npm Package Compare versions

Comparing version 1.1.1-beta to 1.2.0-beta

dist/src/codec/cbor.d.ts

2

dist/src/crypto.d.ts

@@ -9,3 +9,3 @@ export interface AsyncVerifier<A extends number> {

}
export interface Verifier<A extends number> {
export interface Verifier<A extends number = number> {
readonly algorithm: A;

@@ -12,0 +12,0 @@ verify<S extends Signer<A>, T>(payload: ByteView<T>, signature: Signature<T, S>): T;

@@ -1,10 +0,7 @@

export function format<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>({ header, body, signature }: UCAN.Data<C>): UCAN.JWT<UCAN.CBOR<C>>;
export function formatPayload<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>({ header, body }: {
header: UCAN.Header;
body: UCAN.Body<C>;
}): `${UCAN.Header}.${UCAN.Body}`;
export function formatHeader(header: UCAN.Header): `${UCAN.Header}`;
export function formatBody(body: UCAN.Body): `${UCAN.Body}`;
export function formatSignature<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(signature: UCAN.Signature<[UCAN.Header, UCAN.Body<C>], UCAN.Signer<number>>): string;
export function encodeHeader(header: UCAN.Header): import("multiformats/codecs/interface").ByteView<{
export function format<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(model: UCAN.Model<C>): UCAN.JWT<C>;
export function formatPayload<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(model: UCAN.Input<C>): `${UCAN.Header}.${UCAN.Body}`;
export function formatHeader(model: UCAN.Input): `${UCAN.Header}`;
export function formatBody(model: UCAN.Input): `${UCAN.Body}`;
export function formatSignature<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(signature: UCAN.Signature<C>): UCAN.ToString<UCAN.Signature<C>, string>;
export function encodeHeader(model: UCAN.Input): import("multiformats/codecs/interface").ByteView<{
alg: string;

@@ -14,3 +11,3 @@ ucv: `${number}.${number}.${number}`;

}>;
export function encodeBody(body: UCAN.Body): import("multiformats/codecs/interface").ByteView<{
export function encodeBody(body: UCAN.Input): import("multiformats/codecs/interface").ByteView<{
nbf?: number | undefined;

@@ -26,4 +23,4 @@ nnc?: string | undefined;

export function encodeProof(proof: UCAN.Proof): string;
export function encodeAgorithm<Code extends number>(code: Code): "EdDSA" | "RS256";
export function encodeAgorithm(model: UCAN.Input): "EdDSA" | "RS256";
import * as UCAN from "./ucan.js";
//# sourceMappingURL=formatter.d.ts.map
export * from "./ucan.js";
export * as DID from "./did.js";
/** @type {UCAN.Version} */
export const VERSION: UCAN.Version;
export const name: "dag-ucan";
export const raw: 85;
export function encode<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(ucan: UCAN.UCAN<C>): UCAN.ByteView<UCAN.UCAN<C>>;
export function decode<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(bytes: UCAN.ByteView<UCAN.UCAN<C>>): UCAN.View<C>;
/** @type {typeof CBOR.code|typeof RAW.code} */
export const code: typeof CBOR.code | typeof RAW.code;
export function encode<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(ucan: UCAN.UCAN<C>): UCAN.ByteView<UCAN.Model<C>> | UCAN.ByteView<UCAN.JWT<C>>;
export function decode<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(bytes: UCAN.ByteView<UCAN.Model<C>> | UCAN.ByteView<UCAN.JWT<C>>): UCAN.View<C>;
export function link<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(ucan: UCAN.UCAN<C>, options?: {
hasher?: UCAN.MultihashHasher<number> | undefined;
} | undefined): Promise<CID & UCAN.Proof<C, number>>;
export function write<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>, A extends number = 18>(ucan: UCAN.UCAN<C>, { hasher }?: {
export function write<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>, A extends number = 18>(data: UCAN.UCAN<C>, { hasher }?: {
hasher?: UCAN.MultihashHasher<A> | undefined;
} | undefined): Promise<{
cid: CID & UCAN.Proof<C, A>;
bytes: UCAN.ByteView<UCAN.UCAN<C>>;
bytes: UCAN.ByteView<UCAN.JWT<C>> | UCAN.ByteView<UCAN.Model<C>>;
data: UCAN.UCAN<C>;
}>;
export function parse<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(input: UCAN.JWT<UCAN.UCAN<C>>): UCAN.View<C>;
export function format<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(ucan: UCAN.UCAN<C>): UCAN.JWT<UCAN.UCAN<C>>;
export function parse<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(jwt: UCAN.JWT<C>): UCAN.View<C>;
export function format<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(ucan: UCAN.UCAN<C>): UCAN.JWT<C>;
export function issue<A extends number, C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>({ issuer, audience, capabilities, lifetimeInSeconds, expiration, notBefore, facts, proofs, nonce, }: UCAN.UCANOptions<C, A>): Promise<UCAN.View<C>>;
import * as UCAN from "./ucan.js";
import * as CBOR from "./codec/cbor.js";
import * as RAW from "./codec/raw.js";
import * as View from "./view.js";
import { CID } from "multiformats/cid";
//# sourceMappingURL=lib.d.ts.map

@@ -1,4 +0,18 @@

export function parse<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(input: UCAN.JWT<UCAN.UCAN<C>>): UCAN.Data<C>;
export function parseHeader(header: string): UCAN.Header;
export function parseBody<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(input: string): UCAN.Body<C>;
export function parse<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(input: UCAN.JWT<C>): UCAN.Model<C>;
export function parseHeader(header: string): {
version: `${number}.${number}.${number}`;
};
export function parseBody<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(input: string): {
issuer: UCAN.DIDView;
audience: UCAN.DIDView;
expiration: number;
nonce: string | undefined;
notBefore: number | undefined;
facts: UCAN.Fact[];
proofs: UCAN.Proof<UCAN.Capability<UCAN.Ability, `${string}:${string}`>, number>[];
capabilities: C[];
};
export function parseInt(input: unknown, name: string): number;
export function parseCapability(input: unknown, context: string): UCAN.Capability<UCAN.Ability, `${string}:${string}`>;
export function parseCapabilities(input: unknown, context: string): UCAN.Capability<UCAN.Ability, `${string}:${string}`>[];
export function asCapability<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(input: C | (object & {

@@ -8,3 +22,20 @@ can?: unknown;

})): C;
export function parseArray<T>(input: unknown, parser: (input: unknown, context: string) => T, context: string): T[];
export function parseOptionalArray<T>(input: unknown, parser: (input: unknown, context: string) => T, context: string): T[] | undefined;
export function parseStruct<T>(input: unknown, parser: (input: object) => T, context: string): T;
export function parseFact(input: unknown, context: string): UCAN.Fact;
export function parseDID(input: unknown, context: string): UCAN.DIDView;
export function parseOptionalString(input: unknown, context?: string | undefined): string | undefined;
export function parseOptionalInt(input: unknown, context: string): number | undefined;
export function parseVersion(input: unknown, context: string): UCAN.Version;
export function parseBytes(input: unknown, context: string): Uint8Array;
export class ParseError extends TypeError {
/**
* @param {string} message
* @returns {never}
*/
static throw(message: string): never;
get name(): string;
}
import * as UCAN from "./ucan.js";
//# sourceMappingURL=parser.d.ts.map
import type { MultihashDigest, MultihashHasher } from "multiformats/hashes/interface";
import type { MultibaseEncoder } from "multiformats/bases/interface";
import type { code as RAW_CODE } from "multiformats/codecs/raw";
import type { Signer, Signature } from "./crypto.js";
import type { code as CBOR_CODE } from "@ipld/dag-cbor";
import type { Signer } from "./crypto.js";
import * as Crypto from "./crypto.js";
export * from "./crypto.js";
export type { MultihashDigest, MultibaseEncoder, MultihashHasher };
export declare const code = 30912;
export declare type Fact = Record<string, unknown>;

@@ -30,20 +31,46 @@ export interface Agent {

}
export declare type JWT<T> = ToString<T>;
export declare type UCAN<C extends Capability = Capability> = CBOR<C> | RAW<C>;
export interface Data<C extends Capability = Capability> {
readonly header: Header;
readonly body: Body<C>;
readonly signature: Signature<[Header, Body<C>]>;
export declare type JWT<C extends Capability = Capability> = ToString<[
Head,
Payload<C>,
Signature<C>
], `${ToString<Head>}.${ToString<Payload<C>>}.${ToString<Signature<C>>}`>;
export declare type Signature<C extends Capability = Capability> = Crypto.Signature<`${ToString<Head>}.${ToString<Payload<C>>}>`>;
interface Head {
ucv: Version;
alg: "EdDSA" | "RS256";
typ: "JWT";
}
export interface CBOR<C extends Capability = Capability> extends Data<C> {
readonly code: typeof code;
export interface Payload<C extends Capability> {
iss: DID;
aud: DID;
exp: number;
att: C[];
nnc?: string;
nbf?: number;
fct?: Fact[];
prf?: ToString<Proof<C>>;
}
export interface RAW<C extends Capability = Capability> {
readonly code: typeof RAW_CODE;
readonly jwt: JWT<RAW<C>>;
export declare type UCAN<C extends Capability = Capability> = Model<C> | RAW<C>;
export interface Input<C extends Capability = Capability> {
version: Version;
issuer: ByteView<DID>;
audience: ByteView<DID>;
capabilities: C[];
expiration: number;
notBefore?: number;
nonce?: string;
facts: Fact[];
proofs: Proof[];
}
export declare type View<C extends Capability = Capability> = UCAN<C> & Data<C> & Header & Body<C>;
export interface Model<C extends Capability = Capability> extends Input<C> {
signature: Signature<C>;
}
export interface RAW<C extends Capability = Capability> extends Model<C>, ByteView<JWT<C>> {
}
export interface View<C extends Capability = Capability> extends Model<C> {
readonly model: Model<C>;
}
export interface UCANOptions<C extends Capability = Capability, A extends number = number> {
issuer: Issuer<A>;
audience: DID;
audience: Agent;
capabilities: C[];

@@ -57,3 +84,3 @@ lifetimeInSeconds?: number;

}
export declare type Proof<C extends Capability = Capability, A extends number = number> = Link<Data<C>, 1, typeof code, A> | Link<JWT<Data<C>>, 1, typeof RAW_CODE, A>;
export declare type Proof<C extends Capability = Capability, A extends number = number> = Link<Model<C>, 1, typeof CBOR_CODE, A> | Link<JWT<C>, 1, typeof RAW_CODE, A>;
export interface Block<T extends unknown = unknown, C extends number = number, A extends number = number> {

@@ -70,2 +97,4 @@ bytes: ByteView<T>;

export declare type DID<T = unknown> = ToString<T, `did:${string}`>;
export interface DIDView extends ByteView<DID>, Agent {
}
/**

@@ -72,0 +101,0 @@ * Represents an IPLD link to a specific data of type `T`.

@@ -1,31 +0,19 @@

export function cbor<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(data: UCAN.Data<C>): UCAN.View<C>;
export function jwt<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(data: UCAN.Data<C>, jwt: UCAN.JWT<UCAN.RAW<C>>): UCAN.View<C>;
export function cbor<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(data: UCAN.Model<C>): UCAN.View<C>;
export function jwt<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>>(model: UCAN.Model<C>, bytes: UCAN.ByteView<UCAN.JWT<C>>): UCAN.View<C>;
import * as UCAN from "./ucan.js";
/**
* @template {UCAN.Capability} C
* @template {typeof UCAN.code|typeof RAW.code} Code
* @extends {View<C>}
* @implements {UCAN.View<C>}
*/
declare class View<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>, Code extends 30912 | 85> {
declare class View<C extends UCAN.Capability<UCAN.Ability, `${string}:${string}`>> implements UCAN.View<C> {
/**
* @param {Code} code
* @param {UCAN.Data<C>} data
* @param {UCAN.Model<C>} model
*/
constructor(code: Code, { header, body, signature }: UCAN.Data<C>);
constructor(model: UCAN.Model<C>);
/** @readonly */
readonly code: Code;
/** @readonly */
readonly header: UCAN.Header;
/** @readonly */
readonly body: UCAN.Body<C>;
/** @readonly */
readonly signature: UCAN.Signature<[UCAN.Header, UCAN.Body<C>], UCAN.Signer<number>>;
readonly model: UCAN.Model<C>;
get version(): `${number}.${number}.${number}`;
get algorithm(): number;
get issuer(): UCAN.DID<unknown>;
get issuer(): UCAN.ByteView<UCAN.DID<unknown>>;
get audience(): UCAN.ByteView<UCAN.DID<unknown>>;
/**
* @returns {UCAN.DID}
*/
get audience(): UCAN.DID<unknown>;
/**
* @returns {C[]}

@@ -54,5 +42,5 @@ */

get proofs(): UCAN.Proof<UCAN.Capability<UCAN.Ability, `${string}:${string}`>, number>[];
get signature(): UCAN.Signature<C>;
}
import * as RAW from "multiformats/codecs/raw";
export {};
//# sourceMappingURL=view.d.ts.map
{
"name": "@ipld/dag-ucan",
"description": "UCAN codec for IPLD",
"version": "1.1.1-beta",
"version": "1.2.0-beta",
"keywords": [

@@ -56,2 +56,10 @@ "UCAN",

"import": "./src/lib.js"
},
"./src/codec/cbor.js": {
"types": "./dist/src/codec/cbor.d.ts",
"import": "./src/codec/cbor.js"
},
"./src/codec/raw.js": {
"types": "./dist/src/codec/raw.d.ts",
"import": "./src/codec/raw.js"
}

@@ -58,0 +66,0 @@ },

# @ipld/dag-ucan
An implementation of [UCAN][]s representation in [IPLD][], designed for use with [multiformats][].
An implementation of [UCAN][]s in [IPLD][] via [Advanced Data Layout (ADL)](ADL), designed for use with [multiformats][].
## Overview
This library implements multicodec for represenating [UCAN]s natively in [IPLD][]. It uses [DAG-CBOR][] as a primary encoding, which is more compact and has a better hash consitency than a secondary RAW JWT encoding. Every UCAN in primary encoding can be formatted into a JWT string and consumed by spec compliant [UCAN][] implementations. However not every [UCAN][] can be encoded in a primary CBOR representation, as loss of whitespaces and key order would lead to mismatched signature. Library issues UCANs only in primary CBOR representation. When parsing UCANs that can not have valid CBOR representation, secondary RAW representation is used, which allows interop with all existing tokens in the wild.
This library implements [ADL][] for representing [UCAN]s natively in [IPLD][]. It uses [DAG-CBOR][] as a primary encoding, which is hash consistent and more compact than a secondary RAW JWT encoding. Every UCAN in either encoding can be formatted into a valid JWT string and consumed by other spec compliant [UCAN][] implementations. However [UCAN][]s issued by other libraries may end up in represented in secondory RAW JWT encoding, that is because whitespaces and key order in JWT affects signatures and there for can't be represented accurately in CBOR. When parsing UCANs library will use CBOR representation and fallback to RAW JWT, which allows interop with all existing tokens in the wild.

@@ -16,29 +16,25 @@ ### Primary Representation

type UCAN struct {
header Heder
body Body
version String
issuer SigningKey
audience SigningKey
signature Signature
}
type Header struct {
version String
algorithm Algorithm
}
type Body struct {
issuer String
audience String,
capabilities [Capability]
proofs [&UCAN]
expiration Int
proofs [&UCAN]
-- If empty omitted
facts optional [Fact]
facts [Fact]
nonce optional String
notBefore optional Int
} representation map {
field facts default []
field proofs default []
}
type Capability struct {
with String
-- Must be all lowercase
can String
-- can have other fields
with Resource
can Ability
-- can have arbitrary other fields
}

@@ -48,7 +44,9 @@

enum Algorithm {
EdDSA (237) -- 0xed Ed25519 multicodec
RS256 (4613) -- 0x1205 RSA multicodec
} representation int
-- The resource pointer in URI format
type Resource = String
-- Must be all lower-case `/` delimeted with at least one path segment
type Ability = String
-- Signature is computed by seralizing header & body

@@ -59,2 +57,7 @@ -- into corresponding JSON with DAG-JSON (to achieve

type Signature = Bytes
-- multicodec tagged public key
-- 0xed Ed25519
-- 0x1205 RSA
type SigningKey = Bytes
```

@@ -68,3 +71,3 @@

#### `UCAN.parse(jwt: string): UCAN.UCAN`
#### `UCAN.parse(jwt: string): UCAN.View`

@@ -75,3 +78,3 @@ Parses UCAN formatted as JWT string into a representatino that can be encoded, formatted and queried.

const ucan = UCAN.parse(jwt)
ucan.issuer // did:key:zAlice
ucan.issuer.did() // did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi
```

@@ -91,25 +94,33 @@

```ts
UCAN.encode(UCAN.parse(jwt)) // Uint8Array(679)
```
#### `UCAN.decode(bytes: Uint8Array): UCAN.UCAN`
Decodes byte encoded UCAN.
Decodes UCAN from binary representation into object representation.
```ts
UCAN.decode(UCAN.encode(ucan))
```
#### `UCAN.issue(options: UCAN.UCANOptions): Promise<UCAN.UCAN>`
Issues or derives a UCAN.
Issues a signed UCAN.
> Please note that no capability or time bound validation takes place
> Please note that no capability or time bound validation takes place.
```ts
const ucan = await UCAN.issue({
issuer: boris,
audience: 'did:key:z6MkffDZCkCTWreg8868fG1FGFogcJj5X6PY93pPcWDn9bob'
issuer: alice,
audience: bob,
capabilities: [
{
with: "wnfs://boris.fission.name/public/photos/",
can: "wnfs/append",
can: "fs/read",
with: `storage://${alice.did()}/public/photos/`,
},
{
with: "mailto:boris@fission.codes",
can: "msg/send"
}
can: "pin/add",
with: alice.did(),
},
],

@@ -124,1 +135,2 @@ })

[multiformats]: https://github.com/multiformats/js-multiformats
[adl]: https://ipld.io/docs/advanced-data-layouts/

@@ -17,3 +17,3 @@ export interface AsyncVerifier<A extends number> {

export interface Verifier<A extends number> {
export interface Verifier<A extends number = number> {
readonly algorithm: A

@@ -20,0 +20,0 @@ verify<S extends Signer<A>, T>(

import * as UCAN from "./ucan.js"
import * as DID from "./did.js"
import * as json from "@ipld/dag-json"
import { base64urlpad } from "multiformats/bases/base64"
import { algorithm, ED25519, RSA } from "./did.js"
/**
* @template {UCAN.Capability} C
* @param {UCAN.Data<C>} data
* @returns {UCAN.JWT<UCAN.CBOR<C>>}
* @param {UCAN.Model<C>} model
* @returns {UCAN.JWT<C>}
*/
export const format = ({ header, body, signature }) =>
`${formatHeader(header)}.${formatBody(body)}.${formatSignature(signature)}`
export const format = model =>
`${formatHeader(model)}.${formatBody(model)}.${formatSignature(
model.signature
)}`
/**
* @template {UCAN.Capability} C
* @param {object} payload
* @param {UCAN.Header} payload.header
* @param {UCAN.Body<C>} payload.body
* @param {UCAN.Input<C>} model
* @returns {`${UCAN.Header}.${UCAN.Body}`}
*/
export const formatPayload = ({ header, body }) =>
`${formatHeader(header)}.${formatBody(body)}`
export const formatPayload = model =>
`${formatHeader(model)}.${formatBody(model)}`
/**
* @param {UCAN.Header} header
* @param {UCAN.Input} model
* @returns {`${UCAN.Header}`}
*/
export const formatHeader = header =>
base64urlpad.baseEncode(encodeHeader(header))
export const formatHeader = model =>
base64urlpad.baseEncode(encodeHeader(model))
/**
* @param {UCAN.Body} body
* @param {UCAN.Input} model
* @returns {`${UCAN.Body}`}
*/
export const formatBody = body => base64urlpad.baseEncode(encodeBody(body))
export const formatBody = model => base64urlpad.baseEncode(encodeBody(model))
/**
* @template {UCAN.Capability} C
* @param {UCAN.Signature<[UCAN.Header, UCAN.Body<C>]>} signature
* @param {UCAN.Signature<C>} signature
* @returns {UCAN.ToString<UCAN.Signature<C>>}
*/

@@ -43,8 +46,8 @@ export const formatSignature = signature => base64urlpad.baseEncode(signature)

/**
* @param {UCAN.Header} header
* @param {UCAN.Input} model
*/
export const encodeHeader = header =>
export const encodeHeader = model =>
json.encode({
alg: encodeAgorithm(header.algorithm),
ucv: header.version,
alg: encodeAgorithm(model),
ucv: model.version,
typ: "JWT",

@@ -54,8 +57,8 @@ })

/**
* @param {UCAN.Body} body
* @param {UCAN.Input} body
*/
export const encodeBody = body =>
json.encode({
iss: body.issuer,
aud: body.audience,
iss: DID.format(body.issuer),
aud: DID.format(body.audience),
att: body.capabilities,

@@ -76,15 +79,14 @@ exp: body.expiration,

/**
* @template {number} Code
* @param {Code} code
* @param {UCAN.Input} model
*/
export const encodeAgorithm = code => {
switch (code) {
case 0xed:
export const encodeAgorithm = model => {
switch (algorithm(model.issuer)) {
case ED25519:
return "EdDSA"
case 0x1205:
case RSA:
return "RS256"
/* c8 ignore next 2 */
default:
throw new RangeError(`Unknown KeyType "${code}"`)
throw new RangeError(`Unknown KeyType "${algorithm(model.issuer)}"`)
}
}
import * as UCAN from "./ucan.js"
import * as CBOR from "@ipld/dag-cbor"
import * as RAW from "multiformats/codecs/raw"
import * as CBOR from "./codec/cbor.js"
import * as RAW from "./codec/raw.js"
import * as UTF8 from "./utf8.js"

@@ -12,4 +12,5 @@ import * as View from "./view.js"

export * from "./ucan.js"
import { code } from "./ucan.js"
export * as DID from "./did.js"
/** @type {UCAN.Version} */

@@ -19,3 +20,4 @@ export const VERSION = "0.8.1"

export const raw = RAW.code
/** @type {typeof CBOR.code|typeof RAW.code} */
export const code = CBOR.code

@@ -29,42 +31,8 @@ /**

* @param {UCAN.UCAN<C>} ucan
* @returns {UCAN.ByteView<UCAN.UCAN<C>>}
* @returns {UCAN.ByteView<UCAN.Model<C>>|UCAN.ByteView<UCAN.JWT<C>>}
*/
export const encode = ucan => {
switch (ucan.code) {
case code:
return CBOR.encode({
header: {
version: ucan.header.version,
algorithm: ucan.header.algorithm,
},
body: {
issuer: ucan.body.issuer,
audience: ucan.body.audience,
capabilities: ucan.body.capabilities.map(Parser.asCapability),
expiration: ucan.body.expiration,
proofs: ucan.body.proofs,
// leave out optionals unless they are set
...(ucan.body.facts.length > 0 && { facts: ucan.body.facts }),
...(ucan.body.nonce && { nonce: ucan.body.nonce }),
...(ucan.body.notBefore && { notBefore: ucan.body.notBefore }),
},
signature: ucan.signature,
})
case raw:
return /** @type {Uint8Array} */ (UTF8.encode(ucan.jwt))
default:
return invalidCode(ucan)
}
}
export const encode = ucan =>
ucan instanceof Uint8Array ? RAW.encode(ucan) : CBOR.encode(ucan)
/**
* @param {never} ucan
*/
const invalidCode = ({ code: unknown }) => {
throw new TypeError(
`Provided UCAN has unsupported code: ${unknown}, it must be ${code} for CBOR representation or ${raw} for JWT representation`
)
}
/**
* Decodes binary encoded UCAN. It assumes UCAN is in primary IPLD

@@ -76,3 +44,3 @@ * representation and attempts to decode it with DAG-CBOR, if that

* @template {UCAN.Capability} C
* @param {UCAN.ByteView<UCAN.UCAN<C>>} bytes
* @param {UCAN.ByteView<UCAN.Model<C>>|UCAN.ByteView<UCAN.JWT<C>>} bytes
* @returns {UCAN.View<C>}

@@ -82,9 +50,6 @@ */

try {
const data = CBOR.decode(bytes)
data.body.facts = data.body.facts || []
data.body.nonce = data.body.nonce || undefined
data.body.notBefore = data.body.notBefore || undefined
return View.cbor(data)
return CBOR.decode(/** @type {UCAN.ByteView<UCAN.Model<C>>} */ (bytes))
} catch (error) {
return parse(UTF8.decode(/** @type {Uint8Array} */ (bytes)))
const jwt = UTF8.decode(/** @type {UCAN.RAW<C>} */ (bytes))
return parse(jwt)
}

@@ -110,14 +75,18 @@ }

* @template {number} [A=typeof sha256.code]
* @param {UCAN.UCAN<C>} ucan
* @param {UCAN.UCAN<C>} data
* @param {{hasher?: UCAN.MultihashHasher<A>}} [options]
*/
export const write = async (
ucan,
data,
{ hasher = /** @type {UCAN.MultihashHasher<any> } */ (sha256) } = {}
) => {
const bytes = encode(ucan)
const [code, bytes] =
data instanceof Uint8Array
? [RAW.code, RAW.encode(data)]
: [CBOR.code, CBOR.encode(data)]
const cid = /** @type {CID & UCAN.Proof<C, A>} */ (
CID.createV1(ucan.code, await hasher.digest(bytes))
CID.createV1(code, await hasher.digest(bytes))
)
return { cid, bytes }
return { cid, bytes, data }
}

@@ -138,7 +107,8 @@

* @template {UCAN.Capability} C
* @param {UCAN.JWT<UCAN.UCAN<C>>} input
* @param {UCAN.JWT<C>} jwt
* @returns {UCAN.View<C>}
*/
export const parse = input => {
const ucan = Parser.parse(input)
export const parse = jwt => {
const model = Parser.parse(jwt)

@@ -148,5 +118,5 @@ // If formatting UCAN produces same jwt string we can use IPLD representation

// affect how we `encode` the UCAN.
return Formatter.format(ucan) === input
? View.cbor(ucan)
: View.jwt(ucan, /** @type {UCAN.JWT<UCAN.RAW<C>>} */ (input))
return Formatter.format(model) === jwt
? View.cbor(model)
: View.jwt(model, UTF8.encode(jwt))
}

@@ -159,14 +129,6 @@

* @param {UCAN.UCAN<C>} ucan
* @returns {UCAN.JWT<UCAN.UCAN<C>>}
* @returns {UCAN.JWT<C>}
*/
export const format = ucan => {
switch (ucan.code) {
case code:
return Formatter.format(ucan)
case raw:
return ucan.jwt
default:
return invalidCode(ucan)
}
}
export const format = ucan =>
ucan instanceof Uint8Array ? UTF8.decode(ucan) : Formatter.format(ucan)

@@ -194,17 +156,7 @@ /**

}) => {
const header = {
const data = CBOR.match({
version: VERSION,
algorithm: issuer.algorithm,
}
// Validate
if (!audience.startsWith("did:")) {
throw new TypeError("The audience must be a DID")
}
/** @type {UCAN.Body<C>} */
const body = {
issuer: issuer.did(),
audience,
capabilities: capabilities.map(Parser.asCapability),
issuer: parseDID(issuer, "issuer"),
audience: parseDID(audience, "audience"),
capabilities,
facts,

@@ -215,9 +167,26 @@ expiration,

nonce,
}
// Provide fake signature to pass validation
// we'll replace this with actual signature
signature: EMPTY,
})
const payload = UTF8.encode(Formatter.formatPayload({ header, body }))
/** @type {UCAN.Signature<[UCAN.Header, UCAN.Body<C>]>} */
const payload = UTF8.encode(Formatter.formatPayload(data))
/** @type {UCAN.Signature<C>} */
const signature = await issuer.sign(payload)
return View.cbor({ header, body, signature })
return View.cbor({ ...data, signature })
}
/**
*
* @param {unknown & {did?:unknown}} value
* @param {string} context
*/
const parseDID = (value, context) =>
value && typeof value.did === "function"
? Parser.parseDID(value.did(), `${context}.did()`)
: Parser.ParseError.throw(
`The ${context}.did() must be a function that returns DID`
)
const EMPTY = new Uint8Array()

@@ -7,2 +7,3 @@ import * as UCAN from "./ucan.js"

import { identity } from "multiformats/hashes/identity"
import * as DID from "./did.js"
import * as raw from "multiformats/codecs/raw"

@@ -14,4 +15,4 @@

* @template {UCAN.Capability} C
* @param {UCAN.JWT<UCAN.UCAN<C>>} input
* @returns {UCAN.Data<C>}
* @param {UCAN.JWT<C>} input
* @returns {UCAN.Model<C>}
*/

@@ -28,4 +29,4 @@ export const parse = input => {

return {
header: parseHeader(header),
body: parseBody(body),
...parseHeader(header),
...parseBody(body),
signature: base64urlpad.baseDecode(signature),

@@ -37,3 +38,2 @@ }

* @param {string} header
* @returns {UCAN.Header}
*/

@@ -44,6 +44,6 @@ export const parseHeader = header => {

const _type = parseJWT(typ)
const _algorithm = parseAlgorithm(alg)
return {
version: parseUCV(ucv),
algorithm: parseAlgorithm(alg),
version: parseVersion(ucv, "ucv"),
}

@@ -55,18 +55,16 @@ }

* @param {string} input
* @returns {UCAN.Body<C>}
*/
export const parseBody = input => {
/** @type {UCAN.Payload<C>} */
const body = json.decode(base64urlpad.baseDecode(input))
return {
issuer: parseDID(body.iss),
audience: parseDID(body.aud),
expiration: parseInt(body.exp, 10),
issuer: parseDID(body.iss, "iss"),
audience: parseDID(body.aud, "aud"),
expiration: parseInt(body.exp, "exp"),
nonce: parseOptionalString(body.nnc, "nnc"),
notBefore: parseMaybeInt(body.nbf, "nbf"),
notBefore: parseOptionalInt(body.nbf, "nbf"),
facts: parseOptionalArray(body.fct, parseFact, "fct") || [],
proofs: parseProofs(body.prf),
capabilities: /** @type {C[]} */ (
parseArray(body.att, parseCapability, "att")
),
proofs: parseProofs(body.prf, "prf"),
capabilities: /** @type {C[]} */ (parseCapabilities(body.att, "att")),
}

@@ -77,7 +75,27 @@ }

* @param {unknown} input
* @param {string} name
* @returns {number}
*/
export const parseInt = (input, name) =>
Number.isInteger(input)
? /** @type {number} */ (input)
: ParseError.throw(
`Expected integer but instead got '${name}: ${JSON.stringify(input)}'`
)
const parseCapability = input => parseStruct(input, asCapability, "att")
/**
* @param {unknown} input
* @param {string} context
*/
export const parseCapability = (input, context) =>
parseStruct(input, asCapability, context)
/**
* @param {unknown} input
* @param {string} context
*/
export const parseCapabilities = (input, context) =>
parseArray(input, parseCapability, context)
/**
* @template {UCAN.Capability} C

@@ -158,9 +176,9 @@ * @param {object & {can?:unknown, with?:unknown}|C} input

* @param {unknown} input
* @param {(input:unknown) => T} parser
* @param {(input:unknown, context:string) => T} parser
* @param {string} context
* @returns {T[]}
*/
const parseArray = (input, parser, context) =>
export const parseArray = (input, parser, context) =>
Array.isArray(input)
? input.map(parser)
? input.map((element, n) => parser(element, `${context}[${n}]`))
: ParseError.throw(`${context} must be an array`)

@@ -171,7 +189,7 @@

* @param {unknown} input
* @param {(input:unknown) => T} parser
* @param {(input:unknown, context: string) => T} parser
* @param {string} context
* @returns {T[]|undefined}
*/
const parseOptionalArray = (input, parser, context) =>
export const parseOptionalArray = (input, parser, context) =>
input === undefined ? input : parseArray(input, parser, context)

@@ -186,26 +204,31 @@

*/
const parseStruct = (input, parser, context) =>
export const parseStruct = (input, parser, context) =>
input != null && typeof input === "object"
? parser(input)
: ParseError.throw(`${context} must be of type object`)
: ParseError.throw(
`${context} must be of type object, instead got ${input}`
)
/**
* @param {unknown} input
* @param {string} context
* @returns {UCAN.Fact}
*/
const parseFact = input => parseStruct(input, Object, "fct elements")
export const parseFact = (input, context) => parseStruct(input, Object, context)
/**
* @param {unknown} input
* @param {string} context
*/
const parseProofs = input =>
const parseProofs = (input, context) =>
Array.isArray(input)
? parseArray(input, parseProof, "prf")
: [parseProof(input)]
? parseArray(input, parseProof, context)
: [parseProof(input, context)]
/**
* @param {unknown} input
* @param {string} context
* @returns {UCAN.Proof}
*/
const parseProof = input => {
const parseProof = (input, context) => {
const proof =

@@ -215,3 +238,5 @@ typeof input === "string"

: ParseError.throw(
`prf has invalid value ${JSON.stringify(input)}, must be a string`
`${context} has invalid value ${JSON.stringify(
input
)}, must be a string`
)

@@ -229,8 +254,10 @@ try {

* @param {unknown} input
* @returns {UCAN.DID}
* @param {string} context
*/
const parseDID = input =>
export const parseDID = (input, context) =>
typeof input === "string" && input.startsWith("did:")
? /** @type {UCAN.DID} */ (input)
: ParseError.throw(`DID has invalid representation '${input}'`)
? DID.parse(/** @type {UCAN.DID} */ (input))
: ParseError.throw(
`DID has invalid representation '${context}: ${JSON.stringify(input)}'`
)

@@ -241,3 +268,3 @@ /**

*/
const parseOptionalString = (input, context = "Field") => {
export const parseOptionalString = (input, context = "Field") => {
switch (typeof input) {

@@ -254,5 +281,5 @@ case "string":

* @param {unknown} input
* @param {string} [context]
* @param {string} context
*/
const parseMaybeInt = (input, context = "Field") => {
export const parseOptionalInt = (input, context) => {
switch (typeof input) {

@@ -262,3 +289,3 @@ case "undefined":

case "number":
return parseInt(/** @type {any} */ (input), 10)
return parseInt(/** @type {any} */ (input), context)
default:

@@ -273,11 +300,24 @@ return ParseError.throw(

* @param {unknown} input
* @param {string} context
* @returns {UCAN.Version}
*/
const parseUCV = input =>
export const parseVersion = (input, context) =>
/\d+\.\d+\.\d+/.test(/** @type {string} */ (input))
? /** @type {UCAN.Version} */ (input)
: ParseError.throw(`Header has invalid version 'ucv: "${input}"'`)
: ParseError.throw(`Invalid version '${context}: ${JSON.stringify(input)}'`)
/**
*
* @param {unknown} input
* @param {string} context
* @returns {Uint8Array}
*/
export const parseBytes = (input, context) =>
input instanceof Uint8Array
? input
: ParseError.throw(
`${context} must be Uint8Array, instead got ${JSON.stringify(input)}`
)
/**
* @param {unknown} input
* @returns {"JWT"}

@@ -306,3 +346,6 @@ */

class ParseError extends TypeError {
export class ParseError extends TypeError {
get name() {
return "ParseError"
}
/**

@@ -309,0 +352,0 @@ * @param {string} message

@@ -7,3 +7,5 @@ import type {

import type { code as RAW_CODE } from "multiformats/codecs/raw"
import type { Signer, Signature } from "./crypto.js"
import type { code as CBOR_CODE } from "@ipld/dag-cbor"
import type { Signer } from "./crypto.js"
import * as Crypto from "./crypto.js"

@@ -14,3 +16,2 @@ export * from "./crypto.js"

export const code = 0x78c0
export type Fact = Record<string, unknown>

@@ -43,25 +44,62 @@

}
export type JWT<T> = ToString<T>
export type JWT<C extends Capability = Capability> = ToString<
[Head, Payload<C>, Signature<C>],
`${ToString<Head>}.${ToString<Payload<C>>}.${ToString<Signature<C>>}`
>
export type UCAN<C extends Capability = Capability> = CBOR<C> | RAW<C>
export type Signature<C extends Capability = Capability> =
Crypto.Signature<`${ToString<Head>}.${ToString<Payload<C>>}>`>
export interface Data<C extends Capability = Capability> {
readonly header: Header
readonly body: Body<C>
readonly signature: Signature<[Header, Body<C>]>
interface Head {
ucv: Version
alg: "EdDSA" | "RS256"
typ: "JWT"
}
export interface CBOR<C extends Capability = Capability> extends Data<C> {
readonly code: typeof code
export interface Payload<C extends Capability> {
iss: DID
aud: DID
exp: number
att: C[]
nnc?: string
nbf?: number
fct?: Fact[]
prf?: ToString<Proof<C>>
}
export interface RAW<C extends Capability = Capability> {
readonly code: typeof RAW_CODE
readonly jwt: JWT<RAW<C>>
export type UCAN<C extends Capability = Capability> = Model<C> | RAW<C>
// export interface Data<C extends Capability = Capability> {
// readonly header: Header
// readonly body: Body<C>
// readonly signature: Signature<[Header, Body<C>]>
// }
// export interface CBOR<C extends Capability = Capability> extends Data<C> {
// readonly code: typeof code
// }
export interface Input<C extends Capability = Capability> {
version: Version
issuer: ByteView<DID>
audience: ByteView<DID>
capabilities: C[]
expiration: number
notBefore?: number
nonce?: string
facts: Fact[]
proofs: Proof[]
}
export type View<C extends Capability = Capability> = UCAN<C> &
Data<C> &
Header &
Body<C>
export interface Model<C extends Capability = Capability> extends Input<C> {
signature: Signature<C>
}
export interface RAW<C extends Capability = Capability>
extends Model<C>,
ByteView<JWT<C>> {}
export interface View<C extends Capability = Capability> extends Model<C> {
readonly model: Model<C>
}
export interface UCANOptions<

@@ -72,3 +110,3 @@ C extends Capability = Capability,

issuer: Issuer<A>
audience: DID
audience: Agent
capabilities: C[]

@@ -88,3 +126,3 @@ lifetimeInSeconds?: number

A extends number = number
> = Link<Data<C>, 1, typeof code, A> | Link<JWT<Data<C>>, 1, typeof RAW_CODE, A>
> = Link<Model<C>, 1, typeof CBOR_CODE, A> | Link<JWT<C>, 1, typeof RAW_CODE, A>

@@ -112,2 +150,3 @@ export interface Block<

export type DID<T = unknown> = ToString<T, `did:${string}`>
export interface DIDView extends ByteView<DID>, Agent {}

@@ -114,0 +153,0 @@ /**

import * as UCAN from "./ucan.js"
import * as RAW from "multiformats/codecs/raw"
import * as DID from "./did.js"
/**
* @template {UCAN.Capability} C
* @template {typeof UCAN.code|typeof RAW.code} Code
* @extends {View<C>}
* @implements {UCAN.View<C>}
*/
class View {
/**
* @param {Code} code
* @param {UCAN.Data<C>} data
* @param {UCAN.Model<C>} model
*/
constructor(code, { header, body, signature }) {
constructor(model) {
/** @readonly */
this.code = code
/** @readonly */
this.header = header
/** @readonly */
this.body = body
/** @readonly */
this.signature = signature
this.model = model
}
get version() {
return this.header.version
return this.model.version
}
get algorithm() {
return this.header.algorithm
}
get issuer() {
return this.body.issuer
return this.model.issuer
}
/**
* @returns {UCAN.DID}
*/
get audience() {
return this.body.audience
return this.model.audience
}

@@ -47,3 +34,3 @@

get capabilities() {
return this.body.capabilities
return this.model.capabilities
}

@@ -55,3 +42,3 @@

get expiration() {
return this.body.expiration
return this.model.expiration
}

@@ -63,3 +50,3 @@

get notBefore() {
return this.body.notBefore
return this.model.notBefore
}

@@ -72,3 +59,3 @@

get nonce() {
return this.body.nonce
return this.model.nonce
}

@@ -80,3 +67,3 @@

get facts() {
return this.body.facts
return this.model.facts
}

@@ -89,4 +76,8 @@

get proofs() {
return this.body.proofs
return this.model.proofs
}
get signature() {
return this.model.signature
}
}

@@ -96,14 +87,79 @@

* @template {UCAN.Capability} C
* @extends {View<C, typeof RAW.code>}
* @implements {UCAN.View<C>}
*/
class RAWView extends View {
class JWTView extends Uint8Array {
/**
*
* @param {UCAN.Data<C>} data
* @param {UCAN.JWT<UCAN.RAW<C>>} jwt
* @param {UCAN.Model<C>} model
* @param {object} bytes
* @param {ArrayBuffer} bytes.buffer
* @param {number} [bytes.byteOffset]
* @param {number} [bytes.byteLength]
*/
constructor(data, jwt) {
super(RAW.code, data)
this.jwt = jwt
constructor(
model,
{ buffer, byteOffset = 0, byteLength = buffer.byteLength }
) {
super(buffer, byteOffset, byteLength)
this.model = model
}
get version() {
return this.model.version
}
get issuer() {
return this.model.issuer
}
get audience() {
return this.model.audience
}
/**
* @returns {C[]}
*/
get capabilities() {
return this.model.capabilities
}
/**
* @returns {number}
*/
get expiration() {
return this.model.expiration
}
/**
* @returns {undefined|number}
*/
get notBefore() {
return this.model.notBefore
}
/**
* @returns {undefined|string}
*/
get nonce() {
return this.model.nonce
}
/**
* @returns {UCAN.Fact[]}
*/
get facts() {
return this.model.facts
}
/**
* @returns {UCAN.Proof[]}
*/
get proofs() {
return this.model.proofs
}
get signature() {
return this.model.signature
}
}

@@ -113,13 +169,13 @@

* @template {UCAN.Capability} C
* @param {UCAN.Data<C>} data
* @param {UCAN.Model<C>} data
* @returns {UCAN.View<C>}
*/
export const cbor = data => new View(UCAN.code, data)
export const cbor = data => new View(data)
/**
* @template {UCAN.Capability} C
* @param {UCAN.Data<C>} data
* @param {UCAN.JWT<UCAN.RAW<C>>} jwt
* @param {UCAN.Model<C>} model
* @param {UCAN.ByteView<UCAN.JWT<C>>} bytes
* @returns {UCAN.View<C>}
*/
export const jwt = (data, jwt) => new RAWView(data, jwt)
export const jwt = (model, bytes) => new JWTView(model, bytes)

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc