Compare commits
23 Commits
feat/deleg
...
envelope
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ca17ea63d | ||
|
|
658794041e | ||
|
|
f85ece49fa | ||
|
|
da9f2e7bec | ||
|
|
bd1775b2f5 | ||
|
|
285cb5f6e7 | ||
|
|
4c05d866f2 | ||
|
|
646127abe7 | ||
|
|
7cead1bf8d | ||
|
|
16f0f38b43 | ||
|
|
2f183aa6f4 | ||
|
|
6d1b7ee01f | ||
|
|
599c5d30b0 | ||
|
|
1c2f602f4d | ||
|
|
8441f99d5d | ||
|
|
1525aaa139 | ||
|
|
ab4018d218 | ||
|
|
39987eadaa | ||
|
|
b77f8d6bb0 | ||
|
|
719837e3cd | ||
|
|
2205d5d4ce | ||
|
|
3a542ecc85 | ||
|
|
ba1c45c088 |
46
delegation/delegatiom_options.go
Normal file
46
delegation/delegatiom_options.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package delegation
|
||||||
|
|
||||||
|
// Code generated by github.com/selesy/go-options. DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option func(c *config) error
|
||||||
|
|
||||||
|
func newConfig(options ...Option) (config, error) {
|
||||||
|
var c config
|
||||||
|
err := applyConfigOptions(&c, options...)
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyConfigOptions(c *config, options ...Option) error {
|
||||||
|
for _, o := range options {
|
||||||
|
if err := o(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithExpiration(o *time.Time) Option {
|
||||||
|
return func(c *config) error {
|
||||||
|
c.Expiration = o
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithMeta(o map[string]datamodel.Node) Option {
|
||||||
|
return func(c *config) error {
|
||||||
|
c.Meta = o
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNotBefore(o *time.Time) Option {
|
||||||
|
return func(c *config) error {
|
||||||
|
c.NotBefore = o
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
225
delegation/delegation.go
Normal file
225
delegation/delegation.go
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
package delegation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"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/internal/envelope"
|
||||||
|
"github.com/ucan-wg/go-ucan/internal/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Tag = "ucan/dlg@1.0.0-rc.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Delegation struct {
|
||||||
|
envel *envelope.Envelope
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate -command options go run github.com/selesy/go-options
|
||||||
|
//go:generate options -type=config -prefix=With -output=delegatiom_options.go -cmp=false -stringer=false -imports=time,github.com/ipld/go-ipld-prime/datamodel
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Expiration *time.Time
|
||||||
|
Meta map[string]datamodel.Node
|
||||||
|
NotBefore *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(privKey crypto.PrivKey, aud did.DID, sub *did.DID, cmd *command.Command, pol policy.Policy, nonce []byte, opts ...Option) (*Delegation, error) {
|
||||||
|
cfg, err := newConfig(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
issuer, err := did.FromPrivKey(privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !aud.Defined() {
|
||||||
|
return nil, fmt.Errorf("%w: %s", token.ErrMissingRequiredDID, "aud")
|
||||||
|
}
|
||||||
|
audience := aud.String()
|
||||||
|
|
||||||
|
var subject *string
|
||||||
|
if sub != nil {
|
||||||
|
s := sub.String()
|
||||||
|
subject = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := pol.ToIPLD()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta *token.Map__String__Any
|
||||||
|
if len(cfg.Meta) > 0 {
|
||||||
|
m := token.ToIPLDMapStringAny(cfg.Meta)
|
||||||
|
meta = &m
|
||||||
|
}
|
||||||
|
|
||||||
|
var notBefore *int
|
||||||
|
if cfg.NotBefore != nil {
|
||||||
|
n := int(cfg.NotBefore.Unix())
|
||||||
|
notBefore = &n
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiration *int
|
||||||
|
if cfg.Expiration != nil {
|
||||||
|
e := int(cfg.Expiration.Unix())
|
||||||
|
expiration = &e
|
||||||
|
}
|
||||||
|
|
||||||
|
tkn := &token.Token{
|
||||||
|
Issuer: issuer.String(),
|
||||||
|
Audience: &audience,
|
||||||
|
Subject: subject,
|
||||||
|
Command: cmd.String(),
|
||||||
|
Policy: &policy,
|
||||||
|
Nonce: &nonce,
|
||||||
|
Meta: meta,
|
||||||
|
NotBefore: notBefore,
|
||||||
|
Expiration: expiration,
|
||||||
|
}
|
||||||
|
|
||||||
|
envel, err := envelope.New(privKey, tkn, Tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dlg := &Delegation{envel: envel}
|
||||||
|
|
||||||
|
if err := dlg.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Root(privKey crypto.PrivKey, aud did.DID, cmd *command.Command, pol policy.Policy, nonce []byte, opts ...Option) (*Delegation, error) {
|
||||||
|
sub, err := did.FromPrivKey(privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return New(privKey, aud, &sub, cmd, pol, nonce, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) Audience() did.DID {
|
||||||
|
id, _ := did.Parse(*d.envel.TokenPayload().Audience)
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) Command() *command.Command {
|
||||||
|
cmd, _ := command.Parse(d.envel.TokenPayload().Command)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) IsPowerline() bool {
|
||||||
|
return d.envel.TokenPayload().Subject == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) IsRoot() bool {
|
||||||
|
return &d.envel.TokenPayload().Issuer == d.envel.TokenPayload().Subject
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) Issuer() did.DID {
|
||||||
|
id, _ := did.Parse(d.envel.TokenPayload().Issuer)
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) Meta() map[string]datamodel.Node {
|
||||||
|
return d.envel.TokenPayload().Meta.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) Nonce() []byte {
|
||||||
|
return *d.envel.TokenPayload().Nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) Policy() policy.Policy {
|
||||||
|
pol, _ := policy.FromIPLD(*d.envel.TokenPayload().Policy)
|
||||||
|
|
||||||
|
return pol
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) Subject() *did.DID {
|
||||||
|
if d.envel.TokenPayload().Subject == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
id, _ := did.Parse(*d.envel.TokenPayload().Subject)
|
||||||
|
|
||||||
|
return &id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) Validate() error {
|
||||||
|
return errors.Join(
|
||||||
|
d.validateDID("iss", &d.envel.TokenPayload().Issuer, false),
|
||||||
|
d.validateDID("aud", d.envel.TokenPayload().Audience, false),
|
||||||
|
d.validateDID("sub", d.envel.TokenPayload().Subject, true),
|
||||||
|
d.validateCommand(),
|
||||||
|
d.validatePolicy(),
|
||||||
|
d.validateNonce(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) validateCommand() error {
|
||||||
|
_, err := command.Parse(d.envel.TokenPayload().Command)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) validateDID(fieldName string, identity *string, nullableOrOptional bool) error {
|
||||||
|
if identity == nil && !nullableOrOptional {
|
||||||
|
return fmt.Errorf("a required DID is missing: %s", fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := did.Parse(*identity)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if !id.Defined() && !id.Key() {
|
||||||
|
return fmt.Errorf("a required DID is missing: %s", fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) validateNonce() error {
|
||||||
|
if d.envel.TokenPayload().Nonce == nil || len(*d.envel.TokenPayload().Nonce) < 1 {
|
||||||
|
return fmt.Errorf("nonce is required: must not be nil or empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Delegation) validatePolicy() error {
|
||||||
|
if d.envel.TokenPayload().Policy == nil {
|
||||||
|
return fmt.Errorf("the \"pol\" field is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := policy.FromIPLD(*d.envel.TokenPayload().Policy)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Nonce() ([]byte, error) {
|
||||||
|
nonce := make([]byte, 32)
|
||||||
|
|
||||||
|
if _, err := rand.Read(nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nonce, nil
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
type DID string
|
|
||||||
|
|
||||||
# The Delegation payload MUST describe the authorization claims, who is involved, and its validity period.
|
|
||||||
type Payload struct {
|
|
||||||
# Issuer DID (sender)
|
|
||||||
iss DID
|
|
||||||
# Audience DID (receiver)
|
|
||||||
aud DID
|
|
||||||
# Principal that the chain is about (the Subject)
|
|
||||||
sub optional DID
|
|
||||||
|
|
||||||
# The Command to eventually invoke
|
|
||||||
cmd String
|
|
||||||
|
|
||||||
# The delegation policy
|
|
||||||
# It doesn't seem possible to represent it with a schema.
|
|
||||||
pol Any
|
|
||||||
|
|
||||||
# A unique, random nonce
|
|
||||||
nonce Bytes
|
|
||||||
|
|
||||||
# Arbitrary Metadata
|
|
||||||
meta {String : Any}
|
|
||||||
|
|
||||||
# "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
|
||||||
nbf optional Int
|
|
||||||
# The timestamp at which the Invocation becomes invalid
|
|
||||||
exp nullable Int
|
|
||||||
}
|
|
||||||
149
delegation/delegation_test.go
Normal file
149
delegation/delegation_test.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package delegation_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"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"
|
||||||
|
"gotest.tools/v3/golden"
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
@@ -1,33 +1,113 @@
|
|||||||
package delegation
|
package delegation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"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/dagcbor"
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *PayloadModel) EncodeDagCbor() ([]byte, error) {
|
// Encode marshals a Delegation to the format specified by the provided
|
||||||
return ipld.Marshal(dagcbor.Encode, p, PayloadType())
|
// codec.Encoder.
|
||||||
}
|
func (d *Delegation) Encode(encFn codec.Encoder) ([]byte, error) {
|
||||||
|
node, err := d.ToIPLD()
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &p, nil
|
|
||||||
|
return ipld.Encode(node, encFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeDagJson(data []byte) (*PayloadModel, error) {
|
// ToDagCbor marshals the Delegation to the DAG-CBOR format.
|
||||||
var p PayloadModel
|
func (d *Delegation) ToDagCbor() ([]byte, error) {
|
||||||
_, err := ipld.Unmarshal(data, dagjson.Decode, &p, PayloadType())
|
return d.Encode(dagcbor.Encode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDagJson marshals the Delegation to the DAG-JSON format.
|
||||||
|
func (d *Delegation) ToDagJson() ([]byte, error) {
|
||||||
|
return d.Encode(dagjson.Encode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToIPLD wraps the Delegation in an IPLD datamodel.Node.
|
||||||
|
func (d *Delegation) ToIPLD() (datamodel.Node, error) {
|
||||||
|
return d.envel.Wrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode unmarshals the input data using the format specified by the
|
||||||
|
// provided codec.Decoder into a Delegation.
|
||||||
|
//
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Delegation is invalid.
|
||||||
|
func Decode(b []byte, decFn codec.Decoder) (*Delegation, error) {
|
||||||
|
node, err := ipld.Decode(b, decFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &p, nil
|
return FromIPLD(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeReader is the same as Decode, but accept an io.Reader.
|
||||||
|
func DecodeReader(r io.Reader, decFn codec.Decoder) (*Delegation, error) {
|
||||||
|
node, err := ipld.DecodeStreaming(r, decFn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return FromIPLD(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagCbor unmarshals the input data into a Delegation.
|
||||||
|
//
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Delegation is invalid.
|
||||||
|
func FromDagCbor(data []byte) (*Delegation, error) {
|
||||||
|
return Decode(data, dagcbor.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
||||||
|
func FromDagCborReader(r io.Reader) (*Delegation, error) {
|
||||||
|
return DecodeReader(r, dagcbor.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagJson unmarshals the input data into a Delegation.
|
||||||
|
//
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Delegation is invalid.
|
||||||
|
func FromDagJson(data []byte) (*Delegation, error) {
|
||||||
|
return Decode(data, dagjson.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
|
||||||
|
func FromDagJsonReader(r io.Reader) (*Delegation, error) {
|
||||||
|
return DecodeReader(r, dagjson.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromIPLD unwraps a Delegation from the provided IPLD datamodel.Node
|
||||||
|
//
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Delegation is invalid.
|
||||||
|
func FromIPLD(node datamodel.Node) (*Delegation, error) {
|
||||||
|
envel, err := envelope.Unwrap(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if envel.Tag() != Tag {
|
||||||
|
return nil, fmt.Errorf("wrong tag for TokenPayload: received %s but expected %s", envel.Tag(), Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
dlg := &Delegation{
|
||||||
|
envel: envel,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dlg.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
101
delegation/encoding_test.go
Normal file
101
delegation/encoding_test.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package delegation_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/ucan-wg/go-ucan/delegation"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodingRoundTrip(t *testing.T) {
|
||||||
|
const delegationJson = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"/": {
|
||||||
|
"bytes": "QWr0Pk+sSWE1nszuBMQzggbHX4ofJb8QRdwrLJK/AGCx2p4s/xaCRieomfstDjsV4ezBzX1HARvcoNgdwDQ8Aw"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": {
|
||||||
|
"/": {
|
||||||
|
"bytes": "NO0BcQ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ucan/dlg@1.0.0-rc.1": {
|
||||||
|
"aud": "did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2",
|
||||||
|
"cmd": "/foo/bar",
|
||||||
|
"exp": -62135596800,
|
||||||
|
"iss": "did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2",
|
||||||
|
"meta": {
|
||||||
|
"bar": "barr",
|
||||||
|
"foo": "fooo"
|
||||||
|
},
|
||||||
|
"nbf": -62135596800,
|
||||||
|
"nonce": {
|
||||||
|
"/": {
|
||||||
|
"bytes": "X93ORvN1QIXrKPyEP5m5XoVK9VLX9nX8VV/+HlWrp9c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pol": [
|
||||||
|
[
|
||||||
|
"==",
|
||||||
|
".status",
|
||||||
|
"draft"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"all",
|
||||||
|
".reviewer",
|
||||||
|
[
|
||||||
|
"like",
|
||||||
|
".email",
|
||||||
|
"*@example.com"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"any",
|
||||||
|
".tags",
|
||||||
|
[
|
||||||
|
"or",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"==",
|
||||||
|
".",
|
||||||
|
"news"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"==",
|
||||||
|
".",
|
||||||
|
"press"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"sub": "did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`
|
||||||
|
// format: dagJson --> Delegation --> dagCbor --> Delegation --> dagJson
|
||||||
|
// function: FromDagJson() ToDagCbor() FromDagCbor() ToDagJson()
|
||||||
|
|
||||||
|
p1, err := delegation.FromDagJson([]byte(delegationJson))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cborBytes, err := p1.ToDagCbor()
|
||||||
|
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()
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Println("readJson length", len(readJson))
|
||||||
|
fmt.Println("json: ", string(readJson))
|
||||||
|
|
||||||
|
require.JSONEq(t, delegationJson, string(readJson))
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package delegation
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed delegation.ipldsch
|
|
||||||
var schemaBytes []byte
|
|
||||||
|
|
||||||
var (
|
|
||||||
once sync.Once
|
|
||||||
ts *schema.TypeSystem
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
func mustLoadSchema() *schema.TypeSystem {
|
|
||||||
once.Do(func() {
|
|
||||||
ts, err = ipld.LoadSchemaBytes(schemaBytes)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("failed to load IPLD schema: %s", err))
|
|
||||||
}
|
|
||||||
return ts
|
|
||||||
}
|
|
||||||
|
|
||||||
func PayloadType() schema.Type {
|
|
||||||
return mustLoadSchema().TypeByName("Payload")
|
|
||||||
}
|
|
||||||
|
|
||||||
type PayloadModel struct {
|
|
||||||
// Issuer DID (sender)
|
|
||||||
Iss string
|
|
||||||
// Audience DID (receiver)
|
|
||||||
Aud string
|
|
||||||
// Principal that the chain is about (the Subject)
|
|
||||||
// optional: can be nil
|
|
||||||
Sub *string
|
|
||||||
|
|
||||||
// The Command to eventually invoke
|
|
||||||
Cmd string
|
|
||||||
|
|
||||||
// The delegation policy
|
|
||||||
Pol datamodel.Node
|
|
||||||
|
|
||||||
// A unique, random nonce
|
|
||||||
Nonce []byte
|
|
||||||
|
|
||||||
// Arbitrary Metadata
|
|
||||||
// optional: can be nil
|
|
||||||
Meta MetaModel
|
|
||||||
|
|
||||||
// "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
|
||||||
// optional: can be nil
|
|
||||||
Nbf *int64
|
|
||||||
// The timestamp at which the Invocation becomes invalid
|
|
||||||
// optional: can be nil
|
|
||||||
Exp *int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type MetaModel struct {
|
|
||||||
Keys []string
|
|
||||||
Values map[string]datamodel.Node
|
|
||||||
}
|
|
||||||
1
delegation/testdata/new.dagjson
vendored
Normal file
1
delegation/testdata/new.dagjson
vendored
Normal 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"}}]
|
||||||
1
delegation/testdata/root.dagjson
vendored
Normal file
1
delegation/testdata/root.dagjson
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[{"/":{"bytes":"0sjiwG9BOgpezz6qw5UiD+rqOeqFLn4+Qds1PvbnsUBoc3RhF6IVxIeoOXDh1ufv3RHaI/zg4wjYpUwAMpTACw"}},{"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:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2"}}]
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package delegation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/command"
|
|
||||||
"github.com/ucan-wg/go-ucan/capability/policy"
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
|
||||||
)
|
|
||||||
|
|
||||||
type View struct {
|
|
||||||
// Issuer DID (sender)
|
|
||||||
Issuer did.DID
|
|
||||||
// Audience DID (receiver)
|
|
||||||
Audience did.DID
|
|
||||||
// Principal that the chain is about (the Subject)
|
|
||||||
Subject did.DID
|
|
||||||
// The Command to eventually invoke
|
|
||||||
Command *command.Command
|
|
||||||
// The delegation policy
|
|
||||||
Policy policy.Policy
|
|
||||||
// A unique, random nonce
|
|
||||||
Nonce []byte
|
|
||||||
// Arbitrary Metadata
|
|
||||||
Meta map[string]datamodel.Node
|
|
||||||
// "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
|
||||||
NotBefore time.Time
|
|
||||||
// The timestamp at which the Invocation becomes invalid
|
|
||||||
Expiration time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// ViewFromModel build a decoded view of the raw IPLD data.
|
|
||||||
// This function also serves as validation.
|
|
||||||
func ViewFromModel(m PayloadModel) (*View, error) {
|
|
||||||
var view View
|
|
||||||
var err error
|
|
||||||
|
|
||||||
view.Issuer, err = did.Parse(m.Iss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse iss: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.Audience, err = did.Parse(m.Aud)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse audience: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Sub != nil {
|
|
||||||
view.Subject, err = did.Parse(*m.Sub)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse subject: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view.Subject = did.Undef
|
|
||||||
}
|
|
||||||
|
|
||||||
view.Command, err = command.Parse(m.Cmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse command: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.Policy, err = policy.FromIPLD(m.Pol)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse policy: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(m.Nonce) == 0 {
|
|
||||||
return nil, fmt.Errorf("nonce is required")
|
|
||||||
}
|
|
||||||
view.Nonce = m.Nonce
|
|
||||||
|
|
||||||
// TODO: copy?
|
|
||||||
view.Meta = m.Meta.Values
|
|
||||||
|
|
||||||
if m.Nbf != nil {
|
|
||||||
view.NotBefore = time.Unix(*m.Nbf, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Exp != nil {
|
|
||||||
view.Expiration = time.Unix(*m.Exp, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &view, nil
|
|
||||||
}
|
|
||||||
48
did/crypto.go
Normal file
48
did/crypto.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package did
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
"github.com/libp2p/go-libp2p/core/crypto/pb"
|
||||||
|
"github.com/multiformats/go-multicodec"
|
||||||
|
"github.com/multiformats/go-varint"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FromPrivKey(privKey crypto.PrivKey) (DID, error) {
|
||||||
|
return FromPubKey(privKey.GetPublic())
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromPubKey(pubKey crypto.PubKey) (DID, error) {
|
||||||
|
code, ok := map[pb.KeyType]multicodec.Code{
|
||||||
|
pb.KeyType_Ed25519: multicodec.Ed25519Pub,
|
||||||
|
pb.KeyType_RSA: multicodec.RsaPub,
|
||||||
|
pb.KeyType_Secp256k1: multicodec.Secp256k1Pub,
|
||||||
|
pb.KeyType_ECDSA: multicodec.Es256,
|
||||||
|
}[pubKey.Type()]
|
||||||
|
if !ok {
|
||||||
|
return Undef, errors.New("Blah")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := varint.ToUvarint(uint64(code))
|
||||||
|
|
||||||
|
pubBytes, err := pubKey.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return DID{
|
||||||
|
str: string(append(buf, pubBytes...)),
|
||||||
|
code: uint64(code),
|
||||||
|
key: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToPubKey(s string) (crypto.PubKey, error) {
|
||||||
|
id, err := Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return id.PubKey()
|
||||||
|
}
|
||||||
51
did/crypto_test.go
Normal file
51
did/crypto_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package did_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
exampleDIDStr = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
|
||||||
|
examplePubKeyStr = "Lm/M42cB3HkUiODQsXRcweM6TByfzEHGO9ND274JcOY="
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFromPubKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
id, err := did.FromPubKey(examplePubKey(t))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, exampleDID(t), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToPubKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pubKey, err := did.ToPubKey(exampleDIDStr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, examplePubKey(t), pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exampleDID(t *testing.T) did.DID {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
id, err := did.Parse(exampleDIDStr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func examplePubKey(t *testing.T) crypto.PubKey {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
pubKeyCfg, err := crypto.ConfigDecodeKey(examplePubKeyStr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pubKey, err := crypto.UnmarshalEd25519PublicKey(pubKeyCfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return pubKey
|
||||||
|
}
|
||||||
38
did/did.go
38
did/did.go
@@ -4,7 +4,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||||
mbase "github.com/multiformats/go-multibase"
|
mbase "github.com/multiformats/go-multibase"
|
||||||
|
"github.com/multiformats/go-multicodec"
|
||||||
varint "github.com/multiformats/go-varint"
|
varint "github.com/multiformats/go-varint"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,11 +15,15 @@ const KeyPrefix = "did:key:"
|
|||||||
|
|
||||||
const DIDCore = 0x0d1d
|
const DIDCore = 0x0d1d
|
||||||
const Ed25519 = 0xed
|
const Ed25519 = 0xed
|
||||||
|
const RSA = uint64(multicodec.RsaPub)
|
||||||
|
|
||||||
var MethodOffset = varint.UvarintSize(uint64(DIDCore))
|
var MethodOffset = varint.UvarintSize(uint64(DIDCore))
|
||||||
|
|
||||||
|
//
|
||||||
|
// [did:key format]: https://w3c-ccg.github.io/did-method-key/
|
||||||
type DID struct {
|
type DID struct {
|
||||||
key bool
|
key bool
|
||||||
|
code uint64
|
||||||
str string
|
str string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,10 +42,36 @@ func (d DID) Bytes() []byte {
|
|||||||
return []byte(d.str)
|
return []byte(d.str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d DID) Code() uint64 {
|
||||||
|
return d.code
|
||||||
|
}
|
||||||
|
|
||||||
func (d DID) DID() DID {
|
func (d DID) DID() DID {
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d DID) Key() bool {
|
||||||
|
return d.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DID) PubKey() (crypto.PubKey, error) {
|
||||||
|
if !d.key {
|
||||||
|
return nil, fmt.Errorf("unsupported did type: %s", d.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
unmarshaler, ok := map[multicodec.Code]crypto.PubKeyUnmarshaller{
|
||||||
|
multicodec.Ed25519Pub: crypto.UnmarshalEd25519PublicKey,
|
||||||
|
multicodec.RsaPub: crypto.UnmarshalRsaPublicKey,
|
||||||
|
multicodec.Secp256k1Pub: crypto.UnmarshalSecp256k1PublicKey,
|
||||||
|
multicodec.Es256: crypto.UnmarshalECDSAPublicKey,
|
||||||
|
}[multicodec.Code(d.code)]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unsupported multicodec: %d", d.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return unmarshaler(d.Bytes()[varint.UvarintSize(d.code):])
|
||||||
|
}
|
||||||
|
|
||||||
// String formats the decentralized identity document (DID) as a string.
|
// String formats the decentralized identity document (DID) as a string.
|
||||||
func (d DID) String() string {
|
func (d DID) String() string {
|
||||||
if d.key {
|
if d.key {
|
||||||
@@ -54,8 +86,8 @@ func Decode(bytes []byte) (DID, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return Undef, err
|
return Undef, err
|
||||||
}
|
}
|
||||||
if code == Ed25519 {
|
if code == Ed25519 || code == RSA {
|
||||||
return DID{str: string(bytes), key: true}, nil
|
return DID{str: string(bytes), code: code, key: true}, nil
|
||||||
} else if code == DIDCore {
|
} else if code == DIDCore {
|
||||||
return DID{str: string(bytes)}, nil
|
return DID{str: string(bytes)}, nil
|
||||||
}
|
}
|
||||||
@@ -82,5 +114,5 @@ func Parse(str string) (DID, error) {
|
|||||||
varint.PutUvarint(buf, DIDCore)
|
varint.PutUvarint(buf, DIDCore)
|
||||||
suffix, _ := strings.CutPrefix(str, Prefix)
|
suffix, _ := strings.CutPrefix(str, Prefix)
|
||||||
buf = append(buf, suffix...)
|
buf = append(buf, suffix...)
|
||||||
return DID{str: string(buf)}, nil
|
return DID{str: string(buf), code: DIDCore}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
5
doc.go
Normal file
5
doc.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Package ucan provides the core functionality required to grant and
|
||||||
|
// revoke privileges via [UCAN] tokens.
|
||||||
|
//
|
||||||
|
// [UCAN]: https://ucan.xyz
|
||||||
|
package ucan
|
||||||
16
go.mod
16
go.mod
@@ -1,8 +1,8 @@
|
|||||||
module github.com/ucan-wg/go-ucan
|
module github.com/ucan-wg/go-ucan
|
||||||
|
|
||||||
go 1.21
|
go 1.22.0
|
||||||
|
|
||||||
toolchain go1.22.1
|
toolchain go1.22.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
@@ -11,23 +11,31 @@ require (
|
|||||||
github.com/libp2p/go-libp2p v0.36.2
|
github.com/libp2p/go-libp2p v0.36.2
|
||||||
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/selesy/go-options v0.0.0-20240912020512-ed2658318e52
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
|
gotest.tools/v3 v3.5.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||||
|
github.com/fatih/structtag v1.2.0 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
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
|
||||||
golang.org/x/crypto v0.25.0 // indirect
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/mod v0.21.0 // indirect
|
||||||
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
|
golang.org/x/sys v0.25.0 // indirect
|
||||||
|
golang.org/x/tools v0.25.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
|
|||||||
30
go.sum
30
go.sum
@@ -2,13 +2,19 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||||
|
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||||
|
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
|
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
|
||||||
@@ -23,6 +29,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
||||||
|
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
|
||||||
github.com/libp2p/go-libp2p v0.36.2 h1:BbqRkDaGC3/5xfaJakLV/BrpjlAuYqSB0lRvtzL3B/U=
|
github.com/libp2p/go-libp2p v0.36.2 h1:BbqRkDaGC3/5xfaJakLV/BrpjlAuYqSB0lRvtzL3B/U=
|
||||||
github.com/libp2p/go-libp2p v0.36.2/go.mod h1:XO3joasRE4Eup8yCTTP/+kX+g92mOgRaadk46LmPhHY=
|
github.com/libp2p/go-libp2p v0.36.2/go.mod h1:XO3joasRE4Eup8yCTTP/+kX+g92mOgRaadk46LmPhHY=
|
||||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||||
@@ -33,6 +41,8 @@ github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aG
|
|||||||
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
|
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
|
||||||
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
|
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
|
||||||
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
|
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
|
||||||
|
github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8RXVPT8t13tLQ=
|
||||||
|
github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII=
|
||||||
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
|
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
|
||||||
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
|
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
|
||||||
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
|
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
|
||||||
@@ -48,6 +58,8 @@ github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX
|
|||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/selesy/go-options v0.0.0-20240912020512-ed2658318e52 h1:poNWlojS+o3229ZuatLMzK9wFiLuLxo7O170Edggs0o=
|
||||||
|
github.com/selesy/go-options v0.0.0-20240912020512-ed2658318e52/go.mod h1:Cn8TrnJWCWd3dAmejFTpLN8tNVNKNoVVlZzL8ux5EWQ=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||||
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
@@ -63,13 +75,21 @@ github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvS
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
|
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||||
|
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||||
|
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
@@ -79,5 +99,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
|
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||||
|
|||||||
54
internal/cmd/token/main.go
Normal file
54
internal/cmd/token/main.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const header = `// Code generated by internal/cmd/token - DO NOT EDIT.
|
||||||
|
|
||||||
|
package token
|
||||||
|
|
||||||
|
import "github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
slog.Info("Generating Go types for token.ipldsch")
|
||||||
|
|
||||||
|
if err := Run(); err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
slog.Error("Finished but failed to generate and write token_gen.go")
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Finished generating and writing token_gen.go")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run() error {
|
||||||
|
schema, err := os.ReadFile("token.ipldsch")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug(string(schema))
|
||||||
|
|
||||||
|
typeSystem, err := ipld.LoadSchemaBytes(schema)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBufferString(header)
|
||||||
|
|
||||||
|
if err := bindnode.ProduceGoTypes(buf, typeSystem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile("token_gen.go", buf.Bytes(), 0o600)
|
||||||
|
}
|
||||||
309
internal/envelope/envelope.go
Normal file
309
internal/envelope/envelope.go
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
package envelope
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||||
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
|
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||||
|
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
"github.com/libp2p/go-libp2p/core/crypto/pb"
|
||||||
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
"github.com/ucan-wg/go-ucan/internal/token"
|
||||||
|
"github.com/ucan-wg/go-ucan/internal/varsig"
|
||||||
|
)
|
||||||
|
|
||||||
|
// [Envelope] is a signed enclosure for a UCAN v1 Token.
|
||||||
|
//
|
||||||
|
// While the types and functions in this package are not exported,
|
||||||
|
// the names used for types, fields, variables, etc generally use the
|
||||||
|
// names from the specification
|
||||||
|
//
|
||||||
|
// [Envelope]: https://github.com/ucan-wg/spec#envelope
|
||||||
|
type Envelope struct {
|
||||||
|
signature []byte
|
||||||
|
sigPayload *sigPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates an Envelope containing a VarsigHeader and Signature for
|
||||||
|
// the data resulting from wrapping the provided Token in an IPLD
|
||||||
|
// datamodel.Node and encoding it using DAG-CBOR.
|
||||||
|
func New(privKey crypto.PrivKey, token *token.Token, tag string) (*Envelope, error) {
|
||||||
|
sigPayload, err := newSigPayload(privKey.Type(), token, tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cbor, err := sigPayload.cbor()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := privKey.Sign(cbor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Envelope{
|
||||||
|
signature: signature,
|
||||||
|
sigPayload: sigPayload,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap is syntactic sugar for creating an Envelope and wrapping it as an
|
||||||
|
// IPLD datamodel.Node in a single operation.
|
||||||
|
//
|
||||||
|
// Since the Envelope itself isn't returned, use this method only when
|
||||||
|
// the IPLD datamodel.Node is used directly. If the Envelope is also
|
||||||
|
// required, use New followed by Envelope.Wrap to avoid the need to
|
||||||
|
// unwrap the newly created datamodel.Node.
|
||||||
|
func Wrap(privKey crypto.PrivKey, token *token.Token, tag string) (datamodel.Node, error) {
|
||||||
|
env, err := New(privKey, token, tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.Wrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap attempts to crate an Envelope from a datamodel.Node
|
||||||
|
//
|
||||||
|
// There are lots of ways that this can fail and therefore there are
|
||||||
|
// an almost excessive number of check included here and while
|
||||||
|
// attempting to extract the token.Token from one of the inner IPLD
|
||||||
|
// nodes.
|
||||||
|
func Unwrap(node datamodel.Node) (*Envelope, error) {
|
||||||
|
signatureNode, err := node.LookupByIndex(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := signatureNode.AsBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sigPayloadNode, err := node.LookupByIndex(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sigPayload, err := unwrapSigPayload(sigPayloadNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
envel := &Envelope{
|
||||||
|
signature: signature,
|
||||||
|
sigPayload: sigPayload,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := envel.Verify(); !ok || err != nil {
|
||||||
|
return nil, fmt.Errorf("envelope was not signed by issuer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return envel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature returns the cryptographic signature of the Envelope's
|
||||||
|
// SigPayload.
|
||||||
|
func (e *Envelope) Signature() []byte {
|
||||||
|
return e.signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag returns the key that's used to reference the TokenPayload within
|
||||||
|
// this Envelope.
|
||||||
|
func (e *Envelope) Tag() string {
|
||||||
|
return e.sigPayload.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenPayload returns the *token.Token enclosed within this Envelope.
|
||||||
|
func (e *Envelope) TokenPayload() *token.Token {
|
||||||
|
return e.sigPayload.tokenPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarsigHeader is an accessor that returns the [VarsigHeader] from the
|
||||||
|
// underlying [SigPayload] from the [Envelope].
|
||||||
|
//
|
||||||
|
// [Envelope]: https://github.com/ucan-wg/spec#envelope
|
||||||
|
// [SigPayload]: https://github.com/ucan-wg/spec#envelope
|
||||||
|
// [VarsigHeader]: https://github.com/ucan-wg/spec#envelope
|
||||||
|
func (e *Envelope) VarsigHeader() []byte {
|
||||||
|
return e.sigPayload.varsigHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify checks that the [Envelope]'s signature is correct for the
|
||||||
|
// data created by encoding the SigPayload as DAG-CBOR and the public
|
||||||
|
// key passed as the only argument.
|
||||||
|
//
|
||||||
|
// Note that for Delegation and Invocation tokens, the public key
|
||||||
|
// is retrieved from the DID's method specific identifier for the
|
||||||
|
// Issuer field.
|
||||||
|
//
|
||||||
|
// [Envelope]: https://github.com/ucan-wg/spec#envelope
|
||||||
|
func (e *Envelope) Verify() (bool, error) {
|
||||||
|
pubKey, err := did.ToPubKey(e.sigPayload.tokenPayload.Issuer)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cbor, err := e.sigPayload.cbor()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pubKey.Verify(cbor, e.signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap encodes the Envelope as an IPLD datamodel.Node.
|
||||||
|
func (e *Envelope) Wrap() (datamodel.Node, error) {
|
||||||
|
spn, err := e.sigPayload.wrap()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return qp.BuildList(basicnode.Prototype.Any, 2, func(la datamodel.ListAssembler) {
|
||||||
|
qp.ListEntry(la, qp.Bytes(e.signature))
|
||||||
|
qp.ListEntry(la, qp.Node(spn))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The types below are strictly to make it easier to Wrap and Unwrap the
|
||||||
|
// Envelope with an IPLD datamodel.Node. The Envelope itself provides
|
||||||
|
// accessors to the internals of these types.
|
||||||
|
//
|
||||||
|
|
||||||
|
type sigPayload struct {
|
||||||
|
varsigHeader []byte
|
||||||
|
tokenPayload *token.Token
|
||||||
|
tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSigPayload(keyType pb.KeyType, token *token.Token, tag string) (*sigPayload, error) {
|
||||||
|
varsigHeader, err := varsig.Encode(keyType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sigPayload{
|
||||||
|
varsigHeader: varsigHeader,
|
||||||
|
tokenPayload: token,
|
||||||
|
tag: tag,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unwrapSigPayload(node datamodel.Node) (*sigPayload, error) {
|
||||||
|
// Normally we could look up the VarsigHeader and TokenPayload using
|
||||||
|
// node.LookupByString() - this works for the "h" key used for the
|
||||||
|
// VarsigHeader but not for the TokenPayload's key (tag) as all we
|
||||||
|
// know is that it starts with "ucan/" and as explained below, must
|
||||||
|
// decode to a schema.TypedNode for the representation provided by the
|
||||||
|
// token.Prototype().
|
||||||
|
// vvv
|
||||||
|
mi := node.MapIterator()
|
||||||
|
if mi == nil {
|
||||||
|
return nil, fmt.Errorf("the SigPayload node is not a map: %s", node.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
hdrNode datamodel.Node
|
||||||
|
tknNode datamodel.Node
|
||||||
|
tag string
|
||||||
|
)
|
||||||
|
|
||||||
|
keyCount := 0
|
||||||
|
|
||||||
|
for !mi.Done() {
|
||||||
|
k, v, err := mi.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kStr, err := k.AsString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("the SigPayload keys are not strings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyCount++
|
||||||
|
|
||||||
|
if kStr == "h" {
|
||||||
|
hdrNode = v
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(kStr, "ucan/") {
|
||||||
|
tknNode = v
|
||||||
|
tag = kStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyCount != 2 {
|
||||||
|
return nil, fmt.Errorf("the SigPayload map should have exactly two keys: %d", keyCount)
|
||||||
|
}
|
||||||
|
// ^^^
|
||||||
|
|
||||||
|
// Replaces the datamodel.Node in tokenPayloadNode with a
|
||||||
|
// schema.TypedNode so that we can cast it to a *token.Token after
|
||||||
|
// unwrapping it.
|
||||||
|
// vvv
|
||||||
|
nb := token.Prototype().Representation().NewBuilder()
|
||||||
|
|
||||||
|
err := nb.AssignNode(tknNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tknNode = nb.Build()
|
||||||
|
// ^^^
|
||||||
|
|
||||||
|
tokenPayload := bindnode.Unwrap(tknNode)
|
||||||
|
if tokenPayload == nil {
|
||||||
|
return nil, errors.New("failed to Unwrap the TokenPayload")
|
||||||
|
}
|
||||||
|
|
||||||
|
tkn, ok := tokenPayload.(*token.Token)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("failed to assert the TokenPayload type as *token.Token")
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr, err := hdrNode.AsBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sigPayload{
|
||||||
|
varsigHeader: hdr,
|
||||||
|
tokenPayload: tkn,
|
||||||
|
tag: tag,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *sigPayload) cbor() ([]byte, error) {
|
||||||
|
node, err := sp.wrap()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if err = dagcbor.Encode(node, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *sigPayload) wrap() (datamodel.Node, error) {
|
||||||
|
tpn := bindnode.Wrap(sp.tokenPayload, token.Prototype().Type())
|
||||||
|
|
||||||
|
return qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
|
||||||
|
qp.MapEntry(ma, "h", qp.Bytes(sp.varsigHeader))
|
||||||
|
qp.MapEntry(ma, sp.tag, qp.Node(tpn.Representation()))
|
||||||
|
})
|
||||||
|
}
|
||||||
200
internal/envelope/envelope_test.go
Normal file
200
internal/envelope/envelope_test.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package envelope_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"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/ipld/go-ipld-prime/fluent/qp"
|
||||||
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
|
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
"github.com/ucan-wg/go-ucan/internal/envelope"
|
||||||
|
"github.com/ucan-wg/go-ucan/internal/token"
|
||||||
|
"gotest.tools/v3/golden"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
exampleDID = "did:key:z6MkpuK2Amsu1RqcLGgmHHQHhvmeXCCBVsM4XFSg2cCyg4Nh"
|
||||||
|
examplePrivKeyCfg = "CAESQP9v2uqECTuIi45dyg3znQvsryvf2IXmOF/6aws6aCehm0FVrj0zHR5RZSDxWNjcpcJqsGym3sjCungX9Zt5oA4="
|
||||||
|
exampleSignatureStr = "PZV6A2aI7n+MlyADqcqmWhkuyNrgUCDz+qSLSnI9bpasOwOhKUTx95m5Nu5CO/INa1LqzHGioD9+PVf6qdtTBg"
|
||||||
|
exampleTag = "ucan/example@v1.0.0-rc.1"
|
||||||
|
exampleVarsigHeaderStr = "NO0BcQ"
|
||||||
|
|
||||||
|
invalidSignatureStr = "PZV6A2aI7n+MlyADqcqmWhkuyNrgUCDz+qSLSnI9bpasOwOhKUTx95m5Nu5CO/INa1LqzHGioD9+PVf6qdtTBK"
|
||||||
|
|
||||||
|
exampleDAGCBORFilename = "example.dagcbor"
|
||||||
|
exampleDAGJSONFilename = "example.dagjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
envel := exampleEnvelope(t)
|
||||||
|
assert.NotZero(t, envel)
|
||||||
|
|
||||||
|
assert.Equal(t, exampleSignature(t), envel.Signature())
|
||||||
|
assert.Equal(t, exampleTag, envel.Tag())
|
||||||
|
assert.Equal(t, exampleVarsigHeader(t), envel.VarsigHeader())
|
||||||
|
assert.EqualValues(t, exampleGoldenTokenPayload(t), envel.TokenPayload())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
node, err := envelope.Wrap(examplePrivKey(t), exampleToken(t), exampleTag)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cbor, err := ipld.Encode(node, dagcbor.Encode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
golden.AssertBytes(t, cbor, exampleDAGCBORFilename)
|
||||||
|
|
||||||
|
json, err := ipld.Encode(node, dagjson.Encode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
golden.Assert(t, string(json), exampleDAGJSONFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvelope_Verify(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("valid signature by issuer", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
envel := exampleEnvelope(t)
|
||||||
|
ok, err := envel.Verify()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid signature by wrong issuer", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
envel, err := envelope.Unwrap(invalidNodeFromGolden(t))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ok, _ := envel.Verify()
|
||||||
|
assert.False(t, ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvelope_Wrap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
envel := exampleEnvelope(t)
|
||||||
|
|
||||||
|
node, err := envel.Wrap()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cbor, err := ipld.Encode(node, dagcbor.Encode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, golden.Get(t, exampleDAGCBORFilename), cbor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exampleGoldenEnvelope(t *testing.T) *envelope.Envelope {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
envel, err := envelope.Unwrap(exampleGoldenNode(t))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return envel
|
||||||
|
}
|
||||||
|
|
||||||
|
func exampleGoldenNode(t *testing.T) datamodel.Node {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cbor := golden.Get(t, exampleDAGCBORFilename)
|
||||||
|
|
||||||
|
node, err := ipld.Decode(cbor, dagcbor.Decode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func exampleGoldenTokenPayload(t *testing.T) *token.Token {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return exampleGoldenEnvelope(t).TokenPayload()
|
||||||
|
}
|
||||||
|
|
||||||
|
func examplePrivKey(t *testing.T) crypto.PrivKey {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
privKeyEnc, err := crypto.ConfigDecodeKey(examplePrivKeyCfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
privKey, err := crypto.UnmarshalPrivateKey(privKeyEnc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return privKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func exampleEnvelope(t *testing.T) *envelope.Envelope {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
envel, err := envelope.New(examplePrivKey(t), exampleToken(t), exampleTag)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return envel
|
||||||
|
}
|
||||||
|
|
||||||
|
func examplePubKey(t *testing.T) crypto.PubKey {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return examplePrivKey(t).GetPublic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func exampleSignature(t *testing.T) []byte {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
sig, err := base64.RawStdEncoding.DecodeString(exampleSignatureStr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return sig
|
||||||
|
}
|
||||||
|
|
||||||
|
func exampleToken(t *testing.T) *token.Token {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
id, err := did.FromPubKey(examplePubKey(t))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &token.Token{
|
||||||
|
Issuer: id.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func exampleVarsigHeader(t *testing.T) []byte {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
hdr, err := base64.RawStdEncoding.DecodeString(exampleVarsigHeaderStr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return hdr
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalidNodeFromGolden(t *testing.T) datamodel.Node {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
invalidSig, err := base64.RawStdEncoding.DecodeString(invalidSignatureStr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
envelNode := exampleGoldenNode(t)
|
||||||
|
sigPayloadNode, err := envelNode.LookupByIndex(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
node, err := qp.BuildList(basicnode.Prototype.Any, 2, func(la datamodel.ListAssembler) {
|
||||||
|
qp.ListEntry(la, qp.Bytes(invalidSig))
|
||||||
|
qp.ListEntry(la, qp.Node(sigPayloadNode))
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
1
internal/envelope/testdata/example.dagcbor
vendored
Normal file
1
internal/envelope/testdata/example.dagcbor
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
‚X@=•zfˆîŒ— ©Ê¦Z.ÈÚàP óú¤‹Jr=n–¬;¡)Dñ÷™¹6îB;ò
|
||||||
20
internal/envelope/testdata/example.dagjson
vendored
Normal file
20
internal/envelope/testdata/example.dagjson
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"/": {
|
||||||
|
"bytes": "PZV6A2aI7n+MlyADqcqmWhkuyNrgUCDz+qSLSnI9bpasOwOhKUTx95m5Nu5CO/INa1LqzHGioD9+PVf6qdtTBg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": {
|
||||||
|
"/": {
|
||||||
|
"bytes": "NO0BcQ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ucan/example@v1.0.0-rc.1": {
|
||||||
|
"cmd": "",
|
||||||
|
"exp": null,
|
||||||
|
"iss": "did:key:z6MkpuK2Amsu1RqcLGgmHHQHhvmeXCCBVsM4XFSg2cCyg4Nh",
|
||||||
|
"sub": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
24
internal/token/conversion.go
Normal file
24
internal/token/conversion.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToIPLDMapStringAny(m map[string]datamodel.Node) Map__String__Any {
|
||||||
|
keys := make([]string, len(m))
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range m {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return Map__String__Any{
|
||||||
|
Keys: keys,
|
||||||
|
Values: m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromIPLDMapStringAny(m Map__String__Any) map[string]datamodel.Node {
|
||||||
|
return m.Values
|
||||||
|
}
|
||||||
33
internal/token/doc.go
Normal file
33
internal/token/doc.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Package token provides a generic model of the [TokenPayload] required
|
||||||
|
// within an Envelope.
|
||||||
|
//
|
||||||
|
// # Field requirements
|
||||||
|
//
|
||||||
|
// While the Token object represents the wire format of both a UCAN
|
||||||
|
// Delegation token and a UCAN Invocation token, the delegation and
|
||||||
|
// invocation packages are, respectively, responsible for making sure
|
||||||
|
// required fields are included when creating a new Token or when
|
||||||
|
// validating the contents of an Envelope as it's received from
|
||||||
|
// another party. The following table shows the current (as of
|
||||||
|
// 2024-09-11) relationship between optional and nullable fields in
|
||||||
|
// the delegation and invocation views and the payload model:
|
||||||
|
//
|
||||||
|
// | Name | Delegation | Invocation | Token |
|
||||||
|
// | | Required | Nullable | Required | Nullable | |
|
||||||
|
// | ----- | -------- | -------- | -------- | -------- | -------- |
|
||||||
|
// | iss | Yes | No | Yes | No | |
|
||||||
|
// | aud | Yes | No | No | N/A | Optional |
|
||||||
|
// | sub | Yes | Yes | Yes | No | Nullable |
|
||||||
|
// | cmd | Yes | No | Yes | No | |
|
||||||
|
// | pol | Yes | No | X | X | Optional |
|
||||||
|
// | nonce | Yes | No | No | N/A | Optional |
|
||||||
|
// | meta | No | N/A | No | N/A | Optional |
|
||||||
|
// | nbf | No | N/A | X | X | Optional |
|
||||||
|
// | exp | Yes | Yes | Yes | Yes | |
|
||||||
|
// | args | X | X | Yes | No | Optional |
|
||||||
|
// | prf | X | X | Yes | No | Optional |
|
||||||
|
// | iat | X | X | No | N/A | Optional |
|
||||||
|
// | cause | X | X | No | N/A | Optional |
|
||||||
|
//
|
||||||
|
// [TokenPayload]: https://github.com/ucan-wg/spec?tab=readme-ov-file#envelope
|
||||||
|
package token
|
||||||
11
internal/token/errors.go
Normal file
11
internal/token/errors.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrFailedSchemaLoad = errors.New("failed to load IPLD Schema")
|
||||||
|
|
||||||
|
var ErrNoSchemaType = errors.New("schema does not contain type")
|
||||||
|
|
||||||
|
var ErrNodeNotToken = errors.New("IPLD node is not a Token")
|
||||||
|
|
||||||
|
var ErrMissingRequiredDID = errors.New("a required DID is missing")
|
||||||
46
internal/token/schema.go
Normal file
46
internal/token/schema.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||||
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tokenTypeName = "Token"
|
||||||
|
|
||||||
|
//go:embed token.ipldsch
|
||||||
|
var schemaBytes []byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
ts *schema.TypeSystem
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustLoadSchema() *schema.TypeSystem {
|
||||||
|
once.Do(func() {
|
||||||
|
ts, err = ipld.LoadSchemaBytes(schemaBytes)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("%w: %w", ErrFailedSchemaLoad, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
tknType := ts.TypeByName(tokenTypeName)
|
||||||
|
if tknType == nil {
|
||||||
|
panic(fmt.Errorf("%w: %s", ErrNoSchemaType, tokenTypeName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenType() schema.Type {
|
||||||
|
return mustLoadSchema().TypeByName(tokenTypeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Prototype() schema.TypedPrototype {
|
||||||
|
return bindnode.Prototype((*Token)(nil), tokenType())
|
||||||
|
}
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
package delegation
|
package token_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/ucan-wg/go-ucan/internal/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed token.ipldsch
|
||||||
|
var schemaBytes []byte
|
||||||
|
|
||||||
func TestSchemaRoundTrip(t *testing.T) {
|
func TestSchemaRoundTrip(t *testing.T) {
|
||||||
const delegationJson = `
|
const delegationJson = `
|
||||||
{
|
{
|
||||||
@@ -40,22 +47,27 @@ func TestSchemaRoundTrip(t *testing.T) {
|
|||||||
"sub":""
|
"sub":""
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
|
// format: dagJson --> IPLD node --> token --> dagCbor --> IPLD node --> dagJson
|
||||||
// function: DecodeDagJson() EncodeDagCbor() DecodeDagCbor() EncodeDagJson()
|
// function: Unwrap() Wrap()
|
||||||
|
|
||||||
p1, err := DecodeDagJson([]byte(delegationJson))
|
n1, err := ipld.DecodeUsingPrototype([]byte(delegationJson), dagjson.Decode, token.Prototype())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
cborBytes, err := p1.EncodeDagCbor()
|
cborBytes, err := ipld.Encode(n1, dagcbor.Encode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
fmt.Println("cborBytes length", len(cborBytes))
|
fmt.Println("cborBytes length", len(cborBytes))
|
||||||
fmt.Println("cbor", string(cborBytes))
|
fmt.Println("cbor", string(cborBytes))
|
||||||
|
|
||||||
p2, err := DecodeDagCbor(cborBytes)
|
n2, err := ipld.DecodeUsingPrototype(cborBytes, dagcbor.Decode, token.Prototype())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
fmt.Println("read Cbor", p2)
|
fmt.Println("read Cbor", n2)
|
||||||
|
|
||||||
readJson, err := p2.EncodeDagJson()
|
t1, err := token.Unwrap(n2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
n3 := t1.Wrap()
|
||||||
|
|
||||||
|
readJson, err := ipld.Encode(n3, dagjson.Encode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
fmt.Println("readJson length", len(readJson))
|
fmt.Println("readJson length", len(readJson))
|
||||||
fmt.Println("json: ", string(readJson))
|
fmt.Println("json: ", string(readJson))
|
||||||
@@ -65,6 +77,7 @@ func TestSchemaRoundTrip(t *testing.T) {
|
|||||||
|
|
||||||
func BenchmarkSchemaLoad(b *testing.B) {
|
func BenchmarkSchemaLoad(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = ipld.LoadSchemaBytes(schemaBytes)
|
_, _ = ipld.LoadSchemaBytes(schemaBytes)
|
||||||
}
|
}
|
||||||
33
internal/token/token.go
Normal file
33
internal/token/token.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run ../cmd/token/...
|
||||||
|
|
||||||
|
// Unwrap creates a Token from an arbitrary IPLD node or returns an
|
||||||
|
// error if at least the required model fields are not present.
|
||||||
|
//
|
||||||
|
// It is the responsibility of the Delegation and Invocation views
|
||||||
|
// to further validate the presence of the required fields and the
|
||||||
|
// content as needed.
|
||||||
|
func Unwrap(node datamodel.Node) (*Token, error) {
|
||||||
|
iface := bindnode.Unwrap(node)
|
||||||
|
if iface == nil {
|
||||||
|
return nil, ErrNodeNotToken
|
||||||
|
}
|
||||||
|
|
||||||
|
tkn, ok := iface.(*Token)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrNodeNotToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return tkn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap creates an IPLD node representing the Token.
|
||||||
|
func (t *Token) Wrap() datamodel.Node {
|
||||||
|
return bindnode.Wrap(t, tokenType())
|
||||||
|
}
|
||||||
63
internal/token/token.ipldsch
Normal file
63
internal/token/token.ipldsch
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
type CID string
|
||||||
|
|
||||||
|
type Command string
|
||||||
|
|
||||||
|
type DID string
|
||||||
|
|
||||||
|
# Field requirements:
|
||||||
|
#
|
||||||
|
# | Name | Delegation | Invocation | Token |
|
||||||
|
# | | Required | Nullable | Required | Nullable | |
|
||||||
|
# | ----- | -------- | -------- | -------- | -------- | -------- |
|
||||||
|
# | iss | Yes | No | Yes | No | |
|
||||||
|
# | aud | Yes | No | No | N/A | Optional |
|
||||||
|
# | sub | Yes | Yes | Yes | No | Nullable |
|
||||||
|
# | cmd | Yes | No | Yes | No | |
|
||||||
|
# | pol | Yes | No | X | X | Optional |
|
||||||
|
# | nonce | Yes | No | No | N/A | Optional |
|
||||||
|
# | meta | No | N/A | No | N/A | Optional |
|
||||||
|
# | nbf | No | N/A | X | X | Optional |
|
||||||
|
# | exp | Yes | Yes | Yes | Yes | Nullable |
|
||||||
|
# | args | X | X | Yes | No | Optional |
|
||||||
|
# | prf | X | X | Yes | No | Optional |
|
||||||
|
# | iat | X | X | No | N/A | Optional |
|
||||||
|
# | cause | X | X | No | N/A | Optional |
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
# Issuer DID (sender)
|
||||||
|
issuer DID (rename "iss")
|
||||||
|
# Audience DID (receiver)
|
||||||
|
audience optional DID (rename "aud")
|
||||||
|
# Principal that the chain is about (the Subject)
|
||||||
|
subject nullable DID (rename "sub")
|
||||||
|
|
||||||
|
# The Command to eventually invoke
|
||||||
|
command Command (rename "cmd")
|
||||||
|
|
||||||
|
# The delegation policy
|
||||||
|
# It doesn't seem possible to represent it with a schema.
|
||||||
|
policy optional Any (rename "pol")
|
||||||
|
|
||||||
|
# The invocation's arguments
|
||||||
|
arguments optional {String: Any} (rename "args")
|
||||||
|
|
||||||
|
# Delegations that prove the chain of authority
|
||||||
|
Proofs optional [CID] (rename "prf")
|
||||||
|
|
||||||
|
# A unique, random nonce
|
||||||
|
nonce optional Bytes
|
||||||
|
|
||||||
|
# Arbitrary Metadata
|
||||||
|
meta optional {String : Any}
|
||||||
|
|
||||||
|
# "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
||||||
|
notBefore optional Int (rename "nbf")
|
||||||
|
# The timestamp at which the delegation becomes invalid
|
||||||
|
expiration nullable Int (rename "exp")
|
||||||
|
# The timestamp at which the invocation was created
|
||||||
|
issuedAt optional Int
|
||||||
|
|
||||||
|
# An optional CID of the receipt that enqueued this invocation
|
||||||
|
cause optional CID
|
||||||
|
}
|
||||||
31
internal/token/token_gen.go
Normal file
31
internal/token/token_gen.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Code generated by internal/cmd/token - DO NOT EDIT.
|
||||||
|
|
||||||
|
package token
|
||||||
|
|
||||||
|
import "github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
|
||||||
|
type Map struct {
|
||||||
|
Keys []string
|
||||||
|
Values map[string]datamodel.Node
|
||||||
|
}
|
||||||
|
type List []datamodel.Node
|
||||||
|
type Map__String__Any struct {
|
||||||
|
Keys []string
|
||||||
|
Values map[string]datamodel.Node
|
||||||
|
}
|
||||||
|
type List__CID []string
|
||||||
|
type Token struct {
|
||||||
|
Issuer string
|
||||||
|
Audience *string
|
||||||
|
Subject *string
|
||||||
|
Command string
|
||||||
|
Policy *datamodel.Node
|
||||||
|
Arguments *Map__String__Any
|
||||||
|
Proofs *List__CID
|
||||||
|
Nonce *[]uint8
|
||||||
|
Meta *Map__String__Any
|
||||||
|
NotBefore *int
|
||||||
|
Expiration *int
|
||||||
|
IssuedAt *int
|
||||||
|
Cause *string
|
||||||
|
}
|
||||||
55
internal/token/token_test.go
Normal file
55
internal/token/token_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package token_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/ucan-wg/go-ucan/internal/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tkn := &token.Token{}
|
||||||
|
|
||||||
|
node := tkn.Wrap()
|
||||||
|
|
||||||
|
json, err := ipld.Encode(node, dagjson.Encode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Log(string(json))
|
||||||
|
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrototype(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tkn := &token.Token{
|
||||||
|
Issuer: "blah",
|
||||||
|
}
|
||||||
|
n1 := tkn.Wrap()
|
||||||
|
json, err := ipld.Encode(n1, dagjson.Encode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Log(string(json))
|
||||||
|
|
||||||
|
n2, err := ipld.Decode(json, dagjson.Decode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
nb := token.Prototype().Representation().NewBuilder()
|
||||||
|
require.NoError(t, nb.AssignNode(n2))
|
||||||
|
|
||||||
|
n3 := nb.Build()
|
||||||
|
|
||||||
|
tkn2, err := token.Unwrap(n3)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Log(tkn2)
|
||||||
|
|
||||||
|
require.Equal(t, tkn, tkn2)
|
||||||
|
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
7
internal/tools/tools.go
Normal file
7
internal/tools/tools.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build tools
|
||||||
|
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/selesy/go-options"
|
||||||
|
)
|
||||||
@@ -58,7 +58,7 @@ var (
|
|||||||
//
|
//
|
||||||
// [go-libp2p/core/crypto]: github.com/libp2p/go-libp2p/core/crypto
|
// [go-libp2p/core/crypto]: github.com/libp2p/go-libp2p/core/crypto
|
||||||
func Decode(header []byte) (pb.KeyType, error) {
|
func Decode(header []byte) (pb.KeyType, error) {
|
||||||
keyType, ok := decMap[string(header)]
|
keyType, ok := decMap[base64.RawStdEncoding.EncodeToString(header)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return -1, fmt.Errorf("%w: %s", ErrUnknownHeader, header)
|
return -1, fmt.Errorf("%w: %s", ErrUnknownHeader, header)
|
||||||
}
|
}
|
||||||
@@ -82,10 +82,10 @@ func Encode(keyType pb.KeyType) ([]byte, error) {
|
|||||||
return []byte(header), nil
|
return []byte(header), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyTypeToHeader() map[pb.KeyType]string {
|
func keyTypeToHeader() map[pb.KeyType][]byte {
|
||||||
const rsaSigLen = 0x100
|
const rsaSigLen = 0x100
|
||||||
|
|
||||||
return map[pb.KeyType]string{
|
return map[pb.KeyType][]byte{
|
||||||
pb.KeyType_RSA: header(
|
pb.KeyType_RSA: header(
|
||||||
Prefix,
|
Prefix,
|
||||||
multicodec.RsaPub,
|
multicodec.RsaPub,
|
||||||
@@ -117,18 +117,18 @@ func headerToKeyType() map[string]pb.KeyType {
|
|||||||
out := make(map[string]pb.KeyType, len(encMap))
|
out := make(map[string]pb.KeyType, len(encMap))
|
||||||
|
|
||||||
for keyType, header := range encMap {
|
for keyType, header := range encMap {
|
||||||
out[header] = keyType
|
out[base64.RawStdEncoding.EncodeToString(header)] = keyType
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func header(vals ...multicodec.Code) string {
|
func header(vals ...multicodec.Code) []byte {
|
||||||
var buf []byte
|
var buf []byte
|
||||||
|
|
||||||
for _, val := range vals {
|
for _, val := range vals {
|
||||||
buf = binary.AppendUvarint(buf, uint64(val))
|
buf = binary.AppendUvarint(buf, uint64(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64.RawStdEncoding.EncodeToString(buf)
|
return buf
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,14 @@ func TestDecode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExampleDecode() {
|
func ExampleDecode() {
|
||||||
keyType, _ := varsig.Decode([]byte("NIUkEoACcQ"))
|
hdr, err := base64.RawStdEncoding.DecodeString("NIUkEoACcQ")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyType, _ := varsig.Decode(hdr)
|
||||||
fmt.Println(keyType.String())
|
fmt.Println(keyType.String())
|
||||||
// Output:
|
// Output:
|
||||||
// RSA
|
// RSA
|
||||||
@@ -37,7 +44,7 @@ func TestEncode(t *testing.T) {
|
|||||||
|
|
||||||
func ExampleEncode() {
|
func ExampleEncode() {
|
||||||
header, _ := varsig.Encode(pb.KeyType_RSA)
|
header, _ := varsig.Encode(pb.KeyType_RSA)
|
||||||
fmt.Println(string(header))
|
fmt.Println(base64.RawStdEncoding.EncodeToString(header))
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// NIUkEoACcQ
|
// NIUkEoACcQ
|
||||||
|
|||||||
Reference in New Issue
Block a user