- Irakli Gozalishvili, DAG House
- Hugo Dias, DAG House
- Irakli Gozalishvili, DAG House
- Brooklyn Zelenka, Fission
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC2119.
Interplanetary Linked Data (IPLD) is a consistent, highly general data model for content addressed linked data forming any DAG. This data model provides a convenient pivot between many serializations of the same information. Being a chained token model, UCANs can be very naturally expressed via IPLD. However, JWTs are not fully deterministic due to whitespace and key ordering. This specification defines an IPLD Schema for UCAN, and a canonical JWT serialization for compatibility with all other clients.
The core UCAN specification is defined as a JWT to make it easily adoptable with existing tools, and as a common format for all implementations. There are many cases where different encodings are preferred for reasons such as compactness, machine-efficient formats, and so on. This specification outlines a format based on IPLD that can be deterministically encoded and decoded between many serialization formats, while still being able to encode as JWT for compatibility.
Unlike a JWT, the IPLD encoding of UCAN does not require separate header, claims, and signature fields.
type UCAN struct {
v String
iss Principal
aud Principal
s Signature
att [Capability]
-- All proofs are links, however you could still inline proof
-- by using CID with identity hashing algorithm
prf [&UCAN]
exp Int
fct [Fact]
nnc optional String
nbf optional Int
} representation map {
field fct default []
field prf default []
}
Principals MUST use the bytesprefix representation of DIDs. The primary did:key
method MUST use the relevant key-specific representation, such as 0xe7
and 0x1200
. Additional DID methods MAY use the extensible DID
representation 0x0d1d
.
type Principal union {
-- This specification defines principals in terms of `did:key`s (multicodec
-- identifiers for the public key type followed by the raw bytes of the key).
-- We represent those as raw public key bytes prefixed with public key
-- multiformat code.
| secp256k1 "0xe7"
| BLS12381G1 "0xea"
| BLS12381G2 "0xeb"
| Ed25519 "0xed"
| P256 "0x1200"
| P384 "0x1201"
| P512 "0x1202"
| RSA "0x1205"
-- To accomodate additional DID methods we represent those as UTF8 encoding
-- of the DID omitting `did:` prefix itself. E.g. `did:dns:ucan.xyz` can be
-- represented as [0x0d1d, ...new TextEncoder().encode('dns:ucan.xyz')] bytes.
| DID "0x0d1d"
} representation bytesprefix
type Capability struct {
with Resource
can Ability
nb optional NonNormativeFields
}
The resource pointer MUST be formatted as a URI.
type Resource = String
Abilities MUST be lowercase, and MUST be namespaced with /
delimeters. Abilities SHOULD have at least one path segment.
type Ability = String
The nb
capability field is OPTIONAL. When present, it MUST contain any additional domain specific details and/or restrictions of the capability.
type NonNormativeFields = { String: Any }
Facts MUST be structured as maps with string keys.
type Fact { String: Any }
As of UCAN 0.9, all proofs MUST be given as links (CIDs). Note that UCANs MAY be inlined via the identity multihash (0x00
) CID.
&UCAN
The signature MUST be computed by first encoding it as a canonical JWT, and then signed with the issuer's private key. Signatures MUST be encoded as the varsig multiformat representation <varint sig_alg_code><vairint sig_size><bytes sig_output>
. Varsig multiformat codes SHOULD be registered multicodec codes. Unregistered signature types MAY be added via the NonStandardSignature
prefix.
type Signature union {
-- Algorithms here are expected to be valid "varsig" multiformat codes.
| NonStandard "0xd000"
| ES256K "0xd0e7" -- secp256k1
| BLS12381G1 "0xd0ea" -- NB: G1 is a signature for BLS-12381 G2
| BLS12381G2 "0xd0eb" -- NB: G2 is a signature for BLS-12381 G1
| EdDSA "0xd0ed"
| ES256 "0xd01200"
| ES384 "0xd01201"
| ES512 "0xd01202"
| RS256 "0xd01205"
| EIP191 "0xd191"
} representation bytesprefix
Nonstandard signature algorithms MAY be used, but are NOT RECOMMENDED as they are not widely interoperable.
The 0xd000
prefix MUST be used if a nonstandard signature is used. Nonstandard signatures MUST be additionally prefixed with a varint of the signature length, and the UTF-8 UCAN alg
field appended afterwards.
<varint 0xd000><varint sig_size><bytes sig><utf8 alg>`
Per the core UCAN spec, all implementations MUST support JWT encoding. This provides a common representation that all implementations can understand. JWT canonicalization allows for an IPLD UCAN to be expressed as a JWT, retain the JWT signature scheme, and so on for compatibility, while retaining the ability to translate into other formats for storage or transmission among IPLD-enabled peers.
To canonicalize an IPLD UCAN to JWT, the JSON segments MUST fulfill the following requirements:
- All
can
fields MUST be lowercase - All unused optional fields (such as
fct
) that are empty MUST be omitted dag-json
encoding MUST be used- The resulting JWT MUST be base64url encoded per [RFC 7519]
- All segments MUST be joined with
.
s, per RFC 7519
UCAN canonicalization is signalled by CID. If no canonicalization is used, the CID MUST use the raw multicodec. Canonicalized UCANs that wish to signal this encoding MUST use any other CID codec, including but not limited to dag-json
and dag-cbor
.
Validators that have not implemented this specification MUST be provided JWT-encoded UCANs. These validators will be unable to validate the CID in the proofs field. This is not strictly a problem in a semi-trusted scenario, as UCAN only depends on the existence (not the specific CID) of a valid proof for the capabilities being claimed. The security risk is for a malicious peer to provide very long but ultimately invalid proof chains as a denial-of-service vector. This is the case for any validator that does not check the CID hash upon receipt.
Many thanks to Joel Thorstensson and Sergey Ukustov at 3Box Labs for their feedback on the encoding, and considerations on how it would interact with SIWE.
Thanks to Philipp Krüger for his feedback on canonicalization signalling.