@ipld/dag-ucan
Version:
UCAN codec for IPLD
375 lines (329 loc) • 9.46 kB
text/typescript
import type {
MultibaseEncoder,
MultibaseDecoder,
MultihashDigest,
MultihashHasher,
Block as IPLDBlock,
Link as IPLDLink,
Version as LinkVersion,
Phantom,
ByteView,
} from "multiformats"
import type { code as RAW_CODE } from "multiformats/codecs/raw"
import type { code as CBOR_CODE } from "@ipld/dag-cbor"
import * as Crypto from "./crypto.js"
export * from "./crypto.js"
export * as Crypto from "./crypto.js"
export type {
MultibaseEncoder,
MultibaseDecoder,
MultihashDigest,
MultihashHasher,
IPLDBlock,
IPLDLink,
LinkVersion,
Phantom,
ByteView,
}
export type Code =
| MulticodecCode<typeof CBOR_CODE, "CBOR">
| MulticodecCode<typeof RAW_CODE, "Raw">
/**
* This utility type can be used in place of `T[]` where you
* want TS to infer things as tuples as opposed to array.
* Also note that `T[]` could be empty, wheres `Tuple<T>`
* will contain at least one `T`.
*/
export type Tuple<T = unknown> = [T, ...T[]]
/**
* A string-encoded decentralized identity document (DID).
*/
export type DID<Method extends string = string> = `did:${Method}:${string}`
/**
* DID object representation with a `did` accessor for the {@link DID}.
*/
export interface Principal<ID extends DID = DID> {
did(): ID
}
/**
* A byte-encoded {@link DID} that provides a `did` accessor method (see {@link Principal}).
*/
export interface PrincipalView<ID extends DID = DID>
extends ByteView<Principal<ID>>,
Principal<ID> {}
/**
* Entity that can verify UCAN signatures against a {@link Principal} produced
* with the algorithm `A` (see {@link Crypto.Verifier}).
*/
export interface Verifier<
ID extends DID = DID,
SigAlg extends Crypto.SigAlg = Crypto.SigAlg
> extends Crypto.Verifier<SigAlg>,
Principal<ID> {}
export interface Audience extends Principal {}
export interface Issuer extends Principal {}
/**
* Entity that can sign UCANs with keys from a {@link Principal} using the
* signing algorithm A
*/
export interface Signer<
ID extends DID = DID,
SigAlg extends Crypto.SigAlg = Crypto.SigAlg
> extends Crypto.Signer<SigAlg>,
Principal<ID> {
/**
* Integer corresponding to the byteprefix of the {@link Crypto.SigAlg}. It
* is used to tag [signature] so it can self describe what algorithm was used.
*
* [signature]:https://github.com/ucan-wg/ucan-ipld/#25-signature
*/
signatureCode: SigAlg
/**
* Name of the signature algorithm. It is a human readable equivalent of
* the {@link signatureCode}, however it is also used as last segment in
* [Nonstandard Signatures], which is used as an `alg` field of JWT header
* when UCANs are serialized to JWT.
*
* [Nonstandard Signatures]:https://github.com/ucan-wg/ucan-ipld/#251-nonstandard-signatures
*/
signatureAlgorithm: string
}
/**
* Verifiable facts and proofs of knowledge included in a UCAN {@link JWTPayload} in order to
* support claimed capabilities.
* @see https://github.com/ucan-wg/spec/#324-facts
*/
export type Fact = Record<string, unknown>
/**
* The version of the UCAN spec used to produce a specific UCAN.
*/
export type Version = `${number}.${number}.${number}`
/**
* A UCAN header, in the format used by the JWT encoding.
* @see https://github.com/ucan-wg/spec/#31-header
*/
export interface JWTHeader {
ucv: Version
alg: "EdDSA" | "RS256" | string
typ: "JWT"
}
/**
* A UCAN payload, in the format used by the JWT encoding.
* @see https://github.com/ucan-wg/spec/#32-payload
*/
export interface JWTPayload<C extends Capabilities = Capabilities> {
iss: DID
aud: DID
exp: UTCUnixTimestamp | null
att: C
nnc?: Nonce
nbf?: UTCUnixTimestamp
fct?: Fact[]
prf?: ToString<Link>[]
}
/**
* Represents a UCAN encoded as a JWT string.
*/
export type JWT<C extends Capabilities = Capabilities> = ToString<
UCAN<C>,
`${ToString<JWTHeader>}.${ToString<
JWTPayload<C>
>}.${ToString<Crypto.Signature>}`
>
export interface Header {
v: Version
}
export interface Payload<C extends Capabilities = Capabilities> {
iss: Issuer
aud: Audience
att: C
exp: UTCUnixTimestamp | null
nbf?: UTCUnixTimestamp
nnc?: Nonce
fct: Fact[]
prf: Link[]
}
/**
* IPLD representation of a signed UCAN.
*/
export interface Model<C extends Capabilities = Capabilities>
extends Header,
Payload<C> {
s: Crypto.SignatureView
}
export type UCANJSON<T extends UCAN = UCAN> = ToJSON<
T,
{
v: Version
iss: DID
aud: DID
s: Crypto.SignatureJSON
att: ToJSON<T["att"]>
prf: { "/": ToString<Link> }[]
exp: UTCUnixTimestamp
fct?: ToJSON<T["fct"]>
nnc?: Nonce
nbf?: UTCUnixTimestamp
}
>
export interface FromJWT<C extends Capabilities = Capabilities>
extends Model<C> {
jwt: JWT<C>
}
export interface FromModel<C extends Capabilities = Capabilities>
extends Model<C> {
jwt?: never
}
/**
* A signed UCAN in either IPLD or JWT format.
*/
export type UCAN<C extends Capabilities = Capabilities> =
| FromJWT<C>
| FromModel<C>
/**
* Represents a decoded "view" of a UCAN as a JS object that can be used in your domain logic, etc.
*/
export interface View<C extends Capabilities = Capabilities> extends Model<C> {
readonly code: Code
readonly model: Model<C>
readonly issuer: PrincipalView
readonly audience: PrincipalView
readonly version: Version
readonly capabilities: C
readonly expiration: UTCUnixTimestamp
readonly notBefore?: UTCUnixTimestamp
readonly nonce?: Nonce
readonly facts: Fact[]
readonly proofs: Link[]
readonly signature: Crypto.SignatureView
encode(): ByteView<UCAN<C>>
format(): JWT<C>
toJSON(): UCANJSON<this>
}
/**
* Options used when issuing a new UCAN.
*
* @template C - {@link Capability}
* @template A - Signing algorithm
*/
export interface UCANOptions<
C extends Capabilities = Capabilities,
SigAlg extends Crypto.SigAlg = Crypto.SigAlg
> {
issuer: Signer<DID, SigAlg>
audience: Audience
capabilities: C
lifetimeInSeconds?: number
expiration?: UTCUnixTimestamp
notBefore?: UTCUnixTimestamp
nonce?: Nonce
facts?: Fact[]
proofs?: Link[]
}
/**
* Represents an IPLD link to a UCAN in either IPLD or JWT format
*
* @template Cap - {@link Capability}
* @template Encoding - multicodec code corresponding to the encoding
* @template SigAlg - multicodec code corresponding to the hashing algorithm of the CID
*/
export interface Link<
C extends Capabilities = Capabilities,
Encoding extends MulticodecCode = MulticodecCode,
SigAlg extends Crypto.SigAlg = Crypto.SigAlg
> extends IPLDLink<UCAN<C>, Encoding, SigAlg> {}
/**
* Represents a UCAN IPLD block
*
* Note: once we change the Capability generic to an array we can merge this with ucanto transport block
*
* @template C - {@link Capabilities}
* @template Encoding - multicodec code corresponding to the encoding
* @template SigAlg - Multicodec code corresponding to the hashing algorithm of the {@link Link}
*/
export interface Block<
C extends Capabilities = Capabilities,
Encoding extends MulticodecCode = MulticodecCode,
SigAlg extends Crypto.SigAlg = Crypto.SigAlg
> extends IPLDBlock<UCAN<C>, Encoding, SigAlg> {
data?: UCAN<C>
}
/**
* A string that represents some action that a UCAN holder can do.
*/
export type Ability = `${string}/${string}` | "*"
/**
* A string that represents resource a UCAN holder can act upon.
*/
export type Resource = `${string}:${string}`
/**
* Represents an {@link Ability} that a UCAN holder `Can` perform `With` some {@link Resource}.
*
* @template Can - the {@link Ability} (action/verb) the UCAN holder can perform
* @template With - the {@link Resource} (thing/noun) the UCAN holder can perform their `Ability` on / with
*
*/
export interface Capability<
Can extends Ability = Ability,
With extends Resource = Resource,
Caveats extends unknown = unknown
> {
with: With
can: Can
nb?: Caveats
}
export type Capabilities = Tuple<Capability>
/**
* Utility type that retains type information about data of type `In`, encoded
* as type `Out`.
*
* For concrete examples see {@link ToString} and {@link ToJSONString}.
*/
export type Encoded<In, Out> = Out & Phantom<In>
/**
* Data of some type `In`, encoded as a string.
*/
export type ToString<In, Out extends string = string> = Encoded<In, Out>
/**
* Data of some type `In`, encoded as a JSON string.
*/
export type ToJSONString<In, Out extends string = string> = Encoded<In, Out>
export type JSONScalar = null | boolean | number | string
export type JSONObject = {
[key: string]: JSONUnknown | Phantom<unknown>
}
export type JSONUnknown = JSONScalar | JSONObject | JSONUnknown[]
/**
* JSON representation
*/
export type ToJSON<In, Out extends JSONUnknown = IntoJSON<In>> = Encoded<
In,
Out
>
export type IntoJSON<T> = T extends JSONScalar
? T
: T extends { toJSON(): infer U }
? IntoJSON<U>
: T extends Array<infer U>
? IntoJSON<U>[]
: T extends JSONObject
? IntoJSONObject<T>
: never
export type IntoJSONObject<T extends JSONObject> = {
[K in keyof T]: IntoJSON<T[K]>
}
/**
* [Multicodec code] usually used to tag [multiformat].
*
* [multiformat]:https://multiformats.io/
* [multicodec code]:https://github.com/multiformats/multicodec/blob/master/table.csv
*/
export type MulticodecCode<
Code extends number = number,
Name extends string = string
> = Code & Phantom<Name>
/**
* UTC Unix Timestamp
*/
export type UTCUnixTimestamp = number
export type Nonce = string