- OCaml 97%
- Go 2%
- Shell 1%
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-repoat commit0bd37d98d827, 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 tagsucan/dlg@1.0.0anducan/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 tokensucan/dlg@1.0.0-rc.1anducan/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.
Related work #
- iso-ucan, the JavaScript implementation whose test vectors this library replays.
- go-ucan and rs-ucan, the working group's own implementations.