Merge branch 'v1' into v1-policy-subset-selection
This commit is contained in:
@@ -1,82 +0,0 @@
|
|||||||
package delegation_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"gotest.tools/v3/golden"
|
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/delegation"
|
|
||||||
)
|
|
||||||
|
|
||||||
//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":""
|
|
||||||
// }
|
|
||||||
// `
|
|
||||||
|
|
||||||
delegationJson := golden.Get(t, "new.dagjson")
|
|
||||||
privKey := privKey(t, issuerPrivKeyCfg)
|
|
||||||
|
|
||||||
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
|
|
||||||
// function: DecodeDagJson() EncodeDagCbor() DecodeDagCbor() EncodeDagJson()
|
|
||||||
|
|
||||||
p1, err := delegation.FromDagJson([]byte(delegationJson))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cborBytes, err := p1.ToDagCbor(privKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
fmt.Println("cborBytes length", len(cborBytes))
|
|
||||||
fmt.Println("cbor", string(cborBytes))
|
|
||||||
|
|
||||||
p2, err := delegation.FromDagCbor(cborBytes)
|
|
||||||
require.NoError(t, err)
|
|
||||||
fmt.Println("read Cbor", p2)
|
|
||||||
|
|
||||||
readJson, err := p2.ToDagJson(privKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
fmt.Println("readJson length", len(readJson))
|
|
||||||
fmt.Println("json: ", string(readJson))
|
|
||||||
|
|
||||||
require.JSONEq(t, string(delegationJson), string(readJson))
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSchemaLoad(b *testing.B) {
|
|
||||||
b.ReportAllocs()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, _ = ipld.LoadSchemaBytes(schemaBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
go.mod
2
go.mod
@@ -10,6 +10,7 @@ require (
|
|||||||
github.com/libp2p/go-libp2p v0.36.3
|
github.com/libp2p/go-libp2p v0.36.3
|
||||||
github.com/multiformats/go-multibase v0.2.0
|
github.com/multiformats/go-multibase v0.2.0
|
||||||
github.com/multiformats/go-multicodec v0.9.0
|
github.com/multiformats/go-multicodec v0.9.0
|
||||||
|
github.com/multiformats/go-multihash v0.2.3
|
||||||
github.com/multiformats/go-varint v0.0.7
|
github.com/multiformats/go-varint v0.0.7
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
gotest.tools/v3 v3.5.1
|
gotest.tools/v3 v3.5.1
|
||||||
@@ -24,7 +25,6 @@ require (
|
|||||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||||
github.com/multiformats/go-base32 v0.1.0 // indirect
|
github.com/multiformats/go-base32 v0.1.0 // indirect
|
||||||
github.com/multiformats/go-base36 v0.2.0 // indirect
|
github.com/multiformats/go-base36 v0.2.0 // indirect
|
||||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/polydawn/refmt v0.89.0 // indirect
|
github.com/polydawn/refmt v0.89.0 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/command"
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTop(t *testing.T) {
|
func TestTop(t *testing.T) {
|
||||||
@@ -2,12 +2,16 @@ package meta
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrUnsupported = errors.New("failure adding unsupported type to meta")
|
||||||
|
|
||||||
var ErrNotFound = errors.New("key-value not found in meta")
|
var ErrNotFound = errors.New("key-value not found in meta")
|
||||||
|
|
||||||
// Meta is a container for meta key-value pairs in a UCAN token.
|
// Meta is a container for meta key-value pairs in a UCAN token.
|
||||||
@@ -113,8 +117,20 @@ func (m *Meta) Add(key string, val any) error {
|
|||||||
case datamodel.Node:
|
case datamodel.Node:
|
||||||
m.Values[key] = val
|
m.Values[key] = val
|
||||||
default:
|
default:
|
||||||
panic("invalid value type")
|
return fmt.Errorf("%w: %s", ErrUnsupported, fqtn(val))
|
||||||
}
|
}
|
||||||
m.Keys = append(m.Keys, key)
|
m.Keys = append(m.Keys, key)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fqtn(val any) string {
|
||||||
|
var name string
|
||||||
|
|
||||||
|
t := reflect.TypeOf(val)
|
||||||
|
for t.Kind() == reflect.Pointer {
|
||||||
|
name += "*"
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
return name + t.PkgPath() + "." + t.Name()
|
||||||
|
}
|
||||||
|
|||||||
23
pkg/meta/meta_test.go
Normal file
23
pkg/meta/meta_test.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package meta_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMeta_Add(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type Unsupported struct{}
|
||||||
|
|
||||||
|
t.Run("error if not primative or Node", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := (&meta.Meta{}).Add("invalid", &Unsupported{})
|
||||||
|
require.ErrorIs(t, err, meta.ErrUnsupported)
|
||||||
|
assert.ErrorContains(t, err, "*github.com/ucan-wg/go-ucan/pkg/meta_test.Unsupported")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime/must"
|
"github.com/ipld/go-ipld-prime/must"
|
||||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FromIPLD(node datamodel.Node) (Policy, error) {
|
func FromIPLD(node datamodel.Node) (Policy, error) {
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
"github.com/ipld/go-ipld-prime/must"
|
"github.com/ipld/go-ipld-prime/must"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Policy) Filter(sel selector.Selector) Policy {
|
func (p Policy) Filter(sel selector.Selector) Policy {
|
||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy/literal"
|
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMatch(t *testing.T) {
|
func TestMatch(t *testing.T) {
|
||||||
@@ -5,7 +5,7 @@ package policy
|
|||||||
import (
|
import (
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy/selector"
|
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestSupported Forms runs tests against the Selector according to the
|
// TestSupported Forms runs tests against the Selector according to the
|
||||||
@@ -1,19 +1,30 @@
|
|||||||
|
// Package delegation implements the UCAN [delegation] specification with
|
||||||
|
// an immutable Token type as well as methods to convert the Token to and
|
||||||
|
// from the [envelope]-enclosed, signed and DAG-CBOR-encoded form that
|
||||||
|
// should most commonly be used for transport and storage.
|
||||||
|
//
|
||||||
|
// [delegation]: https://github.com/ucan-wg/delegation/tree/v1_ipld
|
||||||
|
// [envelope]: https://github.com/ucan-wg/spec#envelope
|
||||||
package delegation
|
package delegation
|
||||||
|
|
||||||
|
// TODO: change the "delegation" link above when the specification is merged
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/command"
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy"
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Token is an immutable type that holds the fields of a UCAN delegation.
|
||||||
type Token struct {
|
type Token struct {
|
||||||
// Issuer DID (sender)
|
// Issuer DID (sender)
|
||||||
issuer did.DID
|
issuer did.DID
|
||||||
@@ -33,6 +44,8 @@ type Token struct {
|
|||||||
notBefore *time.Time
|
notBefore *time.Time
|
||||||
// The timestamp at which the Invocation becomes invalid
|
// The timestamp at which the Invocation becomes invalid
|
||||||
expiration *time.Time
|
expiration *time.Time
|
||||||
|
// The CID of the Token when enclosed in an Envelope and encoded to DAG-CBOR
|
||||||
|
cid cid.Cid
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a validated Token from the provided parameters and options.
|
// New creates a validated Token from the provided parameters and options.
|
||||||
@@ -50,6 +63,7 @@ func New(privKey crypto.PrivKey, aud did.DID, cmd command.Command, pol policy.Po
|
|||||||
policy: pol,
|
policy: pol,
|
||||||
meta: meta.NewMeta(),
|
meta: meta.NewMeta(),
|
||||||
nonce: nil,
|
nonce: nil,
|
||||||
|
cid: cid.Undef,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
@@ -132,6 +146,13 @@ func (t *Token) Expiration() *time.Time {
|
|||||||
return t.expiration
|
return t.expiration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CID returns the content identifier of the Token model when enclosed
|
||||||
|
// in an Envelope and encoded to DAG-CBOR.
|
||||||
|
// Returns cid.Undef if the token has not been serialized or deserialized yet.
|
||||||
|
func (t *Token) CID() cid.Cid {
|
||||||
|
return t.cid
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Token) validate() error {
|
func (t *Token) validate() error {
|
||||||
var errs error
|
var errs error
|
||||||
|
|
||||||
@@ -151,70 +172,6 @@ func (t *Token) validate() error {
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option func(*Token) error
|
|
||||||
|
|
||||||
// WithExpiration set's the Token's optional "expiration" field to the
|
|
||||||
// value of the provided time.Time.
|
|
||||||
func WithExpiration(exp time.Time) Option {
|
|
||||||
return func(t *Token) error {
|
|
||||||
if exp.Before(time.Now()) {
|
|
||||||
return fmt.Errorf("a Token's expiration should be set to a time in the future: %s", exp.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.expiration = &exp
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMeta adds a key/value pair in the "meta" field.
|
|
||||||
// WithMeta can be used multiple times in the same call.
|
|
||||||
// Accepted types for the value are: bool, string, int, int32, int64, []byte,
|
|
||||||
// and ipld.Node.
|
|
||||||
func WithMeta(key string, val any) Option {
|
|
||||||
return func(t *Token) error {
|
|
||||||
return t.meta.Add(key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNotBefore set's the Token's optional "notBefore" field to the value
|
|
||||||
// of the provided time.Time.
|
|
||||||
func WithNotBefore(nbf time.Time) Option {
|
|
||||||
return func(t *Token) error {
|
|
||||||
if nbf.Before(time.Now()) {
|
|
||||||
return fmt.Errorf("a Token's \"not before\" field should be set to a time in the future: %s", nbf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.notBefore = &nbf
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSubject sets the Tokens's optional "subject" field to the value of
|
|
||||||
// provided did.DID.
|
|
||||||
//
|
|
||||||
// This Option should only be used with the New constructor - since
|
|
||||||
// Subject is a required parameter when creating a Token via the Root
|
|
||||||
// constructor, any value provided via this Option will be silently
|
|
||||||
// overwritten.
|
|
||||||
func WithSubject(sub did.DID) Option {
|
|
||||||
return func(t *Token) error {
|
|
||||||
t.subject = sub
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNonce sets the Token's nonce with the given value.
|
|
||||||
// If this option is not used, a random 12-byte nonce is generated for this required field.
|
|
||||||
func WithNonce(nonce []byte) Option {
|
|
||||||
return func(t *Token) error {
|
|
||||||
t.nonce = nonce
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tokenFromModel build a decoded view of the raw IPLD data.
|
// tokenFromModel build a decoded view of the raw IPLD data.
|
||||||
// This function also serves as validation.
|
// This function also serves as validation.
|
||||||
func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
||||||
@@ -277,6 +234,7 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generateNonce creates a 12-byte random nonce.
|
// generateNonce creates a 12-byte random nonce.
|
||||||
|
// TODO: some crypto scheme require more, is that our case?
|
||||||
func generateNonce() ([]byte, error) {
|
func generateNonce() ([]byte, error) {
|
||||||
res := make([]byte, 12)
|
res := make([]byte, 12)
|
||||||
_, err := rand.Read(res)
|
_, err := rand.Read(res)
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package delegation_test
|
package delegation_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -9,10 +8,10 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/command"
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy"
|
|
||||||
"github.com/ucan-wg/go-ucan/delegation"
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||||
|
"github.com/ucan-wg/go-ucan/tokens/delegation"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -64,6 +63,9 @@ const (
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
`
|
`
|
||||||
|
|
||||||
|
newCID = "zdpuAn9JgGPvnt2WCmTaKktZdbuvcVGTg9bUT5kQaufwUtZ6e"
|
||||||
|
rootCID = "zdpuAkgGmUp5JrXvehGuuw9JA8DLQKDaxtK3R8brDQQVC2i5X"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConstructors(t *testing.T) {
|
func TestConstructors(t *testing.T) {
|
||||||
@@ -86,7 +88,7 @@ func TestConstructors(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("New", func(t *testing.T) {
|
t.Run("New", func(t *testing.T) {
|
||||||
dlg, err := delegation.New(privKey, aud, cmd, pol,
|
tkn, err := delegation.New(privKey, aud, cmd, pol,
|
||||||
delegation.WithNonce([]byte(nonce)),
|
delegation.WithNonce([]byte(nonce)),
|
||||||
delegation.WithSubject(sub),
|
delegation.WithSubject(sub),
|
||||||
delegation.WithExpiration(exp),
|
delegation.WithExpiration(exp),
|
||||||
@@ -95,7 +97,7 @@ func TestConstructors(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data, err := dlg.ToDagJson(privKey)
|
data, err := tkn.ToDagJson(privKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Log(string(data))
|
t.Log(string(data))
|
||||||
@@ -106,7 +108,7 @@ func TestConstructors(t *testing.T) {
|
|||||||
t.Run("Root", func(t *testing.T) {
|
t.Run("Root", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dlg, err := delegation.Root(privKey, aud, cmd, pol,
|
tkn, err := delegation.Root(privKey, aud, cmd, pol,
|
||||||
delegation.WithNonce([]byte(nonce)),
|
delegation.WithNonce([]byte(nonce)),
|
||||||
delegation.WithExpiration(exp),
|
delegation.WithExpiration(exp),
|
||||||
delegation.WithMeta("foo", "fooo"),
|
delegation.WithMeta("foo", "fooo"),
|
||||||
@@ -114,7 +116,7 @@ func TestConstructors(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data, err := dlg.ToDagJson(privKey)
|
data, err := tkn.ToDagJson(privKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Log(string(data))
|
t.Log(string(data))
|
||||||
@@ -123,9 +125,7 @@ func TestConstructors(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func privKey(t *testing.T, privKeyCfg string) crypto.PrivKey {
|
func privKey(t require.TestingT, privKeyCfg string) crypto.PrivKey {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
|
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -134,23 +134,3 @@ func privKey(t *testing.T, privKeyCfg string) crypto.PrivKey {
|
|||||||
|
|
||||||
return privKey
|
return privKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKey(t *testing.T) {
|
|
||||||
// TODO: why is this broken?
|
|
||||||
t.Skip("TODO: why is this broken?")
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ package delegation
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/codec"
|
"github.com/ipld/go-ipld-prime/codec"
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
@@ -11,13 +12,80 @@ import (
|
|||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ToSealed wraps the delegation token in an envelope, generates the
|
||||||
|
// signature, encodes the result to DAG-CBOR and calculates the CID of
|
||||||
|
// the resulting binary data.
|
||||||
|
func (t *Token) ToSealed(privKey crypto.PrivKey) ([]byte, cid.Cid, error) {
|
||||||
|
data, err := t.ToDagCbor(privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := envelope.CIDFromBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSealedWriter is the same as Seal but accepts an io.Writer.
|
||||||
|
func (t *Token) ToSealedWriter(w io.Writer, privKey crypto.PrivKey) (cid.Cid, error) {
|
||||||
|
cidWriter := envelope.NewCIDWriter(w)
|
||||||
|
|
||||||
|
if err := t.ToDagCborWriter(cidWriter, privKey); err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cidWriter.CID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromSealed decodes the provided binary data from the DAG-CBOR format,
|
||||||
|
// verifies that the envelope's signature is correct based on the public
|
||||||
|
// key taken from the issuer (iss) field and calculates the CID of the
|
||||||
|
// incoming data.
|
||||||
|
func FromSealed(data []byte) (*Token, error) {
|
||||||
|
tkn, err := FromDagCbor(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := envelope.CIDFromBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tkn.cid = id
|
||||||
|
|
||||||
|
return tkn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromSealedReader is the same as Unseal but accepts an io.Reader.
|
||||||
|
func FromSealedReader(r io.Reader) (*Token, error) {
|
||||||
|
cidReader := envelope.NewCIDReader(r)
|
||||||
|
|
||||||
|
tkn, err := FromDagCborReader(cidReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := cidReader.CID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tkn.cid = id
|
||||||
|
|
||||||
|
return tkn, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Encode marshals a View to the format specified by the provided
|
// Encode marshals a View to the format specified by the provided
|
||||||
// codec.Encoder.
|
// codec.Encoder.
|
||||||
func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error) {
|
func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error) {
|
||||||
node, err := t.ToIPLD(privKey)
|
node, err := t.toIPLD(privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -27,7 +95,7 @@ func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, err
|
|||||||
|
|
||||||
// EncodeWriter is the same as Encode but accepts an io.Writer.
|
// EncodeWriter is the same as Encode but accepts an io.Writer.
|
||||||
func (t *Token) EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.Encoder) error {
|
func (t *Token) EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.Encoder) error {
|
||||||
node, err := t.ToIPLD(privKey)
|
node, err := t.toIPLD(privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -55,9 +123,81 @@ func (t *Token) ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error {
|
|||||||
return t.EncodeWriter(w, privKey, dagjson.Encode)
|
return t.EncodeWriter(w, privKey, dagjson.Encode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToIPLD wraps the View in an IPLD datamodel.Node.
|
// Decode unmarshals the input data using the format specified by the
|
||||||
func (t *Token) ToIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
// 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) (*Token, 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) (*Token, 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) (*Token, error) {
|
||||||
|
pay, err := envelope.FromDagCbor[*tokenPayloadModel](data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tkn, err := tokenFromModel(*pay)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tkn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
||||||
|
func FromDagCborReader(r io.Reader) (*Token, 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) (*Token, error) {
|
||||||
|
return Decode(data, dagjson.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
|
||||||
|
func FromDagJsonReader(r io.Reader) (*Token, error) {
|
||||||
|
return DecodeReader(r, dagjson.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromIPLD(node datamodel.Node) (*Token, error) {
|
||||||
|
pay, err := envelope.FromIPLD[*tokenPayloadModel](node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tkn, err := tokenFromModel(*pay)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tkn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
||||||
var sub *string
|
var sub *string
|
||||||
|
|
||||||
if t.subject != did.Undef {
|
if t.subject != did.Undef {
|
||||||
s := t.subject.String()
|
s := t.subject.String()
|
||||||
sub = &s
|
sub = &s
|
||||||
@@ -94,64 +234,3 @@ func (t *Token) ToIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
|||||||
|
|
||||||
return envelope.ToIPLD(privKey, model)
|
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) (*Token, 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) (*Token, 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) (*Token, error) {
|
|
||||||
return Decode(data, dagcbor.Decode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
|
||||||
func FromDagCborReader(r io.Reader) (*Token, 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) (*Token, error) {
|
|
||||||
return Decode(data, dagjson.Decode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
|
|
||||||
func FromDagJsonReader(r io.Reader) (*Token, 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) (*Token, error) {
|
|
||||||
tkn, _, err := envelope.FromIPLD[*tokenPayloadModel](node) // TODO add CID to view
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenFromModel(*tkn)
|
|
||||||
}
|
|
||||||
72
tokens/delegation/options.go
Normal file
72
tokens/delegation/options.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package delegation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option is a type that allows optional fields to be set during the
|
||||||
|
// creation of a Token.
|
||||||
|
type Option func(*Token) error
|
||||||
|
|
||||||
|
// WithExpiration set's the Token's optional "expiration" field to the
|
||||||
|
// value of the provided time.Time.
|
||||||
|
func WithExpiration(exp time.Time) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
if exp.Before(time.Now()) {
|
||||||
|
return fmt.Errorf("a Token's expiration should be set to a time in the future: %s", exp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.expiration = &exp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMeta adds a key/value pair in the "meta" field.
|
||||||
|
//
|
||||||
|
// WithMeta can be used multiple times in the same call.
|
||||||
|
// Accepted types for the value are: bool, string, int, int32, int64, []byte,
|
||||||
|
// and ipld.Node.
|
||||||
|
func WithMeta(key string, val any) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
return t.meta.Add(key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNotBefore set's the Token's optional "notBefore" field to the value
|
||||||
|
// of the provided time.Time.
|
||||||
|
func WithNotBefore(nbf time.Time) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
if nbf.Before(time.Now()) {
|
||||||
|
return fmt.Errorf("a Token's \"not before\" field should be set to a time in the future: %s", nbf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.notBefore = &nbf
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSubject sets the Tokens's optional "subject" field to the value of
|
||||||
|
// provided did.DID.
|
||||||
|
//
|
||||||
|
// This Option should only be used with the New constructor - since
|
||||||
|
// Subject is a required parameter when creating a Token via the Root
|
||||||
|
// constructor, any value provided via this Option will be silently
|
||||||
|
// overwritten.
|
||||||
|
func WithSubject(sub did.DID) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
t.subject = sub
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNonce sets the Token's nonce with the given value.
|
||||||
|
// If this option is not used, a random 12-byte nonce is generated for this required field.
|
||||||
|
func WithNonce(nonce []byte) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
t.nonce = nonce
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,12 +10,18 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||||
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// [Tag] is the string used as a key within the SigPayload that identifies
|
||||||
|
// that the TokenPayload is a delegation.
|
||||||
|
//
|
||||||
|
// [Tag]: https://github.com/ucan-wg/delegation/tree/v1_ipld#type-tag
|
||||||
const Tag = "ucan/dlg@1.0.0-rc.1"
|
const Tag = "ucan/dlg@1.0.0-rc.1"
|
||||||
|
|
||||||
|
// TODO: update the above Tag URL once the delegation specification is merged.
|
||||||
|
|
||||||
//go:embed delegation.ipldsch
|
//go:embed delegation.ipldsch
|
||||||
var schemaBytes []byte
|
var schemaBytes []byte
|
||||||
|
|
||||||
177
tokens/delegation/schema_test.go
Normal file
177
tokens/delegation/schema_test.go
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package delegation_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gotest.tools/v3/golden"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/tokens/delegation"
|
||||||
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed delegation.ipldsch
|
||||||
|
var schemaBytes []byte
|
||||||
|
|
||||||
|
func TestSchemaRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
delegationJson := golden.Get(t, "new.dagjson")
|
||||||
|
privKey := privKey(t, issuerPrivKeyCfg)
|
||||||
|
|
||||||
|
t.Run("via buffers", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
|
||||||
|
// function: DecodeDagJson() Seal() Unseal() EncodeDagJson()
|
||||||
|
|
||||||
|
p1, err := delegation.FromDagJson(delegationJson)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cborBytes, id, err := p1.ToSealed(privKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
|
||||||
|
fmt.Println("cborBytes length", len(cborBytes))
|
||||||
|
fmt.Println("cbor", string(cborBytes))
|
||||||
|
|
||||||
|
p2, err := delegation.FromSealed(cborBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, id, p2.CID())
|
||||||
|
fmt.Println("read Cbor", p2)
|
||||||
|
|
||||||
|
readJson, err := p2.ToDagJson(privKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Println("readJson length", len(readJson))
|
||||||
|
fmt.Println("json: ", string(readJson))
|
||||||
|
|
||||||
|
assert.JSONEq(t, string(delegationJson), string(readJson))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("via streaming", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(delegationJson)
|
||||||
|
|
||||||
|
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
|
||||||
|
// function: DecodeDagJson() Seal() Unseal() EncodeDagJson()
|
||||||
|
|
||||||
|
p1, err := delegation.FromDagJsonReader(buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cborBytes := &bytes.Buffer{}
|
||||||
|
id, err := p1.ToSealedWriter(cborBytes, privKey)
|
||||||
|
t.Log(len(id.Bytes()), id.Bytes())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
|
||||||
|
|
||||||
|
// buf = bytes.NewBuffer(cborBytes.Bytes())
|
||||||
|
p2, err := delegation.FromSealedReader(cborBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Log(len(p2.CID().Bytes()), p2.CID().Bytes())
|
||||||
|
assert.Equal(t, envelope.CIDToBase58BTC(id), envelope.CIDToBase58BTC(p2.CID()))
|
||||||
|
|
||||||
|
readJson := &bytes.Buffer{}
|
||||||
|
require.NoError(t, p2.ToDagJsonWriter(readJson, privKey))
|
||||||
|
|
||||||
|
assert.JSONEq(t, string(delegationJson), readJson.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSchemaLoad(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = ipld.LoadSchemaBytes(schemaBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRoundTrip(b *testing.B) {
|
||||||
|
delegationJson := golden.Get(b, "new.dagjson")
|
||||||
|
privKey := privKey(b, issuerPrivKeyCfg)
|
||||||
|
|
||||||
|
b.Run("via buffers", func(b *testing.B) {
|
||||||
|
p1, _ := delegation.FromDagJson(delegationJson)
|
||||||
|
cborBytes, _, _ := p1.ToSealed(privKey)
|
||||||
|
p2, _ := delegation.FromSealed(cborBytes)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
b.Run("FromDagJson", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = delegation.FromDagJson(delegationJson)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Seal", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _, _ = p1.ToSealed(privKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Unseal", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = delegation.FromSealed(cborBytes)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("ToDagJson", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = p2.ToDagJson(privKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("via streaming", func(b *testing.B) {
|
||||||
|
p1, _ := delegation.FromDagJsonReader(bytes.NewReader(delegationJson))
|
||||||
|
cborBuf := &bytes.Buffer{}
|
||||||
|
_, _ = p1.ToSealedWriter(cborBuf, privKey)
|
||||||
|
cborBytes := cborBuf.Bytes()
|
||||||
|
p2, _ := delegation.FromSealedReader(bytes.NewReader(cborBytes))
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
b.Run("FromDagJsonReader", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
reader := bytes.NewReader(delegationJson)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = reader.Seek(0, 0)
|
||||||
|
_, _ = delegation.FromDagJsonReader(reader)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("SealWriter", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
buf.Reset()
|
||||||
|
_, _ = p1.ToSealedWriter(buf, privKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("UnsealReader", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
reader := bytes.NewReader(cborBytes)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = reader.Seek(0, 0)
|
||||||
|
_, _ = delegation.FromSealedReader(reader)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("ToDagJsonReader", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
buf.Reset()
|
||||||
|
_ = p2.ToDagJsonWriter(buf, privKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
124
tokens/internal/envelope/cid.go
Normal file
124
tokens/internal/envelope/cid.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package envelope
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"github.com/multiformats/go-multibase"
|
||||||
|
"github.com/multiformats/go-multicodec"
|
||||||
|
"github.com/multiformats/go-multihash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var b58BTCEnc = multibase.MustNewEncoder(multibase.Base58BTC)
|
||||||
|
|
||||||
|
// CIDToBase56BTC is a utility method to convert a CIDv1 to the canonical
|
||||||
|
// string representation used by UCAN.
|
||||||
|
func CIDToBase58BTC(id cid.Cid) string {
|
||||||
|
return id.Encode(b58BTCEnc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CIDFromBytes returns the UCAN content identifier for an arbitrary slice
|
||||||
|
// of bytes.
|
||||||
|
func CIDFromBytes(b []byte) (cid.Cid, error) {
|
||||||
|
return cid.V1Builder{
|
||||||
|
Codec: uint64(multicodec.DagCbor),
|
||||||
|
MhType: multihash.SHA2_256,
|
||||||
|
MhLength: 0,
|
||||||
|
}.Sum(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.Reader = (*CIDReader)(nil)
|
||||||
|
|
||||||
|
// CIDReader wraps an io.Reader and includes a hash.Hash that is
|
||||||
|
// incrementally updated as data is read from the child io.Reader.
|
||||||
|
type CIDReader struct {
|
||||||
|
hash hash.Hash
|
||||||
|
r io.Reader
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCIDReader initializes a hash.Hash to calculate the CID's hash and
|
||||||
|
// returns the wrapped io.Reader.
|
||||||
|
func NewCIDReader(r io.Reader) *CIDReader {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Reset()
|
||||||
|
|
||||||
|
return &CIDReader{
|
||||||
|
hash: h,
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CID returns the UCAN-formatted cid.Cid created from the hash calculated
|
||||||
|
// as bytes were read from the inner io.Reader.
|
||||||
|
func (r *CIDReader) CID() (cid.Cid, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return cid.Undef, r.err // TODO: Wrap to say it's an error during streaming?
|
||||||
|
}
|
||||||
|
|
||||||
|
return cidFromHash(r.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements io.Reader.
|
||||||
|
func (r *CIDReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = r.r.Read(p)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
r.err = err
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = r.hash.Write(p[:n])
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.Writer = (*CIDWriter)(nil)
|
||||||
|
|
||||||
|
// CIDWriter wraps an io.Writer and includes a hash.Hash that is
|
||||||
|
// incrementally updated as data is written to the child io.Writer.
|
||||||
|
type CIDWriter struct {
|
||||||
|
hash hash.Hash
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCIDWriter initializes a hash.Hash to calculate the CID's hash and
|
||||||
|
// returns the wrapped io.Writer.
|
||||||
|
func NewCIDWriter(w io.Writer) *CIDWriter {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Reset()
|
||||||
|
|
||||||
|
return &CIDWriter{
|
||||||
|
hash: h,
|
||||||
|
w: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CID returns the UCAN-formatted cid.Cid created from the hash calculated
|
||||||
|
// as bytes were written from the inner io.Reader.
|
||||||
|
func (w *CIDWriter) CID() (cid.Cid, error) {
|
||||||
|
return cidFromHash(w.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer.
|
||||||
|
func (w *CIDWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if _, err = w.hash.Write(p); err != nil {
|
||||||
|
w.err = err
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.w.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cidFromHash(hash hash.Hash) (cid.Cid, error) {
|
||||||
|
mh, err := multihash.Encode(hash.Sum(nil), multihash.SHA2_256)
|
||||||
|
if err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cid.NewCidV1(uint64(multicodec.DagCbor), mh), nil
|
||||||
|
}
|
||||||
86
tokens/internal/envelope/cid_test.go
Normal file
86
tokens/internal/envelope/cid_test.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package envelope_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"github.com/multiformats/go-multicodec"
|
||||||
|
"github.com/multiformats/go-multihash"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gotest.tools/v3/golden"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCidFromBytes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
expData := golden.Get(t, "example.dagcbor")
|
||||||
|
expHash, err := multihash.Sum(expData, uint64(multicodec.Sha2_256), -1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
data, err := envelope.ToDagCbor(examplePrivKey(t), newExample(t))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
id, err := envelope.CIDFromBytes(data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, exampleCID, envelope.CIDToBase58BTC(id))
|
||||||
|
assert.Equal(t, expHash, id.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStreaming(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
expData := []byte("this is a test")
|
||||||
|
|
||||||
|
expCID, err := cid.V1Builder{
|
||||||
|
Codec: uint64(multicodec.DagCbor),
|
||||||
|
MhType: multihash.SHA2_256,
|
||||||
|
MhLength: 0,
|
||||||
|
}.Sum(expData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("CIDReader()", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
r, w := io.Pipe() //nolint:varnamelen
|
||||||
|
cidReader := envelope.NewCIDReader(r)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_, err := w.Write(expData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, w.Close())
|
||||||
|
}()
|
||||||
|
|
||||||
|
actData, err := io.ReadAll(cidReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expData, actData)
|
||||||
|
|
||||||
|
actCID, err := cidReader.CID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expCID, actCID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CIDWriter", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
r, w := io.Pipe() //nolint:varnamelen
|
||||||
|
cidWriter := envelope.NewCIDWriter(w)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_, err := cidWriter.Write(expData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, w.Close())
|
||||||
|
}()
|
||||||
|
|
||||||
|
actData, err := io.ReadAll(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expData, actData)
|
||||||
|
|
||||||
|
actCID, err := cidWriter.CID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expCID, actCID)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -16,11 +16,12 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
exampleCID = "zdpuAyw6R5HvKSPzztuzXNYFx3ZGoMHMuAsXL6u3xLGQriRXQ"
|
||||||
exampleDID = "did:key:z6MkpuK2Amsu1RqcLGgmHHQHhvmeXCCBVsM4XFSg2cCyg4Nh"
|
exampleDID = "did:key:z6MkpuK2Amsu1RqcLGgmHHQHhvmeXCCBVsM4XFSg2cCyg4Nh"
|
||||||
exampleGreeting = "world"
|
exampleGreeting = "world"
|
||||||
examplePrivKeyCfg = "CAESQP9v2uqECTuIi45dyg3znQvsryvf2IXmOF/6aws6aCehm0FVrj0zHR5RZSDxWNjcpcJqsGym3sjCungX9Zt5oA4="
|
examplePrivKeyCfg = "CAESQP9v2uqECTuIi45dyg3znQvsryvf2IXmOF/6aws6aCehm0FVrj0zHR5RZSDxWNjcpcJqsGym3sjCungX9Zt5oA4="
|
||||||
@@ -8,8 +8,7 @@
|
|||||||
//
|
//
|
||||||
// Decoding functions in this package likewise perform the signature
|
// Decoding functions in this package likewise perform the signature
|
||||||
// verification using a public key extracted from the TokenPayload as
|
// verification using a public key extracted from the TokenPayload as
|
||||||
// described by requirement two below. Additionally, the decode functions
|
// described by requirement two below.
|
||||||
// also return the CID for the verified Envelope.
|
|
||||||
//
|
//
|
||||||
// Types that wish to be marshaled and unmarshaled from the using
|
// Types that wish to be marshaled and unmarshaled from the using
|
||||||
// is package have two requirements.
|
// is package have two requirements.
|
||||||
@@ -30,7 +29,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/codec"
|
"github.com/ipld/go-ipld-prime/codec"
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
@@ -41,8 +39,9 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
"github.com/ucan-wg/go-ucan/internal/varsig"
|
"github.com/ucan-wg/go-ucan/tokens/internal/varsig"
|
||||||
)
|
)
|
||||||
|
|
||||||
const varsigHeaderKey = "h"
|
const varsigHeaderKey = "h"
|
||||||
@@ -66,20 +65,20 @@ type Tokener interface {
|
|||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// Tokener is invalid.
|
// Tokener is invalid.
|
||||||
func Decode[T Tokener](b []byte, decFn codec.Decoder) (T, cid.Cid, error) {
|
func Decode[T Tokener](b []byte, decFn codec.Decoder) (T, error) {
|
||||||
node, err := ipld.Decode(b, decFn)
|
node, err := ipld.Decode(b, decFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return *new(T), cid.Undef, err
|
return *new(T), err
|
||||||
}
|
}
|
||||||
|
|
||||||
return FromIPLD[T](node)
|
return FromIPLD[T](node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeReader is the same as Decode, but accept an io.Reader.
|
// DecodeReader is the same as Decode, but accept an io.Reader.
|
||||||
func DecodeReader[T Tokener](r io.Reader, decFn codec.Decoder) (T, cid.Cid, error) {
|
func DecodeReader[T Tokener](r io.Reader, decFn codec.Decoder) (T, error) {
|
||||||
node, err := ipld.DecodeStreaming(r, decFn)
|
node, err := ipld.DecodeStreaming(r, decFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return *new(T), cid.Undef, err
|
return *new(T), err
|
||||||
}
|
}
|
||||||
|
|
||||||
return FromIPLD[T](node)
|
return FromIPLD[T](node)
|
||||||
@@ -89,12 +88,24 @@ func DecodeReader[T Tokener](r io.Reader, decFn codec.Decoder) (T, cid.Cid, erro
|
|||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// Tokener is invalid.
|
// Tokener is invalid.
|
||||||
func FromDagCbor[T Tokener](b []byte) (T, cid.Cid, error) {
|
func FromDagCbor[T Tokener](b []byte) (T, error) {
|
||||||
return Decode[T](b, dagcbor.Decode)
|
undef := *new(T)
|
||||||
|
|
||||||
|
node, err := ipld.Decode(b, dagcbor.Decode)
|
||||||
|
if err != nil {
|
||||||
|
return undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tkn, err := fromIPLD[T](node)
|
||||||
|
if err != nil {
|
||||||
|
return undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tkn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
||||||
func FromDagCborReader[T Tokener](r io.Reader) (T, cid.Cid, error) {
|
func FromDagCborReader[T Tokener](r io.Reader) (T, error) {
|
||||||
return DecodeReader[T](r, dagcbor.Decode)
|
return DecodeReader[T](r, dagcbor.Decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,12 +113,12 @@ func FromDagCborReader[T Tokener](r io.Reader) (T, cid.Cid, error) {
|
|||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// Tokener is invalid.
|
// Tokener is invalid.
|
||||||
func FromDagJson[T Tokener](b []byte) (T, cid.Cid, error) {
|
func FromDagJson[T Tokener](b []byte) (T, error) {
|
||||||
return Decode[T](b, dagjson.Decode)
|
return Decode[T](b, dagjson.Decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
|
// FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
|
||||||
func FromDagJsonReader[T Tokener](r io.Reader) (T, cid.Cid, error) {
|
func FromDagJsonReader[T Tokener](r io.Reader) (T, error) {
|
||||||
return DecodeReader[T](r, dagjson.Decode)
|
return DecodeReader[T](r, dagjson.Decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,111 +126,117 @@ func FromDagJsonReader[T Tokener](r io.Reader) (T, cid.Cid, error) {
|
|||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// Tokener is invalid.
|
// Tokener is invalid.
|
||||||
func FromIPLD[T Tokener](node datamodel.Node) (T, cid.Cid, error) {
|
func FromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
||||||
|
undef := *new(T)
|
||||||
|
|
||||||
|
tkn, err := fromIPLD[T](node)
|
||||||
|
if err != nil {
|
||||||
|
return undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tkn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
||||||
undef := *new(T)
|
undef := *new(T)
|
||||||
|
|
||||||
signatureNode, err := node.LookupByIndex(0)
|
signatureNode, err := node.LookupByIndex(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
signature, err := signatureNode.AsBytes()
|
signature, err := signatureNode.AsBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sigPayloadNode, err := node.LookupByIndex(1)
|
sigPayloadNode, err := node.LookupByIndex(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
varsigHeaderNode, err := sigPayloadNode.LookupByString(varsigHeaderKey)
|
varsigHeaderNode, err := sigPayloadNode.LookupByString(varsigHeaderKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenPayloadNode, err := sigPayloadNode.LookupByString(undef.Tag())
|
tokenPayloadNode, err := sigPayloadNode.LookupByString(undef.Tag())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This needs to be done before converting this node to it's schema
|
// This needs to be done before converting this node to its schema
|
||||||
// representation (afterwards, the field might be renamed os it's safer
|
// representation (afterwards, the field might be renamed os it's safer
|
||||||
// to use the wire name).
|
// to use the wire name).
|
||||||
issuerNode, err := tokenPayloadNode.LookupByString("iss")
|
issuerNode, err := tokenPayloadNode.LookupByString("iss")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
// ^^^
|
|
||||||
|
|
||||||
// Replaces the datamodel.Node in tokenPayloadNode with a
|
// Replaces the datamodel.Node in tokenPayloadNode with a
|
||||||
// schema.TypedNode so that we can cast it to a *token.Token after
|
// schema.TypedNode so that we can cast it to a *token.Token after
|
||||||
// unwrapping it.
|
// unwrapping it.
|
||||||
// vvv
|
|
||||||
nb := undef.Prototype().Representation().NewBuilder()
|
nb := undef.Prototype().Representation().NewBuilder()
|
||||||
|
|
||||||
err = nb.AssignNode(tokenPayloadNode)
|
err = nb.AssignNode(tokenPayloadNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenPayloadNode = nb.Build()
|
tokenPayloadNode = nb.Build()
|
||||||
// ^^^
|
|
||||||
|
|
||||||
tokenPayload := bindnode.Unwrap(tokenPayloadNode)
|
tokenPayload := bindnode.Unwrap(tokenPayloadNode)
|
||||||
if tokenPayload == nil {
|
if tokenPayload == nil {
|
||||||
return undef, cid.Undef, errors.New("failed to Unwrap the TokenPayload")
|
return undef, errors.New("failed to Unwrap the TokenPayload")
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn, ok := tokenPayload.(T)
|
tkn, ok := tokenPayload.(T)
|
||||||
if !ok {
|
if !ok {
|
||||||
return undef, cid.Undef, errors.New("failed to assert the TokenPayload type as *token.Token")
|
return undef, errors.New("failed to assert the TokenPayload type as *token.Token")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the issuer's DID contains a public key with a type that
|
// Check that the issuer's DID contains a public key with a type that
|
||||||
// matches the VarsigHeader and then verify the SigPayload.
|
// matches the VarsigHeader and then verify the SigPayload.
|
||||||
// vvv
|
|
||||||
issuer, err := issuerNode.AsString()
|
issuer, err := issuerNode.AsString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerDID, err := did.Parse(issuer)
|
issuerDID, err := did.Parse(issuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerPubKey, err := issuerDID.PubKey()
|
issuerPubKey, err := issuerDID.PubKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerVarsigHeader, err := varsig.Encode(issuerPubKey.Type())
|
issuerVarsigHeader, err := varsig.Encode(issuerPubKey.Type())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
varsigHeader, err := varsigHeaderNode.AsBytes()
|
varsigHeader, err := varsigHeaderNode.AsBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(varsigHeader) != string(issuerVarsigHeader) {
|
if string(varsigHeader) != string(issuerVarsigHeader) {
|
||||||
return undef, cid.Undef, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
return undef, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ipld.Encode(sigPayloadNode, dagcbor.Encode)
|
data, err := ipld.Encode(sigPayloadNode, dagcbor.Encode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, cid.Undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err = issuerPubKey.Verify(data, signature)
|
ok, err = issuerPubKey.Verify(data, signature)
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
return undef, cid.Undef, errors.New("failed to verify the token's signature")
|
return undef, errors.New("failed to verify the token's signature")
|
||||||
}
|
}
|
||||||
// ^^^
|
|
||||||
|
|
||||||
return tkn, cid.Undef, nil
|
return tkn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode marshals a Tokener to the format specified by the provided
|
// Encode marshals a Tokener to the format specified by the provided
|
||||||
@@ -2,11 +2,12 @@ package envelope_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/ucan-wg/go-ucan/internal/envelope"
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ func TestDecode(t *testing.T) {
|
|||||||
|
|
||||||
data := golden.Get(t, "example.dagcbor")
|
data := golden.Get(t, "example.dagcbor")
|
||||||
|
|
||||||
tkn, _, err := envelope.FromDagCbor[*Example](data)
|
tkn, err := envelope.FromDagCbor[*Example](data)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, exampleGreeting, tkn.Hello)
|
assert.Equal(t, exampleGreeting, tkn.Hello)
|
||||||
assert.Equal(t, exampleDID, tkn.Issuer)
|
assert.Equal(t, exampleDID, tkn.Issuer)
|
||||||
@@ -29,7 +30,7 @@ func TestDecode(t *testing.T) {
|
|||||||
|
|
||||||
data := golden.Get(t, "example.dagjson")
|
data := golden.Get(t, "example.dagjson")
|
||||||
|
|
||||||
tkn, _, err := envelope.FromDagJson[*Example](data)
|
tkn, err := envelope.FromDagJson[*Example](data)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, exampleGreeting, tkn.Hello)
|
assert.Equal(t, exampleGreeting, tkn.Hello)
|
||||||
assert.Equal(t, exampleDID, tkn.Issuer)
|
assert.Equal(t, exampleDID, tkn.Issuer)
|
||||||
@@ -59,12 +60,27 @@ func TestEncode(t *testing.T) {
|
|||||||
func TestRoundtrip(t *testing.T) {
|
func TestRoundtrip(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("via FromDagCbor/ToDagCbor", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dataIn := golden.Get(t, exampleDAGCBORFilename)
|
||||||
|
|
||||||
|
tkn, err := envelope.FromDagCbor[*Example](dataIn)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, exampleGreeting, tkn.Hello)
|
||||||
|
assert.Equal(t, exampleDID, tkn.Issuer)
|
||||||
|
|
||||||
|
dataOut, err := envelope.ToDagCbor(examplePrivKey(t), newExample(t))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, dataIn, dataOut)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("via FromDagCborReader/ToDagCborWriter", func(t *testing.T) {
|
t.Run("via FromDagCborReader/ToDagCborWriter", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
data := golden.Get(t, exampleDAGCBORFilename)
|
data := golden.Get(t, exampleDAGCBORFilename)
|
||||||
|
|
||||||
tkn, _, err := envelope.FromDagCborReader[*Example](bytes.NewReader(data))
|
tkn, err := envelope.FromDagCborReader[*Example](bytes.NewReader(data))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, exampleGreeting, tkn.Hello)
|
assert.Equal(t, exampleGreeting, tkn.Hello)
|
||||||
assert.Equal(t, exampleDID, tkn.Issuer)
|
assert.Equal(t, exampleDID, tkn.Issuer)
|
||||||
@@ -74,27 +90,62 @@ func TestRoundtrip(t *testing.T) {
|
|||||||
assert.Equal(t, data, w.Bytes())
|
assert.Equal(t, data, w.Bytes())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("via FromDagCbor/ToDagCbor", func(t *testing.T) {
|
t.Run("via FromDagJson/ToDagJson", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dataIn := golden.Get(t, exampleDAGCBORFilename)
|
dataIn := golden.Get(t, exampleDAGJSONFilename)
|
||||||
|
|
||||||
tkn, _, err := envelope.FromDagCbor[*Example](dataIn)
|
tkn, err := envelope.FromDagJson[*Example](dataIn)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, exampleGreeting, tkn.Hello)
|
assert.Equal(t, exampleGreeting, tkn.Hello)
|
||||||
assert.Equal(t, exampleDID, tkn.Issuer)
|
assert.Equal(t, exampleDID, tkn.Issuer)
|
||||||
|
|
||||||
dataOut, err := envelope.ToDagCbor(examplePrivKey(t), newExample(t))
|
dataOut, err := envelope.ToDagJson(examplePrivKey(t), newExample(t))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, dataIn, dataOut)
|
assert.Equal(t, dataIn, dataOut)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("via FromDagJsonReader/ToDagJsonrWriter", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
data := golden.Get(t, exampleDAGJSONFilename)
|
||||||
|
|
||||||
|
tkn, err := envelope.FromDagJsonReader[*Example](bytes.NewReader(data))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, exampleGreeting, tkn.Hello)
|
||||||
|
assert.Equal(t, exampleDID, tkn.Issuer)
|
||||||
|
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
require.NoError(t, envelope.ToDagJsonWriter(w, examplePrivKey(t), newExample(t)))
|
||||||
|
assert.Equal(t, data, w.Bytes())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromIPLD_with_invalid_signature(t *testing.T) {
|
func TestFromIPLD_with_invalid_signature(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
node := invalidNodeFromGolden(t)
|
node := invalidNodeFromGolden(t)
|
||||||
tkn, _, err := envelope.FromIPLD[*Example](node)
|
tkn, err := envelope.FromIPLD[*Example](node)
|
||||||
assert.Nil(t, tkn)
|
assert.Nil(t, tkn)
|
||||||
require.EqualError(t, err, "failed to verify the token's signature")
|
require.EqualError(t, err, "failed to verify the token's signature")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHash(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
msg := []byte("this is a test")
|
||||||
|
|
||||||
|
hash1 := sha256.Sum256(msg)
|
||||||
|
|
||||||
|
hasher := sha256.New()
|
||||||
|
|
||||||
|
for _, b := range msg {
|
||||||
|
hasher.Write([]byte{b})
|
||||||
|
}
|
||||||
|
|
||||||
|
hash2 := hasher.Sum(nil)
|
||||||
|
hash3 := hasher.Sum(nil)
|
||||||
|
|
||||||
|
require.Equal(t, hash1[:], hash2)
|
||||||
|
require.Equal(t, hash1[:], hash3)
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/libp2p/go-libp2p/core/crypto/pb"
|
"github.com/libp2p/go-libp2p/core/crypto/pb"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/internal/varsig"
|
"github.com/ucan-wg/go-ucan/tokens/internal/varsig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecode(t *testing.T) {
|
func TestDecode(t *testing.T) {
|
||||||
Reference in New Issue
Block a user