Skip to content

Commit

Permalink
chore(envelope): refactor to decode token.Token to schema.TypedNode
Browse files Browse the repository at this point in the history
  • Loading branch information
smoyer64 committed Sep 13, 2024
1 parent 16f0f38 commit 7cead1b
Show file tree
Hide file tree
Showing 17 changed files with 363 additions and 291 deletions.
18 changes: 9 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
module github.com/ucan-wg/go-ucan

go 1.21
go 1.22.0

toolchain go1.22.1
toolchain go1.22.4

require (
github.com/gobwas/glob v0.2.3
github.com/ipfs/go-cid v0.4.1
github.com/ipld/go-ipld-prime v0.21.0
github.com/launchdarkly/go-options v1.3.0
github.com/libp2p/go-libp2p v0.36.2
github.com/multiformats/go-multibase v0.2.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/selesy/go-options v0.0.0-20240912020512-ed2658318e52
github.com/stretchr/testify v1.9.0
gotest.tools/v3 v3.5.1
)

require (
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.0.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/minio/sha256-simd v1.0.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.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/polydawn/refmt v0.89.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/tools v0.23.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
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
Expand Down
105 changes: 12 additions & 93 deletions go.sum

Large diffs are not rendered by default.

219 changes: 116 additions & 103 deletions internal/envelope/envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@ package envelope
import (
"bytes"
"errors"
"fmt"
"strings"

"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"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/multiformats/go-multibase"
"github.com/multiformats/go-multicodec"
"github.com/multiformats/go-multihash"
"github.com/multiformats/go-varint"
"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 types implementing Tokener.
// [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 {
Expand All @@ -29,8 +31,8 @@ type Envelope struct {
}

// New creates an Envelope containing a VarsigHeader and Signature for
// the data resulting from wrapping the provided Tokener in and IPLD
// datamodel.Node and encoding it using DAG-CBOR
// 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 {
Expand All @@ -56,7 +58,7 @@ func New(privKey crypto.PrivKey, token *token.Token, tag string) (*Envelope, err
// 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, us this method only when
// 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.
Expand All @@ -69,7 +71,13 @@ func Wrap(privKey crypto.PrivKey, token *token.Token, tag string) (datamodel.Nod
return env.Wrap()
}

func Unwrap(node datamodel.Node, tag string) (*Envelope, error) {
// 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
Expand All @@ -85,7 +93,7 @@ func Unwrap(node datamodel.Node, tag string) (*Envelope, error) {
return nil, err
}

sigPayload, err := unwrapSigPayload(sigPayloadNode, tag)
sigPayload, err := unwrapSigPayload(sigPayloadNode)
if err != nil {
return nil, err
}
Expand All @@ -96,35 +104,20 @@ func Unwrap(node datamodel.Node, tag string) (*Envelope, error) {
}, nil
}

func (e *Envelope) CID() (cid.Cid, error) {
node, err := e.Wrap()
if err != nil {
return cid.Undef, nil
}

cbor, err := ipld.Encode(node, dagcbor.Encode)
if err != nil {
return cid.Undef, nil
}

data := varint.ToUvarint(uint64(multicodec.DagCbor))
data = append(data, cbor...)

hash, err := multihash.Sum(data, uint64(multicodec.Sha2_256), -1)
if err != nil {
return cid.Undef, err
}

return cid.NewCidV1(multibase.Base58BTC, hash), nil
}

// Signature is an accessor that returns the cryptographic signature
// that was created when the Envelope was created or unwrapped.
// Signature returns the cryptographic signature of the Envelope's
// SigPayload.
func (e *Envelope) Signature() []byte {
return e.signature
}

func (e *Envelope) Token() *token.Token {
// 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
}

Expand All @@ -142,11 +135,17 @@ func (e *Envelope) VarsigHeader() []byte {
// data created by encoding the SigPayload as DAG-CBOR and the public
// key passed as the only argument.
//
// Note that for Delegation and Invocation Tokeners, the public key
// is retrieved from the DID's method specific identifier.
// 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(pubKey crypto.PubKey) (bool, error) {
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
Expand All @@ -157,35 +156,15 @@ func (e *Envelope) Verify(pubKey crypto.PubKey) (bool, error) {

// Wrap encodes the Envelope as an IPLD datamodel.Node.
func (e *Envelope) Wrap() (datamodel.Node, error) {
sn := bindnode.Wrap(&e.signature, nil)

spn, err := e.sigPayload.wrap()
if err != nil {
return nil, err
}

np := basicnode.Prototype.Any

lb := np.NewBuilder()

la, err := lb.BeginList(2)
if err != nil {
return nil, err
}

if err = la.AssembleValue().AssignNode(sn); err != nil {
return nil, err
}

if err := la.AssembleValue().AssignNode(spn); err != nil {
return nil, err
}

if err := la.Finish(); err != nil {
return nil, err
}

return lb.Build(), nil
return qp.BuildList(basicnode.Prototype.Any, 2, func(la datamodel.ListAssembler) {
qp.ListEntry(la, qp.Bytes(e.signature))
qp.ListEntry(la, qp.Node(spn))
})
}

//
Expand Down Expand Up @@ -213,35 +192,90 @@ func newSigPayload(keyType pb.KeyType, token *token.Token, tag string) (*sigPayl
}, nil
}

func unwrapSigPayload(node datamodel.Node, tag string) (*sigPayload, error) {
tokenPayloadNode, err := node.LookupByString(tag)
if err != nil {
return nil, err
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())
}

tokenPayload := bindnode.Unwrap(tokenPayloadNode)
if tokenPayload == nil {
return nil, errors.New("unexpected type") // TODO
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
}
}

token, ok := tokenPayload.(*token.Token)
if !ok {
return nil, errors.New("unexpected type") // TODO
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()

headerNode, err := node.LookupByString("h")
err := nb.AssignNode(tknNode)
if err != nil {
return nil, err
}

header, err := headerNode.AsBytes()
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: header,
tokenPayload: token,
varsigHeader: hdr,
tokenPayload: tkn,
tag: tag,
}, nil
}

Expand All @@ -262,29 +296,8 @@ func (sp *sigPayload) cbor() ([]byte, error) {
func (sp *sigPayload) wrap() (datamodel.Node, error) {
tpn := bindnode.Wrap(sp.tokenPayload, token.Prototype().Type())

np := basicnode.Prototype.Any
mb := np.NewBuilder()

ma, err := mb.BeginMap(2)
if err != nil {
return nil, err
}

ha, err := ma.AssembleEntry("h")
if err != nil {
return nil, err
}
ha.AssignBytes(sp.varsigHeader)

ta, err := ma.AssembleEntry(sp.tag)
if err != nil {
return nil, err
}
ta.AssignNode(tpn.Representation())

if err := ma.Finish(); err != nil {
return nil, err
}

return mb.Build(), nil
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()))
})
}
Loading

0 comments on commit 7cead1b

Please sign in to comment.