diff --git a/internal/envelope/envelope.go b/internal/envelope/envelope.go index 9d3498d..0d58bbb 100644 --- a/internal/envelope/envelope.go +++ b/internal/envelope/envelope.go @@ -4,39 +4,35 @@ import ( "bytes" "errors" + "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/node/basicnode" "github.com/ipld/go-ipld-prime/node/bindnode" - "github.com/ipld/go-ipld-prime/schema" 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/internal/token" "github.com/ucan-wg/go-ucan/internal/varsig" ) -// Tokener represents a type that can be wrapped in a UCAN Envelope. -type Tokener interface { - // Tag returns the a string that indicates which of the sub- - // specifications define the structure of the underlying type. - Tag() string - - Prototype() schema.TypedPrototype -} - // [Envelope] is a signed enclosure for types implementing Tokener. // // [Envelope]: https://github.com/ucan-wg/spec#envelope -type Envelope[T Tokener] struct { +type Envelope struct { signature []byte - sigPayload *sigPayload[T] + sigPayload *sigPayload } // 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 -func New[T Tokener](privKey crypto.PrivKey, token T) (*Envelope[T], error) { - sigPayload, err := newSigPayload[T](privKey.Type(), token) +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 } @@ -51,7 +47,7 @@ func New[T Tokener](privKey crypto.PrivKey, token T) (*Envelope[T], error) { return nil, err } - return &Envelope[T]{ + return &Envelope{ signature: signature, sigPayload: sigPayload, }, nil @@ -64,8 +60,8 @@ func New[T Tokener](privKey crypto.PrivKey, token T) (*Envelope[T], error) { // 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[T Tokener](privKey crypto.PrivKey, token T) (datamodel.Node, error) { - env, err := New[T](privKey, token) +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 } @@ -73,7 +69,7 @@ func Wrap[T Tokener](privKey crypto.PrivKey, token T) (datamodel.Node, error) { return env.Wrap() } -func Unwrap[T Tokener](node datamodel.Node) (*Envelope[T], error) { +func Unwrap(node datamodel.Node, tag string) (*Envelope, error) { signatureNode, err := node.LookupByIndex(0) if err != nil { return nil, err @@ -89,24 +85,46 @@ func Unwrap[T Tokener](node datamodel.Node) (*Envelope[T], error) { return nil, err } - sigPayload, err := unwrapSigPayload[T](sigPayloadNode) + sigPayload, err := unwrapSigPayload(sigPayloadNode, tag) if err != nil { return nil, err } - return &Envelope[T]{ + return &Envelope{ signature: signature, sigPayload: sigPayload, }, 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. -func (e *Envelope[T]) Signature() []byte { +func (e *Envelope) Signature() []byte { return e.signature } -func (e *Envelope[T]) Token() T { +func (e *Envelope) Token() *token.Token { return e.sigPayload.tokenPayload } @@ -116,7 +134,7 @@ func (e *Envelope[T]) Token() T { // [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[T]) VarsigHeader() []byte { +func (e *Envelope) VarsigHeader() []byte { return e.sigPayload.varsigHeader } @@ -128,7 +146,7 @@ func (e *Envelope[T]) VarsigHeader() []byte { // is retrieved from the DID's method specific identifier. // // [Envelope]: https://github.com/ucan-wg/spec#envelope -func (e *Envelope[T]) Verify(pubKey crypto.PubKey) (bool, error) { +func (e *Envelope) Verify(pubKey crypto.PubKey) (bool, error) { cbor, err := e.sigPayload.cbor() if err != nil { return false, err @@ -138,7 +156,7 @@ func (e *Envelope[T]) Verify(pubKey crypto.PubKey) (bool, error) { } // Wrap encodes the Envelope as an IPLD datamodel.Node. -func (e *Envelope[T]) Wrap() (datamodel.Node, error) { +func (e *Envelope) Wrap() (datamodel.Node, error) { sn := bindnode.Wrap(&e.signature, nil) spn, err := e.sigPayload.wrap() @@ -176,25 +194,27 @@ func (e *Envelope[T]) Wrap() (datamodel.Node, error) { // accessors to the internals of these types. // -type sigPayload[T Tokener] struct { +type sigPayload struct { varsigHeader []byte - tokenPayload T + tokenPayload *token.Token + tag string } -func newSigPayload[T Tokener](keyType pb.KeyType, token T) (*sigPayload[T], error) { +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[T]{ + return &sigPayload{ varsigHeader: varsigHeader, tokenPayload: token, + tag: tag, }, nil } -func unwrapSigPayload[T Tokener](node datamodel.Node) (*sigPayload[T], error) { - tokenPayloadNode, err := node.LookupByString((*new(T)).Tag()) +func unwrapSigPayload(node datamodel.Node, tag string) (*sigPayload, error) { + tokenPayloadNode, err := node.LookupByString(tag) if err != nil { return nil, err } @@ -204,7 +224,7 @@ func unwrapSigPayload[T Tokener](node datamodel.Node) (*sigPayload[T], error) { return nil, errors.New("unexpected type") // TODO } - token, ok := tokenPayload.(T) + token, ok := tokenPayload.(*token.Token) if !ok { return nil, errors.New("unexpected type") // TODO } @@ -219,13 +239,13 @@ func unwrapSigPayload[T Tokener](node datamodel.Node) (*sigPayload[T], error) { return nil, err } - return &sigPayload[T]{ + return &sigPayload{ varsigHeader: header, tokenPayload: token, - }, nil // TODO + }, nil } -func (sp *sigPayload[T]) cbor() ([]byte, error) { +func (sp *sigPayload) cbor() ([]byte, error) { node, err := sp.wrap() if err != nil { return nil, err @@ -239,8 +259,8 @@ func (sp *sigPayload[T]) cbor() ([]byte, error) { return buf.Bytes(), nil } -func (sp *sigPayload[T]) wrap() (datamodel.Node, error) { - tpn := bindnode.Wrap(sp.tokenPayload, sp.tokenPayload.Prototype().Type(), token.BindnodeOptions()...) +func (sp *sigPayload) wrap() (datamodel.Node, error) { + tpn := bindnode.Wrap(sp.tokenPayload, token.Prototype().Type()) np := basicnode.Prototype.Any mb := np.NewBuilder() @@ -256,11 +276,11 @@ func (sp *sigPayload[T]) wrap() (datamodel.Node, error) { } ha.AssignBytes(sp.varsigHeader) - ta, err := ma.AssembleEntry(sp.tokenPayload.Tag()) + ta, err := ma.AssembleEntry(sp.tag) if err != nil { return nil, err } - ta.AssignNode(tpn) + ta.AssignNode(tpn.Representation()) if err := ma.Finish(); err != nil { return nil, err diff --git a/internal/envelope/envelope_test.go b/internal/envelope/envelope_test.go index 3ba3458..aa13605 100644 --- a/internal/envelope/envelope_test.go +++ b/internal/envelope/envelope_test.go @@ -8,11 +8,8 @@ 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/node/bindnode" - "github.com/ipld/go-ipld-prime/schema" "github.com/libp2p/go-libp2p/core/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -23,36 +20,13 @@ import ( ) const ( - exampleSignature = "a5BocvMSlifrDzWN7MQpDZ4cEciwe+b9twdQ7d5EZ/LlW3w1VIjk34ci8LqmzMCMwqJsoBqevArUMNS86RrDOLZEl+71+nSf1GJ9fK/E2o7ONSPTQt1wALH1xhJ4S/h5o8v0sWP/PWBvolSfMpro9lN1xCi9zC4iuFmizqdjOd3Ba3txHD5DGAculWBiob3N1mjkXZPbQYEQteCoLwSNDCmmHCE7VpRUkoi832N7UVHlu1FFucENB31qBWZQ+JTj8/oV56Do+LbhrDDiabNkTxulwQ7u+hdKA30vA6FWaA6QW+UE2/mCEKM5wvVAohLPZsapGXP6LoEcbBM3O758dw" + exampleSignature = "G9EFlDm5csIZR+byd5qMFxuaN/gsZmPSeoecW2PqWW8+wYWna9zx0peX1g7mUdo4ZTLTTr8LJSxuF1JFOJR0EsjgM0c8OHuX0WpSv8U+KSNxonbZpZqO8lyI/kW4crl/k9QrWMXtyHLEOS1OD3q9SsNGsf62fk1AMH9W+D2JVBVWdWAYFVXVkXQ+RbJi21lWYc9v/JtHSJbbuCbwhRqEsTBdhcYnyfFLcgLZvR9vqM636gA3ebRjZGZJOiAvxwdTOzlVxtw/552pAx8Od3hRGc5xdG5jGu2/OwIn9UMoXPQl7pMUYqk1nfqN3C7kDelIaQlgoAGyfssepB1tMRH/KA" exampleTag = "ucan/example@v1.0.0-rc.1" exampleVarsigHeader = "NIUkEoACcQ" invalidSignature = "a5BocvMSlifrDzWN7MQpDZ4cEciwe+b9twdQ7d5EZ/LlW3w1VIjk34ci8LqmzMCMwqJsoBqevArUMNS86RrDOLZEl+71+nSf1GJ9fK/E2o7ONSPTQt1wALH1xhJ4S/h5o8v0sWP/PWBvolSfMpro9lN1xCi9zC4iuFmizqdjOd3Ba3txHD5DGAculWBiob3N1mjkXZPbQYEQteCoLwSNDCmmHCE7VpRUkoi832N7UVHlu1FFucENB31qBWZQ+JTj8/oV56Do+LbhrDDiabNkTxulwQ7u+hdKA30vA6FWaA6QW+UE2/mCEKM5wvVAohLPZsapGXP6LoEcbBM3O758dx" priCfg = "CAASqAkwggSkAgEAAoIBAQDq39Aou82MEteoEz+iKpu7zwJc0dZfomAfB4Zpnl8+WhUOhZyveHDD9lr/UCc/fcN5ufeyZxutDRvIXcmUGG5DNTVRZ10ywT/wN8KO+x/hZ5QIxBAsCFukcyHbAPseLYpAK0J0HNnQhtF6cQcQkuThCnZH/Ofj42d7snuztbBUxwjButvHYHiWwolcJUeb99HCpGwtJYjkp004roFBqjkLayP4AWHrnW4mtCY0rw86gRCT60N1XBZ9zXKw+LJeuQg3RUgZqBL6hvVIs1LAY5ie0LSXVkdjg9bmV7j5SKJBgk9ABoKvt35/KSWA5HW/6g3y/UITCD2DQDrTFv8xzDIvAgMBAAECggEAFoGr6LtWTv3fPHPbvSZoFe8YQty4tiFRJKgL8UMDzW3EZsfW49metKh+v8hmemcKvDddzPKkbEi9SM3z6wUMS9Rlb4+AFsT94370Xc8ilu7d+JkRE6cZYQDHVb0aUyH6BXwfuhCprpm8qQb7rlLlK8tc2jkZ33SDDg9kWywl4XmiDabKm0fOJd68KGuO5FNpCfpipqG3ok/FYuKlSqpCz+7QH2p3z2eVGTa/uIbDMNxkFBoIuhEJT43eDR2elPOrSL9+AYgBPVrzcJoRRxZFVvDDQ+RIwI/A62DvZ3IpFEyzEk2VZwWpLWYKnAUElcSegxx22K7S4BAaWjL86I6cAQKBgQDr8Y1NLYKYffHJAkmWE/ssGcih5C06gOo9WInBU8ZroY4LAhzKLstS0yKsM0OtRSFhZsmCdR3M/IHO94c3KsXu+KtA7r9AG+58LMpmob1mvyGsXIowHFMAd95s3FDd/HOE10tMyrIE1c7eWLfII+s6yGo8MS0WHXOSFBlioukGbwKBgQD+1v3vC+iUNP0FMhVxnhGgONyUb0X+AEw9GOLeCpsugZqRXnharYSiTjGPjwPaT2YBVkyaraoX6VwK+ys3RCngUg4s9IeHUYsR6Xa0oheAlME0ZDtuzi5+lDo+Zpsg8vepgx6v//bKvUcJb+9YDcKlMfQgFnSb3bAwUN9Ru79wQQKBgD7Z/9wZTXq1whzbwSJ7fCNJUwrdL7cv9DYXScr4OBkf1ijUjTrGsF8F42yf011q1vONYAyiiie69BFgGuL1P/jiwSvw7X10c1kczWX9m+is7ZlupVkfknTDebriDaC0yUkP2P1B2Z40HoFYfMyR1O25yaLzLqF/gvPc6s49u3l9AoGBAOZ9d2EdKTfbEToAyYpgyFpc84zBc9G/XTUpbBAeEasnh7CRfFOvezX9eS/5zydGBuGQt2pzRlOoOhqof7bVzPZZ4P5iEK6gXyNNQJMxxAYFBRYozeRzUXQlBuTnksljWAMWV8whu4o1VanAdv7yOymEm+Ply4QqJzAcBU/8erLBAoGBAJ6120n/Q8B7kNW/tZvJ4Xi0u/kSEodNQ9TpF44SB32bn/aWwfe7qFS1x+3omO7XWcx3FLUUPhITQjmQcbNa2yWY0UZoqnzkHhDmJeG2PUILEMCHSLCKQHS+PNJxqEWvwQe2mX/gJIjf14U9983hgLnOL7gH9sVYf9M9yA8NVlem" ) -//go:embed testdata/example.ipldsch -var exampleSchema []byte - -var _ envelope.Tokener = (*Example)(nil) - -type Example struct { - Hello string - Id *did.DID -} - -func (e *Example) Tag() string { - return exampleTag -} - -func (e *Example) Prototype() schema.TypedPrototype { - ts, err := ipld.LoadSchemaBytes(exampleSchema) - if err != nil { - panic(err) - } - - return bindnode.Prototype((*Example)(nil), ts.TypeByName("Example"), token.DIDConverter()) -} - func TestNew(t *testing.T) { t.Parallel() @@ -66,17 +40,14 @@ func TestNew(t *testing.T) { assert.Equal(t, varsigHeader, env.VarsigHeader()) tkn := env.Token() - assert.IsType(t, (*Example)(nil), tkn) - assert.Equal(t, exampleTag, tkn.Tag()) - assert.Equal(t, "world", tkn.Hello) + assert.IsType(t, (*token.Token)(nil), tkn) + // TODO } func TestWrap(t *testing.T) { t.Parallel() - envNode, err := envelope.Wrap(rsaPrivateKey(t), &Example{ - Hello: "world", - }) + envNode, err := envelope.Wrap(rsaPrivateKey(t), exampleToken(t), exampleTag) assert.NoError(t, err) assert.NotNil(t, envNode) @@ -103,7 +74,7 @@ func TestEnvelope_Wrap(t *testing.T) { t.Log(buf.String()) - env1, err := envelope.Unwrap[*Example](envNode) + env1, err := envelope.Unwrap(envNode, exampleTag) require.NoError(t, err) assert.NotNil(t, env1) @@ -111,7 +82,6 @@ func TestEnvelope_Wrap(t *testing.T) { assert.Equal(t, env.Signature(), env1.Signature()) assert.Equal(t, env.VarsigHeader(), env1.VarsigHeader()) assert.Equal(t, env.Token(), env1.Token()) - t.Log("Got here") // t.Fail() } @@ -141,17 +111,30 @@ func TestEnvelope_Verify(t *testing.T) { }) } -func exampleEnvelope(t *testing.T) *envelope.Envelope[*Example] { +func exampleEnvelope(t *testing.T) *envelope.Envelope { t.Helper() - env, err := envelope.New(rsaPrivateKey(t), &Example{ - Hello: "world", - }) + env, err := envelope.New(rsaPrivateKey(t), exampleToken(t), exampleTag) require.NoError(t, err) + t.Log("exampleEnvelope.Signature", base64.RawStdEncoding.EncodeToString(env.Signature())) + return env } +func exampleToken(t *testing.T) *token.Token { + t.Helper() + + id, err := did.Parse("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK") + require.NoError(t, err) + _ = id // TODO: + + tkn, err := token.New() // TODO: fields + require.NoError(t, err) + + return tkn +} + func rsaPrivateKey(t *testing.T) crypto.PrivKey { t.Helper() diff --git a/internal/envelope/testdata/example.cbor b/internal/envelope/testdata/example.cbor index c107c3b..64bf364 100644 Binary files a/internal/envelope/testdata/example.cbor and b/internal/envelope/testdata/example.cbor differ diff --git a/internal/envelope/testdata/example.ipldsch b/internal/envelope/testdata/example.ipldsch deleted file mode 100644 index 8cb7ee6..0000000 --- a/internal/envelope/testdata/example.ipldsch +++ /dev/null @@ -1,6 +0,0 @@ -type DID string - -type Example struct { - hello String - id DID -} \ No newline at end of file diff --git a/internal/envelope/testdata/example.json b/internal/envelope/testdata/example.json index 681677e..cbda4b0 100644 --- a/internal/envelope/testdata/example.json +++ b/internal/envelope/testdata/example.json @@ -1 +1 @@ -[{"/":{"bytes":"a5BocvMSlifrDzWN7MQpDZ4cEciwe+b9twdQ7d5EZ/LlW3w1VIjk34ci8LqmzMCMwqJsoBqevArUMNS86RrDOLZEl+71+nSf1GJ9fK/E2o7ONSPTQt1wALH1xhJ4S/h5o8v0sWP/PWBvolSfMpro9lN1xCi9zC4iuFmizqdjOd3Ba3txHD5DGAculWBiob3N1mjkXZPbQYEQteCoLwSNDCmmHCE7VpRUkoi832N7UVHlu1FFucENB31qBWZQ+JTj8/oV56Do+LbhrDDiabNkTxulwQ7u+hdKA30vA6FWaA6QW+UE2/mCEKM5wvVAohLPZsapGXP6LoEcbBM3O758dw"}},{"h":{"/":{"bytes":"NIUkEoACcQ"}},"ucan/example@v1.0.0-rc.1":{"hello":"world","id":"did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"}}] \ No newline at end of file +[{"/":{"bytes":"G9EFlDm5csIZR+byd5qMFxuaN/gsZmPSeoecW2PqWW8+wYWna9zx0peX1g7mUdo4ZTLTTr8LJSxuF1JFOJR0EsjgM0c8OHuX0WpSv8U+KSNxonbZpZqO8lyI/kW4crl/k9QrWMXtyHLEOS1OD3q9SsNGsf62fk1AMH9W+D2JVBVWdWAYFVXVkXQ+RbJi21lWYc9v/JtHSJbbuCbwhRqEsTBdhcYnyfFLcgLZvR9vqM636gA3ebRjZGZJOiAvxwdTOzlVxtw/552pAx8Od3hRGc5xdG5jGu2/OwIn9UMoXPQl7pMUYqk1nfqN3C7kDelIaQlgoAGyfssepB1tMRH/KA"}},{"h":{"/":{"bytes":"NIUkEoACcQ"}},"ucan/example@v1.0.0-rc.1":{"cmd":"","exp":null,"iss":"","sub":null}}] \ No newline at end of file