DAG JOSE week one update
13 Sep 2020This has been the first week I’ve worked on the dag-jose implementation for Go (which I introduced here). I’ve been focusing on exploring the go-ipld-prime codebase and building a proof of concept implementation of the dag-jose format. I’ve build a basic library which reads JWS’s written by the javascript implementation and verifies them, and writes JWS’s which the javascript implementation is able to read and verify.
This proof of concept implementation can be found in this repo. The README and comments there go into more detail if you’re interested.
I’m going to briefly walk through the API which looks most promising, this will necessarily include some explanation of what go-ipld-prime
is and how it works. Note that I am still getting to grips with the go-ipld-prime
codebase so if something looks incorrect or weird it is most likely a mistake on my part.
Registering format encoders and decoders
Encoders and decoders are registered in a global registry in the cidlink
package. We register the dag-jose
encoder and decoder in the init
function of the dagjose
module. That means that you just nead to import dagjose
somewhere in your program to get the encoder and decoder registered.
In practice the encoder and decoder are effectively the same as the dag-cbor
encoder and decoder, most of the work in this library is in implementing ipld.NodeBuilder
and ipld.Node
.
Reading JWS objects
go-ipld-prime
is where all new development on IPLD in Go is happening. It’s completely agnostic about how you retrieve data, the API revolves around creating a ipld.Link
and then calling Link.Load(..)
to retrieve the ipld.Node
the link points to; in practice the link will be a CID. Link.Load
takes an ipld.NodeBuilder
as an argument, this is an interface which knows how to create an ipld.Node
from the data the Link
points to. This means that the type of NodeBuilder
you pass to Load
determines the underlying type of Node
you will get back.
Given all this there are two extension points we will need to provide in order to load JWS objects. We will need some data structure which implements ipld.Node
to return from the Link.Load(..)
and we will need an implementation of ipld.NodeBuilder
which creates that Node
implementation. To make this concrete, this is what that API currently looks like:
import (
"dagjoseroundtrip/dagjose" // Name of experimental dagjose package, importing registers the encoder and decoder
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
)
// Here we're creating a `CID` which points to a JWS written with the javascript implementation
jwsCid, err := cid.Decode(
"bagcqcerafzecmo67npzj56gfyukh3tbwxywgfsmxirr4oq57s6sddou4p5dq"
)
if err != nil {
panic(err)
}
// cidlink.Link is an implementation of `ipld.Link` backed by a CID
jwsLnk := cidlink.Link{Cid: jwsCid}
// This is the `NodeBuilder` which knows how to build a `dagjose.DagJOSE` object
builder := dagjose.NewBuilder()
err = lnk.Load(
context.Background(),
ipld.LinkContext{},
builder,
<an implementation of ipld.Loader, which knows how to get the block data from IPFS>,
)
if err != nil {
panic(err)
}
n := builder.Build()
jwsNode := n.(*dagjose.DagJOSE), nil
This dagjose.DagJOSE
object currently provides a single method: GeneralJSONSerialization
which returns the JSON serialization of the JOSE object as a string, this can be used to interface with the go-jose
library, like so:
privateKey := <obtain an instance of gojose.PrivateKey>
sig, err := gojose.ParseSigned(jwsNode.GeneralJSONSerialization())
if err != nil {
panic(err)
}
verifiedPayload, err := sig.Verify(privateKey.Public())
if err != nil {
panic(err)
}
Writing JWS objects
Writing JWS objects is handled almost entirely by the ipld.Node
implementation. ipld.Node
is a general interface which describes what any datum in the IPLD data model can look like. By implementing ipld.Node
for dagjose.DagJOSE
we can then just pass the dagjose.DagJOSE
to the dag-cbor
encoder, which knows how to write an ipld.Node
out to CBOR. This means that to write a JWS you do this:
dagJws, err := dagjose.NewDagJWS("<the general JSON serialization of a JWS>")
if err != nil {
panic(err)
}
linkBuilder := cidlink.LinkBuilder{Prefix: cid.Prefix{
Version: 1, // Usually '1'.
Codec: 0x85, // 0x71 means "dag-jose" -- See the multicodecs table: https://github.com/multiformats/multicodec/
MhType: 0x15, // 0x15 means "sha3-384" -- See the multicodecs table: https://github.com/multiformats/multicodec/
MhLength: 48, // sha3-224 hash has a 48-byte sum.
}}
link, err := linkBuilder.Build(
context.Background(),
ipld.LinkContext{},
dagJws,
<an implementation of `ipld.Storer` which knows how to store the raw block data>
)
if err != nil {
panic(err)
}
By specifying 0x85
as the multicodec of the link we are creating we tell cidlink
that it needs to use the encoder we have registered, and the encoder we have registered just passes through to the dag-cbor
encoder, which works because we have implemented ipld.Node
for the dagjws.DagJOSE
struct.
Next Steps
I’ve got a working interop test with the javascript codebase, but currently the implementation is very exploratory and contains several hacks. The next step is to pull this exploratory code together into a proper library. We also need to update the specification to reflect some decisions we have made about exactly how to represent JOSE objects in IPLD, I will be working to get those updates included in the specification PR
That’s all for now!