From c66dd5b2a4b12b51d62652d085079f2423aea061 Mon Sep 17 00:00:00 2001 From: Steve Moyer Date: Wed, 18 Sep 2024 08:22:28 -0400 Subject: [PATCH] feat(envelope): decode functions also return the Envelope's CID --- internal/envelope/ipld.go | 77 +++++++++++++++++++--------------- internal/envelope/ipld_test.go | 10 ++--- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/internal/envelope/ipld.go b/internal/envelope/ipld.go index 3dffc3c..96b687f 100644 --- a/internal/envelope/ipld.go +++ b/internal/envelope/ipld.go @@ -8,7 +8,8 @@ // // Decoding functions in this package likewise perform the signature // verification using a public key extracted from the TokenPayload as -// described by requirement two below. +// described by requirement two below. Additionally, the decode functions +// also return the CID for the verified Envelope. // // Types that wish to be marshaled and unmarshaled from the using // is package have two requirements. @@ -20,8 +21,8 @@ // This field must contain the string representation of a // "did:key" so that a public key can be extracted from the // -// [Envelope]: -// [TokenPayload]: +// [Envelope]:https://github.com/ucan-wg/spec#envelope +// [TokenPayload]: https://github.com/ucan-wg/spec#envelope // [UCAN]: https://ucan.xyz package envelope @@ -31,6 +32,7 @@ import ( "io" "strings" + "github.com/ipfs/go-cid" "github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime/codec" "github.com/ipld/go-ipld-prime/codec/dagcbor" @@ -45,8 +47,17 @@ import ( "github.com/ucan-wg/go-ucan/internal/varsig" ) +// Tokener must be implemented by types that wish to be enclosed in a +// UCAN Envelope (presumbably one of the UCAN token types). type Tokener interface { + // Prototype provides the schema representation for an IPLD type so + // that the incoming datamodel.Kinds can be mapped to the appropriate + // schema.Kinds. Prototype() schema.TypedPrototype + + // Tag returns the expected key denoting the name of the IPLD node + // that should be processed as the token payload while decoding + // incoming bytes. Tag() string } @@ -55,20 +66,20 @@ type Tokener interface { // // An error is returned if the conversion fails, or if the resulting // Tokener is invalid. -func Decode[T Tokener](b []byte, decFn codec.Decoder) (T, error) { +func Decode[T Tokener](b []byte, decFn codec.Decoder) (T, cid.Cid, error) { node, err := ipld.Decode(b, decFn) if err != nil { - return *new(T), err + return *new(T), cid.Undef, err } return FromIPLD[T](node) } // DecodeReader is the same as Decode, but accept an io.Reader. -func DecodeReader[T Tokener](r io.Reader, decFn codec.Decoder) (T, error) { +func DecodeReader[T Tokener](r io.Reader, decFn codec.Decoder) (T, cid.Cid, error) { node, err := ipld.DecodeStreaming(r, decFn) if err != nil { - return *new(T), err + return *new(T), cid.Undef, err } return FromIPLD[T](node) @@ -78,12 +89,12 @@ func DecodeReader[T Tokener](r io.Reader, decFn codec.Decoder) (T, error) { // // An error is returned if the conversion fails, or if the resulting // Tokener is invalid. -func FromDagCbor[T Tokener](b []byte) (T, error) { +func FromDagCbor[T Tokener](b []byte) (T, cid.Cid, error) { return Decode[T](b, dagcbor.Decode) } // FromDagCborReader is the same as FromDagCbor, but accept an io.Reader. -func FromDagCborReader[T Tokener](r io.Reader) (T, error) { +func FromDagCborReader[T Tokener](r io.Reader) (T, cid.Cid, error) { return DecodeReader[T](r, dagcbor.Decode) } @@ -91,12 +102,12 @@ func FromDagCborReader[T Tokener](r io.Reader) (T, error) { // // An error is returned if the conversion fails, or if the resulting // Tokener is invalid. -func FromDagJson[T Tokener](b []byte) (T, error) { +func FromDagJson[T Tokener](b []byte) (T, cid.Cid, error) { return Decode[T](b, dagjson.Decode) } // FromDagJsonReader is the same as FromDagJson, but accept an io.Reader. -func FromDagJsonReader[T Tokener](r io.Reader) (T, error) { +func FromDagJsonReader[T Tokener](r io.Reader) (T, cid.Cid, error) { return DecodeReader[T](r, dagjson.Decode) } @@ -104,22 +115,22 @@ func FromDagJsonReader[T Tokener](r io.Reader) (T, error) { // // An error is returned if the conversion fails, or if the resulting // Tokener is invalid. -func FromIPLD[T Tokener](node datamodel.Node) (T, error) { +func FromIPLD[T Tokener](node datamodel.Node) (T, cid.Cid, error) { undef := *new(T) signatureNode, err := node.LookupByIndex(0) if err != nil { - return undef, err + return undef, cid.Undef, err } signature, err := signatureNode.AsBytes() if err != nil { - return undef, err + return undef, cid.Undef, err } sigPayloadNode, err := node.LookupByIndex(1) if err != nil { - return undef, err + return undef, cid.Undef, err } // Normally we could look up the VarsigHeader and TokenPayload using @@ -131,7 +142,7 @@ func FromIPLD[T Tokener](node datamodel.Node) (T, error) { // vvv mi := sigPayloadNode.MapIterator() if mi == nil { - return undef, fmt.Errorf("the SigPayload node is not a map: %s", sigPayloadNode.Kind().String()) + return undef, cid.Undef, fmt.Errorf("the SigPayload node is not a map: %s", sigPayloadNode.Kind().String()) } var ( @@ -145,12 +156,12 @@ func FromIPLD[T Tokener](node datamodel.Node) (T, error) { for !mi.Done() { k, v, err := mi.Next() if err != nil { - return undef, err + return undef, cid.Undef, err } kStr, err := k.AsString() if err != nil { - return undef, fmt.Errorf("the SigPayload keys are not strings: %w", err) + return undef, cid.Undef, fmt.Errorf("the SigPayload keys are not strings: %w", err) } keyCount++ @@ -168,11 +179,11 @@ func FromIPLD[T Tokener](node datamodel.Node) (T, error) { } if keyCount != 2 { - return undef, fmt.Errorf("the SigPayload map should have exactly two keys: %d", keyCount) + return undef, cid.Undef, fmt.Errorf("the SigPayload map should have exactly two keys: %d", keyCount) } if undef.Tag() != tag { - return undef, fmt.Errorf("the TokenPayload tag doesn't match the Tokener tag: expected %s, got %s", undef.Tag(), tag) + return undef, cid.Undef, fmt.Errorf("the TokenPayload tag doesn't match the Tokener tag: expected %s, got %s", undef.Tag(), tag) } // This needs to be done before converting this node to it's schema @@ -180,7 +191,7 @@ func FromIPLD[T Tokener](node datamodel.Node) (T, error) { // to use the wire name). issuerNode, err := tokenPayloadNode.LookupByString("iss") if err != nil { - return undef, err + return undef, cid.Undef, err } // ^^^ @@ -193,7 +204,7 @@ func FromIPLD[T Tokener](node datamodel.Node) (T, error) { err = nb.AssignNode(tokenPayloadNode) if err != nil { - return undef, err + return undef, cid.Undef, err } tokenPayloadNode = nb.Build() @@ -201,12 +212,12 @@ func FromIPLD[T Tokener](node datamodel.Node) (T, error) { tokenPayload := bindnode.Unwrap(tokenPayloadNode) if tokenPayload == nil { - return undef, errors.New("failed to Unwrap the TokenPayload") + return undef, cid.Undef, errors.New("failed to Unwrap the TokenPayload") } tkn, ok := tokenPayload.(T) if !ok { - return undef, errors.New("failed to assert the TokenPayload type as *token.Token") + return undef, cid.Undef, errors.New("failed to assert the TokenPayload type as *token.Token") } // Check that the issuer's DID contains a public key with a type that @@ -214,45 +225,45 @@ func FromIPLD[T Tokener](node datamodel.Node) (T, error) { // vvv issuer, err := issuerNode.AsString() if err != nil { - return undef, err + return undef, cid.Undef, err } issuerDID, err := did.Parse(issuer) if err != nil { - return undef, err + return undef, cid.Undef, err } issuerPubKey, err := issuerDID.PubKey() if err != nil { - return undef, err + return undef, cid.Undef, err } issuerVarsigHeader, err := varsig.Encode(issuerPubKey.Type()) if err != nil { - return undef, err + return undef, cid.Undef, err } varsigHeader, err := varsigHeaderNode.AsBytes() if err != nil { - return undef, err + return undef, cid.Undef, err } if string(varsigHeader) != string(issuerVarsigHeader) { - return undef, errors.New("the VarsigHeader key type doesn't match the issuer's key type") + return undef, cid.Undef, errors.New("the VarsigHeader key type doesn't match the issuer's key type") } data, err := ipld.Encode(sigPayloadNode, dagcbor.Encode) if err != nil { - return undef, err + return undef, cid.Undef, err } ok, err = issuerPubKey.Verify(data, signature) if err != nil || !ok { - return undef, errors.New("failed to verify the token's signature") + return undef, cid.Undef, errors.New("failed to verify the token's signature") } // ^^^ - return tkn, nil + return tkn, cid.Undef, nil } // Encode marshals a Tokener to the format specified by the provided diff --git a/internal/envelope/ipld_test.go b/internal/envelope/ipld_test.go index 4633ba2..132106d 100644 --- a/internal/envelope/ipld_test.go +++ b/internal/envelope/ipld_test.go @@ -18,7 +18,7 @@ func TestDecode(t *testing.T) { data := golden.Get(t, "example.dagcbor") - tkn, err := envelope.FromDagCbor[*Example](data) + tkn, _, err := envelope.FromDagCbor[*Example](data) require.NoError(t, err) assert.Equal(t, exampleGreeting, tkn.Hello) assert.Equal(t, exampleDID, tkn.Issuer) @@ -29,7 +29,7 @@ func TestDecode(t *testing.T) { data := golden.Get(t, "example.dagjson") - tkn, err := envelope.FromDagJson[*Example](data) + tkn, _, err := envelope.FromDagJson[*Example](data) require.NoError(t, err) assert.Equal(t, exampleGreeting, tkn.Hello) assert.Equal(t, exampleDID, tkn.Issuer) @@ -64,7 +64,7 @@ func TestRoundtrip(t *testing.T) { data := golden.Get(t, exampleDAGCBORFilename) - tkn, err := envelope.FromDagCborReader[*Example](bytes.NewReader(data)) + tkn, _, err := envelope.FromDagCborReader[*Example](bytes.NewReader(data)) require.NoError(t, err) assert.Equal(t, exampleGreeting, tkn.Hello) assert.Equal(t, exampleDID, tkn.Issuer) @@ -79,7 +79,7 @@ func TestRoundtrip(t *testing.T) { dataIn := golden.Get(t, exampleDAGCBORFilename) - tkn, err := envelope.FromDagCbor[*Example](dataIn) + tkn, _, err := envelope.FromDagCbor[*Example](dataIn) require.NoError(t, err) assert.Equal(t, exampleGreeting, tkn.Hello) assert.Equal(t, exampleDID, tkn.Issuer) @@ -94,7 +94,7 @@ func TestFromIPLD_with_invalid_signature(t *testing.T) { t.Parallel() node := invalidNodeFromGolden(t) - tkn, err := envelope.FromIPLD[*Example](node) + tkn, _, err := envelope.FromIPLD[*Example](node) assert.Nil(t, tkn) require.EqualError(t, err, "failed to verify the token's signature") }