feat(delegation): update to provide encoding/decoding straight from/to View

This commit is contained in:
Steve Moyer
2024-09-18 12:20:54 -04:00
parent baf3edcf88
commit f44b5cb921
8 changed files with 379 additions and 74 deletions

1
delegation/delegation.go Normal file
View File

@@ -0,0 +1 @@
package delegation

View File

@@ -0,0 +1,142 @@
package delegation_test
import (
"crypto/rand"
"testing"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/did"
)
const (
nonce = "6roDhGi0kiNriQAz7J3d+bOeoI/tj8ENikmQNbtjnD0"
AudiencePrivKeyCfg = "CAESQL1hvbXpiuk2pWr/XFbfHJcZNpJ7S90iTA3wSCTc/BPRneCwPnCZb6c0vlD6ytDWqaOt0HEOPYnqEpnzoBDprSM="
AudienceDID = "did:key:z6Mkq5YmbJcTrPExNDi26imrTCpKhepjBFBSHqrBDN2ArPkv"
issuerPrivKeyCfg = "CAESQLSql38oDmQXIihFFaYIjb73mwbPsc7MIqn4o8PN4kRNnKfHkw5gRP1IV9b6d0estqkZayGZ2vqMAbhRixjgkDU="
issuerDID = "did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2"
subjectPrivKeyCfg = "CAESQL9RtjZ4dQBeXtvDe53UyvslSd64kSGevjdNiA1IP+hey5i/3PfRXSuDr71UeJUo1fLzZ7mGldZCOZL3gsIQz5c="
subjectDID = "did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2"
subJectCmd = "/foo/bar"
subjectPol = `
[
[
"==",
".status",
"draft"
],
[
"all",
".reviewer",
[
"like",
".email",
"*@example.com"
]
],
[
"any",
".tags",
[
"or",
[
[
"==",
".",
"news"
],
[
"==",
".",
"press"
]
]
]
]
]
`
)
// func TestConstructors(t *testing.T) {
// t.Parallel()
// privKey := privKey(t, issuerPrivKeyCfg)
// aud, err := did.Parse(AudienceDID)
// sub, err := did.Parse(subjectDID)
// require.NoError(t, err)
// cmd, err := command.Parse(subJectCmd)
// require.NoError(t, err)
// pol, err := policy.FromDagJson(subjectPol)
// require.NoError(t, err)
// exp := time.Time{}
// meta := map[string]datamodel.Node{
// "foo": basicnode.NewString("fooo"),
// "bar": basicnode.NewString("barr"),
// }
// t.Run("New", func(t *testing.T) {
// dlg, err := delegation.New(privKey, aud, &sub, cmd, pol, []byte(nonce), delegation.WithExpiration(&exp), delegation.WithMeta(meta))
// require.NoError(t, err)
// data, err := dlg.ToDagJson()
// require.NoError(t, err)
// t.Log(string(data))
// golden.Assert(t, string(data), "new.dagjson")
// })
// t.Run("Root", func(t *testing.T) {
// t.Parallel()
// dlg, err := delegation.Root(privKey, aud, cmd, pol, []byte(nonce), delegation.WithExpiration(&exp), delegation.WithMeta(meta))
// require.NoError(t, err)
// data, err := dlg.ToDagJson()
// require.NoError(t, err)
// t.Log(string(data))
// golden.Assert(t, string(data), "root.dagjson")
// })
// }
func privKey(t *testing.T, privKeyCfg string) crypto.PrivKey {
t.Helper()
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
require.NoError(t, err)
privKey, err := crypto.UnmarshalPrivateKey(privKeyMar)
require.NoError(t, err)
return privKey
}
func TestKey(t *testing.T) {
t.Skip()
priv, _, err := crypto.GenerateEd25519Key(rand.Reader)
require.NoError(t, err)
privMar, err := crypto.MarshalPrivateKey(priv)
require.NoError(t, err)
privCfg := crypto.ConfigEncodeKey(privMar)
t.Log(privCfg)
id, err := did.FromPubKey(priv.GetPublic())
require.NoError(t, err)
t.Log(id)
t.Fail()
}

View File

@@ -1,33 +0,0 @@
package delegation
import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagcbor"
"github.com/ipld/go-ipld-prime/codec/dagjson"
)
func (p *PayloadModel) EncodeDagCbor() ([]byte, error) {
return ipld.Marshal(dagcbor.Encode, p, PayloadType())
}
func (p *PayloadModel) EncodeDagJson() ([]byte, error) {
return ipld.Marshal(dagjson.Encode, p, PayloadType())
}
func DecodeDagCbor(data []byte) (*PayloadModel, error) {
var p PayloadModel
_, err := ipld.Unmarshal(data, dagcbor.Decode, &p, PayloadType())
if err != nil {
return nil, err
}
return &p, nil
}
func DecodeDagJson(data []byte) (*PayloadModel, error) {
var p PayloadModel
_, err := ipld.Unmarshal(data, dagjson.Decode, &p, PayloadType())
if err != nil {
return nil, err
}
return &p, nil
}

168
delegation/ipld.go Normal file
View File

@@ -0,0 +1,168 @@
package delegation
import (
"io"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec"
"github.com/ipld/go-ipld-prime/codec/dagcbor"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/internal/envelope"
)
// Encode marshals a View to the format specified by the provided
// codec.Encoder.
func (d *View) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error) {
node, err := d.ToIPLD(privKey)
if err != nil {
return nil, err
}
return ipld.Encode(node, encFn)
}
// EncodeWriter is the same as Encode but accepts an io.Writer.
func (d *View) EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.Encoder) error {
node, err := d.ToIPLD(privKey)
if err != nil {
return err
}
return ipld.EncodeStreaming(w, node, encFn)
}
// ToDagCbor marshals the View to the DAG-CBOR format.
func (d *View) ToDagCbor(privKey crypto.PrivKey) ([]byte, error) {
return d.Encode(privKey, dagcbor.Encode)
}
// ToDagCborWriter is the same as ToDagCbor but it accepts an io.Writer.
func (d *View) ToDagCborWriter(w io.Writer, privKey crypto.PrivKey) error {
return d.EncodeWriter(w, privKey, dagcbor.Encode)
}
// ToDagJson marshals the View to the DAG-JSON format.
func (d *View) ToDagJson(privKey crypto.PrivKey) ([]byte, error) {
return d.Encode(privKey, dagjson.Encode)
}
// ToDagJsonWriter is the same as ToDagJson but it accepts an io.Writer.
func (d *View) ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error {
return d.EncodeWriter(w, privKey, dagjson.Encode)
}
// ToIPLD wraps the View in an IPLD datamodel.Node.
func (d *View) ToIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
var sub *string
if d.Subject != did.Undef {
s := d.Subject.String()
sub = &s
}
pol, err := d.Policy.ToIPLD()
if err != nil {
return nil, err
}
metaKeys := make([]string, len(d.Meta))
i := 0
for k := range d.Meta {
metaKeys[i] = k
i++
}
var nbf *int64
if d.NotBefore != nil {
u := d.NotBefore.Unix()
nbf = &u
}
var exp *int64
if d.Expiration != nil {
u := d.Expiration.Unix()
exp = &u
}
model := &PayloadModel{
Iss: d.Issuer.String(),
Aud: d.Audience.String(),
Sub: sub,
Cmd: d.Command.String(),
Pol: pol,
Nonce: d.Nonce,
Meta: MetaModel{
Keys: metaKeys,
Values: d.Meta,
},
Nbf: nbf,
Exp: exp,
}
return envelope.ToIPLD(privKey, model)
}
// Decode unmarshals the input data using the format specified by the
// provided codec.Decoder into a View.
//
// An error is returned if the conversion fails, or if the resulting
// View is invalid.
func Decode(b []byte, decFn codec.Decoder) (*View, error) {
node, err := ipld.Decode(b, decFn)
if err != nil {
return nil, err
}
return FromIPLD(node)
}
// DecodeReader is the same as Decode, but accept an io.Reader.
func DecodeReader(r io.Reader, decFn codec.Decoder) (*View, error) {
node, err := ipld.DecodeStreaming(r, decFn)
if err != nil {
return nil, err
}
return FromIPLD(node)
}
// FromDagCbor unmarshals the input data into a View.
//
// An error is returned if the conversion fails, or if the resulting
// View is invalid.
func FromDagCbor(data []byte) (*View, error) {
return Decode(data, dagcbor.Decode)
}
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
func FromDagCborReader(r io.Reader) (*View, error) {
return DecodeReader(r, dagcbor.Decode)
}
// FromDagJson unmarshals the input data into a View.
//
// An error is returned if the conversion fails, or if the resulting
// View is invalid.
func FromDagJson(data []byte) (*View, error) {
return Decode(data, dagjson.Decode)
}
// FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
func FromDagJsonReader(r io.Reader) (*View, error) {
return DecodeReader(r, dagjson.Decode)
}
// FromIPLD unwraps a View from the provided IPLD datamodel.Node
//
// An error is returned if the conversion fails, or if the resulting
// View is invalid.
func FromIPLD(node datamodel.Node) (*View, error) {
tkn, _, err := envelope.FromIPLD[*PayloadModel](node) // TODO add CID to view
if err != nil {
return nil, err
}
return ViewFromModel(*tkn)
}

View File

@@ -7,9 +7,13 @@ import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/ipld/go-ipld-prime/schema"
"github.com/ucan-wg/go-ucan/internal/envelope"
)
const Tag = "ucan/dlg@1.0.0-rc.1"
//go:embed delegation.ipldsch
var schemaBytes []byte
@@ -33,6 +37,8 @@ func PayloadType() schema.Type {
return mustLoadSchema().TypeByName("Payload")
}
var _ envelope.Tokener = (*PayloadModel)(nil)
type PayloadModel struct {
// Issuer DID (sender)
Iss string
@@ -63,6 +69,14 @@ type PayloadModel struct {
Exp *int64
}
func (e *PayloadModel) Prototype() schema.TypedPrototype {
return bindnode.Prototype((*PayloadModel)(nil), PayloadType())
}
func (*PayloadModel) Tag() string {
return Tag
}
type MetaModel struct {
Keys []string
Values map[string]datamodel.Node

View File

@@ -1,66 +1,76 @@
package delegation
package delegation_test
import (
_ "embed"
"fmt"
"testing"
"github.com/ipld/go-ipld-prime"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/delegation"
"gotest.tools/v3/golden"
)
//go:embed delegation.ipldsch
var schemaBytes []byte
func TestSchemaRoundTrip(t *testing.T) {
const delegationJson = `
{
"aud":"did:key:def456",
"cmd":"/foo/bar",
"exp":123456,
"iss":"did:key:abc123",
"meta":{
"bar":"baaar",
"foo":"fooo"
},
"nbf":123456,
"nonce":{
"/":{
"bytes":"c3VwZXItcmFuZG9t"
}
},
"pol":[
["==", ".status", "draft"],
["all", ".reviewer", [
["like", ".email", "*@example.com"]]
],
["any", ".tags", [
["or", [
["==", ".", "news"],
["==", ".", "press"]]
]]
]
],
"sub":""
}
`
// const delegationJson = `
// {
// "aud":"did:key:def456",
// "cmd":"/foo/bar",
// "exp":123456,
// "iss":"did:key:abc123",
// "meta":{
// "bar":"baaar",
// "foo":"fooo"
// },
// "nbf":123456,
// "nonce":{
// "/":{
// "bytes":"c3VwZXItcmFuZG9t"
// }
// },
// "pol":[
// ["==", ".status", "draft"],
// ["all", ".reviewer", [
// ["like", ".email", "*@example.com"]]
// ],
// ["any", ".tags", [
// ["or", [
// ["==", ".", "news"],
// ["==", ".", "press"]]
// ]]
// ]
// ],
// "sub":""
// }
// `
delegationJson := golden.Get(t, "new.dagjson")
privKey := privKey(t, issuerPrivKeyCfg)
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
// function: DecodeDagJson() EncodeDagCbor() DecodeDagCbor() EncodeDagJson()
p1, err := DecodeDagJson([]byte(delegationJson))
p1, err := delegation.FromDagJson([]byte(delegationJson))
require.NoError(t, err)
cborBytes, err := p1.EncodeDagCbor()
cborBytes, err := p1.ToDagCbor(privKey)
require.NoError(t, err)
fmt.Println("cborBytes length", len(cborBytes))
fmt.Println("cbor", string(cborBytes))
p2, err := DecodeDagCbor(cborBytes)
p2, err := delegation.FromDagCbor(cborBytes)
require.NoError(t, err)
fmt.Println("read Cbor", p2)
readJson, err := p2.EncodeDagJson()
readJson, err := p2.ToDagJson(privKey)
require.NoError(t, err)
fmt.Println("readJson length", len(readJson))
fmt.Println("json: ", string(readJson))
require.JSONEq(t, delegationJson, string(readJson))
require.JSONEq(t, string(delegationJson), string(readJson))
}
func BenchmarkSchemaLoad(b *testing.B) {

1
delegation/testdata/new.dagjson vendored Normal file
View File

@@ -0,0 +1 @@
[{"/":{"bytes":"P2lPLfdMuZuc4NPZ0mbozU+/bn5xoWlJsu+Fvaxi4ICYXVJb9/wiTTht3WJEFqjxXLxfTl4BMZF3J1CNvMPqBg"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6Mkq5YmbJcTrPExNDi26imrTCpKhepjBFBSHqrBDN2ArPkv","cmd":"/foo/bar","exp":-62135596800,"iss":"did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]],"sub":"did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2"}}]

View File

@@ -27,9 +27,9 @@ type View struct {
// Arbitrary Metadata
Meta map[string]datamodel.Node
// "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
NotBefore time.Time
NotBefore *time.Time
// The timestamp at which the Invocation becomes invalid
Expiration time.Time
Expiration *time.Time
}
// ViewFromModel build a decoded view of the raw IPLD data.
@@ -76,11 +76,13 @@ func ViewFromModel(m PayloadModel) (*View, error) {
view.Meta = m.Meta.Values
if m.Nbf != nil {
view.NotBefore = time.Unix(*m.Nbf, 0)
t := time.Unix(*m.Nbf, 0)
view.NotBefore = &t
}
if m.Exp != nil {
view.Expiration = time.Unix(*m.Exp, 0)
t := time.Unix(*m.Exp, 0)
view.Expiration = &t
}
return &view, nil