UCAN 1.0.0 delegations and invocations, offline

README.md

ucan #

User Controlled Authorization Network (UCAN) 1.0.0 for OCaml, offline.

ucan reads, writes and validates UCAN 1.0.0 tokens: the DAG-CBOR envelope with its varsig header, delegations with their commands and policy language, invocations, and the validation of an invocation's proof chain against its subject. Principals are did:key over Ed25519, P-256 and secp256k1, the three signature keys of UCAN's cryptosuite, and every token is named by its CIDv1 (DAG-CBOR, SHA-256). Nothing reaches the network: proofs are resolved by CID from what the caller holds.

Example #

A delegation's policy constrains the arguments of the invocations it proves:

# let policy =
    Ucan.Policy.of_ipld
      (`List
        [ `List [ `String "=="; `String ".from"; `String "alice@example.com" ];
          `List
            [ `String "any"; `String ".to";
              `List [ `String "like"; `String "."; `String "*@example.com" ] ] ])
    |> Result.get_ok;;
val policy : Ucan.Policy.t = <abstr>
# Ucan.Policy.validate policy
    ~args:
      (`Map
        [ ("from", `String "alice@example.com");
          ("to", `List [ `String "bob@example.com" ]) ]);;
- : bool = true

A command proves itself and the commands under it:

# let command s = Result.get_ok (Ucan.Command.of_string s);;
val command : string -> Ucan.Command.t = <fun>
# Ucan.Command.proves (command "/crypto") (command "/crypto/sign");;
- : bool = true
# Ucan.Command.proves (command "/crypto") (command "/cryptocurrency");;
- : bool = false

Ucan.Chain.validate checks an invocation envelope against the delegations its prf field names, at a given time, and returns the chain from the root.

Interoperability #

The tests replay tokens written by two other implementations:

  • iso-ucan 2.0.0, from the delegation, policy and proof-chain vectors of hugomrdias/iso-repo at commit 0bd37d98d827, all signed with Ed25519, and one ES256K delegation iso-ucan signs with its secp256k1 signer between two keys of the did:key test vectors. This library reproduces both signatures byte for byte, so the bytes and the CID of either delegation are the oracle's. They carry the 1.0.0 tags ucan/dlg@1.0.0 and ucan/inv@1.0.0.
  • go-ucan v1.2.0, the latest tag, pinned in the generator's go.mod: one delegation and one invocation sealed by its public API, with the CIDs it computes. go-ucan still tags its tokens ucan/dlg@1.0.0-rc.1 and ucan/inv@1.0.0-rc.1. The tag is the only difference. Both implementations write the same 8-byte varsig header (3401ed01ed011371, Ed25519 over DAG-CBOR) and the same payload fields with the same encoding. Re-signed by the same key under go-ucan's tag, the delegation this library writes for go-ucan's fields matches go-ucan's bytes exactly.

Ucan.Envelope.of_bytes therefore reads a 1.0.0-rc.1 token as a 1.0.0 token. It keeps the tag the token was signed under, so a re-encode gives the same bytes and the same CID, and Ucan.Envelope.final_tag returns the 1.0.0 tag. Tokens are only ever written under the 1.0.0 tags, and every other version is refused.

An ES256K signature verifies only in its low-S form, which iso-ucan's and go-ucan's signers both write and iso-ucan alone requires; the other form would name the same delegation by a second CID.

  • iso-ucan, the JavaScript implementation whose test vectors this library replays.
  • go-ucan and rs-ucan, the working group's own implementations.

References #