1 Commits

Author SHA1 Message Date
Fabio Bozzo
7376256672 fix length check per-kind bug 2024-09-16 14:04:01 +02:00
135 changed files with 2368 additions and 12491 deletions

View File

@@ -1,34 +0,0 @@
name: go continuous benchmark
on:
push:
branches:
- v1
permissions:
contents: write
deployments: write
jobs:
benchmark:
name: Run Go continuous benchmark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
with:
go-version: "stable"
- name: Run benchmark
run: go test -v ./... -bench=. -run=xxx -benchmem | tee output.txt
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
name: Go Benchmark
tool: 'go'
output-file-path: output.txt
github-token: ${{ secrets.GITHUB_TOKEN }}
# Push and deploy GitHub pages branch automatically
auto-push: true
# Show alert with commit comment on detecting possible performance regression
alert-threshold: '200%'
comment-on-alert: true

View File

@@ -7,7 +7,7 @@ jobs:
fail-fast: false
matrix:
os: [ "ubuntu" ]
go: [ "1.22.x", "1.23.x", ]
go: [ "1.21.x", "1.22.x", "1.23.x", ]
env:
COVERAGES: ""
runs-on: ${{ matrix.os }}-latest
@@ -22,6 +22,6 @@ jobs:
go version
go env
- name: Run tests
run: go test -v ./... -tags jwx_es256k
run: go test -v ./...
- name: Check formatted
run: gofmt -l .

View File

@@ -29,7 +29,7 @@ Verbatim copies of both licenses are included below:
```
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

View File

@@ -1,77 +0,0 @@
<div align="center">
<a href="https://github.com/ucan-wg/go-ucan" target="_blank">
<img src="https://raw.githubusercontent.com/ucan-wg/go-ucan/v1/assets/logo.png" alt="go-ucan Logo" height="250"></img>
</a>
<h1 align="center">go-ucan</h1>
<p>
<img src="https://img.shields.io/badge/UCAN-v1.0.0--rc.1-blue" alt="UCAN v1.0.0-rc.1">
<a href="https://github.com/ucan-wg/go-ucan/tags">
<img alt="GitHub Tag" src="https://img.shields.io/github/v/tag/ucan-wg/go-ucan">
</a>
<a href="https://github.com/ucan-wg/go-ucan/actions?query=">
<img src="https://github.com/ucan-wg/go-ucan/actions/workflows/gotest.yml/badge.svg" alt="Build Status">
</a>
<a href="https://ucan-wg.github.io/go-ucan/dev/bench/">
<img alt="Go benchmarks" src="https://img.shields.io/badge/Benchmarks-go-blue">
</a>
<a href="https://github.com/ucan-wg/go-ucan/blob/v1/LICENSE.md">
<img alt="Apache 2.0 + MIT License" src="https://img.shields.io/badge/License-Apache--2.0+MIT-green">
</a>
<a href="https://pkg.go.dev/github.com/ucan-wg/go-ucan">
<img src="https://img.shields.io/badge/Docs-godoc-blue" alt="Docs">
</a>
<a href="https://discord.gg/JSyFG6XgVM">
<img src="https://img.shields.io/static/v1?label=Discord&message=join%20us!&color=mediumslateblue" alt="Discord">
</a>
</p>
</div>
This is a go library to help the next generation of web and decentralized applications make use
of UCANs in their authorization flows.
User Controlled Authorization Networks (UCANs) are a way of doing authorization where users are fully in control. OAuth is designed for a centralized world, UCAN is the distributed user controlled version.
## Resources
### Specifications
The UCAN specification is separated in multiple sub-spec:
- [Main specification](https://github.com/ucan-wg/spec)
- [Delegation](https://github.com/ucan-wg/delegation/tree/v1_ipld)
- [Invocation](https://github.com/ucan-wg/invocation)
Not implemented yet:
- [Revocation](https://github.com/ucan-wg/revocation/tree/first-draft)
- [Promise](https://github.com/ucan-wg/promise/tree/v1-rc1)
### Talks
- [Decentralizing Auth, and UCAN Too - Brooklyn Zelenka (2023)](https://www.youtube.com/watch?v=MuHfrqw9gQA)
- [What's New in UCAN 1.0 - Brooklyn Zelenka (2024)](https://www.youtube.com/watch?v=-uohQzZcwF4)
## Status
`go-ucan` currently support the required parts of the UCAN specification: the main specification, delegation and invocation.
Besides that, `go-ucan` also includes:
- a simplified [DID](https://www.w3.org/TR/did-core/) and [did-key](https://w3c-ccg.github.io/did-method-key/) implementation
- a [token container](https://github.com/ucan-wg/go-ucan/tree/v1/pkg/container) with CBOR and CAR format, to package and carry tokens together
- support for encrypted values in token's metadata
## Getting Help
For usage questions, usecases, or issues reach out to us in our `go-ucan`
[Discord channel](https://discord.gg/3EHEQ6M8BC).
We would be happy to try to answer your question or try opening a new issue on
Github.
## UCAN Gopher
Artwork by [Bruno Monts](https://www.instagram.com/bruno_monts). Thank you [Renee French](http://reneefrench.blogspot.com/) for creating the [Go Gopher](https://blog.golang.org/gopher)
## License
This project is licensed under the double license [Apache 2.0 + MIT](https://github.com/ucan-wg/go-ucan/blob/v1/LICENSE.md).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -16,12 +16,15 @@ var _ fmt.Stringer = (*Command)(nil)
// by one or more slash-separated Segments of lowercase characters.
//
// [Command]: https://github.com/ucan-wg/spec#command
type Command string
type Command struct {
segments []string
}
// New creates a validated command from the provided list of segment strings.
// An error is returned if an invalid Command would be formed
func New(segments ...string) Command {
return Top().Join(segments...)
// New creates a validated command from the provided list of segment
// strings. An error is returned if an invalid Command would be
// formed
func New(segments ...string) *Command {
return &Command{segments: segments}
}
// Parse verifies that the provided string contains the required
@@ -29,26 +32,26 @@ func New(segments ...string) Command {
// Command.
//
// [segment structure]: https://github.com/ucan-wg/spec#segment-structure
func Parse(s string) (Command, error) {
func Parse(s string) (*Command, error) {
if !strings.HasPrefix(s, "/") {
return "", ErrRequiresLeadingSlash
return nil, ErrRequiresLeadingSlash
}
if len(s) > 1 && strings.HasSuffix(s, "/") {
return "", ErrDisallowsTrailingSlash
return nil, ErrDisallowsTrailingSlash
}
if s != strings.ToLower(s) {
return "", ErrRequiresLowercase
return nil, ErrRequiresLowercase
}
// The leading slash will result in the first element from strings.Split
// being an empty string which is removed as strings.Join will ignore it.
return Command(s), nil
return &Command{strings.Split(s, "/")[1:]}, nil
}
// MustParse is the same as Parse, but panic() if the parsing fail.
func MustParse(s string) Command {
func MustParse(s string) *Command {
c, err := Parse(s)
if err != nil {
panic(err)
@@ -56,14 +59,15 @@ func MustParse(s string) Command {
return c
}
// Top is the most powerful capability.
// [Top] is the most powerful capability.
//
// This function returns a Command that is a wildcard and therefore represents the
// most powerful ability. As such, it should be handled with care and used sparingly.
// most powerful abilily. As such it should be handle with care and used
// sparingly.
//
// [Top]: https://github.com/ucan-wg/spec#-aka-top
func Top() Command {
return Command(separator)
func Top() *Command {
return New()
}
// IsValid returns true if the provided string is a valid UCAN command.
@@ -74,62 +78,18 @@ func IsValid(s string) bool {
// Join appends segments to the end of this command using the required
// segment separator.
func (c Command) Join(segments ...string) Command {
size := 0
for _, s := range segments {
size += len(s)
}
if size == 0 {
return c
}
buf := make([]byte, 0, len(c)+size+len(segments))
buf = append(buf, []byte(c)...)
for _, s := range segments {
if s != "" {
if len(buf) > 1 {
buf = append(buf, separator...)
}
buf = append(buf, []byte(s)...)
}
}
return Command(buf)
func (c *Command) Join(segments ...string) *Command {
return &Command{append(c.segments, segments...)}
}
// Segments returns the ordered segments that comprise the Command as a
// slice of strings.
func (c Command) Segments() []string {
if c == separator {
return nil
}
return strings.Split(string(c), separator)[1:]
}
// Covers returns true if the command is identical or a parent of the given other command.
func (c Command) Covers(other Command) bool {
// fast-path, equivalent to the code below (verified with fuzzing)
if !strings.HasPrefix(string(other), string(c)) {
return false
}
return c == separator || len(c) == len(other) || other[len(c)] == separator[0]
/* -------
otherSegments := other.Segments()
if len(otherSegments) < len(c.Segments()) {
return false
}
for i, s := range c.Segments() {
if otherSegments[i] != s {
return false
}
}
return true
*/
func (c *Command) Segments() []string {
return c.segments
}
// String returns the composed representation the command. This is also
// the required wire representation (before IPLD encoding occurs.)
func (c Command) String() string {
return string(c)
func (c *Command) String() string {
return "/" + strings.Join(c.segments, "/")
}

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/capability/command"
)
func TestTop(t *testing.T) {
@@ -13,81 +13,73 @@ func TestTop(t *testing.T) {
}
func TestIsValidCommand(t *testing.T) {
t.Parallel()
t.Run("succeeds when", func(t *testing.T) {
t.Parallel()
for _, testcase := range validTestcases(t) {
testcase := testcase
t.Run(testcase.name, func(t *testing.T) {
t.Parallel()
require.True(t, command.IsValid(testcase.inp))
})
}
})
t.Run("fails when", func(t *testing.T) {
t.Parallel()
for _, testcase := range invalidTestcases(t) {
testcase := testcase
t.Run(testcase.name, func(t *testing.T) {
t.Parallel()
require.False(t, command.IsValid(testcase.inp))
})
}
})
}
func TestNew(t *testing.T) {
require.Equal(t, command.Top(), command.New())
require.Equal(t, "/foo", command.New("foo").String())
require.Equal(t, "/foo/bar", command.New("foo", "bar").String())
require.Equal(t, "/foo/bar/baz", command.New("foo", "bar/baz").String())
}
func TestParseCommand(t *testing.T) {
t.Parallel()
t.Run("succeeds when", func(t *testing.T) {
t.Parallel()
for _, testcase := range validTestcases(t) {
testcase := testcase
t.Run(testcase.name, func(t *testing.T) {
t.Parallel()
cmd, err := command.Parse("/elem0/elem1/elem2")
require.NoError(t, err)
require.NotEmpty(t, cmd)
require.NotNil(t, cmd)
})
}
})
t.Run("fails when", func(t *testing.T) {
t.Parallel()
for _, testcase := range invalidTestcases(t) {
testcase := testcase
t.Run(testcase.name, func(t *testing.T) {
t.Parallel()
cmd, err := command.Parse(testcase.inp)
require.ErrorIs(t, err, testcase.err)
require.Zero(t, cmd)
require.Nil(t, cmd)
})
}
})
}
func TestEquality(t *testing.T) {
require.True(t, command.MustParse("/foo/bar/baz") == command.MustParse("/foo/bar/baz"))
require.False(t, command.MustParse("/foo/bar/baz") == command.MustParse("/foo/bar/bazz"))
require.False(t, command.MustParse("/foo/bar") == command.MustParse("/foo/bar/baz"))
}
func TestJoin(t *testing.T) {
require.Equal(t, "/foo", command.Top().Join("foo").String())
require.Equal(t, "/foo/bar", command.Top().Join("foo/bar").String())
require.Equal(t, "/foo/bar", command.Top().Join("foo", "bar").String())
require.Equal(t, "/faz/boz/foo/bar", command.MustParse("/faz/boz").Join("foo/bar").String())
require.Equal(t, "/faz/boz/foo/bar", command.MustParse("/faz/boz").Join("foo", "bar").String())
}
func TestSegments(t *testing.T) {
require.Empty(t, command.Top().Segments())
require.Equal(t, []string{"foo", "bar", "baz"}, command.MustParse("/foo/bar/baz").Segments())
}
func TestCovers(t *testing.T) {
require.True(t, command.MustParse("/foo/bar/baz").Covers(command.MustParse("/foo/bar/baz")))
require.True(t, command.MustParse("/foo/bar").Covers(command.MustParse("/foo/bar/baz")))
require.False(t, command.MustParse("/foo/bar/baz").Covers(command.MustParse("/foo/bar")))
require.True(t, command.MustParse("/").Covers(command.MustParse("/foo")))
require.True(t, command.MustParse("/").Covers(command.MustParse("/foo/bar/baz")))
require.False(t, command.MustParse("/foo").Covers(command.MustParse("/foo00")))
require.False(t, command.MustParse("/foo/bar").Covers(command.MustParse("/foo/bar00")))
}
type testcase struct {
name string
inp string

View File

@@ -9,7 +9,7 @@ import (
"github.com/ipld/go-ipld-prime/must"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
"github.com/ucan-wg/go-ucan/capability/policy/selector"
)
func FromIPLD(node datamodel.Node) (Policy, error) {
@@ -61,7 +61,7 @@ func statementFromIPLD(path string, node datamodel.Node) (Statement, error) {
if err != nil {
return nil, err
}
return negation{statement: statement}, nil
return Not(statement), nil
case KindAnd, KindOr:
arg2, _ := node.LookupByIndex(1)
@@ -93,11 +93,11 @@ func statementFromIPLD(path string, node datamodel.Node) (Statement, error) {
if pattern.Kind() != datamodel.Kind_String {
return nil, ErrNotAString(combinePath(path, op, 2))
}
g, err := parseGlob(must.String(pattern))
res, err := Like(sel, must.String(pattern))
if err != nil {
return nil, ErrInvalidPattern(combinePath(path, op, 2), err)
}
return wildcard{selector: sel, pattern: g}, nil
return res, nil
case KindAll, KindAny:
sel, err := arg2AsSelector(op)
@@ -232,7 +232,7 @@ func statementToIPLD(statement Statement) (datamodel.Node, error) {
if err != nil {
return nil, err
}
err = listBuilder.AssembleValue().AssignString(string(statement.pattern))
err = listBuilder.AssembleValue().AssignString(statement.pattern)
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,52 @@
package literal
import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/basicnode"
)
func Node(n ipld.Node) ipld.Node {
return n
}
func Link(cid ipld.Link) ipld.Node {
nb := basicnode.Prototype.Link.NewBuilder()
nb.AssignLink(cid)
return nb.Build()
}
func Bool(val bool) ipld.Node {
nb := basicnode.Prototype.Bool.NewBuilder()
nb.AssignBool(val)
return nb.Build()
}
func Int(val int64) ipld.Node {
nb := basicnode.Prototype.Int.NewBuilder()
nb.AssignInt(val)
return nb.Build()
}
func Float(val float64) ipld.Node {
nb := basicnode.Prototype.Float.NewBuilder()
nb.AssignFloat(val)
return nb.Build()
}
func String(val string) ipld.Node {
nb := basicnode.Prototype.String.NewBuilder()
nb.AssignString(val)
return nb.Build()
}
func Bytes(val []byte) ipld.Node {
nb := basicnode.Prototype.Bytes.NewBuilder()
nb.AssignBytes(val)
return nb.Build()
}
func Null() ipld.Node {
nb := basicnode.Prototype.Any.NewBuilder()
nb.AssignNull()
return nb.Build()
}

183
capability/policy/match.go Normal file
View File

@@ -0,0 +1,183 @@
package policy
import (
"cmp"
"fmt"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/must"
"github.com/ucan-wg/go-ucan/capability/policy/selector"
)
// Match determines if the IPLD node matches the policy document.
func Match(policy Policy, node ipld.Node) bool {
for _, stmt := range policy {
ok := matchStatement(stmt, node)
if !ok {
return false
}
}
return true
}
func matchStatement(statement Statement, node ipld.Node) bool {
switch statement.Kind() {
case KindEqual:
if s, ok := statement.(equality); ok {
one, many, err := selector.Select(s.selector, node)
if err != nil {
return false
}
if one != nil {
return datamodel.DeepEqual(s.value, one)
}
if many != nil {
for _, n := range many {
if eq := datamodel.DeepEqual(s.value, n); eq {
return true
}
}
}
return false
}
case KindGreaterThan:
if s, ok := statement.(equality); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
return isOrdered(s.value, one, gt)
}
case KindGreaterThanOrEqual:
if s, ok := statement.(equality); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
return isOrdered(s.value, one, gte)
}
case KindLessThan:
if s, ok := statement.(equality); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
return isOrdered(s.value, one, lt)
}
case KindLessThanOrEqual:
if s, ok := statement.(equality); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
return isOrdered(s.value, one, lte)
}
case KindNot:
if s, ok := statement.(negation); ok {
return !matchStatement(s.statement, node)
}
case KindAnd:
if s, ok := statement.(connective); ok {
for _, cs := range s.statements {
r := matchStatement(cs, node)
if !r {
return false
}
}
return true
}
case KindOr:
if s, ok := statement.(connective); ok {
if len(s.statements) == 0 {
return true
}
for _, cs := range s.statements {
r := matchStatement(cs, node)
if r {
return true
}
}
return false
}
case KindLike:
if s, ok := statement.(wildcard); ok {
one, _, err := selector.Select(s.selector, node)
if err != nil || one == nil {
return false
}
v, err := one.AsString()
if err != nil {
return false
}
return s.glob.Match(v)
}
case KindAll:
if s, ok := statement.(quantifier); ok {
_, many, err := selector.Select(s.selector, node)
if err != nil || many == nil {
return false
}
for _, n := range many {
ok := matchStatement(s.statement, n)
if !ok {
return false
}
}
return true
}
case KindAny:
if s, ok := statement.(quantifier); ok {
one, many, err := selector.Select(s.selector, node)
if err != nil {
return false
}
if one != nil {
ok := matchStatement(s.statement, one)
if ok {
return true
}
}
if many != nil {
for _, n := range many {
ok := matchStatement(s.statement, n)
if ok {
return true
}
}
}
return false
}
}
panic(fmt.Errorf("unimplemented statement kind: %s", statement.Kind()))
}
func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) bool) bool {
if expected.Kind() == ipld.Kind_Int && actual.Kind() == ipld.Kind_Int {
a := must.Int(actual)
b := must.Int(expected)
return satisfies(cmp.Compare(a, b))
}
if expected.Kind() == ipld.Kind_Float && actual.Kind() == ipld.Kind_Float {
a, err := actual.AsFloat()
if err != nil {
panic(fmt.Errorf("extracting node float: %w", err))
}
b, err := expected.AsFloat()
if err != nil {
panic(fmt.Errorf("extracting selector float: %w", err))
}
return satisfies(cmp.Compare(a, b))
}
return false
}
func gt(order int) bool { return order == 1 }
func gte(order int) bool { return order == 0 || order == 1 }
func lt(order int) bool { return order == -1 }
func lte(order int) bool { return order == 0 || order == -1 }

View File

@@ -0,0 +1,492 @@
package policy
import (
"fmt"
"testing"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/capability/policy/literal"
"github.com/ucan-wg/go-ucan/capability/policy/selector"
)
func TestMatch(t *testing.T) {
t.Run("equality", func(t *testing.T) {
t.Run("string", func(t *testing.T) {
np := basicnode.Prototype.String
nb := np.NewBuilder()
nb.AssignString("test")
nd := nb.Build()
pol := Policy{Equal(selector.MustParse("."), literal.String("test"))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{Equal(selector.MustParse("."), literal.String("test2"))}
ok = Match(pol, nd)
require.False(t, ok)
pol = Policy{Equal(selector.MustParse("."), literal.Int(138))}
ok = Match(pol, nd)
require.False(t, ok)
})
t.Run("int", func(t *testing.T) {
np := basicnode.Prototype.Int
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := Policy{Equal(selector.MustParse("."), literal.Int(138))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{Equal(selector.MustParse("."), literal.Int(1138))}
ok = Match(pol, nd)
require.False(t, ok)
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
ok = Match(pol, nd)
require.False(t, ok)
})
t.Run("float", func(t *testing.T) {
np := basicnode.Prototype.Float
nb := np.NewBuilder()
nb.AssignFloat(1.138)
nd := nb.Build()
pol := Policy{Equal(selector.MustParse("."), literal.Float(1.138))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{Equal(selector.MustParse("."), literal.Float(11.38))}
ok = Match(pol, nd)
require.False(t, ok)
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
ok = Match(pol, nd)
require.False(t, ok)
})
t.Run("IPLD Link", func(t *testing.T) {
l0 := cidlink.Link{Cid: cid.MustParse("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")}
l1 := cidlink.Link{Cid: cid.MustParse("bafkreifau35r7vi37tvbvfy3hdwvgb4tlflqf7zcdzeujqcjk3rsphiwte")}
np := basicnode.Prototype.Link
nb := np.NewBuilder()
nb.AssignLink(l0)
nd := nb.Build()
pol := Policy{Equal(selector.MustParse("."), literal.Link(l0))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{Equal(selector.MustParse("."), literal.Link(l1))}
ok = Match(pol, nd)
require.False(t, ok)
pol = Policy{Equal(selector.MustParse("."), literal.String("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq"))}
ok = Match(pol, nd)
require.False(t, ok)
})
t.Run("string in map", func(t *testing.T) {
np := basicnode.Prototype.Map
nb := np.NewBuilder()
ma, _ := nb.BeginMap(1)
ma.AssembleKey().AssignString("foo")
ma.AssembleValue().AssignString("bar")
ma.Finish()
nd := nb.Build()
pol := Policy{Equal(selector.MustParse(".foo"), literal.String("bar"))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{Equal(selector.MustParse(".[\"foo\"]"), literal.String("bar"))}
ok = Match(pol, nd)
require.True(t, ok)
pol = Policy{Equal(selector.MustParse(".foo"), literal.String("baz"))}
ok = Match(pol, nd)
require.False(t, ok)
pol = Policy{Equal(selector.MustParse(".foobar"), literal.String("bar"))}
ok = Match(pol, nd)
require.False(t, ok)
})
t.Run("string in list", func(t *testing.T) {
np := basicnode.Prototype.List
nb := np.NewBuilder()
la, _ := nb.BeginList(1)
la.AssembleValue().AssignString("foo")
la.Finish()
nd := nb.Build()
pol := Policy{Equal(selector.MustParse(".[0]"), literal.String("foo"))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{Equal(selector.MustParse(".[1]"), literal.String("foo"))}
ok = Match(pol, nd)
require.False(t, ok)
})
})
t.Run("inequality", func(t *testing.T) {
t.Run("gt int", func(t *testing.T) {
np := basicnode.Prototype.Int
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := Policy{GreaterThan(selector.MustParse("."), literal.Int(1))}
ok := Match(pol, nd)
require.True(t, ok)
})
t.Run("gte int", func(t *testing.T) {
np := basicnode.Prototype.Int
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(1))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(138))}
ok = Match(pol, nd)
require.True(t, ok)
})
t.Run("gt float", func(t *testing.T) {
np := basicnode.Prototype.Float
nb := np.NewBuilder()
nb.AssignFloat(1.38)
nd := nb.Build()
pol := Policy{GreaterThan(selector.MustParse("."), literal.Float(1))}
ok := Match(pol, nd)
require.True(t, ok)
})
t.Run("gte float", func(t *testing.T) {
np := basicnode.Prototype.Float
nb := np.NewBuilder()
nb.AssignFloat(1.38)
nd := nb.Build()
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1.38))}
ok = Match(pol, nd)
require.True(t, ok)
})
t.Run("lt int", func(t *testing.T) {
np := basicnode.Prototype.Int
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := Policy{LessThan(selector.MustParse("."), literal.Int(1138))}
ok := Match(pol, nd)
require.True(t, ok)
})
t.Run("lte int", func(t *testing.T) {
np := basicnode.Prototype.Int
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := Policy{LessThanOrEqual(selector.MustParse("."), literal.Int(1138))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{LessThanOrEqual(selector.MustParse("."), literal.Int(138))}
ok = Match(pol, nd)
require.True(t, ok)
})
})
t.Run("negation", func(t *testing.T) {
np := basicnode.Prototype.Bool
nb := np.NewBuilder()
nb.AssignBool(false)
nd := nb.Build()
pol := Policy{Not(Equal(selector.MustParse("."), literal.Bool(true)))}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{Not(Equal(selector.MustParse("."), literal.Bool(false)))}
ok = Match(pol, nd)
require.False(t, ok)
})
t.Run("conjunction", func(t *testing.T) {
np := basicnode.Prototype.Int
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := Policy{
And(
GreaterThan(selector.MustParse("."), literal.Int(1)),
LessThan(selector.MustParse("."), literal.Int(1138)),
),
}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{
And(
GreaterThan(selector.MustParse("."), literal.Int(1)),
Equal(selector.MustParse("."), literal.Int(1138)),
),
}
ok = Match(pol, nd)
require.False(t, ok)
pol = Policy{And()}
ok = Match(pol, nd)
require.True(t, ok)
})
t.Run("disjunction", func(t *testing.T) {
np := basicnode.Prototype.Int
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := Policy{
Or(
GreaterThan(selector.MustParse("."), literal.Int(138)),
LessThan(selector.MustParse("."), literal.Int(1138)),
),
}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{
Or(
GreaterThan(selector.MustParse("."), literal.Int(138)),
Equal(selector.MustParse("."), literal.Int(1138)),
),
}
ok = Match(pol, nd)
require.False(t, ok)
pol = Policy{Or()}
ok = Match(pol, nd)
require.True(t, ok)
})
t.Run("wildcard", func(t *testing.T) {
pattern := `Alice\*, Bob*, Carol.`
for _, s := range []string{
"Alice*, Bob, Carol.",
"Alice*, Bob, Dan, Erin, Carol.",
"Alice*, Bob , Carol.",
"Alice*, Bob*, Carol.",
} {
func(s string) {
t.Run(fmt.Sprintf("pass %s", s), func(t *testing.T) {
np := basicnode.Prototype.String
nb := np.NewBuilder()
nb.AssignString(s)
nd := nb.Build()
statement, err := Like(selector.MustParse("."), pattern)
require.NoError(t, err)
pol := Policy{statement}
ok := Match(pol, nd)
require.True(t, ok)
})
}(s)
}
for _, s := range []string{
"Alice*, Bob, Carol",
"Alice*, Bob*, Carol!",
"Alice Cooper, Bob, Carol.",
"Alice, Bob, Carol.",
" Alice*, Bob, Carol. ",
} {
func(s string) {
t.Run(fmt.Sprintf("fail %s", s), func(t *testing.T) {
np := basicnode.Prototype.String
nb := np.NewBuilder()
nb.AssignString(s)
nd := nb.Build()
statement, err := Like(selector.MustParse("."), pattern)
require.NoError(t, err)
pol := Policy{statement}
ok := Match(pol, nd)
require.False(t, ok)
})
}(s)
}
})
t.Run("quantification", func(t *testing.T) {
buildValueNode := func(v int64) ipld.Node {
np := basicnode.Prototype.Map
nb := np.NewBuilder()
ma, _ := nb.BeginMap(1)
ma.AssembleKey().AssignString("value")
ma.AssembleValue().AssignInt(v)
ma.Finish()
return nb.Build()
}
t.Run("all", func(t *testing.T) {
np := basicnode.Prototype.List
nb := np.NewBuilder()
la, _ := nb.BeginList(5)
la.AssembleValue().AssignNode(buildValueNode(5))
la.AssembleValue().AssignNode(buildValueNode(10))
la.AssembleValue().AssignNode(buildValueNode(20))
la.AssembleValue().AssignNode(buildValueNode(50))
la.AssembleValue().AssignNode(buildValueNode(100))
la.Finish()
nd := nb.Build()
pol := Policy{
All(
selector.MustParse(".[]"),
GreaterThan(selector.MustParse(".value"), literal.Int(2)),
),
}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{
All(
selector.MustParse(".[]"),
GreaterThan(selector.MustParse(".value"), literal.Int(20)),
),
}
ok = Match(pol, nd)
require.False(t, ok)
})
t.Run("any", func(t *testing.T) {
np := basicnode.Prototype.List
nb := np.NewBuilder()
la, _ := nb.BeginList(5)
la.AssembleValue().AssignNode(buildValueNode(5))
la.AssembleValue().AssignNode(buildValueNode(10))
la.AssembleValue().AssignNode(buildValueNode(20))
la.AssembleValue().AssignNode(buildValueNode(50))
la.AssembleValue().AssignNode(buildValueNode(100))
la.Finish()
nd := nb.Build()
pol := Policy{
Any(
selector.MustParse(".[]"),
GreaterThan(selector.MustParse(".value"), literal.Int(60)),
),
}
ok := Match(pol, nd)
require.True(t, ok)
pol = Policy{
Any(
selector.MustParse(".[]"),
GreaterThan(selector.MustParse(".value"), literal.Int(100)),
),
}
ok = Match(pol, nd)
require.False(t, ok)
})
})
}
func TestPolicyExamples(t *testing.T) {
makeNode := func(data string) ipld.Node {
nd, err := ipld.Decode([]byte(data), dagjson.Decode)
require.NoError(t, err)
return nd
}
evaluate := func(statement string, data ipld.Node) bool {
// we need to wrap statement with [] to make them a policy
policy := fmt.Sprintf("[%s]", statement)
pol, err := FromDagJson(policy)
require.NoError(t, err)
return Match(pol, data)
}
t.Run("And", func(t *testing.T) {
data := makeNode(`{ "name": "Katie", "age": 35, "nationalities": ["Canadian", "South African"] }`)
require.True(t, evaluate(`["and", []]`, data))
require.True(t, evaluate(`
["and", [
["==", ".name", "Katie"],
[">=", ".age", 21]
]]`, data))
require.False(t, evaluate(`
["and", [
["==", ".name", "Katie"],
[">=", ".age", 21],
["==", ".nationalities", ["American"]]
]]`, data))
})
t.Run("Or", func(t *testing.T) {
data := makeNode(`{ "name": "Katie", "age": 35, "nationalities": ["Canadian", "South African"] }`)
require.True(t, evaluate(`["or", []]`, data))
require.True(t, evaluate(`
["or", [
["==", ".name", "Katie"],
[">", ".age", 45]
]]
`, data))
})
t.Run("Not", func(t *testing.T) {
data := makeNode(`{ "name": "Katie", "nationalities": ["Canadian", "South African"] }`)
require.True(t, evaluate(`
["not",
["and", [
["==", ".name", "Katie"],
["==", ".nationalities", ["American"]]
]]
]
`, data))
})
t.Run("All", func(t *testing.T) {
data := makeNode(`{"a": [{"b": 1}, {"b": 2}, {"z": [7, 8, 9]}]}`)
require.False(t, evaluate(`["all", ".a", [">", ".b", 0]]`, data))
})
t.Run("Any", func(t *testing.T) {
data := makeNode(`{"a": [{"b": 1}, {"b": 2}, {"z": [7, 8, 9]}]}`)
require.True(t, evaluate(`["any", ".a", ["==", ".b", 2]]`, data))
})
}

125
capability/policy/policy.go Normal file
View File

@@ -0,0 +1,125 @@
package policy
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#policy
import (
"github.com/gobwas/glob"
"github.com/ipld/go-ipld-prime"
"github.com/ucan-wg/go-ucan/capability/policy/selector"
)
const (
KindEqual = "==" // implemented by equality
KindGreaterThan = ">" // implemented by equality
KindGreaterThanOrEqual = ">=" // implemented by equality
KindLessThan = "<" // implemented by equality
KindLessThanOrEqual = "<=" // implemented by equality
KindNot = "not" // implemented by negation
KindAnd = "and" // implemented by connective
KindOr = "or" // implemented by connective
KindLike = "like" // implemented by wildcard
KindAll = "all" // implemented by quantifier
KindAny = "any" // implemented by quantifier
)
type Policy []Statement
type Statement interface {
Kind() string
}
type equality struct {
kind string
selector selector.Selector
value ipld.Node
}
func (e equality) Kind() string {
return e.kind
}
func Equal(selector selector.Selector, value ipld.Node) Statement {
return equality{kind: KindEqual, selector: selector, value: value}
}
func GreaterThan(selector selector.Selector, value ipld.Node) Statement {
return equality{kind: KindGreaterThan, selector: selector, value: value}
}
func GreaterThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
return equality{kind: KindGreaterThanOrEqual, selector: selector, value: value}
}
func LessThan(selector selector.Selector, value ipld.Node) Statement {
return equality{kind: KindLessThan, selector: selector, value: value}
}
func LessThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
return equality{kind: KindLessThanOrEqual, selector: selector, value: value}
}
type negation struct {
statement Statement
}
func (n negation) Kind() string {
return KindNot
}
func Not(stmt Statement) Statement {
return negation{statement: stmt}
}
type connective struct {
kind string
statements []Statement
}
func (c connective) Kind() string {
return c.kind
}
func And(stmts ...Statement) Statement {
return connective{kind: KindAnd, statements: stmts}
}
func Or(stmts ...Statement) Statement {
return connective{kind: KindOr, statements: stmts}
}
type wildcard struct {
selector selector.Selector
pattern string
glob glob.Glob // not serialized
}
func (n wildcard) Kind() string {
return KindLike
}
func Like(selector selector.Selector, pattern string) (Statement, error) {
g, err := glob.Compile(pattern)
if err != nil {
return nil, err
}
return wildcard{selector: selector, pattern: pattern, glob: g}, nil
}
type quantifier struct {
kind string
selector selector.Selector
statement Statement
}
func (n quantifier) Kind() string {
return n.kind
}
func All(selector selector.Selector, statement Statement) Statement {
return quantifier{kind: KindAll, selector: selector, statement: statement}
}
func Any(selector selector.Selector, statement Statement) Statement {
return quantifier{kind: KindAny, selector: selector, statement: statement}
}

View File

@@ -2,18 +2,10 @@ package selector
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
)
var (
indexRegex = regexp.MustCompile(`^-?\d+$`)
sliceRegex = regexp.MustCompile(`^((\-?\d+:\-?\d*)|(\-?\d*:\-?\d+))$`)
fieldRegex = regexp.MustCompile(`^\.[a-zA-Z_]*?$`)
)
func Parse(str string) (Selector, error) {
if len(str) == 0 {
return nil, newParseError("empty selector", str, 0, "")
@@ -21,12 +13,6 @@ func Parse(str string) (Selector, error) {
if string(str[0]) != "." {
return nil, newParseError("selector must start with identity segment '.'", str, 0, string(str[0]))
}
if str == "." {
return Selector{segment{str: ".", identity: true}}, nil
}
if str == ".?" {
return Selector{segment{str: ".?", identity: true, optional: true}}, nil
}
col := 0
var sel Selector
@@ -34,70 +20,56 @@ func Parse(str string) (Selector, error) {
seg := tok
opt := strings.HasSuffix(tok, "?")
if opt {
seg = strings.TrimRight(tok, "?")
seg = tok[0 : len(tok)-1]
}
switch {
case seg == ".":
switch seg {
case ".":
if len(sel) > 0 && sel[len(sel)-1].Identity() {
return nil, newParseError("selector contains unsupported recursive descent segment: '..'", str, col, tok)
}
sel = append(sel, segment{str: ".", identity: true})
sel = append(sel, Identity)
case "[]":
sel = append(sel, segment{tok, false, opt, true, nil, "", 0})
default:
if strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]") {
lookup := seg[1 : len(seg)-1]
case seg == "[]":
sel = append(sel, segment{str: tok, optional: opt, iterator: true})
case strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]"):
lookup := seg[1 : len(seg)-1]
switch {
// index, [123]
case indexRegex.MatchString(lookup):
idx, err := strconv.Atoi(lookup)
if err != nil {
return nil, newParseError("invalid index", str, col, tok)
}
sel = append(sel, segment{str: tok, optional: opt, index: idx})
// explicit field, ["abcd"]
case strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\""):
fieldName := lookup[1 : len(lookup)-1]
if strings.Contains(fieldName, ":") {
if indexRegex.MatchString(lookup) { // index
idx, err := strconv.Atoi(lookup)
if err != nil {
return nil, newParseError("invalid index", str, col, tok)
}
sel = append(sel, segment{str: tok, optional: opt, index: idx})
} else if strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\"") { // explicit field
sel = append(sel, segment{str: tok, optional: opt, field: lookup[1 : len(lookup)-1]})
} else if sliceRegex.MatchString(lookup) { // slice [3:5] or [:5] or [3:]
var rng []int
splt := strings.Split(lookup, ":")
if splt[0] == "" {
rng = append(rng, 0)
} else {
i, err := strconv.Atoi(splt[0])
if err != nil {
return nil, newParseError("invalid slice index", str, col, tok)
}
rng = append(rng, i)
}
if splt[1] != "" {
i, err := strconv.Atoi(splt[1])
if err != nil {
return nil, newParseError("invalid slice index", str, col, tok)
}
rng = append(rng, i)
}
sel = append(sel, segment{str: tok, optional: opt, slice: rng})
} else {
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
}
sel = append(sel, segment{str: tok, optional: opt, field: fieldName})
// slice [3:5] or [:5] or [3:], also negative numbers
case sliceRegex.MatchString(lookup):
var rng [2]int64
splt := strings.Split(lookup, ":")
if splt[0] == "" {
rng[0] = math.MinInt
} else {
i, err := strconv.ParseInt(splt[0], 10, 0)
if err != nil {
return nil, newParseError("invalid slice index", str, col, tok)
}
rng[0] = i
}
if splt[1] == "" {
rng[1] = math.MaxInt
} else {
i, err := strconv.ParseInt(splt[1], 10, 0)
if err != nil {
return nil, newParseError("invalid slice index", str, col, tok)
}
rng[1] = i
}
sel = append(sel, segment{str: tok, optional: opt, slice: rng[:]})
default:
} else if fieldRegex.MatchString(seg) {
sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]})
} else {
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
}
case fieldRegex.MatchString(seg):
sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]})
default:
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
}
col += len(tok)
}

View File

@@ -0,0 +1,462 @@
package selector
import (
"fmt"
"regexp"
"strings"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ipld/go-ipld-prime/schema"
)
// Selector describes a UCAN policy selector, as specified here:
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#selectors
type Selector []segment
func (s Selector) String() string {
var res strings.Builder
for _, seg := range s {
res.WriteString(seg.String())
}
return res.String()
}
var Identity = segment{".", true, false, false, nil, "", 0}
var (
indexRegex = regexp.MustCompile(`^-?\d+$`)
sliceRegex = regexp.MustCompile(`^((\-?\d+:\-?\d*)|(\-?\d*:\-?\d+))$`)
fieldRegex = regexp.MustCompile(`^\.[a-zA-Z_]*?$`)
)
type segment struct {
str string
identity bool
optional bool
iterator bool
slice []int
field string
index int
}
// String returns the segment's string representation.
func (s segment) String() string {
return s.str
}
// Identity flags that this selector is the identity selector.
func (s segment) Identity() bool {
return s.identity
}
// Optional flags that this selector is optional.
func (s segment) Optional() bool {
return s.optional
}
// Iterator flags that this selector is an iterator segment.
func (s segment) Iterator() bool {
return s.iterator
}
// Slice flags that this segment targets a range of a slice.
func (s segment) Slice() []int {
return s.slice
}
// Field is the name of a field in a struct/map.
func (s segment) Field() string {
return s.field
}
// Index is an index of a slice.
func (s segment) Index() int {
return s.index
}
// Select uses a selector to extract an IPLD node or set of nodes from the
// passed subject node.
func Select(sel Selector, subject ipld.Node) (ipld.Node, []ipld.Node, error) {
return resolve(sel, subject, nil)
}
func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.Node, error) {
cur := subject
for i, seg := range sel {
if seg.Identity() {
continue
}
// 1st level: handle the different segment types (iterator, field, slice, index)
// 2nd level: handle different node kinds (list, map, string, bytes)
switch {
case seg.Iterator():
if cur == nil || cur.Kind() == datamodel.Kind_Null {
if seg.Optional() {
// build empty list
nb := basicnode.Prototype.List.NewBuilder()
assembler, err := nb.BeginList(0)
if err != nil {
return nil, nil, err
}
if err = assembler.Finish(); err != nil {
return nil, nil, err
}
return nb.Build(), nil, nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
}
} else {
var many []ipld.Node
switch cur.Kind() {
case datamodel.Kind_List:
it := cur.ListIterator()
for !it.Done() {
_, v, err := it.Next()
if err != nil {
return nil, nil, err
}
// check if there are more iterator segments
if len(sel) > i+1 && sel[i+1].Iterator() {
if v.Kind() == datamodel.Kind_List {
// recursively resolve the remaining selector segments
var o ipld.Node
var m []ipld.Node
o, m, err = resolve(sel[i+1:], v, at)
if err != nil {
// if the segment is optional and an error occurs, skip the current iteration.
if seg.Optional() {
continue
} else {
return nil, nil, err
}
}
if m != nil {
many = append(many, m...)
} else if o != nil {
many = append(many, o)
}
} else {
// if the current value is not a list and the next segment is optional, skip the current iteration
if sel[i+1].Optional() {
continue
} else {
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(v)), at)
}
}
} else {
// if there are no more iterator segments, append the current value to the result
many = append(many, v)
}
}
case datamodel.Kind_Map:
it := cur.MapIterator()
for !it.Done() {
_, v, err := it.Next()
if err != nil {
return nil, nil, err
}
if len(sel) > i+1 && sel[i+1].Iterator() {
if v.Kind() == datamodel.Kind_List {
var o ipld.Node
var m []ipld.Node
o, m, err = resolve(sel[i+1:], v, at)
if err != nil {
if seg.Optional() {
continue
} else {
return nil, nil, err
}
}
if m != nil {
many = append(many, m...)
} else if o != nil {
many = append(many, o)
}
} else {
if sel[i+1].Optional() {
continue
} else {
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(v)), at)
}
}
} else {
many = append(many, v)
}
}
default:
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
}
return nil, many, nil
}
case seg.Field() != "":
at = append(at, seg.Field())
if cur == nil {
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
}
} else {
switch cur.Kind() {
case datamodel.Kind_Map:
n, err := cur.LookupByString(seg.Field())
if err != nil {
if isMissing(err) {
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("object has no field named: %s", seg.Field()), at)
}
} else {
return nil, nil, err
}
} else {
cur = n
}
case datamodel.Kind_List:
var many []ipld.Node
it := cur.ListIterator()
for !it.Done() {
_, v, err := it.Next()
if err != nil {
return nil, nil, err
}
if v.Kind() == datamodel.Kind_Map {
n, err := v.LookupByString(seg.Field())
if err == nil {
many = append(many, n)
}
}
}
if len(many) > 0 {
cur = nil
return nil, many, nil
} else if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("no elements in list have field named: %s", seg.Field()), at)
}
default:
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
}
}
}
case seg.Slice() != nil:
if cur == nil {
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
}
} else {
slice := seg.Slice()
var start, end, length int64
switch cur.Kind() {
case datamodel.Kind_List:
length = cur.Length()
start, end = resolveSliceIndices(slice, length)
case datamodel.Kind_Bytes:
b, _ := cur.AsBytes()
length = int64(len(b))
start, end = resolveSliceIndices(slice, length)
case datamodel.Kind_String:
str, _ := cur.AsString()
length = int64(len(str))
start, end = resolveSliceIndices(slice, length)
default:
return nil, nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
}
if start < 0 || end < start || end > length {
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("slice out of bounds: [%d:%d]", start, end), at)
}
} else {
switch cur.Kind() {
case datamodel.Kind_List:
if end > cur.Length() {
end = cur.Length()
}
nb := basicnode.Prototype.List.NewBuilder()
assembler, _ := nb.BeginList(int64(end - start))
for i := start; i < end; i++ {
item, _ := cur.LookupByIndex(int64(i))
assembler.AssembleValue().AssignNode(item)
}
assembler.Finish()
cur = nb.Build()
case datamodel.Kind_Bytes:
b, _ := cur.AsBytes()
l := int64(len(b))
if end > l {
end = l
}
cur = basicnode.NewBytes(b[start:end])
case datamodel.Kind_String:
str, _ := cur.AsString()
l := int64(len(str))
if end > l {
end = l
}
cur = basicnode.NewString(str[start:end])
}
}
}
default:
at = append(at, fmt.Sprintf("%d", seg.Index()))
if cur == nil {
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
}
} else {
idx := seg.Index()
switch cur.Kind() {
case datamodel.Kind_List:
if idx < 0 {
idx = int(cur.Length()) + idx
}
if idx < 0 || idx >= int(cur.Length()) {
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
}
} else {
cur, _ = cur.LookupByIndex(int64(idx))
}
case datamodel.Kind_String:
str, _ := cur.AsString()
if idx < 0 {
idx = len(str) + idx
}
if idx < 0 || idx >= len(str) {
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
}
} else {
cur = basicnode.NewString(string(str[idx]))
}
case datamodel.Kind_Bytes:
b, _ := cur.AsBytes()
if idx < 0 {
idx = len(b) + idx
}
if idx < 0 || idx >= len(b) {
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
}
} else {
cur = basicnode.NewInt(int64(b[idx]))
}
default:
return nil, nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
}
}
}
}
return cur, nil, nil
}
// resolveSliceIndices resolves the start and end indices for slicing a list or byte array.
//
// It takes the slice indices from the selector segment and the length of the list or byte array,
// and returns the resolved start and end indices. Negative indices are supported.
//
// Parameters:
// - slice: The slice indices from the selector segment.
// - length: The length of the list or byte array being sliced.
//
// Returns:
// - start: The resolved start index for slicing.
// - end: The resolved end index for slicing.
func resolveSliceIndices(slice []int, length int64) (int64, int64) {
start, end := int64(0), length
if len(slice) > 0 {
start = int64(slice[0])
if start < 0 {
start = length + start
if start < 0 {
start = 0
}
}
}
if len(slice) > 1 {
end = int64(slice[1])
if end <= 0 {
end = length + end
if end < start {
end = start
}
}
}
return start, end
}
func kindString(n datamodel.Node) string {
if n == nil {
return "null"
}
return n.Kind().String()
}
func isMissing(err error) bool {
if _, ok := err.(datamodel.ErrNotExists); ok {
return true
}
if _, ok := err.(schema.ErrNoSuchField); ok {
return true
}
if _, ok := err.(schema.ErrInvalidKey); ok {
return true
}
return false
}
type resolutionerr struct {
msg string
at []string
}
func (r resolutionerr) Name() string {
return "ResolutionError"
}
func (r resolutionerr) Message() string {
return fmt.Sprintf("can not resolve path: .%s", strings.Join(r.at, "."))
}
func (r resolutionerr) At() []string {
return r.at
}
func (r resolutionerr) Error() string {
return r.Message()
}
func newResolutionError(message string, at []string) error {
return resolutionerr{message, at}
}

View File

@@ -0,0 +1,499 @@
package selector
import (
"fmt"
"strings"
"testing"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/must"
basicnode "github.com/ipld/go-ipld-prime/node/basic"
"github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/ipld/go-ipld-prime/printer"
"github.com/stretchr/testify/require"
)
func TestParse(t *testing.T) {
t.Run("identity", func(t *testing.T) {
sel, err := Parse(".")
require.NoError(t, err)
require.Equal(t, 1, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
})
t.Run("field", func(t *testing.T) {
sel, err := Parse(".foo")
require.NoError(t, err)
require.Equal(t, 1, len(sel))
require.False(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Equal(t, sel[0].Field(), "foo")
require.Empty(t, sel[0].Index())
})
t.Run("explicit field", func(t *testing.T) {
sel, err := Parse(`.["foo"]`)
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Equal(t, sel[1].Field(), "foo")
require.Empty(t, sel[1].Index())
})
t.Run("index", func(t *testing.T) {
sel, err := Parse(".[138]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Equal(t, sel[1].Index(), 138)
})
t.Run("negative index", func(t *testing.T) {
sel, err := Parse(".[-138]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Equal(t, sel[1].Index(), -138)
})
t.Run("iterator", func(t *testing.T) {
sel, err := Parse(".[]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.True(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
})
t.Run("optional field", func(t *testing.T) {
sel, err := Parse(".foo?")
require.NoError(t, err)
require.Equal(t, 1, len(sel))
require.False(t, sel[0].Identity())
require.True(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Equal(t, sel[0].Field(), "foo")
require.Empty(t, sel[0].Index())
})
t.Run("optional explicit field", func(t *testing.T) {
sel, err := Parse(`.["foo"]?`)
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Equal(t, sel[1].Field(), "foo")
require.Empty(t, sel[1].Index())
})
t.Run("optional index", func(t *testing.T) {
sel, err := Parse(".[138]?")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Equal(t, sel[1].Index(), 138)
})
t.Run("optional iterator", func(t *testing.T) {
sel, err := Parse(".[]?")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.True(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
})
t.Run("nesting", func(t *testing.T) {
str := `.foo.["bar"].[138]?.baz[1:]`
sel, err := Parse(str)
require.NoError(t, err)
printSegments(sel)
require.Equal(t, str, sel.String())
require.Equal(t, 7, len(sel))
require.False(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Equal(t, sel[0].Field(), "foo")
require.Empty(t, sel[0].Index())
require.True(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
require.False(t, sel[2].Identity())
require.False(t, sel[2].Optional())
require.False(t, sel[2].Iterator())
require.Empty(t, sel[2].Slice())
require.Equal(t, sel[2].Field(), "bar")
require.Empty(t, sel[2].Index())
require.True(t, sel[3].Identity())
require.False(t, sel[3].Optional())
require.False(t, sel[3].Iterator())
require.Empty(t, sel[3].Slice())
require.Empty(t, sel[3].Field())
require.Empty(t, sel[3].Index())
require.False(t, sel[4].Identity())
require.True(t, sel[4].Optional())
require.False(t, sel[4].Iterator())
require.Empty(t, sel[4].Slice())
require.Empty(t, sel[4].Field())
require.Equal(t, sel[4].Index(), 138)
require.False(t, sel[5].Identity())
require.False(t, sel[5].Optional())
require.False(t, sel[5].Iterator())
require.Empty(t, sel[5].Slice())
require.Equal(t, sel[5].Field(), "baz")
require.Empty(t, sel[5].Index())
require.False(t, sel[6].Identity())
require.False(t, sel[6].Optional())
require.False(t, sel[6].Iterator())
require.Equal(t, sel[6].Slice(), []int{1})
require.Empty(t, sel[6].Field())
require.Empty(t, sel[6].Index())
})
t.Run("non dotted", func(t *testing.T) {
_, err := Parse("foo")
require.NotNil(t, err)
fmt.Println(err)
})
t.Run("non quoted", func(t *testing.T) {
_, err := Parse(".[foo]")
require.NotNil(t, err)
fmt.Println(err)
})
}
func printSegments(s Selector) {
for i, seg := range s {
fmt.Printf("%d: %s\n", i, seg.String())
}
}
func TestSelect(t *testing.T) {
type name struct {
First string
Middle *string
Last string
}
type interest struct {
Name string
Outdoor bool
Experience int
}
type user struct {
Name name
Age int
Nationalities []string
Interests []interest
}
ts, err := ipld.LoadSchemaBytes([]byte(`
type User struct {
name Name
age Int
nationalities [String]
interests [Interest]
}
type Name struct {
first String
middle optional String
last String
}
type Interest struct {
name String
outdoor Bool
experience Int
}
`))
require.NoError(t, err)
typ := ts.TypeByName("User")
am := "Joan"
alice := user{
Name: name{First: "Alice", Middle: &am, Last: "Wonderland"},
Age: 24,
Nationalities: []string{"British"},
Interests: []interest{
{Name: "Cycling", Outdoor: true, Experience: 4},
{Name: "Chess", Outdoor: false, Experience: 2},
},
}
bob := user{
Name: name{First: "Bob", Last: "Builder"},
Age: 35,
Nationalities: []string{"Canadian", "South African"},
Interests: []interest{
{Name: "Snowboarding", Outdoor: true, Experience: 8},
{Name: "Reading", Outdoor: false, Experience: 25},
},
}
anode := bindnode.Wrap(&alice, typ)
bnode := bindnode.Wrap(&bob, typ)
t.Run("identity", func(t *testing.T) {
sel, err := Parse(".")
require.NoError(t, err)
one, many, err := Select(sel, anode)
require.NoError(t, err)
require.NotEmpty(t, one)
require.Empty(t, many)
fmt.Println(printer.Sprint(one))
age := must.Int(must.Node(one.LookupByString("age")))
require.Equal(t, int64(alice.Age), age)
})
t.Run("nested property", func(t *testing.T) {
sel, err := Parse(".name.first")
require.NoError(t, err)
one, many, err := Select(sel, anode)
require.NoError(t, err)
require.NotEmpty(t, one)
require.Empty(t, many)
fmt.Println(printer.Sprint(one))
name := must.String(one)
require.Equal(t, alice.Name.First, name)
one, many, err = Select(sel, bnode)
require.NoError(t, err)
require.NotEmpty(t, one)
require.Empty(t, many)
fmt.Println(printer.Sprint(one))
name = must.String(one)
require.Equal(t, bob.Name.First, name)
})
t.Run("optional nested property", func(t *testing.T) {
sel, err := Parse(".name.middle?")
require.NoError(t, err)
one, many, err := Select(sel, anode)
require.NoError(t, err)
require.NotEmpty(t, one)
require.Empty(t, many)
fmt.Println(printer.Sprint(one))
name := must.String(one)
require.Equal(t, *alice.Name.Middle, name)
one, many, err = Select(sel, bnode)
require.NoError(t, err)
require.Empty(t, one)
require.Empty(t, many)
})
t.Run("not exists", func(t *testing.T) {
sel, err := Parse(".name.foo")
require.NoError(t, err)
one, many, err := Select(sel, anode)
require.Error(t, err)
require.Empty(t, one)
require.Empty(t, many)
fmt.Println(err)
require.ErrorAs(t, err, &resolutionerr{}, "error was not a resolution error")
})
t.Run("optional not exists", func(t *testing.T) {
sel, err := Parse(".name.foo?")
require.NoError(t, err)
one, many, err := Select(sel, anode)
require.NoError(t, err)
require.Empty(t, one)
require.Empty(t, many)
})
t.Run("iterator", func(t *testing.T) {
sel, err := Parse(".interests[]")
require.NoError(t, err)
one, many, err := Select(sel, anode)
require.NoError(t, err)
require.Empty(t, one)
require.NotEmpty(t, many)
for _, n := range many {
fmt.Println(printer.Sprint(n))
}
iname := must.String(must.Node(many[0].LookupByString("name")))
require.Equal(t, alice.Interests[0].Name, iname)
iname = must.String(must.Node(many[1].LookupByString("name")))
require.Equal(t, alice.Interests[1].Name, iname)
})
t.Run("map iterator", func(t *testing.T) {
sel, err := Parse(".interests[0][]")
require.NoError(t, err)
one, many, err := Select(sel, anode)
require.NoError(t, err)
require.Empty(t, one)
require.NotEmpty(t, many)
for _, n := range many {
fmt.Println(printer.Sprint(n))
}
require.Equal(t, alice.Interests[0].Name, must.String(many[0]))
require.Equal(t, alice.Interests[0].Experience, int(must.Int(many[2])))
})
}
func FuzzParse(f *testing.F) {
selectorCorpus := []string{
`.`, `.[]`, `.[]?`, `.[][]?`, `.x`, `.["x"]`, `.[0]`, `.[-1]`, `.[0]`,
`.[0]`, `.[0:2]`, `.[1:]`, `.[:2]`, `.[0:2]`, `.[1:]`, `.x?`, `.x?`,
`.x?`, `.["x"]?`, `.length?`, `.[4]?`, `.[]`, `.[][]`, `.x`, `.x`, `.x`,
`.length`, `.[4]`,
}
for _, selector := range selectorCorpus {
f.Add(selector)
}
f.Fuzz(func(t *testing.T, selector string) {
// only look for panic()
_, _ = Parse(selector)
})
}
func FuzzParseAndSelect(f *testing.F) {
selectorCorpus := []string{
`.`, `.[]`, `.[]?`, `.[][]?`, `.x`, `.["x"]`, `.[0]`, `.[-1]`, `.[0]`,
`.[0]`, `.[0:2]`, `.[1:]`, `.[:2]`, `.[0:2]`, `.[1:]`, `.x?`, `.x?`,
`.x?`, `.["x"]?`, `.length?`, `.[4]?`, `.[]`, `.[][]`, `.x`, `.x`, `.x`,
`.length`, `.[4]`,
}
subjectCorpus := []string{
`{"x":1}`, `[1, 2]`, `null`, `[[1], 2, [3]]`, `{"x": 1 }`, `{"x": 1}`,
`[1, 2]`, `[1, 2]`, `"Hi"`, `{"/":{"bytes":"AAE"}`, `[0, 1, 2]`,
`[0, 1, 2]`, `[0, 1, 2]`, `"hello"`, `{"/":{"bytes":"AAEC"}}`, `{}`,
`null`, `[]`, `{}`, `[1, 2]`, `[0, 1]`, `null`, `[[1], 2, [3]]`, `{}`,
`null`, `[]`, `[1, 2]`, `[0, 1]`,
}
for i := 0; ; i++ {
switch {
case i < len(selectorCorpus) && i < len(subjectCorpus):
f.Add(selectorCorpus[i], subjectCorpus[i])
continue
case i > len(selectorCorpus):
f.Add("", subjectCorpus[i])
continue
case i > len(subjectCorpus):
f.Add(selectorCorpus[i], "")
continue
}
break
}
f.Fuzz(func(t *testing.T, selector, subject string) {
sel, err := Parse(selector)
if err != nil {
t.Skip()
}
np := basicnode.Prototype.Any
nb := np.NewBuilder()
err = dagjson.Decode(nb, strings.NewReader(subject))
if err != nil {
t.Skip()
}
node := nb.Build()
if node == nil {
t.Skip()
}
// look for panic()
_, _, _ = Select(sel, node)
})
}

View File

@@ -1,15 +1,18 @@
package selector_test
import (
"bytes"
"strings"
"testing"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/datamodel"
basicnode "github.com/ipld/go-ipld-prime/node/basic"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
"github.com/ucan-wg/go-ucan/capability/policy/selector"
)
// TestSupported Forms runs tests against the Selector according to the
@@ -23,15 +26,17 @@ func TestSupportedForms(t *testing.T) {
Output string
}
// Pass and return a node
// Pass
for _, testcase := range []Testcase{
{Name: "Identity", Selector: `.`, Input: `{"x":1}`, Output: `{"x":1}`},
{Name: "Iterator", Selector: `.[]`, Input: `[1, 2]`, Output: `[1, 2]`},
{Name: "Optional Null Iterator", Selector: `.[]?`, Input: `null`, Output: `[]`},
{Name: "Optional Iterator", Selector: `.[][]?`, Input: `[[1], 2, [3]]`, Output: `[1, 3]`},
{Name: "Object Key", Selector: `.x`, Input: `{"x": 1 }`, Output: `1`},
{Name: "Quoted Key", Selector: `.["x"]`, Input: `{"x": 1}`, Output: `1`},
{Name: "Index", Selector: `.[0]`, Input: `[1, 2]`, Output: `1`},
{Name: "Negative Index", Selector: `.[-1]`, Input: `[1, 2]`, Output: `2`},
{Name: "String Index", Selector: `.[0]`, Input: `"Hi"`, Output: `"H"`},
{Name: "Bytes Index", Selector: `.[0]`, Input: `{"/":{"bytes":"AAE"}}`, Output: `0`},
{Name: "Array Slice", Selector: `.[0:2]`, Input: `[0, 1, 2]`, Output: `[0, 1]`},
{Name: "Array Slice", Selector: `.[1:]`, Input: `[0, 1, 2]`, Output: `[1, 2]`},
@@ -47,16 +52,35 @@ func TestSupportedForms(t *testing.T) {
require.NoError(t, err)
// attempt to select
res, err := sel.Select(makeNode(t, tc.Input))
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
require.NoError(t, err)
require.NotNil(t, res)
require.NotEqual(t, node != nil, len(nodes) > 0) // XOR (only one of node or nodes should be set)
// make an IPLD List node from a []datamodel.Node
if node == nil {
nb := basicnode.Prototype.List.NewBuilder()
la, err := nb.BeginList(int64(len(nodes)))
require.NoError(t, err)
for _, n := range nodes {
// TODO: This code is probably not needed if the Select operation properly prunes nil values - e.g.: Optional Iterator
if n == nil {
n = datamodel.Null
}
require.NoError(t, la.AssembleValue().AssignNode(n))
}
require.NoError(t, la.Finish())
node = nb.Build()
}
exp := makeNode(t, tc.Output)
require.True(t, ipld.DeepEqual(exp, res))
equalIPLD(t, exp, node)
})
}
// No error and return null, as optional
// null
for _, testcase := range []Testcase{
{Name: "Optional Missing Key", Selector: `.x?`, Input: `{}`},
{Name: "Optional Null Key", Selector: `.x?`, Input: `null`},
@@ -73,15 +97,19 @@ func TestSupportedForms(t *testing.T) {
require.NoError(t, err)
// attempt to select
res, err := sel.Select(makeNode(t, tc.Input))
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
require.NoError(t, err)
require.Nil(t, res)
// TODO: should Select return a single node which is sometimes a list or null?
// require.Equal(t, datamodel.Null, node)
assert.Nil(t, node)
assert.Empty(t, nodes)
})
}
// fail to select and return an error
// error
for _, testcase := range []Testcase{
{Name: "Null Iterator", Selector: `.[]`, Input: `null`},
{Name: "Nested Iterator", Selector: `.[][]`, Input: `[[1], 2, [3]]`},
{Name: "Missing Key", Selector: `.x`, Input: `{}`},
{Name: "Null Key", Selector: `.x`, Input: `null`},
{Name: "Array Key", Selector: `.x`, Input: `[]`},
@@ -96,13 +124,31 @@ func TestSupportedForms(t *testing.T) {
require.NoError(t, err)
// attempt to select
res, err := sel.Select(makeNode(t, tc.Input))
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
require.Error(t, err)
require.Nil(t, res)
assert.Nil(t, node)
assert.Empty(t, nodes)
})
}
}
func equalIPLD(t *testing.T, expected datamodel.Node, actual datamodel.Node) bool {
t.Helper()
exp, act := &bytes.Buffer{}, &bytes.Buffer{}
if err := dagjson.Encode(expected, exp); err != nil {
return assert.Fail(t, "Failed to encode json for expected IPLD node")
}
if err := dagjson.Encode(actual, act); err != nil {
return assert.Fail(t, "Failed to encode JSON for actual IPLD node")
}
require.JSONEq(t, exp.String(), act.String())
return true
}
func makeNode(t *testing.T, dagJsonInput string) ipld.Node {
t.Helper()

View File

@@ -20,7 +20,7 @@ type Payload struct {
nonce Bytes
# Arbitrary Metadata
meta optional {String : Any}
meta {String : Any}
# "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
nbf optional Int

33
delegation/encoding.go Normal file
View File

@@ -0,0 +1,33 @@
package delegation
import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagcbor"
"github.com/ipld/go-ipld-prime/codec/dagjson"
)
func (p *PayloadModel) EncodeDagCbor() ([]byte, error) {
return ipld.Marshal(dagcbor.Encode, p, PayloadType())
}
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 {
return nil, err
}
return &p, nil
}
func DecodeDagJson(data []byte) (*PayloadModel, error) {
var p PayloadModel
_, err := ipld.Unmarshal(data, dagjson.Decode, &p, PayloadType())
if err != nil {
return nil, err
}
return &p, nil
}

69
delegation/schema.go Normal file
View File

@@ -0,0 +1,69 @@
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
}

71
delegation/schema_test.go Normal file
View File

@@ -0,0 +1,71 @@
package delegation
import (
"fmt"
"testing"
"github.com/ipld/go-ipld-prime"
"github.com/stretchr/testify/require"
)
func TestSchemaRoundTrip(t *testing.T) {
const delegationJson = `
{
"aud":"did:key:def456",
"cmd":"/foo/bar",
"exp":123456,
"iss":"did:key:abc123",
"meta":{
"bar":"baaar",
"foo":"fooo"
},
"nbf":123456,
"nonce":{
"/":{
"bytes":"c3VwZXItcmFuZG9t"
}
},
"pol":[
["==", ".status", "draft"],
["all", ".reviewer", [
["like", ".email", "*@example.com"]]
],
["any", ".tags", [
["or", [
["==", ".", "news"],
["==", ".", "press"]]
]]
]
],
"sub":""
}
`
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
// function: DecodeDagJson() EncodeDagCbor() DecodeDagCbor() EncodeDagJson()
p1, err := DecodeDagJson([]byte(delegationJson))
require.NoError(t, err)
cborBytes, err := p1.EncodeDagCbor()
require.NoError(t, err)
fmt.Println("cborBytes length", len(cborBytes))
fmt.Println("cbor", string(cborBytes))
p2, err := DecodeDagCbor(cborBytes)
require.NoError(t, err)
fmt.Println("read Cbor", p2)
readJson, err := p2.EncodeDagJson()
require.NoError(t, err)
fmt.Println("readJson length", len(readJson))
fmt.Println("json: ", string(readJson))
require.JSONEq(t, delegationJson, string(readJson))
}
func BenchmarkSchemaLoad(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _ = ipld.LoadSchemaBytes(schemaBytes)
}
}

87
delegation/view.go Normal file
View File

@@ -0,0 +1,87 @@
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
}

View File

@@ -1,31 +0,0 @@
## did
### Testing
The test suite for this package includes test vectors provided by the
authors of the [`did:key` method specification](https://w3c-ccg.github.io/did-method-key/).
Some of these tests provide the public key associated with a `did:key`
as JWKs and an extra (test-only) dependency has been added to unmarshal
the JWK into a Go `struct`. Support for the `secp256k1` encryption
algorithm is experimental (but stable in my experience) and requires the
addition of the following build tag to properly run:
```
// go:build jwx_es256k
```
WARNING: These tests will not run by default!
To include these tests from the CLI, execute the following command:
```
go test -v ./did -tags jwx_es256k
```
It should also be possible to configure your IDE to run these tests. For
instance, in Codium, add the following JSON snippet to your local project
configuration:
```
"go.testTags": "jwx_es256k",
```

View File

@@ -1,231 +0,0 @@
package did
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"errors"
"fmt"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
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"
)
// GenerateEd25519 generates an Ed25519 private key and the matching DID.
// This is the RECOMMENDED algorithm.
func GenerateEd25519() (crypto.PrivKey, DID, error) {
priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
return nil, Undef, nil
}
did, err := FromPubKey(pub)
return priv, did, err
}
// GenerateRSA generates a RSA private key and the matching DID.
func GenerateRSA() (crypto.PrivKey, DID, error) {
// NIST Special Publication 800-57 Part 1 Revision 5
// Section 5.6.1.1 (Table 2)
// Paraphrased: 2048-bit RSA keys are secure until 2030 and 3072-bit keys are recommended for longer-term security.
const keyLength = 3072
priv, pub, err := crypto.GenerateRSAKeyPair(keyLength, rand.Reader)
if err != nil {
return nil, Undef, nil
}
did, err := FromPubKey(pub)
return priv, did, err
}
// GenerateEd25519 generates a Secp256k1 private key and the matching DID.
func GenerateSecp256k1() (crypto.PrivKey, DID, error) {
priv, pub, err := crypto.GenerateSecp256k1Key(rand.Reader)
if err != nil {
return nil, Undef, nil
}
did, err := FromPubKey(pub)
return priv, did, err
}
// GenerateECDSA generates an ECDSA private key and the matching DID
// for the default P256 curve.
func GenerateECDSA() (crypto.PrivKey, DID, error) {
return GenerateECDSAWithCurve(P256)
}
// GenerateECDSAWithCurve generates an ECDSA private key and matching
// DID for the user-supplied curve
func GenerateECDSAWithCurve(code multicodec.Code) (crypto.PrivKey, DID, error) {
var curve elliptic.Curve
switch code {
case P256:
curve = elliptic.P256()
case P384:
curve = elliptic.P384()
case P521:
curve = elliptic.P521()
default:
return nil, Undef, errors.New("unsupported ECDSA curve")
}
priv, pub, err := crypto.GenerateECDSAKeyPairWithCurve(curve, rand.Reader)
if err != nil {
return nil, Undef, err
}
did, err := FromPubKey(pub)
return priv, did, err
}
// FromPrivKey is a convenience function that returns the DID associated
// with the public key associated with the provided private key.
func FromPrivKey(privKey crypto.PrivKey) (DID, error) {
return FromPubKey(privKey.GetPublic())
}
// FromPubKey returns a did:key constructed from the provided public key.
func FromPubKey(pubKey crypto.PubKey) (DID, error) {
var code multicodec.Code
switch pubKey.Type() {
case pb.KeyType_Ed25519:
code = multicodec.Ed25519Pub
case pb.KeyType_RSA:
code = RSA
case pb.KeyType_Secp256k1:
code = Secp256k1
case pb.KeyType_ECDSA:
var err error
if code, err = codeForCurve(pubKey); err != nil {
return Undef, err
}
default:
return Undef, errors.New("unsupported key type")
}
if pubKey.Type() == pb.KeyType_ECDSA && code == Secp256k1 {
var err error
pubKey, err = coerceECDSAToSecp256k1(pubKey)
if err != nil {
return Undef, err
}
}
var bytes []byte
switch pubKey.Type() {
case pb.KeyType_ECDSA:
pkix, err := pubKey.Raw()
if err != nil {
return Undef, err
}
publicKey, err := x509.ParsePKIXPublicKey(pkix)
if err != nil {
return Undef, err
}
ecdsaPublicKey := publicKey.(*ecdsa.PublicKey)
bytes = elliptic.MarshalCompressed(ecdsaPublicKey.Curve, ecdsaPublicKey.X, ecdsaPublicKey.Y)
case pb.KeyType_Ed25519, pb.KeyType_Secp256k1:
var err error
if bytes, err = pubKey.Raw(); err != nil {
return Undef, err
}
case pb.KeyType_RSA:
var err error
pkix, err := pubKey.Raw()
if err != nil {
return Undef, err
}
publicKey, err := x509.ParsePKIXPublicKey(pkix)
if err != nil {
return Undef, err
}
bytes = x509.MarshalPKCS1PublicKey(publicKey.(*rsa.PublicKey))
}
return DID{
code: code,
bytes: string(append(varint.ToUvarint(uint64(code)), bytes...)),
}, nil
}
// ToPubKey returns the crypto.PubKey encapsulated in the DID formed by
// parsing the provided string.
func ToPubKey(s string) (crypto.PubKey, error) {
id, err := Parse(s)
if err != nil {
return nil, err
}
return id.PubKey()
}
func codeForCurve(pubKey crypto.PubKey) (multicodec.Code, error) {
stdPub, err := crypto.PubKeyToStdKey(pubKey)
if err != nil {
return multicodec.Identity, err
}
ecdsaPub, ok := stdPub.(*ecdsa.PublicKey)
if !ok {
return multicodec.Identity, errors.New("failed to assert type for code to curve")
}
switch ecdsaPub.Curve {
case elliptic.P256():
return P256, nil
case elliptic.P384():
return P384, nil
case elliptic.P521():
return P521, nil
case secp256k1.S256():
return Secp256k1, nil
default:
return multicodec.Identity, fmt.Errorf("unsupported ECDSA curve: %s", ecdsaPub.Curve.Params().Name)
}
}
// secp256k1.S256 is a valid ECDSA curve, but the go-libp2p/core/crypto
// package treats it as a different type and has a different format for
// the raw bytes of the public key.
//
// If a valid ECDSA public key was created using the secp256k1.S256 curve,
// this function will "convert" it from a crypto.ECDSAPubKey to a
// crypto.Secp256k1PublicKey.
func coerceECDSAToSecp256k1(pubKey crypto.PubKey) (crypto.PubKey, error) {
stdPub, err := crypto.PubKeyToStdKey(pubKey)
if err != nil {
return nil, err
}
ecdsaPub, ok := stdPub.(*ecdsa.PublicKey)
if !ok {
return nil, errors.New("failed to assert type for secp256k1 coersion")
}
ecdsaPubBytes := append([]byte{0x04}, append(ecdsaPub.X.Bytes(), ecdsaPub.Y.Bytes()...)...)
secp256k1Pub, err := secp256k1.ParsePubKey(ecdsaPubBytes)
if err != nil {
return nil, err
}
cryptoPub := crypto.Secp256k1PublicKey(*secp256k1Pub)
return &cryptoPub, nil
}

View File

@@ -1,108 +0,0 @@
package did_test
import (
"crypto/elliptic"
"crypto/rand"
"testing"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/crypto/pb"
"github.com/multiformats/go-multicodec"
"github.com/stretchr/testify/assert"
"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()
_, ecdsaP256, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P256(), rand.Reader)
require.NoError(t, err)
_, ecdsaP384, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P384(), rand.Reader)
require.NoError(t, err)
_, ecdsaP521, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P521(), rand.Reader)
require.NoError(t, err)
_, ecdsaSecp256k1, err := crypto.GenerateECDSAKeyPairWithCurve(secp256k1.S256(), rand.Reader)
require.NoError(t, err)
_, ed25519, err := crypto.GenerateEd25519Key(rand.Reader)
require.NoError(t, err)
_, rsa, err := crypto.GenerateRSAKeyPair(2048, rand.Reader)
require.NoError(t, err)
_, secp256k1PubKey1, err := crypto.GenerateSecp256k1Key(rand.Reader)
require.NoError(t, err)
test := func(pub crypto.PubKey, code multicodec.Code) func(t *testing.T) {
t.Helper()
return func(t *testing.T) {
t.Parallel()
id, err := did.FromPubKey(pub)
require.NoError(t, err)
p, err := id.PubKey()
require.NoError(t, err)
assert.Equal(t, pub, p)
}
}
t.Run("ECDSA with P256 curve", test(ecdsaP256, did.P256))
t.Run("ECDSA with P384 curve", test(ecdsaP384, did.P384))
t.Run("ECDSA with P521 curve", test(ecdsaP521, did.P521))
t.Run("Ed25519", test(ed25519, did.Ed25519))
t.Run("RSA", test(rsa, did.RSA))
t.Run("secp256k1", test(secp256k1PubKey1, did.Secp256k1))
t.Run("ECDSA with secp256k1 curve (coerced)", func(t *testing.T) {
t.Parallel()
id, err := did.FromPubKey(ecdsaSecp256k1)
require.NoError(t, err)
p, err := id.PubKey()
require.NoError(t, err)
require.Equal(t, pb.KeyType_Secp256k1, p.Type())
})
t.Run("unmarshaled example key (secp256k1)", func(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
}

View File

@@ -1,140 +1,86 @@
package did
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"fmt"
"strings"
crypto "github.com/libp2p/go-libp2p/core/crypto"
mbase "github.com/multiformats/go-multibase"
"github.com/multiformats/go-multicodec"
varint "github.com/multiformats/go-varint"
)
// Signature algorithms from the [did:key specification]
//
// [did:key specification]: https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm
const (
X25519 = multicodec.X25519Pub
Ed25519 = multicodec.Ed25519Pub // UCAN required/recommended
P256 = multicodec.P256Pub // UCAN required
P384 = multicodec.P384Pub
P521 = multicodec.P521Pub
Secp256k1 = multicodec.Secp256k1Pub // UCAN required
RSA = multicodec.RsaPub
)
const Prefix = "did:"
const KeyPrefix = "did:key:"
const DIDCore = 0x0d1d
const Ed25519 = 0xed
var MethodOffset = varint.UvarintSize(uint64(DIDCore))
type DID struct {
key bool
str string
}
// Undef can be used to represent a nil or undefined DID, using DID{}
// directly is also acceptable.
var Undef = DID{}
// DID is a Decentralized Identifier of the did:key type, directly holding a cryptographic public key.
// [did:key format]: https://w3c-ccg.github.io/did-method-key/
type DID struct {
code multicodec.Code
bytes string // as string instead of []byte to allow the == operator
}
// Parse returns the DID from the string representation or an error if
// the prefix and method are incorrect, if an unknown encryption algorithm
// is specified or if the method-specific-identifier's bytes don't
// represent a public key for the specified encryption algorithm.
func Parse(str string) (DID, error) {
const keyPrefix = "did:key:"
if !strings.HasPrefix(str, keyPrefix) {
return Undef, fmt.Errorf("must start with 'did:key'")
}
baseCodec, bytes, err := mbase.Decode(str[len(keyPrefix):])
if err != nil {
return Undef, err
}
if baseCodec != mbase.Base58BTC {
return Undef, fmt.Errorf("not Base58BTC encoded")
}
code, _, err := varint.FromUvarint(bytes)
if err != nil {
return Undef, err
}
switch multicodec.Code(code) {
case Ed25519, P256, Secp256k1, RSA:
return DID{bytes: string(bytes), code: multicodec.Code(code)}, nil
default:
return Undef, fmt.Errorf("unsupported did:key multicodec: 0x%x", code)
}
}
// MustParse is like Parse but panics instead of returning an error.
func MustParse(str string) DID {
did, err := Parse(str)
if err != nil {
panic(err)
}
return did
}
// Defined tells if the DID is defined, not equal to Undef.
func (d DID) Defined() bool {
return d.code != 0 || len(d.bytes) > 0
return d.str != ""
}
// PubKey returns the public key encapsulated by the did:key.
func (d DID) PubKey() (crypto.PubKey, error) {
unmarshaler, ok := map[multicodec.Code]crypto.PubKeyUnmarshaller{
X25519: crypto.UnmarshalEd25519PublicKey,
Ed25519: crypto.UnmarshalEd25519PublicKey,
P256: ecdsaPubKeyUnmarshaler(elliptic.P256()),
P384: ecdsaPubKeyUnmarshaler(elliptic.P384()),
P521: ecdsaPubKeyUnmarshaler(elliptic.P521()),
Secp256k1: crypto.UnmarshalSecp256k1PublicKey,
RSA: rsaPubKeyUnmarshaller,
}[d.code]
if !ok {
return nil, fmt.Errorf("unsupported multicodec: %d", d.code)
func (d DID) Bytes() []byte {
if !d.Defined() {
return nil
}
return []byte(d.str)
}
codeSize := varint.UvarintSize(uint64(d.code))
return unmarshaler([]byte(d.bytes)[codeSize:])
func (d DID) DID() DID {
return d
}
// String formats the decentralized identity document (DID) as a string.
func (d DID) String() string {
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes))
return "did:key:" + key
if d.key {
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str))
return "did:key:" + key
}
return "did:" + d.str[MethodOffset:]
}
func ecdsaPubKeyUnmarshaler(curve elliptic.Curve) crypto.PubKeyUnmarshaller {
return func(data []byte) (crypto.PubKey, error) {
x, y := elliptic.UnmarshalCompressed(curve, data)
func Decode(bytes []byte) (DID, error) {
code, _, err := varint.FromUvarint(bytes)
if err != nil {
return Undef, err
}
if code == Ed25519 {
return DID{str: string(bytes), key: true}, nil
} else if code == DIDCore {
return DID{str: string(bytes)}, nil
}
return Undef, fmt.Errorf("unsupported DID encoding: 0x%x", code)
}
ecdsaPublicKey := &ecdsa.PublicKey{
Curve: curve,
X: x,
Y: y,
}
func Parse(str string) (DID, error) {
if !strings.HasPrefix(str, Prefix) {
return Undef, fmt.Errorf("must start with 'did:'")
}
pkix, err := x509.MarshalPKIXPublicKey(ecdsaPublicKey)
if strings.HasPrefix(str, KeyPrefix) {
code, bytes, err := mbase.Decode(str[len(KeyPrefix):])
if err != nil {
return nil, err
return Undef, err
}
return crypto.UnmarshalECDSAPublicKey(pkix)
if code != mbase.Base58BTC {
return Undef, fmt.Errorf("not Base58BTC encoded")
}
return Decode(bytes)
}
}
func rsaPubKeyUnmarshaller(data []byte) (crypto.PubKey, error) {
rsaPublicKey, err := x509.ParsePKCS1PublicKey(data)
if err != nil {
return nil, err
}
pkix, err := x509.MarshalPKIXPublicKey(rsaPublicKey)
if err != nil {
return nil, err
}
return crypto.UnmarshalRsaPublicKey(pkix)
buf := make([]byte, MethodOffset)
varint.PutUvarint(buf, DIDCore)
suffix, _ := strings.CutPrefix(str, Prefix)
buf = append(buf, suffix...)
return DID{str: string(buf)}, nil
}

View File

@@ -2,40 +2,78 @@ package did
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParseDIDKey(t *testing.T) {
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
d, err := Parse(str)
require.NoError(t, err)
require.Equal(t, str, d.String())
if err != nil {
t.Fatalf("%v", err)
}
if d.String() != str {
t.Fatalf("expected %v to equal %v", d.String(), str)
}
}
func TestMustParseDIDKey(t *testing.T) {
func TestDecodeDIDKey(t *testing.T) {
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
require.NotPanics(t, func() {
d := MustParse(str)
require.Equal(t, str, d.String())
})
str = "did:key:z7Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
require.Panics(t, func() {
MustParse(str)
})
d0, err := Parse(str)
if err != nil {
t.Fatalf("%v", err)
}
d1, err := Decode(d0.Bytes())
if err != nil {
t.Fatalf("%v", err)
}
if d1.String() != str {
t.Fatalf("expected %v to equal %v", d1.String(), str)
}
}
func TestParseDIDWeb(t *testing.T) {
str := "did:web:up.web3.storage"
d, err := Parse(str)
if err != nil {
t.Fatalf("%v", err)
}
if d.String() != str {
t.Fatalf("expected %v to equal %v", d.String(), str)
}
}
func TestDecodeDIDWeb(t *testing.T) {
str := "did:web:up.web3.storage"
d0, err := Parse(str)
if err != nil {
t.Fatalf("%v", err)
}
d1, err := Decode(d0.Bytes())
if err != nil {
t.Fatalf("%v", err)
}
if d1.String() != str {
t.Fatalf("expected %v to equal %v", d1.String(), str)
}
}
func TestEquivalence(t *testing.T) {
undef0 := DID{}
undef1 := Undef
u0 := DID{}
u1 := Undef
if u0 != u1 {
t.Fatalf("undef DID not equivalent")
}
did0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
require.NoError(t, err)
did1, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
require.NoError(t, err)
d0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
if err != nil {
t.Fatalf("%v", err)
}
require.True(t, undef0 == undef1)
require.False(t, undef0 == did0)
require.True(t, did0 == did1)
require.False(t, undef1 == did1)
d1, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
if err != nil {
t.Fatalf("%v", err)
}
if d0 != d1 {
t.Fatalf("two equivalent DID not equivalent")
}
}

View File

@@ -1,127 +0,0 @@
// Package didtest provides Personas that can be used for testing. Each
// Persona has a name, crypto.PrivKey and associated crypto.PubKey and
// did.DID.
package didtest
import (
"fmt"
"testing"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/did"
)
const (
alicePrivKeyB64 = "CAESQHdNJLBBiuc1AdwPHBkubB2KS1p0cv2JEF7m8tfwtrcm5ajaYPm+XmVCmtcHOF2lGDlmaiDA7emfwD3IrcyES0M="
bobPrivKeyB64 = "CAESQHBz+AIop1g+9iBDj+ufUc/zm9/ry7c6kDFO8Wl/D0+H63V9hC6s9l4npf3pYEFCjBtlR0AMNWMoFQKSlYNKo20="
carolPrivKeyB64 = "CAESQPrCgkcHnYFXDT9AlAydhPECBEivEuuVx9dJxLjVvDTmJIVNivfzg6H4mAiPfYS+5ryVVUZTHZBzvMuvvvG/Ks0="
danPrivKeyB64 = "CAESQCgNhzofKhC+7hW6x+fNd7iMPtQHeEmKRhhlduf/I7/TeOEFYAEflbJ0sAhMeDJ/HQXaAvsWgHEbJ3ZLhP8q2B0="
erinPrivKeyB64 = "CAESQKhCJo5UBpQcthko8DKMFsbdZ+qqQ5oc01CtLCqrE90dF2GfRlrMmot3WPHiHGCmEYi5ZMEHuiSI095e/6O4Bpw="
frankPrivKeyB64 = "CAESQDlXPKsy3jHh7OWTWQqyZF95Ueac5DKo7xD0NOBE5F2BNr1ZVxRmJ2dBELbOt8KP9sOACcO9qlCB7uMA1UQc7sk="
)
// Persona is a generic participant used for cryptographic testing.
type Persona int
// The provided Personas were selected from the first few generic
// participants listed in this [table].
//
// [table]: https://en.wikipedia.org/wiki/Alice_and_Bob#Cryptographic_systems
const (
PersonaAlice Persona = iota
PersonaBob
PersonaCarol
PersonaDan
PersonaErin
PersonaFrank
)
var privKeys map[Persona]crypto.PrivKey
func init() {
privKeys = make(map[Persona]crypto.PrivKey, 6)
for persona, privKeyCfg := range privKeyB64() {
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
if err != nil {
return
}
privKey, err := crypto.UnmarshalPrivateKey(privKeyMar)
if err != nil {
return
}
privKeys[persona] = privKey
}
}
// DID returns a did.DID based on the Persona's Ed25519 public key.
func (p Persona) DID() did.DID {
d, err := did.FromPrivKey(p.PrivKey())
if err != nil {
panic(err)
}
return d
}
// Name returns the username of the Persona.
func (p Persona) Name() string {
name, ok := map[Persona]string{
PersonaAlice: "Alice",
PersonaBob: "Bob",
PersonaCarol: "Carol",
PersonaDan: "Dan",
PersonaErin: "Erin",
PersonaFrank: "Frank",
}[p]
if !ok {
panic(fmt.Sprintf("Unknown persona: %v", p))
}
return name
}
// PrivKey returns the Ed25519 private key for the Persona.
func (p Persona) PrivKey() crypto.PrivKey {
return privKeys[p]
}
// PubKey returns the Ed25519 public key for the Persona.
func (p Persona) PubKey() crypto.PubKey {
return p.PrivKey().GetPublic()
}
// PubKeyConfig returns the marshaled and encoded Ed25519 public key
// for the Persona.
func (p Persona) PubKeyConfig(t *testing.T) string {
pubKeyMar, err := crypto.MarshalPublicKey(p.PrivKey().GetPublic())
require.NoError(t, err)
return crypto.ConfigEncodeKey(pubKeyMar)
}
func privKeyB64() map[Persona]string {
return map[Persona]string{
PersonaAlice: alicePrivKeyB64,
PersonaBob: bobPrivKeyB64,
PersonaCarol: carolPrivKeyB64,
PersonaDan: danPrivKeyB64,
PersonaErin: erinPrivKeyB64,
PersonaFrank: frankPrivKeyB64,
}
}
// Personas returns an (alphabetically) ordered list of the defined
// Persona values.
func Personas() []Persona {
return []Persona{
PersonaAlice,
PersonaBob,
PersonaCarol,
PersonaDan,
PersonaErin,
PersonaFrank,
}
}

View File

@@ -1,82 +0,0 @@
//go:build jwx_es256k
package did_test
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"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/did/testvectors"
)
// TestDidKeyVectors executes tests read from the [test vector files] provided
// as part of the DID Key method's [specification].
//
// [test vector files]: https://github.com/w3c-ccg/did-method-key/tree/main/test-vectors
// [specification]: https://w3c-ccg.github.io/did-method-key
func TestDidKeyVectors(t *testing.T) {
t.Parallel()
for _, f := range []string{
// TODO: These test vectors are not supported by go-libp2p/core/crypto
// "bls12381.json",
"ed25519-x25519.json",
"nist-curves.json",
"rsa.json",
"secp256k1.json",
// This test vector only contains a DID Document
// "x25519.json",
} {
vectors := loadTestVectors(t, f)
t.Run(f, func(t *testing.T) {
t.Parallel()
for k, vector := range vectors {
t.Run(k, func(t *testing.T) {
// round-trip pubkey-->did-->pubkey, verified against the test vectors.
exp := vectorPubKey(t, vector)
id, err := did.FromPubKey(exp)
require.NoError(t, err, f, k)
act, err := id.PubKey()
require.NoError(t, err)
assert.Equal(t, k, id.String(), f, k)
assert.Equal(t, exp, act)
})
}
})
}
}
func loadTestVectors(t *testing.T, filename string) testvectors.Vectors {
t.Helper()
data, err := os.ReadFile(filepath.Join("testvectors", filename))
require.NoError(t, err)
var vs testvectors.Vectors
require.NoError(t, json.Unmarshal(data, &vs))
return vs
}
func vectorPubKey(t *testing.T, v testvectors.Vector) crypto.PubKey {
t.Helper()
pubKey, err := v.PubKey()
require.NoError(t, err)
require.NotZero(t, pubKey)
return pubKey
}

View File

@@ -1,231 +0,0 @@
{
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY": {
"verificationKeyPair": {
"id": "#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"publicKeyBase58": "25EEkQtcLKsEzQ6JTo9cg4W7NHpaurn4Wg6LaNPFq6JQXnrP91SDviUz7KrJVMJd76CtAZFsRLYzvgX2JGxo2ccUHtuHk7ELCWwrkBDfrXCFVfqJKDootee9iVaF6NpdJtBE",
"privateKeyBase58": "8TXrPTbhefHvcz2vkGsDLBZT2UMeemveLKbdh5JZCvvn"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"verificationMethod": [
{
"id": "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"publicKeyBase58": "25EEkQtcLKsEzQ6JTo9cg4W7NHpaurn4Wg6LaNPFq6JQXnrP91SDviUz7KrJVMJd76CtAZFsRLYzvgX2JGxo2ccUHtuHk7ELCWwrkBDfrXCFVfqJKDootee9iVaF6NpdJtBE"
}
],
"assertionMethod": [
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"
],
"authentication": [
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"
],
"capabilityInvocation": [
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"
],
"capabilityDelegation": [
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"
]
}
},
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY": {
"verificationKeyPair": {
"id": "#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"publicKeyBase58": "t5QqHdxR4C6QJWJAnk3qVd2DMr4MVFEefdP43i7fLbR5A2qJkE5bqgEtyzpNsDViGEsMKHMdpo7fKbPMhGihbfxz3Dv2Hw36XvprLHBA5DDFSphmy91oHQFdahQMei2HjoE",
"privateKeyBase58": "URWBZN9g2ZfKVdAz1L8pvVwEBqCbGBozt4p8Cootb35"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"verificationMethod": [
{
"id": "did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"publicKeyBase58": "t5QqHdxR4C6QJWJAnk3qVd2DMr4MVFEefdP43i7fLbR5A2qJkE5bqgEtyzpNsDViGEsMKHMdpo7fKbPMhGihbfxz3Dv2Hw36XvprLHBA5DDFSphmy91oHQFdahQMei2HjoE"
}
],
"assertionMethod": [
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY"
],
"authentication": [
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY"
],
"capabilityInvocation": [
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY"
],
"capabilityDelegation": [
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY"
]
}
},
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW": {
"verificationKeyPair": {
"id": "#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"publicKeyBase58": "25VFRgQEfbJ3Pit6Z3mnZbKPK9BdQYGwdmfdcmderjYZ12BFNQYeowjMN1AYKKKcacF3UH35ZNpBqCR8y8QLeeaGLL7UKdKLcFje3VQnosesDNHsU8jBvtvYmLJusxXsSUBC",
"privateKeyBase58": "48FTGTBBhezV7Ldk5g392NSxP2hwgEgWiSZQkMoNri7E"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"verificationMethod": [
{
"id": "did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"publicKeyBase58": "25VFRgQEfbJ3Pit6Z3mnZbKPK9BdQYGwdmfdcmderjYZ12BFNQYeowjMN1AYKKKcacF3UH35ZNpBqCR8y8QLeeaGLL7UKdKLcFje3VQnosesDNHsU8jBvtvYmLJusxXsSUBC"
}
],
"assertionMethod": [
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"
],
"authentication": [
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"
],
"capabilityInvocation": [
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"
],
"capabilityDelegation": [
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"
]
}
},
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU": {
"verificationKeyPair": {
"id": "#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"publicKeyBase58": "21LWABB5R6mqxvcU6LWMMt9yCAVyt8C1mHREs1EAX23fLcAEPMK4dWx59Jd6RpJ5geGt881vH9yPzZyC8WpHhS2g296mumPxJA3Aghp9jMoACE13rtTie8FYdgzgUw24eboA",
"privateKeyBase58": "86rp8w6Q7zgDdKqYxZsdTyhZogzwbcR7wf3VQrhV3xLG"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"verificationMethod": [
{
"id": "did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"publicKeyBase58": "21LWABB5R6mqxvcU6LWMMt9yCAVyt8C1mHREs1EAX23fLcAEPMK4dWx59Jd6RpJ5geGt881vH9yPzZyC8WpHhS2g296mumPxJA3Aghp9jMoACE13rtTie8FYdgzgUw24eboA"
}
],
"assertionMethod": [
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU"
],
"authentication": [
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU"
],
"capabilityInvocation": [
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU"
],
"capabilityDelegation": [
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU"
]
}
},
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA": {
"verificationKeyPair": {
"id": "#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"publicKeyBase58": "21XhJ3o4ZSgDgRoyP4Pp8agXMwLycuRa1U6fM4ZzJBxH3gJEQbiuwP3Qh2zNoofNrBKPqp3FgXxGvW84cFwMD29oA7Q9w3L8Sjcc3e9mZqFgs8iWxSsDNRcbQdoYtGaxu11r",
"privateKeyBase58": "5LjJ3yibKGP4zKbNgqeiQ284g8LJYnbF7ZBve7Ke9qZ5"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"verificationMethod": [
{
"id": "did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"publicKeyBase58": "21XhJ3o4ZSgDgRoyP4Pp8agXMwLycuRa1U6fM4ZzJBxH3gJEQbiuwP3Qh2zNoofNrBKPqp3FgXxGvW84cFwMD29oA7Q9w3L8Sjcc3e9mZqFgs8iWxSsDNRcbQdoYtGaxu11r"
}
],
"assertionMethod": [
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA"
],
"authentication": [
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA"
],
"capabilityInvocation": [
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA"
],
"capabilityDelegation": [
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA"
]
}
},
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk": {
"verificationKeyPair": {
"id": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty",
"type": "JsonWebKey2020",
"controller": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk",
"publicKeyJwk": {
"kty": "EC",
"crv": "BLS12381_G1",
"x": "im0OQGMTkh4YEhAl16hQwUQTcOaRqIqThqtSwksFK7WaH6Qywypmc3VIDyydmYTe"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "BLS12381_G1",
"x": "im0OQGMTkh4YEhAl16hQwUQTcOaRqIqThqtSwksFK7WaH6Qywypmc3VIDyydmYTe",
"d": "S7Z1TuL05WHge8od0_mW8b3sRM747caCffsLwS6JZ-c"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk",
"verificationMethod": [
{
"id": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty",
"type": "JsonWebKey2020",
"controller": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk",
"publicKeyJwk": {
"kty": "EC",
"crv": "BLS12381_G1",
"x": "im0OQGMTkh4YEhAl16hQwUQTcOaRqIqThqtSwksFK7WaH6Qywypmc3VIDyydmYTe"
}
}
],
"assertionMethod": [
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty"
],
"authentication": [
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty"
],
"capabilityInvocation": [
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty"
],
"capabilityDelegation": [
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty"
]
}
}
}

View File

@@ -1,293 +0,0 @@
{
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp": {
"seed": "0000000000000000000000000000000000000000000000000000000000000000",
"verificationKeyPair": {
"id": "#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"publicKeyBase58": "4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS"
},
"keyAgreementKeyPair": {
"id": "#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"publicKeyBase58": "7By6kV2t2d188odEM4ExAve1UithKT6dLva4dwsDT3ak",
"privateKeyBase58": "6QN8DfuN9hjgHgPvLXqgzqYE3jRRGRrmJQZkd5tL8paR"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"verificationMethod": [
{
"id": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"publicKeyBase58": "4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS"
},
{
"id": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"publicKeyBase58": "7By6kV2t2d188odEM4ExAve1UithKT6dLva4dwsDT3ak"
}
],
"assertionMethod": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
],
"authentication": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
],
"capabilityInvocation": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
],
"capabilityDelegation": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
],
"keyAgreement": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW"
]
}
},
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG": {
"seed": "0000000000000000000000000000000000000000000000000000000000000001",
"verificationKeyPair": {
"id": "#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"publicKeyBase58": "6ASf5EcmmEHTgDJ4X4ZT5vT6iHVJBXPg5AN5YoTCpGWt"
},
"keyAgreementKeyPair": {
"id": "#z6LSrHyXiPBhUbvPUtyUCdf32sniiMGPTAesgHrtEa4FePtr",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"publicKeyBase58": "FcoNC5NqP9CePWbhfz95iHaEsCjGkZUioK9Ck7Qiw286",
"privateKeyBase58": "HBTcN2MrXNRj9xF9oi8QqYyuEPv3JLLjQKuEgW9oxVKP"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"verificationMethod": [
{
"id": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"publicKeyBase58": "6ASf5EcmmEHTgDJ4X4ZT5vT6iHVJBXPg5AN5YoTCpGWt"
},
{
"id": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6LSrHyXiPBhUbvPUtyUCdf32sniiMGPTAesgHrtEa4FePtr",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"publicKeyBase58": "FcoNC5NqP9CePWbhfz95iHaEsCjGkZUioK9Ck7Qiw286"
}
],
"assertionMethod": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"
],
"authentication": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"
],
"capabilityInvocation": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"
],
"capabilityDelegation": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"
],
"keyAgreement": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6LSrHyXiPBhUbvPUtyUCdf32sniiMGPTAesgHrtEa4FePtr"
]
}
},
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf": {
"seed": "0000000000000000000000000000000000000000000000000000000000000002",
"verificationKeyPair": {
"id": "#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"publicKeyBase58": "8pM1DN3RiT8vbom5u1sNryaNT1nyL8CTTW3b5PwWXRBH"
},
"keyAgreementKeyPair": {
"id": "#z6LSkkqoZRC34AEpbkhZCqLDcHQVAxuLpQ7kC8XCXMVUfvjE",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"publicKeyBase58": "A5fe37PAxhX5WNKngBpGHhC1KpNE7nwbK9oX2tqwxYxU",
"privateKeyBase58": "ACa4PPJ1LnPNq1iwS33V3Akh7WtnC71WkKFZ9ccM6sX2"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"verificationMethod": [
{
"id": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"publicKeyBase58": "8pM1DN3RiT8vbom5u1sNryaNT1nyL8CTTW3b5PwWXRBH"
},
{
"id": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6LSkkqoZRC34AEpbkhZCqLDcHQVAxuLpQ7kC8XCXMVUfvjE",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"publicKeyBase58": "A5fe37PAxhX5WNKngBpGHhC1KpNE7nwbK9oX2tqwxYxU"
}
],
"assertionMethod": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"
],
"authentication": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"
],
"capabilityInvocation": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"
],
"capabilityDelegation": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"
],
"keyAgreement": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6LSkkqoZRC34AEpbkhZCqLDcHQVAxuLpQ7kC8XCXMVUfvjE"
]
}
},
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ": {
"seed": "0000000000000000000000000000000000000000000000000000000000000003",
"verificationKeyPair": {
"id": "#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"publicKeyBase58": "HPYVwAQmskwT1qEEeRzhoomyfyupJGASQQtCXSNG8XS2"
},
"keyAgreementKeyPair": {
"id": "#z6LSiUo6AEDat8Ze4nQzDo67SGuHLLwsUGkxndHGUjsywHow",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"publicKeyBase58": "7ocvdvQinfqtyQ3Dh9aA7ggoVCQkmfaoueZazHETDv3B",
"privateKeyBase58": "FZrzd1osCnbK6y6MJzMBW1RcVfL524sNKhSbqRwMuwHT"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"verificationMethod": [
{
"id": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"publicKeyBase58": "HPYVwAQmskwT1qEEeRzhoomyfyupJGASQQtCXSNG8XS2"
},
{
"id": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6LSiUo6AEDat8Ze4nQzDo67SGuHLLwsUGkxndHGUjsywHow",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"publicKeyBase58": "7ocvdvQinfqtyQ3Dh9aA7ggoVCQkmfaoueZazHETDv3B"
}
],
"assertionMethod": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"
],
"authentication": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"
],
"capabilityInvocation": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"
],
"capabilityDelegation": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"
],
"keyAgreement": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6LSiUo6AEDat8Ze4nQzDo67SGuHLLwsUGkxndHGUjsywHow"
]
}
},
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU": {
"seed": "0000000000000000000000000000000000000000000000000000000000000005",
"verificationKeyPair": {
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"type": "JsonWebKey2020",
"controller": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "_eT7oDCtAC98L31MMx9J0T-w7HR-zuvsY08f9MvKne8"
},
"privateKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "_eT7oDCtAC98L31MMx9J0T-w7HR-zuvsY08f9MvKne8",
"d": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU"
}
},
"keyAgreementKeyPair": {
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6LSmArkPSdTKjEESsExHRrSwUzYUHgDuWDewXc4nocasvFU",
"type": "JsonWebKey2020",
"controller": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"publicKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "jRIz3oriXDNZmnb35XQb7K1UIlz3ae1ao1YSqLeBXHs"
},
"privateKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "jRIz3oriXDNZmnb35XQb7K1UIlz3ae1ao1YSqLeBXHs",
"d": "aEAAB3VBFPCQtgF3N__wRiXhMOgeiRGstpPC3gnJ1Eo"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"verificationMethod": [
{
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"type": "JsonWebKey2020",
"controller": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "_eT7oDCtAC98L31MMx9J0T-w7HR-zuvsY08f9MvKne8"
}
},
{
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6LSmArkPSdTKjEESsExHRrSwUzYUHgDuWDewXc4nocasvFU",
"type": "JsonWebKey2020",
"controller": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"publicKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "jRIz3oriXDNZmnb35XQb7K1UIlz3ae1ao1YSqLeBXHs"
}
}
],
"assertionMethod": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU"
],
"authentication": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU"
],
"capabilityInvocation": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU"
],
"capabilityDelegation": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU"
],
"keyAgreement": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6LSmArkPSdTKjEESsExHRrSwUzYUHgDuWDewXc4nocasvFU"
]
}
}
}

View File

@@ -1,371 +0,0 @@
{
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv": {
"verificationMethod": {
"id": "#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"type": "JsonWebKey2020",
"controller": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns",
"y": "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns",
"y": "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM",
"d": "gPh-VvVS8MbvKQ9LSVVmfnxnKjHn4Tqj0bmbpehRlpc"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"verificationMethod": [
{
"id": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"type": "JsonWebKey2020",
"controller": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns",
"y": "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM"
}
}
],
"assertionMethod": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
],
"authentication": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
],
"capabilityInvocation": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
],
"capabilityDelegation": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
],
"keyAgreement": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
]
}
},
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169": {
"verificationMethod": {
"id": "#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"type": "JsonWebKey2020",
"controller": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI",
"y": "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI",
"y": "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU",
"d": "YjRs6vNvw4sYrzVVY8ipkEpDAD9PFqw1sUnvPRMA-WI"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"verificationMethod": [
{
"id": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"type": "JsonWebKey2020",
"controller": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI",
"y": "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU"
}
}
],
"assertionMethod": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
],
"authentication": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
],
"capabilityInvocation": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
],
"capabilityDelegation": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
],
"keyAgreement": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
]
}
},
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9": {
"verificationMethod": {
"id": "#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"type": "JsonWebKey2020",
"controller": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
"y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
"y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv",
"d": "hAyGZNj9031guBCdpAOaZkO-E5m-LKLYnMIq0-msrp8JLctseaOeNTHmP3uKVWwX"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"verificationMethod": [
{
"id": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"type": "JsonWebKey2020",
"controller": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
"y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv"
}
}
],
"assertionMethod": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
],
"authentication": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
],
"capabilityInvocation": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
],
"capabilityDelegation": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
],
"keyAgreement": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
]
}
},
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54": {
"verificationMethod": {
"id": "#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"type": "JsonWebKey2020",
"controller": "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "CA-iNoHDg1lL8pvX3d1uvExzVfCz7Rn6tW781Ub8K5MrDf2IMPyL0RTDiaLHC1JT",
"y": "Kpnrn8DkXUD3ge4mFxi-DKr0DYO2KuJdwNBrhzLRtfMa3WFMZBiPKUPfJj8dYNl_"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "CA-iNoHDg1lL8pvX3d1uvExzVfCz7Rn6tW781Ub8K5MrDf2IMPyL0RTDiaLHC1JT",
"y": "Kpnrn8DkXUD3ge4mFxi-DKr0DYO2KuJdwNBrhzLRtfMa3WFMZBiPKUPfJj8dYNl_",
"d": "Xe1HHeh-UsrJPRNLR_Y06VTrWpZYBXi7a7kiRqCgwnAOlJZPwE-xzL3DIIVMavAL"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"verificationMethod": [
{
"id": "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"type": "JsonWebKey2020",
"controller": "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "CA-iNoHDg1lL8pvX3d1uvExzVfCz7Rn6tW781Ub8K5MrDf2IMPyL0RTDiaLHC1JT",
"y": "Kpnrn8DkXUD3ge4mFxi-DKr0DYO2KuJdwNBrhzLRtfMa3WFMZBiPKUPfJj8dYNl_"
}
}
],
"assertionMethod": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
],
"authentication": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
],
"capabilityInvocation": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
],
"capabilityDelegation": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
],
"keyAgreement": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
]
}
},
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7": {
"verificationMethod": {
"id": "#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"type": "JsonWebKey2020",
"controller": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
"y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
"y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC",
"d": "AHwRaNaGs0jkj_pT6PK2aHep7dJK-yxyoL2bIfVRAceq1baxoiFDo3W14c8E2YZn1k5S53r4a11flhQdaB5guJ_X"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"verificationMethod": [
{
"id": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"type": "JsonWebKey2020",
"controller": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
"y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC"
}
}
],
"assertionMethod": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
],
"authentication": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
],
"capabilityInvocation": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
],
"capabilityDelegation": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
],
"keyAgreement": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
]
}
},
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f": {
"verificationMethod": {
"id": "#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"type": "JsonWebKey2020",
"controller": "did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "AQgyFy6EwH3_u_KXPw8aTXTY7WSVytmbuJeFpq4U6LipxtSmBJe_jjRzms9qubnwm_fGoHMQlvQ1vzS2YLusR2V0",
"y": "Ab06MCcgoG7dM2I-VppdLV1k3lDoeHMvyYqHVfP05Ep2O7Zu0Qwd6IVzfZi9K0KMDud22wdnGUpUtFukZo0EeO15"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "AQgyFy6EwH3_u_KXPw8aTXTY7WSVytmbuJeFpq4U6LipxtSmBJe_jjRzms9qubnwm_fGoHMQlvQ1vzS2YLusR2V0",
"y": "Ab06MCcgoG7dM2I-VppdLV1k3lDoeHMvyYqHVfP05Ep2O7Zu0Qwd6IVzfZi9K0KMDud22wdnGUpUtFukZo0EeO15",
"d": "AbheZ-AA58LP4BpopCGCLH8ZoMdkdJaVOS6KK2NNmDCisr5_Ifxl-qcunrkOJ0CSauA4LJyNbCWcy28Bo6zgHTXQ"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"verificationMethod": [
{
"id": "did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"type": "JsonWebKey2020",
"controller": "did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "AQgyFy6EwH3_u_KXPw8aTXTY7WSVytmbuJeFpq4U6LipxtSmBJe_jjRzms9qubnwm_fGoHMQlvQ1vzS2YLusR2V0",
"y": "Ab06MCcgoG7dM2I-VppdLV1k3lDoeHMvyYqHVfP05Ep2O7Zu0Qwd6IVzfZi9K0KMDud22wdnGUpUtFukZo0EeO15"
}
}
],
"assertionMethod": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
],
"authentication": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
],
"capabilityInvocation": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
],
"capabilityDelegation": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
],
"keyAgreement": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
]
}
},
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb": {
"verificationMethod": {
"id": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"type": "P256Key2021",
"controller": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"publicKeyBase58": "ekVhkcBFq3w7jULLkBVye6PwaTuMbhJYuzwFnNcgQAPV",
"privateKeyBase58": "9p4VRzdmhsnq869vQjVCTrRry7u4TtfRxhvBFJTGU2Cp"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/multikey-2021/v1"
],
"id": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"verificationMethod": [
{
"id": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"type": "P256Key2021",
"controller": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"publicKeyBase58": "ekVhkcBFq3w7jULLkBVye6PwaTuMbhJYuzwFnNcgQAPV"
}
],
"assertionMethod": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
],
"authentication": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
],
"capabilityInvocation": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
],
"capabilityDelegation": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
],
"keyAgreement": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
]
}
}
}

View File

@@ -1,106 +0,0 @@
{
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i": {
"publicKeyJwk": {
"kty": "RSA",
"n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
"e": "AQAB"
},
"privateKeyJwk": {
"kty": "RSA",
"n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
"e": "AQAB",
"d": "Eym3sT4KLwBzo5pl5nY83-hAti92iLQRizkrKe22RbNi9Y1kKOBatdtGaJqFVztZZu5ERGKNuTd5VdsjJeekSbXviVGRtdHNCvgmRZlWA5261AgIUPxMmKW062GmGJbKQvscFfziBgHK6tyDBd8cZavqMFHi-7ilMYF7IsFBcJKM85x_30pnfd4YwhGQIc9hzv238aOwYKg8c-MzYhEVUnL273jaiLVlfZWQ5ca-GXJHmdOb_Y4fE5gpXfPFBseqleXsMp0VuXxCEsN30LIJHYscdPtbzLD3LFbuMJglFbQqYqssqymILGqJ7Tc2mB2LmXevfqRWz5D7A_K1WzvuoQ",
"p": "ANwlk-eVXPQplCmr7VddX8MAlN5YWvfXkbJe2KOhyS7naSlfMyeW6I0z6q6MAI4h8cs9yEzwmN1oEl_6tZ_-NPd1Oda2Hq5jHx0Jq2P5exIMMbzTTHbB-LjMB4c-b1DZLOrL7ZpCS-CcEHvBz4phzHa7gqz2SrNIGozufbjS_tK5",
"q": "AM6nKRFqRgHiUtGc0xJawpXJeokGhJQFfinDlakjkptuRQNv0BOz8fRUxk6zwwYrx-T_Yk-0oAFsD8qWIgiXg8Wf0bdRW0L0dIH4c6ff3mSREXeAT2h3XDaF0F1YKns08WyYWtOuIiYWChyO9sweK7AUuaOJ-6lr6lElzTGHVf-l",
"dp": "AIHFBPK2cRzchaIq3rVpLVHdveNzYexG_nOOxVVvwRANCUiB_b2Qj3Ts7aIGlS0zhTyxJql0Cig5eNtrBjVRvBdC2t1ebaeOdoC_enBsV8fDuG3-gExg-ySz4JwwiZ2252tg2qbb_a5hULYjARwpmkVDMzyR0mbsUfpRe3q_pcbB",
"dq": "Id2bCVOVLXHdiKReor9k7A8cmaAL0gYkasu2lwVRXU9w1-NXAiOXHydVaEhlSXmbRJflkJJVNmZzIAwCf830tko-oAAhKJPPFA2XRoeVdn2fkynf2YrV_cloICP2skI23kkJeW8sAXnTJmL3ZvP6zNxYn8hZCaa5u5qqSdeX7FE",
"qi": "WKIToXXnjl7GDbz7jCNbX9nWYOE5BDNzVmwiVOnyGoTZfwJ_qtgizj7pOapxi6dT9S9mMavmeAi6LAsEe1WUWtaKSNhbNh0PUGGXlXHGlhkS8jI1ot0e-scrHAuACE567YQ4VurpNorPKtZ5UENXIn74DEmt4l5m6902VF3X5Wo"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i",
"verificationMethod": [
{
"id": "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i",
"type": "JsonWebKey2020",
"controller": "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i",
"publicKeyJwk": {
"kty": "RSA",
"n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
"e": "AQAB"
}
}
],
"authentication": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
],
"assertionMethod": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
],
"capabilityDelegation": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
],
"capabilityInvocation": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
],
"keyAgreement": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
]
}
},
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2": {
"publicKeyJwk": {
"kty": "RSA",
"n": "qMCkFFRFWtzUyZeK8mgJdyM6SEQcXC5E6JwCRVDld-jlJs8sXNOE_vliexq34wZRQ4hk53-JPFlvZ_QjRgIxdUxSMiZ3S5hlNVvvRaue6SMakA9ugQhnfXaWORro0UbPuHLms-bg5StDP8-8tIezu9c1H1FjwPcdbV6rAvKhyhnsM10qP3v2CPbdE0q3FOsihoKuTelImtO110E7N6fLn4U3EYbC4OyViqlrP1o_1M-R-tiM1cb4pD7XKJnIs6ryZdfOQSPBJwjNqSdN6Py_tdrFgPDTyacSSdpTVADOM2IMAoYbhV1N5APhnjOHBRFyKkF1HffQKpmXQLBqvUNNjuhmpVKWBtrTdcCKrglFXiw0cKGHKxIirjmiOlB_HYHg5UdosyE3_1Txct2U7-WBB6QXak1UgxCzgKYBDI8UPA0RlkUuHHP_Zg0fVXrXIInHO04MYxUeSps5qqyP6dJBu_v_BDn3zUq6LYFwJ_-xsU7zbrKYB4jaRlHPoCj_eDC-rSA2uQ4KXHBB8_aAqNFC9ukWxc26Ifz9dF968DLuL30bi-ZAa2oUh492Pw1bg89J7i4qTsOOfpQvGyDV7TGhKuUG3Hbumfr2w16S-_3EI2RIyd1nYsflE6ZmCkZQMG_lwDAFXaqfyGKEDouJuja4XH8r4fGWeGTrozIoniXT1HU",
"e": "AQAB"
},
"privateKeyJwk": {
"kty": "RSA",
"n": "qMCkFFRFWtzUyZeK8mgJdyM6SEQcXC5E6JwCRVDld-jlJs8sXNOE_vliexq34wZRQ4hk53-JPFlvZ_QjRgIxdUxSMiZ3S5hlNVvvRaue6SMakA9ugQhnfXaWORro0UbPuHLms-bg5StDP8-8tIezu9c1H1FjwPcdbV6rAvKhyhnsM10qP3v2CPbdE0q3FOsihoKuTelImtO110E7N6fLn4U3EYbC4OyViqlrP1o_1M-R-tiM1cb4pD7XKJnIs6ryZdfOQSPBJwjNqSdN6Py_tdrFgPDTyacSSdpTVADOM2IMAoYbhV1N5APhnjOHBRFyKkF1HffQKpmXQLBqvUNNjuhmpVKWBtrTdcCKrglFXiw0cKGHKxIirjmiOlB_HYHg5UdosyE3_1Txct2U7-WBB6QXak1UgxCzgKYBDI8UPA0RlkUuHHP_Zg0fVXrXIInHO04MYxUeSps5qqyP6dJBu_v_BDn3zUq6LYFwJ_-xsU7zbrKYB4jaRlHPoCj_eDC-rSA2uQ4KXHBB8_aAqNFC9ukWxc26Ifz9dF968DLuL30bi-ZAa2oUh492Pw1bg89J7i4qTsOOfpQvGyDV7TGhKuUG3Hbumfr2w16S-_3EI2RIyd1nYsflE6ZmCkZQMG_lwDAFXaqfyGKEDouJuja4XH8r4fGWeGTrozIoniXT1HU",
"e": "AQAB",
"d": "TMq1H-clVG7PihkjCqJbRFLMj9wmx6_qfauYwPBKK-HYfWujdW5vxBO6Q-jpqy7RxhiISmxYCBVuw_BuKMqQtR8Q_G9StBzaWYjHfn3Vp6Poz4umLqOjbI2NWNks_ybpGbd30oAK8V5ZkO04ozJpkN4i92hzK3mIc5-z1HiTNUPMn6cStab0VCn6em_ylltV774CEcRJ3OLgid7OUspRt_rID3qyreYbOulTu5WXHIGEnZDzrciIlz1dbcVldpUhD0VAP5ZErD2uUP5oztBNcTTn0YBF8CrOALuQVdaz_t_sNS3P0kWeT1eQ0QwDskO5Hw-Aey2tFeWk1bQyLoQ1A0jsw8mDbkO2zrGfJoxmVBkueTK-q64_n1kV7W1aeJFRj4NwEWmwcrs8GSOGOn38fGB_Y3Kci04qvD6L0QZbFkAVzcJracnxbTdHCEX0jsAAPbYC8M_8PyrPJvPC4IAAWTRrSRbysb7r7viRf4A1vTK9VT7uYyxj7Kzx2cU12d9QBXYfdQ2744bUE7HqN-Vh2rHvv2l5v6vzBRoZ5_OhHHVeUYwC9LouE9lSVAObbFM-Qe1SvzbbwN91LziI7UzUc_xMAEiNwt6PpnIAWAhdvSRawEllTwUcn89udHd5UhiAcm-RQOqXIdA9Aly6d8TT8R1p-ZnQ_gbZyBZeS39AuvU=",
"p": "1p4cypsJeTyVXXc5bQpvzVenPy78OHXtGcFQnbTjW8x1GsvJ-rlHAcjUImd44pgNQNe-iYpeUg3KqfONeedNgQCFd8kP7GoVAd45mEvsGBXvjoCXOBMQlsf8UU_hm_LKhVvTvTmMGoudnNv5qYNDMCGJGzwoG-aSvROlIoXzHmDnusZ-hKsDxM9j0PPz21t99Y_Fr30Oq3FIWXPVmLYmfyZYQkxm9a9WNMkqRbwJuMwGI6V9ABsQ1dW_KJzp_aEBbJLcDr9DsWhm9ErLeAlzyaDYEai6wCtKm9em4LDwCbKhJq3hWEp1sIG-hwx1sk7N4i-b8lBijjEQE-dbSQxUlw==",
"q": "yUqMejfrttGujadj7Uf7q91KM7nbQGny4TjD-CqibcFE-s2_DExCgP1wfhUPfJr2uPQDIe4g12uaNoa5GbCSDaQwEmQpurC_5mazt-z-_tbI24hoPQm5Hq67fZz-jDE_3OccLPLIWtajJqmxHbbB5VqskMuXo8KDxPRfBQBhykmb9_5M8pY2ggZOV4shCUn5E9nOnvibvw5Wx4CBtWUtca4rhpd3mVen1d8xCe4xTG_ni_w1lwdxzU1GmRFqgTuZWzL0r2FKzJg7hju1SOEe4tKMxQ-xs2HyNaMM__SLsNmS3lsYZ8r2hqcjEMQQZI0T_O-3BjIpyg986P8j055E0w==",
"dp": "DujzJRw6P0L3OYQT6EBmXgSt6NTRzvZaX4SvnhU4CmOc6xynTpTamwQhwLYhjtRzb0LNyO5k-RxeLQpvlL1-A-1OWHEOeyUvim6u36a-ozm659KFLu8cIu2H2PpMuTHX4gXsIuRBmIKEk6YwpRcqbsiVpt-6BZ4yKZKY0Vou9rhSwQYTOhJLc7vYumaIVX_4szumxzdP8pcvKI_EkhRtfj3iudBnAsCIo6gqGKgkoMMD1iwkEALRW5m66w5jrywlVi6pvRiKkmOna2da1V8KvUJAYJGxT7JyP3tu64M_Wd0gFvjTg_fAT1_kJau27YlOAl2-Xso43poH_OoAzIVfxw==",
"dq": "XI6Z76z9BxB9mgcpTLc3wzw63XQNnB3bn7JRcjBwhdVD2at3uLjsL5HaAy-98kbzQfJ56kUr9sI0o_Po8yYc0ob3z80c3wpdAx2gb-dbDWVH8KJVhBOPestPzR--cEpJGlNuwkBU3mgplyKaHZamq8a46M-lB5jurEbN1mfpj3GvdSYKzdVCdSFfLqP76eCI1pblinW4b-6w-oVdn0JJ1icHPpkxVmJW-2Hok69iHcqrBtRO9AZpTsTEvKekeI4mIyhYGLi9AzzQyhV0c3GImTXFoutng5t7GyzBUoRpI0W4YeQzYa6TEzGRTylIfGPemATF_OReENp0TlLbb3gsHw==",
"qi": "m7uZk4AsOfJ1V2RY8lmEF518toCV7juKuS_b_OUx8B0dRG0_kbF1cH-Tmrgsya3bwkYx5HeZG81rX7SRjh-0nVPOMW3tGqU5U9f59DXqvOItJIJ6wvWvWXnuna2-NstYCotFQWadIKjk4wjEKj-a4NJt4D_F4csyeyqWOH2DiUFzBGGxxdEoD5t_HEeNXuWQ6-SiV0x5ZVMln3TSh7IOMl70Smm8HcQF5mOsWg3N0wIg-yffxPrs6r15TRuW1MfT-bZk2GLrtHF1TkIoT1e00jWK4eBl2oRxiJGONUBMTEHV85Fr0yztnA99AgHnrMbE_4ehvev4h5DEWvFyFuJN_g=="
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2",
"verificationMethod": [
{
"id": "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2",
"type": "JsonWebKey2020",
"controller": "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2",
"publicKeyJwk": {
"kty": "RSA",
"n": "qMCkFFRFWtzUyZeK8mgJdyM6SEQcXC5E6JwCRVDld-jlJs8sXNOE_vliexq34wZRQ4hk53-JPFlvZ_QjRgIxdUxSMiZ3S5hlNVvvRaue6SMakA9ugQhnfXaWORro0UbPuHLms-bg5StDP8-8tIezu9c1H1FjwPcdbV6rAvKhyhnsM10qP3v2CPbdE0q3FOsihoKuTelImtO110E7N6fLn4U3EYbC4OyViqlrP1o_1M-R-tiM1cb4pD7XKJnIs6ryZdfOQSPBJwjNqSdN6Py_tdrFgPDTyacSSdpTVADOM2IMAoYbhV1N5APhnjOHBRFyKkF1HffQKpmXQLBqvUNNjuhmpVKWBtrTdcCKrglFXiw0cKGHKxIirjmiOlB_HYHg5UdosyE3_1Txct2U7-WBB6QXak1UgxCzgKYBDI8UPA0RlkUuHHP_Zg0fVXrXIInHO04MYxUeSps5qqyP6dJBu_v_BDn3zUq6LYFwJ_-xsU7zbrKYB4jaRlHPoCj_eDC-rSA2uQ4KXHBB8_aAqNFC9ukWxc26Ifz9dF968DLuL30bi-ZAa2oUh492Pw1bg89J7i4qTsOOfpQvGyDV7TGhKuUG3Hbumfr2w16S-_3EI2RIyd1nYsflE6ZmCkZQMG_lwDAFXaqfyGKEDouJuja4XH8r4fGWeGTrozIoniXT1HU",
"e": "AQAB"
}
}
],
"authentication": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
],
"assertionMethod": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
],
"capabilityDelegation": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
],
"capabilityInvocation": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
],
"keyAgreement": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
]
}
}
}

View File

@@ -1,257 +0,0 @@
{
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme": {
"seed": "9085d2bef69286a6cbb51623c8fa258629945cd55ca705cc4e66700396894e0c",
"verificationKeyPair": {
"id": "#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"publicKeyBase58": "23o6Sau8NxxzXcgSc3PLcNxrzrZpbLeBn1izfv3jbKhuv",
"privateKeyBase58": "AjA4cyPUbbfW5wr6iZeRbJLhgH3qDt6q6LMkRw36KpxT"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"verificationMethod": [
{
"id": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"publicKeyBase58": "23o6Sau8NxxzXcgSc3PLcNxrzrZpbLeBn1izfv3jbKhuv"
}
],
"assertionMethod": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
],
"authentication": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
],
"capabilityInvocation": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
],
"capabilityDelegation": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
],
"keyAgreement": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
]
}
},
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2": {
"seed": "f0f4df55a2b3ff13051ea814a8f24ad00f2e469af73c363ac7e9fb999a9072ed",
"verificationKeyPair": {
"id": "#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"publicKeyBase58": "291KzQhqCPC18PqH83XKhxv1HdqrdnxyS7dh15t2uNRzJ",
"privateKeyBase58": "HDbR1D5W3CoNbUKYzUbHH2PRF1atshtVupXgXTQhNB9E"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"verificationMethod": [
{
"id": "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"publicKeyBase58": "291KzQhqCPC18PqH83XKhxv1HdqrdnxyS7dh15t2uNRzJ"
}
],
"assertionMethod": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
],
"authentication": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
],
"capabilityInvocation": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
],
"capabilityDelegation": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
],
"keyAgreement": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
]
}
},
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N": {
"seed": "6b0b91287ae3348f8c2f2552d766f30e3604867e34adc37ccbb74a8e6b893e02",
"verificationKeyPair": {
"id": "#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"publicKeyBase58": "oesQ92MLiAkt2pjBcJFbW7H4DvzKJv22cotjYbmC2JEe",
"privateKeyBase58": "8CrrWVdzDnvaS7vS5dd2HetFSebwEN46XEFrNDdtWZSZ"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"verificationMethod": [
{
"id": "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"publicKeyBase58": "oesQ92MLiAkt2pjBcJFbW7H4DvzKJv22cotjYbmC2JEe"
}
],
"assertionMethod": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
],
"authentication": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
],
"capabilityInvocation": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
],
"capabilityDelegation": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
],
"keyAgreement": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
]
}
},
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy": {
"seed": "c0a6a7c560d37d7ba81ecee9543721ff48fea3e0fb827d42c1868226540fac15",
"verificationKeyPair": {
"id": "#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"publicKeyBase58": "pg3p1vprqePgUoqfAQ1TTgxhL6zLYhHyzooR1pqLxo9F",
"privateKeyBase58": "Dy2fnt8ba4NmbRBXas9bo1BtYgpYFr6ThpFhJbuA3PRn"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"verificationMethod": [
{
"id": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"publicKeyBase58": "pg3p1vprqePgUoqfAQ1TTgxhL6zLYhHyzooR1pqLxo9F"
}
],
"assertionMethod": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
],
"authentication": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
],
"capabilityInvocation": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
],
"capabilityDelegation": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
],
"keyAgreement": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
]
}
},
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj": {
"seed": "175a232d440be1e0788f25488a73d9416c04b6f924bea6354bf05dd2f1a75133",
"verificationKeyPair": {
"id": "#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"publicKeyBase58": "24waDFAUAS16UpZwQQTXVEAmm17rQRjadjuAeBDW8aqL1",
"privateKeyBase58": "2aA6WgZnPiVMBX3LvKSTg3KaFKyzfKpvEacixB3yyTgv"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"verificationMethod": [
{
"id": "did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"publicKeyBase58": "24waDFAUAS16UpZwQQTXVEAmm17rQRjadjuAeBDW8aqL1"
}
],
"assertionMethod": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
],
"authentication": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
],
"capabilityInvocation": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
],
"capabilityDelegation": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
],
"keyAgreement": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
]
}
},
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS": {
"verificationKeyPair": {
"id": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"type": "JsonWebKey2020",
"controller": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"publicKeyJwk": {
"kty": "EC",
"crv": "secp256k1",
"x": "TEIJN9vnTq1EXMkqzo7yN_867-foKc2pREv45Fw_QA8",
"y": "9yiymlzdxKCiRbYq7p-ArRB-C1ytjHE-eb7RDTi6rVc"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "secp256k1",
"x": "TEIJN9vnTq1EXMkqzo7yN_867-foKc2pREv45Fw_QA8",
"y": "9yiymlzdxKCiRbYq7p-ArRB-C1ytjHE-eb7RDTi6rVc",
"d": "J5yKm7OXFsXDEutteGYeT0CAfQJwIlHLSYkQxKtgiyo"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"verificationMethod": [
{
"id": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"type": "JsonWebKey2020",
"controller": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"publicKeyJwk": {
"kty": "EC",
"crv": "secp256k1",
"x": "TEIJN9vnTq1EXMkqzo7yN_867-foKc2pREv45Fw_QA8",
"y": "9yiymlzdxKCiRbYq7p-ArRB-C1ytjHE-eb7RDTi6rVc"
}
}
],
"assertionMethod": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
],
"authentication": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
],
"capabilityInvocation": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
],
"capabilityDelegation": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
],
"keyAgreement": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
]
}
}
}

View File

@@ -1,163 +0,0 @@
//go:build jwx_es256k
package testvectors
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"errors"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/mr-tron/base58"
)
type Vectors map[string]Vector
// This is pretty gross but the structure allows the repeated Verifier,
// PublicKeyJwk and PublicKeyBase58 account for the fact that the test
// files are very inconsistent.
type Vector struct {
VerificationKeyPair Verifier
VerificationMethod Verifier
PublicKeyJwk json.RawMessage
DidDocument json.RawMessage // TODO: if we start producing DID documents, we should test this too
}
type Verifier struct {
ID string
Type string
PublicKeyBase58 string
PublicKeyJwk json.RawMessage
}
func (v Vector) PubKey() (crypto.PubKey, error) {
// If the public key is in base58
if pubB58 := v.PubKeyBase58(); len(pubB58) > 0 {
pubBytes, err := base58.Decode(pubB58)
if err != nil {
return nil, err
}
t, err := v.PubKeyType()
if err != nil {
return nil, err
}
var unmarshaler crypto.PubKeyUnmarshaller
switch t {
case "Ed25519VerificationKey2018":
unmarshaler = crypto.UnmarshalEd25519PublicKey
case "EcdsaSecp256k1VerificationKey2019":
unmarshaler = crypto.UnmarshalSecp256k1PublicKey
// This is weak as it assumes the P256 curve - that's all the vectors contain (for now)
case "P256Key2021":
unmarshaler = compressedEcdsaPublicKeyUnmarshaler
default:
return nil, errors.New("failed to resolve unmarshaler")
}
return unmarshaler(pubBytes)
}
// If the public key is in a JWK
if pubJwk := v.PubKeyJwk(); len(pubJwk) > 0 {
key, err := jwk.ParseKey(pubJwk)
if err != nil {
return nil, err
}
var a any
if err := key.Raw(&a); err != nil {
return nil, err
}
switch a.(type) {
case *ecdsa.PublicKey:
epub := a.(*ecdsa.PublicKey)
if epub.Curve == secp256k1.S256() {
bytes := append([]byte{0x04}, append(epub.X.Bytes(), epub.Y.Bytes()...)...)
return crypto.UnmarshalSecp256k1PublicKey(bytes)
}
asn1, err := x509.MarshalPKIXPublicKey(epub)
if err != nil {
return nil, err
}
return crypto.UnmarshalECDSAPublicKey(asn1)
case ed25519.PublicKey:
return crypto.UnmarshalEd25519PublicKey(a.(ed25519.PublicKey))
case *rsa.PublicKey:
asn1, err := x509.MarshalPKIXPublicKey(a.(*rsa.PublicKey))
if err != nil {
return nil, err
}
return crypto.UnmarshalRsaPublicKey(asn1)
default:
return nil, errors.New("unsupported key type")
}
}
// If we don't find a public key at all
return nil, errors.New("vector's public key not found")
}
func (v Vector) PubKeyBase58() string {
if len(v.VerificationKeyPair.PublicKeyBase58) > 0 {
return v.VerificationKeyPair.PublicKeyBase58
}
return v.VerificationMethod.PublicKeyBase58
}
func (v Vector) PubKeyJwk() json.RawMessage {
if len(v.VerificationKeyPair.PublicKeyJwk) > 0 {
return v.VerificationKeyPair.PublicKeyJwk
}
if len(v.VerificationMethod.PublicKeyJwk) > 0 {
return v.VerificationMethod.PublicKeyJwk
}
return v.PublicKeyJwk
}
func (v Vector) PubKeyType() (string, error) {
if len(v.VerificationKeyPair.Type) > 0 {
return v.VerificationKeyPair.Type, nil
}
if len(v.VerificationMethod.Type) > 0 {
return v.VerificationMethod.Type, nil
}
return "", errors.New("vector's type not found")
}
func compressedEcdsaPublicKeyUnmarshaler(data []byte) (crypto.PubKey, error) {
x, y := elliptic.UnmarshalCompressed(elliptic.P256(), data)
ecdsaPublicKey := ecdsa.PublicKey{
Curve: elliptic.P256(),
X: x,
Y: y,
}
asn1, err := x509.MarshalPKIXPublicKey(&ecdsaPublicKey)
if err != nil {
return nil, err
}
return crypto.UnmarshalECDSAPublicKey(asn1)
}

View File

@@ -1,80 +0,0 @@
{
"didDocument": {
"did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
"verificationMethod": [
{
"id": "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
"publicKeyBase58": "4Dy8E9UaZscuPUf2GLxV44RCNL7oxmEXXkgWXaug1WKV"
}
],
"keyAgreement": [
"did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F"
]
},
"did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha",
"verificationMethod": [
{
"id": "did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha#z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha",
"publicKeyBase58": "J3PiFeuSyLugy4DKn87TwK5cnruRgPtxouzXUqg99Avp"
}
],
"keyAgreement": [
"did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha#z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha"
]
},
"did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ",
"verificationMethod": [
{
"id": "did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ#z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ",
"publicKeyBase58": "CgTbngDMe7yHHfxPMvhpaFRpFoQWKgXAgwenJj8PsFDe"
}
],
"keyAgreement": [
"did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ#z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ"
]
},
"did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz",
"verificationMethod": [
{
"id": "did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz#z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz",
"type": "JsonWebKey2020",
"controller": "did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz",
"publicKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "467ap28wHJGEXJAb4mLrokqq8A-txA_KmoQTcj31XzU"
}
}
],
"keyAgreement": [
"did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz#z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz"
]
}
}
}

24
go.mod
View File

@@ -1,44 +1,34 @@
module github.com/ucan-wg/go-ucan
go 1.23
go 1.21
toolchain go1.22.1
require (
github.com/dave/jennifer v1.7.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
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/lestrrat-go/jwx/v2 v2.1.1
github.com/libp2p/go-libp2p v0.36.3
github.com/mr-tron/base58 v1.2.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/stretchr/testify v1.9.0
gotest.tools/v3 v3.5.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // 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/segmentio/asm v1.2.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/sys v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)

44
go.sum
View File

@@ -1,19 +1,12 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
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/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
@@ -30,22 +23,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.1.1 h1:Y2ltVl8J6izLYFs54BVcpXLv5msSW4o8eXwnzZLI32E=
github.com/lestrrat-go/jwx/v2 v2.1.1/go.mod h1:4LvZg7oxu6Q5VJwn7Mk/UwooNRnTHUpXBj2C4j3HNx0=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
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.3 h1:NHz30+G7D8Y8YmznrVZZla0ofVANrvBl2c+oARfMeDQ=
github.com/libp2p/go-libp2p v0.36.3/go.mod h1:4Y5vFyCUiJuluEPmpnKYf6WFx5ViKPUYs/ixe9ANFZ8=
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/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
@@ -54,8 +33,6 @@ 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-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-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/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
@@ -71,8 +48,6 @@ 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/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
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/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
@@ -80,9 +55,6 @@ github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hg
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@@ -91,8 +63,6 @@ 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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
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/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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=
@@ -102,16 +72,12 @@ 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=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
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/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=

View File

@@ -24,6 +24,7 @@
package varsig
import (
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
@@ -129,5 +130,5 @@ func header(vals ...multicodec.Code) string {
buf = binary.AppendUvarint(buf, uint64(val))
}
return string(buf)
return base64.RawStdEncoding.EncodeToString(buf)
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/libp2p/go-libp2p/core/crypto/pb"
"github.com/stretchr/testify/assert"
"github.com/ucan-wg/go-ucan/token/internal/varsig"
"github.com/ucan-wg/go-ucan/internal/varsig"
)
func TestDecode(t *testing.T) {
@@ -21,14 +21,7 @@ func TestDecode(t *testing.T) {
}
func ExampleDecode() {
hdr, err := base64.RawStdEncoding.DecodeString("NIUkEoACcQ")
if err != nil {
fmt.Println(err.Error())
return
}
keyType, _ := varsig.Decode(hdr)
keyType, _ := varsig.Decode([]byte("NIUkEoACcQ"))
fmt.Println(keyType.String())
// Output:
// RSA
@@ -44,7 +37,7 @@ func TestEncode(t *testing.T) {
func ExampleEncode() {
header, _ := varsig.Encode(pb.KeyType_RSA)
fmt.Println(base64.RawStdEncoding.EncodeToString(header))
fmt.Println(string(header))
// Output:
// NIUkEoACcQ

View File

@@ -1,137 +0,0 @@
// Package args provides the type that represents the Arguments passed to
// a command within an invocation.Token as well as a convenient Add method
// to incrementally build the underlying map.
package args
import (
"fmt"
"sort"
"strings"
"github.com/ipld/go-ipld-prime"
"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/printer"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)
// Args are the Command's arguments when an invocation Token is processed by the executor.
// This also serves as a way to construct the underlying IPLD data with minimum allocations
// and transformations, while hiding the IPLD complexity from the caller.
type Args struct {
// This type must be compatible with the IPLD type represented by the IPLD
// schema { String : Any }.
Keys []string
Values map[string]ipld.Node
}
// New returns a pointer to an initialized Args value.
func New() *Args {
return &Args{
Values: map[string]ipld.Node{},
}
}
// Add inserts a key/value pair in the Args set.
//
// Accepted types for val are any CBOR compatible type, or directly IPLD values.
func (a *Args) Add(key string, val any) error {
if _, ok := a.Values[key]; ok {
return fmt.Errorf("duplicate key %q", key)
}
node, err := literal.Any(val)
if err != nil {
return err
}
a.Values[key] = node
a.Keys = append(a.Keys, key)
return nil
}
// Include merges the provided arguments into the existing arguments.
//
// If duplicate keys are encountered, the new value is silently dropped
// without causing an error.
func (a *Args) Include(other *Args) {
for _, key := range other.Keys {
if _, ok := a.Values[key]; ok {
// don't overwrite
continue
}
a.Values[key] = other.Values[key]
a.Keys = append(a.Keys, key)
}
}
// ToIPLD wraps an instance of an Args with an ipld.Node.
func (a *Args) ToIPLD() (ipld.Node, error) {
sort.Strings(a.Keys)
return qp.BuildMap(basicnode.Prototype.Any, int64(len(a.Keys)), func(ma datamodel.MapAssembler) {
for _, key := range a.Keys {
qp.MapEntry(ma, key, qp.Node(a.Values[key]))
}
})
}
// Equals tells if two Args hold the same values.
func (a *Args) Equals(other *Args) bool {
if len(a.Keys) != len(other.Keys) {
return false
}
if len(a.Values) != len(other.Values) {
return false
}
for _, key := range a.Keys {
if !ipld.DeepEqual(a.Values[key], other.Values[key]) {
return false
}
}
return true
}
func (a *Args) String() string {
sort.Strings(a.Keys)
buf := strings.Builder{}
buf.WriteString("{")
for _, key := range a.Keys {
buf.WriteString("\n\t")
buf.WriteString(key)
buf.WriteString(": ")
buf.WriteString(strings.ReplaceAll(printer.Sprint(a.Values[key]), "\n", "\n\t"))
buf.WriteString(",")
}
if len(a.Keys) > 0 {
buf.WriteString("\n")
}
buf.WriteString("}")
return buf.String()
}
// ReadOnly returns a read-only version of Args.
func (a *Args) ReadOnly() ReadOnly {
return ReadOnly{args: a}
}
// Clone makes a deep copy.
func (a *Args) Clone() *Args {
res := &Args{
Keys: make([]string, len(a.Keys)),
Values: make(map[string]ipld.Node, len(a.Values)),
}
copy(res.Keys, a.Keys)
for k, v := range a.Values {
res.Values[k] = v
}
return res
}

View File

@@ -1,179 +0,0 @@
package args_test
import (
"sync"
"testing"
"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/schema"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/args"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)
func TestArgs(t *testing.T) {
t.Parallel()
const (
intKey = "intKey"
mapKey = "mapKey"
nilKey = "nilKey"
boolKey = "boolKey"
linkKey = "linkKey"
listKey = "listKey"
nodeKey = "nodeKey"
uintKey = "uintKey"
bytesKey = "bytesKey"
floatKey = "floatKey"
stringKey = "stringKey"
)
const (
expIntVal = int64(-42)
expBoolVal = true
expUintVal = uint(42)
expStringVal = "stringVal"
)
var (
expMapVal = map[string]string{"keyOne": "valOne", "keyTwo": "valTwo"}
// expNilVal = (map[string]string)(nil)
expLinkVal = cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm")
expListVal = []string{"elem1", "elem2", "elem3"}
expNodeVal = literal.String("nodeVal")
expBytesVal = []byte{0xde, 0xad, 0xbe, 0xef}
expFloatVal = 42.0
)
argsIn := args.New()
for _, a := range []struct {
key string
val any
}{
{key: intKey, val: expIntVal},
{key: mapKey, val: expMapVal},
// {key: nilKey, val: expNilVal},
{key: boolKey, val: expBoolVal},
{key: linkKey, val: expLinkVal},
{key: listKey, val: expListVal},
{key: uintKey, val: expUintVal},
{key: nodeKey, val: expNodeVal},
{key: bytesKey, val: expBytesVal},
{key: floatKey, val: expFloatVal},
{key: stringKey, val: expStringVal},
} {
require.NoError(t, argsIn.Add(a.key, a.val))
}
// Round-trip to DAG-CBOR
argsOut := roundTripThroughDAGCBOR(t, argsIn)
assert.ElementsMatch(t, argsIn.Keys, argsOut.Keys)
assert.Equal(t, argsIn.Values, argsOut.Values)
actMapVal := map[string]string{}
mit := argsOut.Values[mapKey].MapIterator()
for !mit.Done() {
k, v, err := mit.Next()
require.NoError(t, err)
ks := must(k.AsString())
vs := must(v.AsString())
actMapVal[ks] = vs
}
actListVal := []string{}
lit := argsOut.Values[listKey].ListIterator()
for !lit.Done() {
_, v, err := lit.Next()
require.NoError(t, err)
vs := must(v.AsString())
actListVal = append(actListVal, vs)
}
assert.Equal(t, expIntVal, must(argsOut.Values[intKey].AsInt()))
assert.Equal(t, expMapVal, actMapVal) // TODO: special accessor
// TODO: the nil map comes back empty (but the right type)
// assert.Equal(t, expNilVal, actNilVal)
assert.Equal(t, expBoolVal, must(argsOut.Values[boolKey].AsBool()))
assert.Equal(t, expLinkVal.String(), must(argsOut.Values[linkKey].AsLink()).(datamodel.Link).String()) // TODO: special accessor
assert.Equal(t, expListVal, actListVal) // TODO: special accessor
assert.Equal(t, expNodeVal, argsOut.Values[nodeKey])
assert.Equal(t, expUintVal, uint(must(argsOut.Values[uintKey].AsInt())))
assert.Equal(t, expBytesVal, must(argsOut.Values[bytesKey].AsBytes()))
assert.Equal(t, expFloatVal, must(argsOut.Values[floatKey].AsFloat()))
assert.Equal(t, expStringVal, must(argsOut.Values[stringKey].AsString()))
}
func TestArgs_Include(t *testing.T) {
t.Parallel()
argsIn := args.New()
require.NoError(t, argsIn.Add("key1", "val1"))
require.NoError(t, argsIn.Add("key2", "val2"))
argsOther := args.New()
require.NoError(t, argsOther.Add("key2", "valOther")) // This should not overwrite key2 above
require.NoError(t, argsOther.Add("key3", "val3"))
require.NoError(t, argsOther.Add("key4", "val4"))
argsIn.Include(argsOther)
assert.Len(t, argsIn.Values, 4)
assert.Equal(t, "val1", must(argsIn.Values["key1"].AsString()))
assert.Equal(t, "val2", must(argsIn.Values["key2"].AsString()))
assert.Equal(t, "val3", must(argsIn.Values["key3"].AsString()))
assert.Equal(t, "val4", must(argsIn.Values["key4"].AsString()))
}
const (
argsSchema = "type Args { String : Any }"
argsName = "Args"
)
var (
once sync.Once
ts *schema.TypeSystem
err error
)
func argsType() schema.Type {
once.Do(func() {
ts, err = ipld.LoadSchemaBytes([]byte(argsSchema))
})
if err != nil {
panic(err)
}
return ts.TypeByName(argsName)
}
func roundTripThroughDAGCBOR(t *testing.T, argsIn *args.Args) *args.Args {
t.Helper()
node, err := argsIn.ToIPLD()
require.NoError(t, err)
data, err := ipld.Encode(node, dagcbor.Encode)
require.NoError(t, err)
var argsOut args.Args
_, err = ipld.Unmarshal(data, dagcbor.Decode, &argsOut, argsType())
require.NoError(t, err)
return &argsOut
}
func must[T any](t T, err error) T {
if err != nil {
panic(err)
}
return t
}

View File

@@ -1,23 +0,0 @@
package args
import "github.com/ipld/go-ipld-prime"
type ReadOnly struct {
args *Args
}
func (r ReadOnly) ToIPLD() (ipld.Node, error) {
return r.args.ToIPLD()
}
func (r ReadOnly) Equals(other *Args) bool {
return r.args.Equals(other)
}
func (r ReadOnly) String() string {
return r.args.String()
}
func (r ReadOnly) WriteableClone() *Args {
return r.args.Clone()
}

View File

@@ -1,86 +0,0 @@
# Token container
## Why do I need that?
Some common situation asks to package multiple tokens together:
- calling a service requires sending an invocation, alongside the matching delegations
- sending a series of revocations
- \<insert your application specific scenario here>
The UCAN specification defines how a single token is serialized (envelope with signature, IPLD encoded as Dag-cbor), but it's entirely left open how to package multiple tokens together. To be clear, this is a correct thing to do for a specification, as different ways equally valid to solve that problem exists and can coexist. Any wire format holding a list of bytes would do (cbor, json, csv ...).
**go-ucan** however, provide an opinionated implementation, which may or may not work in your situation.
Some experiment has been done over which format is appropriate, and two have been selected:
- **DAG-CBOR** of a list of bytes, as a low overhead option
- **CAR** file, as a somewhat common ways to cary arbitrary blocks of data
Notably, **compression is not included**, even though it does work reasonably well. This is because your transport medium might already do it, or should.
## Wire format consideration
Several possible formats have been explored:
- CAR files (binary or base64)
- DAG-CBOR (binary or base64)
Additionally, gzip and deflate compression has been experimented with.
Below are the results in terms of storage used, as percentage and byte overhead over the raw tokens:
| Token count | car | carBase64 | carGzip | carGzipBase64 | cbor | cborBase64 | cborGzip | cborGzipBase64 | cborFlate | cborFlateBase64 |
|-------------|-----|-----------|---------|---------------|------|------------|----------|----------------|-----------|-----------------|
| 1 | 15 | 54 | 7 | 42 | 0 | 35 | \-8 | 22 | \-12 | 16 |
| 2 | 12 | 49 | \-12 | 15 | 0 | 34 | \-25 | 0 | \-28 | \-3 |
| 3 | 11 | 48 | \-21 | 4 | 0 | 34 | \-32 | \-10 | \-34 | \-11 |
| 4 | 10 | 47 | \-26 | \-1 | 0 | 34 | \-36 | \-15 | \-37 | \-17 |
| 5 | 10 | 47 | \-28 | \-4 | 0 | 34 | \-38 | \-18 | \-40 | \-20 |
| 6 | 10 | 47 | \-30 | \-7 | 0 | 34 | \-40 | \-20 | \-40 | \-20 |
| 7 | 10 | 46 | \-31 | \-8 | 0 | 34 | \-41 | \-21 | \-42 | \-22 |
| 8 | 9 | 46 | \-32 | \-10 | 0 | 34 | \-42 | \-22 | \-42 | \-23 |
| 9 | 9 | 46 | \-33 | \-11 | 0 | 34 | \-43 | \-23 | \-43 | \-24 |
| 10 | 9 | 46 | \-34 | \-12 | 0 | 34 | \-43 | \-25 | \-44 | \-25 |
![Overhead %](img/overhead_percent.png)
| Token count | car | carBase64 | carGzip | carGzipBase64 | cbor | cborBase64 | cborGzip | cborGzipBase64 | cborFlate | cborFlateBase64 |
|-------------|-----|-----------|---------|---------------|------|------------|----------|----------------|-----------|-----------------|
| 1 | 64 | 226 | 29 | 178 | 4 | 146 | \-35 | 94 | \-52 | 70 |
| 2 | 102 | 412 | \-107 | 128 | 7 | 288 | \-211 | 0 | \-234 | \-32 |
| 3 | 140 | 602 | \-270 | 58 | 10 | 430 | \-405 | \-126 | \-429 | \-146 |
| 4 | 178 | 792 | \-432 | \-28 | 13 | 572 | \-602 | \-252 | \-617 | \-288 |
| 5 | 216 | 978 | \-582 | \-94 | 16 | 714 | \-805 | \-386 | \-839 | \-418 |
| 6 | 254 | 1168 | \-759 | \-176 | 19 | 856 | \-1001 | \-508 | \-1018 | \-520 |
| 7 | 292 | 1358 | \-908 | \-246 | 22 | 998 | \-1204 | \-634 | \-1229 | \-650 |
| 8 | 330 | 1544 | \-1085 | \-332 | 25 | 1140 | \-1398 | \-756 | \-1423 | \-792 |
| 9 | 368 | 1734 | \-1257 | \-414 | 28 | 1282 | \-1614 | \-894 | \-1625 | \-930 |
| 10 | 406 | 1924 | \-1408 | \-508 | 31 | 1424 | \-1804 | \-1040 | \-1826 | \-1060 |
![img.png](img/overhead_bytes.png)
Following is the performance aspect, with CPU usage and memory allocation:
| | Write ns/op | Read ns/op | Write B/op | Read B/op | Write allocs/op | Read allocs/op |
|-----------------|-------------|------------|------------|-----------|-----------------|----------------|
| car | 8451 | 1474630 | 17928 | 149437 | 59 | 2631 |
| carBase64 | 16750 | 1437678 | 24232 | 151502 | 61 | 2633 |
| carGzip | 320253 | 1581412 | 823887 | 192272 | 76 | 2665 |
| carGzipBase64 | 343305 | 1486269 | 828782 | 198543 | 77 | 2669 |
| cbor | 6419 | 1301554 | 16368 | 138891 | 25 | 2534 |
| cborBase64 | 12860 | 1386728 | 20720 | 140962 | 26 | 2536 |
| cborGzip | 310106 | 1379146 | 822742 | 182003 | 42 | 2585 |
| cborGzipBase64 | 317001 | 1462548 | 827640 | 189283 | 43 | 2594 |
| cborFlate | 327112 | 1555007 | 822473 | 181537 | 40 | 2591 |
| cborFlateBase64 | 311276 | 1456562 | 826042 | 188665 | 41 | 2596 |
(BEWARE: logarithmic scale)
![img.png](img/cpu.png)
![img_1.png](img/alloc_byte.png)
![img_2.png](img/alloc_count.png)
Conclusion:
- CAR files are heavy for this usage, notably because they carry the CIDs of the tokens
- compression works quite well and warrants its usage even with a single token
- DAG-CBOR outperform CAR files everywhere, and comes with a tiny ~3 bytes per token overhead.
**Formats beside DAG-CBOR and CAR, with or without base64, have been removed. They are in the git history though.**

View File

@@ -1,263 +0,0 @@
package container
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io"
"iter"
"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"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/node/basicnode"
)
/*
Note: below is essentially a re-implementation of the CAR file v1 read and write.
This exists here for two reasons:
- go-car's API forces to go through an IPLD getter or through a blockstore API
- generally, go-car is a very complex and large dependency
*/
// EmptyCid is a "zero" Cid: zero-length "identity" multihash with "raw" codec
// It can be used to have at least one root in a CARv1 file (making it legal), yet
// denote that it can be ignored.
var EmptyCid = cid.MustParse([]byte{01, 55, 00, 00})
type carBlock struct {
c cid.Cid
data []byte
}
// writeCar writes a CARv1 file containing the blocks from the iterator.
// If no roots are provided, a single EmptyCid is used as root to make the file
// spec compliant.
func writeCar(w io.Writer, roots []cid.Cid, blocks iter.Seq2[carBlock, error]) error {
if len(roots) == 0 {
roots = []cid.Cid{EmptyCid}
}
h := carHeader{
Roots: roots,
Version: 1,
}
hb, err := h.Write()
if err != nil {
return err
}
err = ldWrite(w, hb)
if err != nil {
return err
}
for block, err := range blocks {
if err != nil {
return err
}
err = ldWrite(w, block.c.Bytes(), block.data)
if err != nil {
return err
}
}
return nil
}
// readCar reads a CARv1 file from the reader, and return a block iterator.
// Roots are ignored.
func readCar(r io.Reader) (roots []cid.Cid, blocks iter.Seq2[carBlock, error], err error) {
br := bufio.NewReader(r)
hb, err := ldRead(br)
if err != nil {
return nil, nil, err
}
h, err := readHeader(hb)
if err != nil {
return nil, nil, err
}
if h.Version != 1 {
return nil, nil, fmt.Errorf("invalid car version: %d", h.Version)
}
return h.Roots, func(yield func(block carBlock, err error) bool) {
for {
block, err := readBlock(br)
if err == io.EOF {
return
}
if err != nil {
if !yield(carBlock{}, err) {
return
}
}
if !yield(block, nil) {
return
}
}
}, nil
}
// readBlock reads a section from the reader and decode a (cid+data) block.
func readBlock(r *bufio.Reader) (carBlock, error) {
raw, err := ldRead(r)
if err != nil {
return carBlock{}, err
}
n, c, err := cid.CidFromReader(bytes.NewReader(raw))
if err != nil {
return carBlock{}, err
}
data := raw[n:]
// integrity check
hashed, err := c.Prefix().Sum(data)
if err != nil {
return carBlock{}, err
}
if !hashed.Equals(c) {
return carBlock{}, fmt.Errorf("mismatch in content integrity, name: %s, data: %s", c, hashed)
}
return carBlock{c: c, data: data}, nil
}
// maxAllowedSectionSize dictates the maximum number of bytes that a CARv1 header
// or section is allowed to occupy without causing a decode to error.
// This cannot be supplied as an option, only adjusted as a global. You should
// use v2#NewReader instead since it allows for options to be passed in.
var maxAllowedSectionSize uint = 32 << 20 // 32MiB
// ldRead performs a length-delimited read of a section from the reader.
// A section is composed of an uint length followed by the data.
func ldRead(r *bufio.Reader) ([]byte, error) {
if _, err := r.Peek(1); err != nil { // no more blocks, likely clean io.EOF
return nil, err
}
l, err := binary.ReadUvarint(r)
if err != nil {
if err == io.EOF {
return nil, io.ErrUnexpectedEOF // don't silently pretend this is a clean EOF
}
return nil, err
}
if l == 0 {
return nil, fmt.Errorf("invalid zero size section")
}
if l > uint64(maxAllowedSectionSize) { // Don't OOM
return nil, fmt.Errorf("malformed car; header is bigger than MaxAllowedSectionSize")
}
buf := make([]byte, l)
if _, err := io.ReadFull(r, buf); err != nil {
if err == io.EOF {
// we should be able to read the promised bytes, this is not normal
return nil, io.ErrUnexpectedEOF
}
return nil, err
}
return buf, nil
}
// ldWrite performs a length-delimited write of a section on the writer.
// A section is composed of an uint length followed by the data.
func ldWrite(w io.Writer, d ...[]byte) error {
var sum uint64
for _, s := range d {
sum += uint64(len(s))
}
buf := make([]byte, 8)
n := binary.PutUvarint(buf, sum)
_, err := w.Write(buf[:n])
if err != nil {
return err
}
for _, s := range d {
_, err = w.Write(s)
if err != nil {
return err
}
}
return nil
}
type carHeader struct {
Roots []cid.Cid
Version uint64
}
const rootsKey = "roots"
const versionKey = "version"
func readHeader(data []byte) (*carHeader, error) {
var header carHeader
nd, err := ipld.Decode(data, dagcbor.Decode)
if err != nil {
return nil, err
}
if nd.Length() != 2 {
return nil, fmt.Errorf("malformed car header")
}
rootsNd, err := nd.LookupByString(rootsKey)
if err != nil {
return nil, fmt.Errorf("malformed car header")
}
it := rootsNd.ListIterator()
if it == nil {
return nil, fmt.Errorf("malformed car header")
}
header.Roots = make([]cid.Cid, 0, rootsNd.Length())
for !it.Done() {
_, nd, err := it.Next()
if err != nil {
return nil, err
}
lk, err := nd.AsLink()
if err != nil {
return nil, err
}
switch lk := lk.(type) {
case cidlink.Link:
header.Roots = append(header.Roots, lk.Cid)
default:
return nil, fmt.Errorf("malformed car header")
}
}
versionNd, err := nd.LookupByString(versionKey)
if err != nil {
return nil, fmt.Errorf("malformed car header")
}
version, err := versionNd.AsInt()
if err != nil {
return nil, fmt.Errorf("malformed car header")
}
header.Version = uint64(version)
return &header, nil
}
func (ch *carHeader) Write() ([]byte, error) {
nd, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
qp.MapEntry(ma, rootsKey, qp.List(int64(len(ch.Roots)), func(la datamodel.ListAssembler) {
for _, root := range ch.Roots {
qp.ListEntry(la, qp.Link(cidlink.Link{Cid: root}))
}
}))
qp.MapEntry(ma, versionKey, qp.Int(1))
})
if err != nil {
return nil, err
}
return ipld.Encode(nd, dagcbor.Encode)
}

View File

@@ -1,85 +0,0 @@
package container
import (
"bytes"
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestCarRoundTrip(t *testing.T) {
// this car file is a complex and legal CARv1 file
original, err := os.ReadFile("testdata/sample-v1.car")
require.NoError(t, err)
roots, it, err := readCar(bytes.NewReader(original))
require.NoError(t, err)
var blks []carBlock
for blk, err := range it {
require.NoError(t, err)
blks = append(blks, blk)
}
require.Len(t, blks, 1049)
buf := bytes.NewBuffer(nil)
err = writeCar(buf, roots, func(yield func(carBlock, error) bool) {
for _, blk := range blks {
if !yield(blk, nil) {
return
}
}
})
require.NoError(t, err)
// Bytes equal after the round-trip
require.Equal(t, original, buf.Bytes())
}
func FuzzCarRoundTrip(f *testing.F) {
// Note: this fuzzing is somewhat broken.
// After some time, the fuzzer discover that a varint can be serialized in different
// ways that lead to the same integer value. This means that the CAR format can have
// multiple legal binary representation for the exact same data, which is what we are
// trying to detect here. Ideally, the format would be stricter, but that's how things
// are.
example, err := os.ReadFile("testdata/sample-v1.car")
require.NoError(f, err)
f.Add(example)
f.Fuzz(func(t *testing.T, data []byte) {
roots, blocksIter, err := readCar(bytes.NewReader(data))
if err != nil {
// skip invalid binary
t.Skip()
}
// reading all the blocks, which force reading and verifying the full file
var blocks []carBlock
for block, err := range blocksIter {
if err != nil {
// error reading, invalid data
t.Skip()
}
blocks = append(blocks, block)
}
var buf bytes.Buffer
err = writeCar(&buf, roots, func(yield func(carBlock, error) bool) {
for _, blk := range blocks {
if !yield(blk, nil) {
return
}
}
})
require.NoError(t, err)
// test if the round-trip produce a byte-equal CAR
require.Equal(t, data, buf.Bytes())
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -1,161 +0,0 @@
package container
import (
"encoding/base64"
"errors"
"fmt"
"io"
"iter"
"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/ucan-wg/go-ucan/token"
"github.com/ucan-wg/go-ucan/token/delegation"
"github.com/ucan-wg/go-ucan/token/invocation"
)
var ErrNotFound = fmt.Errorf("not found")
// Reader is a token container reader. It exposes the tokens conveniently decoded.
type Reader map[cid.Cid]token.Token
// GetToken returns an arbitrary decoded token, from its CID.
// If not found, ErrNotFound is returned.
func (ctn Reader) GetToken(cid cid.Cid) (token.Token, error) {
tkn, ok := ctn[cid]
if !ok {
return nil, ErrNotFound
}
return tkn, nil
}
// GetDelegation is the same as GetToken but only return a delegation.Token, with the right type.
func (ctn Reader) GetDelegation(cid cid.Cid) (*delegation.Token, error) {
tkn, err := ctn.GetToken(cid)
if errors.Is(err, ErrNotFound) {
return nil, delegation.ErrDelegationNotFound
}
if err != nil {
return nil, err
}
if tkn, ok := tkn.(*delegation.Token); ok {
return tkn, nil
}
return nil, delegation.ErrDelegationNotFound
}
// GetAllDelegations returns all the delegation.Token in the container.
func (ctn Reader) GetAllDelegations() iter.Seq2[cid.Cid, *delegation.Token] {
return func(yield func(cid.Cid, *delegation.Token) bool) {
for c, t := range ctn {
if t, ok := t.(*delegation.Token); ok {
if !yield(c, t) {
return
}
}
}
}
}
// GetInvocation returns the first found invocation.Token.
// If none are found, ErrNotFound is returned.
func (ctn Reader) GetInvocation() (*invocation.Token, error) {
for _, t := range ctn {
if inv, ok := t.(*invocation.Token); ok {
return inv, nil
}
}
return nil, ErrNotFound
}
func FromCar(r io.Reader) (Reader, error) {
_, it, err := readCar(r)
if err != nil {
return nil, err
}
ctn := make(Reader)
for block, err := range it {
if err != nil {
return nil, err
}
err = ctn.addToken(block.data)
if err != nil {
return nil, err
}
}
return ctn, nil
}
func FromCarBase64(r io.Reader) (Reader, error) {
return FromCar(base64.NewDecoder(base64.StdEncoding, r))
}
func FromCbor(r io.Reader) (Reader, error) {
n, err := ipld.DecodeStreaming(r, dagcbor.Decode)
if err != nil {
return nil, err
}
if n.Kind() != datamodel.Kind_Map {
return nil, fmt.Errorf("invalid container format: expected map")
}
if n.Length() != 1 {
return nil, fmt.Errorf("invalid container format: expected single version key")
}
// get the first (and only) key-value pair
it := n.MapIterator()
key, tokensNode, err := it.Next()
if err != nil {
return nil, err
}
version, err := key.AsString()
if err != nil {
return nil, fmt.Errorf("invalid container format: version must be string")
}
if version != currentContainerVersion {
return nil, fmt.Errorf("unsupported container version: %s", version)
}
if tokensNode.Kind() != datamodel.Kind_List {
return nil, fmt.Errorf("invalid container format: tokens must be a list")
}
ctn := make(Reader, tokensNode.Length())
it2 := tokensNode.ListIterator()
for !it2.Done() {
_, val, err := it2.Next()
if err != nil {
return nil, err
}
data, err := val.AsBytes()
if err != nil {
return nil, err
}
err = ctn.addToken(data)
if err != nil {
return nil, err
}
}
return ctn, nil
}
func FromCborBase64(r io.Reader) (Reader, error) {
return FromCbor(base64.NewDecoder(base64.StdEncoding, r))
}
func (ctn Reader) addToken(data []byte) error {
tkn, c, err := token.FromSealed(data)
if err != nil {
return err
}
ctn[c] = tkn
return nil
}

View File

@@ -1,205 +0,0 @@
package container
import (
"bytes"
"crypto/rand"
"fmt"
"io"
"strings"
"testing"
"time"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/policy"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
"github.com/ucan-wg/go-ucan/token/delegation"
)
func TestContainerRoundTrip(t *testing.T) {
for _, tc := range []struct {
name string
writer func(ctn Writer, w io.Writer) error
reader func(io.Reader) (Reader, error)
}{
{"car", Writer.ToCar, FromCar},
{"carBase64", Writer.ToCarBase64, FromCarBase64},
{"cbor", Writer.ToCbor, FromCbor},
{"cborBase64", Writer.ToCborBase64, FromCborBase64},
} {
t.Run(tc.name, func(t *testing.T) {
tokens := make(map[cid.Cid]*delegation.Token)
var dataSize int
writer := NewWriter()
for i := 0; i < 10; i++ {
dlg, c, data := randToken()
writer.AddSealed(c, data)
tokens[c] = dlg
dataSize += len(data)
}
buf := bytes.NewBuffer(nil)
err := tc.writer(writer, buf)
require.NoError(t, err)
t.Logf("data size %d", dataSize)
t.Logf("container overhead: %d%%, %d bytes", int(float32(buf.Len()-dataSize)/float32(dataSize)*100.0), buf.Len()-dataSize)
reader, err := tc.reader(bytes.NewReader(buf.Bytes()))
require.NoError(t, err)
for c, dlg := range tokens {
tknRead, err := reader.GetToken(c)
require.NoError(t, err)
// require.Equal fails as time.Time holds a wall time that is going to be
// different, even if it represents the same event.
// We need to do the following instead.
dlgRead := tknRead.(*delegation.Token)
require.Equal(t, dlg.Issuer(), dlgRead.Issuer())
require.Equal(t, dlg.Audience(), dlgRead.Audience())
require.Equal(t, dlg.Subject(), dlgRead.Subject())
require.Equal(t, dlg.Command(), dlgRead.Command())
require.Equal(t, dlg.Policy(), dlgRead.Policy())
require.Equal(t, dlg.Nonce(), dlgRead.Nonce())
require.True(t, dlg.Meta().Equals(dlgRead.Meta()))
if dlg.NotBefore() != nil {
// within 1s as the original value gets truncated to seconds when serialized
require.WithinDuration(t, *dlg.NotBefore(), *dlgRead.NotBefore(), time.Second)
}
if dlg.Expiration() != nil {
// within 1s as the original value gets truncated to seconds when serialized
require.WithinDuration(t, *dlg.Expiration(), *dlgRead.Expiration(), time.Second)
}
}
})
}
}
func BenchmarkContainerSerialisation(b *testing.B) {
var duration strings.Builder
var allocByte strings.Builder
var allocCount strings.Builder
for _, builder := range []strings.Builder{duration, allocByte, allocCount} {
builder.WriteString("car\tcarBase64\tcarGzip\tcarGzipBase64\tcbor\tcborBase64\tcborGzip\tcborGzipBase64\tcborFlate\tcborFlateBase64\n")
}
for _, tc := range []struct {
name string
writer func(ctn Writer, w io.Writer) error
reader func(io.Reader) (Reader, error)
}{
{"car", Writer.ToCar, FromCar},
{"carBase64", Writer.ToCarBase64, FromCarBase64},
{"cbor", Writer.ToCbor, FromCbor},
{"cborBase64", Writer.ToCborBase64, FromCborBase64},
} {
writer := NewWriter()
for i := 0; i < 10; i++ {
_, c, data := randToken()
writer.AddSealed(c, data)
}
buf := bytes.NewBuffer(nil)
_ = tc.writer(writer, buf)
b.Run(tc.name+"_write", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
buf := bytes.NewBuffer(nil)
_ = tc.writer(writer, buf)
}
})
b.Run(tc.name+"_read", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _ = tc.reader(bytes.NewReader(buf.Bytes()))
}
})
}
}
func randDID() (crypto.PrivKey, did.DID) {
privKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
panic(err)
}
d, err := did.FromPrivKey(privKey)
if err != nil {
panic(err)
}
return privKey, d
}
func randomString(length int) string {
b := make([]byte, length/2+1)
_, _ = rand.Read(b)
return fmt.Sprintf("%x", b)[0:length]
}
func randToken() (*delegation.Token, cid.Cid, []byte) {
priv, iss := randDID()
_, aud := randDID()
cmd := command.New("foo", "bar")
pol := policy.MustConstruct(
policy.All(".[]",
policy.GreaterThan(".value", literal.Int(2)),
),
)
opts := []delegation.Option{
delegation.WithExpiration(time.Now().Add(time.Hour)),
delegation.WithSubject(iss),
}
for i := 0; i < 3; i++ {
opts = append(opts, delegation.WithMeta(randomString(8), randomString(10)))
}
t, err := delegation.New(iss, aud, cmd, pol, opts...)
if err != nil {
panic(err)
}
b, c, err := t.ToSealed(priv)
if err != nil {
panic(err)
}
return t, c, b
}
func FuzzContainerRead(f *testing.F) {
// Generate a corpus
for tokenCount := 0; tokenCount < 10; tokenCount++ {
writer := NewWriter()
for i := 0; i < tokenCount; i++ {
_, c, data := randToken()
writer.AddSealed(c, data)
}
buf := bytes.NewBuffer(nil)
err := writer.ToCbor(buf)
require.NoError(f, err)
f.Add(buf.Bytes())
}
f.Fuzz(func(t *testing.T, data []byte) {
start := time.Now()
// search for panics
_, _ = FromCbor(bytes.NewReader(data))
if time.Since(start) > 100*time.Millisecond {
panic("too long")
}
})
}

Binary file not shown.

View File

@@ -1,65 +0,0 @@
package container
import (
"encoding/base64"
"io"
"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"
)
// TODO: should we have a multibase to wrap the cbor? but there is no reader/write in go-multibase :-(
const currentContainerVersion = "ctn-v1"
// Writer is a token container writer. It provides a convenient way to aggregate and serialize tokens together.
type Writer map[cid.Cid][]byte
func NewWriter() Writer {
return make(Writer)
}
// AddSealed includes a "sealed" token (serialized with a ToSealed* function) in the container.
func (ctn Writer) AddSealed(cid cid.Cid, data []byte) {
ctn[cid] = data
}
func (ctn Writer) ToCar(w io.Writer) error {
return writeCar(w, nil, func(yield func(carBlock, error) bool) {
for c, bytes := range ctn {
if !yield(carBlock{c: c, data: bytes}, nil) {
return
}
}
})
}
func (ctn Writer) ToCarBase64(w io.Writer) error {
w2 := base64.NewEncoder(base64.StdEncoding, w)
defer w2.Close()
return ctn.ToCar(w2)
}
func (ctn Writer) ToCbor(w io.Writer) error {
node, err := qp.BuildMap(basicnode.Prototype.Any, 1, func(ma datamodel.MapAssembler) {
qp.MapEntry(ma, currentContainerVersion, qp.List(int64(len(ctn)), func(la datamodel.ListAssembler) {
for _, bytes := range ctn {
qp.ListEntry(la, qp.Bytes(bytes))
}
}))
})
if err != nil {
return err
}
return ipld.EncodeStreaming(w, node, dagcbor.Encode)
}
func (ctn Writer) ToCborBase64(w io.Writer) error {
w2 := base64.NewEncoder(base64.StdEncoding, w)
defer w2.Close()
return ctn.ToCbor(w2)
}

View File

@@ -1,132 +0,0 @@
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"fmt"
"io"
)
// KeySize represents valid AES key sizes
type KeySize int
const (
KeySize128 KeySize = 16 // AES-128
KeySize192 KeySize = 24 // AES-192
KeySize256 KeySize = 32 // AES-256 (recommended)
)
// IsValid returns true if the key size is valid for AES
func (ks KeySize) IsValid() bool {
switch ks {
case KeySize128, KeySize192, KeySize256:
return true
default:
return false
}
}
var ErrShortCipherText = errors.New("ciphertext too short")
var ErrNoEncryptionKey = errors.New("encryption key is required")
var ErrInvalidKeySize = errors.New("invalid key size: must be 16, 24, or 32 bytes")
var ErrZeroKey = errors.New("encryption key cannot be all zeros")
// GenerateKey generates a random AES key of default size KeySize256 (32 bytes).
// Returns an error if the specified size is invalid or if key generation fails.
func GenerateKey() ([]byte, error) {
return GenerateKeyWithSize(KeySize256)
}
// GenerateKeyWithSize generates a random AES key of the specified size.
// Returns an error if the specified size is invalid or if key generation fails.
func GenerateKeyWithSize(size KeySize) ([]byte, error) {
if !size.IsValid() {
return nil, ErrInvalidKeySize
}
key := make([]byte, size)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return nil, fmt.Errorf("failed to generate AES key: %w", err)
}
return key, nil
}
// EncryptWithAESKey encrypts data using AES-GCM with the provided key.
// The key must be 16, 24, or 32 bytes long (for AES-128, AES-192, or AES-256).
// Returns the encrypted data with the nonce prepended, or an error if encryption fails.
func EncryptWithAESKey(data, key []byte) ([]byte, error) {
if err := validateAESKey(key); err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, data, nil), nil
}
// DecryptStringWithAESKey decrypts data that was encrypted with EncryptWithAESKey.
// The key must match the one used for encryption.
// Expects the input to have a prepended nonce.
// Returns the decrypted data or an error if decryption fails.
func DecryptStringWithAESKey(data, key []byte) ([]byte, error) {
if err := validateAESKey(key); err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
if len(data) < gcm.NonceSize() {
return nil, ErrShortCipherText
}
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
decrypted, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return decrypted, nil
}
func validateAESKey(key []byte) error {
if key == nil {
return ErrNoEncryptionKey
}
if !KeySize(len(key)).IsValid() {
return ErrInvalidKeySize
}
// check if key is all zeros
for _, b := range key {
if b != 0 {
return nil
}
}
return ErrZeroKey
}

View File

@@ -1,124 +0,0 @@
package crypto
import (
"bytes"
"crypto/rand"
"testing"
"github.com/stretchr/testify/require"
)
func TestAESEncryption(t *testing.T) {
t.Parallel()
key := make([]byte, 32) // generated random 32-byte key
_, errKey := rand.Read(key)
require.NoError(t, errKey)
tests := []struct {
name string
data []byte
key []byte
wantErr error
}{
{
name: "valid encryption/decryption",
data: []byte("hello world"),
key: key,
},
{
name: "nil key returns error",
data: []byte("hello world"),
key: nil,
wantErr: ErrNoEncryptionKey,
},
{
name: "empty data",
data: []byte{},
key: key,
},
{
name: "invalid key size",
data: []byte("hello world"),
key: make([]byte, 31),
wantErr: ErrInvalidKeySize,
},
{
name: "zero key returns error",
data: []byte("hello world"),
key: make([]byte, 32),
wantErr: ErrZeroKey,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
encrypted, err := EncryptWithAESKey(tt.data, tt.key)
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
return
}
require.NoError(t, err)
decrypted, err := DecryptStringWithAESKey(encrypted, tt.key)
require.NoError(t, err)
if tt.key == nil {
require.Equal(t, tt.data, encrypted)
require.Equal(t, tt.data, decrypted)
} else {
require.NotEqual(t, tt.data, encrypted)
require.True(t, bytes.Equal(tt.data, decrypted))
}
})
}
}
func TestDecryptionErrors(t *testing.T) {
t.Parallel()
key := make([]byte, 32)
_, err := rand.Read(key)
require.NoError(t, err)
tests := []struct {
name string
data []byte
key []byte
errMsg string
}{
{
name: "short ciphertext",
data: []byte("short"),
key: key,
errMsg: "ciphertext too short",
},
{
name: "invalid ciphertext",
data: make([]byte, 16), // just nonce size
key: key,
errMsg: "message authentication failed",
},
{
name: "missing key",
data: []byte("<22>`M<><4D><EFBFBD>l\u001AIF<49>\u0012<31><32><EFBFBD>=h<>?<3F>c<EFBFBD> <20><>\u0012<31><32><EFBFBD><EFBFBD>\u001C<31>\u0018Ƽ(g"),
key: nil,
errMsg: "encryption key is required",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
_, err := DecryptStringWithAESKey(tt.data, tt.key)
require.Error(t, err)
require.Contains(t, err.Error(), tt.errMsg)
})
}
}

View File

@@ -1,213 +0,0 @@
package meta
import (
"errors"
"fmt"
"strings"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/printer"
"github.com/ucan-wg/go-ucan/pkg/meta/internal/crypto"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)
var ErrNotFound = errors.New("key not found in meta")
var ErrNotEncryptable = errors.New("value of this type cannot be encrypted")
// Meta is a container for meta key-value pairs in a UCAN token.
// This also serves as a way to construct the underlying IPLD data with minimum allocations
// and transformations, while hiding the IPLD complexity from the caller.
type Meta struct {
// This type must be compatible with the IPLD type represented by the IPLD
// schema { String : Any }.
Keys []string
Values map[string]ipld.Node
}
// NewMeta constructs a new Meta.
func NewMeta() *Meta {
return &Meta{Values: map[string]ipld.Node{}}
}
// GetBool retrieves a value as a bool.
// Returns ErrNotFound if the given key is missing.
// Returns datamodel.ErrWrongKind if the value has the wrong type.
func (m *Meta) GetBool(key string) (bool, error) {
v, ok := m.Values[key]
if !ok {
return false, ErrNotFound
}
return v.AsBool()
}
// GetString retrieves a value as a string.
// Returns ErrNotFound if the given key is missing.
// Returns datamodel.ErrWrongKind if the value has the wrong type.
func (m *Meta) GetString(key string) (string, error) {
v, ok := m.Values[key]
if !ok {
return "", ErrNotFound
}
return v.AsString()
}
// GetEncryptedString decorates GetString and decrypt its output with the given symmetric encryption key.
func (m *Meta) GetEncryptedString(key string, encryptionKey []byte) (string, error) {
v, err := m.GetBytes(key)
if err != nil {
return "", err
}
decrypted, err := crypto.DecryptStringWithAESKey(v, encryptionKey)
if err != nil {
return "", err
}
return string(decrypted), nil
}
// GetInt64 retrieves a value as an int64.
// Returns ErrNotFound if the given key is missing.
// Returns datamodel.ErrWrongKind if the value has the wrong type.
func (m *Meta) GetInt64(key string) (int64, error) {
v, ok := m.Values[key]
if !ok {
return 0, ErrNotFound
}
return v.AsInt()
}
// GetFloat64 retrieves a value as a float64.
// Returns ErrNotFound if the given key is missing.
// Returns datamodel.ErrWrongKind if the value has the wrong type.
func (m *Meta) GetFloat64(key string) (float64, error) {
v, ok := m.Values[key]
if !ok {
return 0, ErrNotFound
}
return v.AsFloat()
}
// GetBytes retrieves a value as a []byte.
// Returns ErrNotFound if the given key is missing.
// Returns datamodel.ErrWrongKind if the value has the wrong type.
func (m *Meta) GetBytes(key string) ([]byte, error) {
v, ok := m.Values[key]
if !ok {
return nil, ErrNotFound
}
return v.AsBytes()
}
// GetEncryptedBytes decorates GetBytes and decrypt its output with the given symmetric encryption key.
func (m *Meta) GetEncryptedBytes(key string, encryptionKey []byte) ([]byte, error) {
v, err := m.GetBytes(key)
if err != nil {
return nil, err
}
decrypted, err := crypto.DecryptStringWithAESKey(v, encryptionKey)
if err != nil {
return nil, err
}
return decrypted, nil
}
// GetNode retrieves a value as a raw IPLD node.
// Returns ErrNotFound if the given key is missing.
// Returns datamodel.ErrWrongKind if the value has the wrong type.
func (m *Meta) GetNode(key string) (ipld.Node, error) {
v, ok := m.Values[key]
if !ok {
return nil, ErrNotFound
}
return v, nil
}
// Add adds a key/value pair in the meta set.
// Accepted types for val are any CBOR compatible type, or directly IPLD values.
func (m *Meta) Add(key string, val any) error {
if _, ok := m.Values[key]; ok {
return fmt.Errorf("duplicate key %q", key)
}
node, err := literal.Any(val)
if err != nil {
return err
}
m.Keys = append(m.Keys, key)
m.Values[key] = node
return nil
}
// AddEncrypted adds a key/value pair in the meta set.
// The value is encrypted with the given encryptionKey.
// Accepted types for the value are: string, []byte.
func (m *Meta) AddEncrypted(key string, val any, encryptionKey []byte) error {
var encrypted []byte
var err error
switch val := val.(type) {
case string:
encrypted, err = crypto.EncryptWithAESKey([]byte(val), encryptionKey)
if err != nil {
return err
}
case []byte:
encrypted, err = crypto.EncryptWithAESKey(val, encryptionKey)
if err != nil {
return err
}
default:
return ErrNotEncryptable
}
return m.Add(key, encrypted)
}
// Equals tells if two Meta hold the same key/values.
func (m *Meta) Equals(other *Meta) bool {
if len(m.Keys) != len(other.Keys) {
return false
}
if len(m.Values) != len(other.Values) {
return false
}
for _, key := range m.Keys {
if !ipld.DeepEqual(m.Values[key], other.Values[key]) {
return false
}
}
return true
}
func (m *Meta) String() string {
buf := strings.Builder{}
buf.WriteString("{")
for key, node := range m.Values {
buf.WriteString("\n\t")
buf.WriteString(key)
buf.WriteString(": ")
buf.WriteString(strings.ReplaceAll(printer.Sprint(node), "\n", "\n\t"))
buf.WriteString(",")
}
if len(m.Values) > 0 {
buf.WriteString("\n")
}
buf.WriteString("}")
return buf.String()
}
// ReadOnly returns a read-only version of Meta.
func (m *Meta) ReadOnly() ReadOnly {
return ReadOnly{m: m}
}

View File

@@ -1,77 +0,0 @@
package meta_test
import (
"crypto/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/meta"
)
func TestMeta_Add(t *testing.T) {
t.Parallel()
type Unsupported struct{}
t.Run("error if not primitive or Node", func(t *testing.T) {
t.Parallel()
err := (&meta.Meta{}).Add("invalid", &Unsupported{})
require.Error(t, err)
})
t.Run("encrypted meta", func(t *testing.T) {
t.Parallel()
key := make([]byte, 32)
_, err := rand.Read(key)
require.NoError(t, err)
m := meta.NewMeta()
// string encryption
err = m.AddEncrypted("secret", "hello world", key)
require.NoError(t, err)
_, err = m.GetString("secret")
require.Error(t, err) // the ciphertext is saved as []byte instead of string
decrypted, err := m.GetEncryptedString("secret", key)
require.NoError(t, err)
require.Equal(t, "hello world", decrypted)
// bytes encryption
originalBytes := make([]byte, 128)
_, err = rand.Read(originalBytes)
require.NoError(t, err)
err = m.AddEncrypted("secret-bytes", originalBytes, key)
require.NoError(t, err)
encryptedBytes, err := m.GetBytes("secret-bytes")
require.NoError(t, err)
require.NotEqual(t, originalBytes, encryptedBytes)
decryptedBytes, err := m.GetEncryptedBytes("secret-bytes", key)
require.NoError(t, err)
require.Equal(t, originalBytes, decryptedBytes)
// error cases
t.Run("error on unsupported type", func(t *testing.T) {
err := m.AddEncrypted("invalid", 123, key)
require.ErrorIs(t, err, meta.ErrNotEncryptable)
})
t.Run("error on invalid key size", func(t *testing.T) {
err := m.AddEncrypted("invalid", "test", []byte("short-key"))
require.Error(t, err)
require.Contains(t, err.Error(), "invalid key size")
})
t.Run("error on nil key", func(t *testing.T) {
err := m.AddEncrypted("invalid", "test", nil)
require.Error(t, err)
require.Contains(t, err.Error(), "encryption key is required")
})
})
}

View File

@@ -1,50 +0,0 @@
package meta
import (
"github.com/ipld/go-ipld-prime"
)
// ReadOnly wraps a Meta into a read-only facade.
type ReadOnly struct {
m *Meta
}
func (r ReadOnly) GetBool(key string) (bool, error) {
return r.m.GetBool(key)
}
func (r ReadOnly) GetString(key string) (string, error) {
return r.m.GetString(key)
}
func (r ReadOnly) GetEncryptedString(key string, encryptionKey []byte) (string, error) {
return r.m.GetEncryptedString(key, encryptionKey)
}
func (r ReadOnly) GetInt64(key string) (int64, error) {
return r.m.GetInt64(key)
}
func (r ReadOnly) GetFloat64(key string) (float64, error) {
return r.m.GetFloat64(key)
}
func (r ReadOnly) GetBytes(key string) ([]byte, error) {
return r.m.GetBytes(key)
}
func (r ReadOnly) GetEncryptedBytes(key string, encryptionKey []byte) ([]byte, error) {
return r.m.GetEncryptedBytes(key, encryptionKey)
}
func (r ReadOnly) GetNode(key string) (ipld.Node, error) {
return r.m.GetNode(key)
}
func (r ReadOnly) Equals(other ReadOnly) bool {
return r.m.Equals(other.m)
}
func (r ReadOnly) String() string {
return r.m.String()
}

View File

@@ -1,79 +0,0 @@
package policy
import "fmt"
type glob string
// parseGlob ensures that the pattern conforms to the spec: only '*' and escaped '\*' are allowed.
func parseGlob(pattern string) (glob, error) {
for i := 0; i < len(pattern); i++ {
if pattern[i] == '*' {
continue
}
if pattern[i] == '\\' && i+1 < len(pattern) && pattern[i+1] == '*' {
i++ // skip the escaped '*'
continue
}
if pattern[i] == '\\' && i+1 < len(pattern) {
i++ // skip the escaped character
continue
}
if pattern[i] == '\\' {
return "", fmt.Errorf("invalid escape sequence")
}
}
return glob(pattern), nil
}
func mustParseGlob(pattern string) glob {
g, err := parseGlob(pattern)
if err != nil {
panic(err)
}
return g
}
// Match matches a string against the glob pattern with * wildcards, handling escaped '\*' literals.
func (pattern glob) Match(str string) bool {
// i is the index for the pattern
// j is the index for the string
var i, j int
// starIdx keeps track of the position of the last * in the pattern.
// matchIdx keeps track of the position in the string where the last * matched.
var starIdx, matchIdx int = -1, -1
for j < len(str) {
if i < len(pattern) && (pattern[i] == str[j] || pattern[i] == '\\' && i+1 < len(pattern) && pattern[i+1] == str[j]) {
// characters match or if there's an escaped character that matches
if pattern[i] == '\\' {
// skip the escape character
i++
}
i++
j++
} else if i < len(pattern) && pattern[i] == '*' {
// there's a * wildcard in the pattern
starIdx = i
matchIdx = j
i++
} else if starIdx != -1 {
// there's a previous * wildcard, backtrack
i = starIdx + 1
matchIdx++
j = matchIdx
} else {
// no match found
return false
}
}
// check for remaining characters in the pattern
for i < len(pattern) && pattern[i] == '*' {
i++
}
// the entire pattern is processed, it's a match
return i == len(pattern)
}

View File

@@ -1,73 +0,0 @@
package policy
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSimpleGlobMatch(t *testing.T) {
tests := []struct {
pattern string
str string
matches bool
}{
// Basic matching
{"*", "anything", true},
{"a*", "abc", true},
{"*c", "abc", true},
{"a*c", "abc", true},
{"a*c", "abxc", true},
{"a*c", "ac", true},
{"a*c", "a", false},
{"a*c", "ab", false},
// Escaped characters
{"a\\*c", "a*c", true},
{"a\\*c", "abc", false},
// Mixed wildcards and literals
{"a*b*c", "abc", true},
{"a*b*c", "aXbYc", true},
{"a*b*c", "aXbY", false},
{"a*b*c", "abYc", true},
{"a*b*c", "aXbc", true},
{"a*b*c", "aXbYcZ", false},
// Edge cases
{"", "", true},
{"", "a", false},
{"*", "", true},
{"*", "a", true},
{"\\*", "*", true},
{"\\*", "a", false},
// Specified test cases
{"Alice\\*, Bob*, Carol.", "Alice*, Bob, Carol.", true},
{"Alice\\*, Bob*, Carol.", "Alice*, Bob, Dan, Erin, Carol.", true},
{"Alice\\*, Bob*, Carol.", "Alice*, Bob , Carol.", true},
{"Alice\\*, Bob*, Carol.", "Alice*, Bob*, Carol.", true},
{"Alice\\*, Bob*, Carol.", "Alice*, Bob, Carol", false},
{"Alice\\*, Bob*, Carol.", "Alice*, Bob*, Carol!", false},
{"Alice\\*, Bob*, Carol.", "Alice, Bob, Carol.", false},
{"Alice\\*, Bob*, Carol.", "Alice Cooper, Bob, Carol.", false},
{"Alice\\*, Bob*, Carol.", " Alice*, Bob, Carol. ", false},
}
for _, tt := range tests {
t.Run(tt.pattern+"_"+tt.str, func(t *testing.T) {
g, err := parseGlob(tt.pattern)
require.NoError(t, err)
require.Equal(t, tt.matches, g.Match(tt.str))
})
}
}
func BenchmarkGlob(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
g := mustParseGlob("Alice\\*, Bob*, Carol.")
g.Match("Alice*, Bob*, Carol!")
}
}

View File

@@ -1,187 +0,0 @@
// Package literal holds a collection of functions to create IPLD types to use in policies, selector and args.
package literal
import (
"fmt"
"reflect"
"sort"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/fluent/qp"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/node/basicnode"
)
var Bool = basicnode.NewBool
var Int = basicnode.NewInt
var Float = basicnode.NewFloat
var String = basicnode.NewString
var Bytes = basicnode.NewBytes
var Link = basicnode.NewLink
func LinkCid(cid cid.Cid) ipld.Node {
return Link(cidlink.Link{Cid: cid})
}
func Null() ipld.Node {
nb := basicnode.Prototype.Any.NewBuilder()
nb.AssignNull()
return nb.Build()
}
// Map creates an IPLD node from a map[string]any
func Map[T any](m map[string]T) (ipld.Node, error) {
return qp.BuildMap(basicnode.Prototype.Any, int64(len(m)), func(ma datamodel.MapAssembler) {
// deterministic iteration
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
qp.MapEntry(ma, key, anyAssemble(m[key]))
}
})
}
// List creates an IPLD node from a []any
func List[T any](l []T) (ipld.Node, error) {
return qp.BuildList(basicnode.Prototype.Any, int64(len(l)), func(la datamodel.ListAssembler) {
for _, val := range l {
qp.ListEntry(la, anyAssemble(val))
}
})
}
// Any creates an IPLD node from any value
// If possible, use another dedicated function for your type for performance.
func Any(v any) (res ipld.Node, err error) {
// TODO: handle uint overflow below
// some fast path
switch val := v.(type) {
case bool:
return basicnode.NewBool(val), nil
case string:
return basicnode.NewString(val), nil
case int:
return basicnode.NewInt(int64(val)), nil
case int8:
return basicnode.NewInt(int64(val)), nil
case int16:
return basicnode.NewInt(int64(val)), nil
case int32:
return basicnode.NewInt(int64(val)), nil
case int64:
return basicnode.NewInt(val), nil
case uint:
return basicnode.NewInt(int64(val)), nil
case uint8:
return basicnode.NewInt(int64(val)), nil
case uint16:
return basicnode.NewInt(int64(val)), nil
case uint32:
return basicnode.NewInt(int64(val)), nil
case uint64:
return basicnode.NewInt(int64(val)), nil
case float32:
return basicnode.NewFloat(float64(val)), nil
case float64:
return basicnode.NewFloat(val), nil
case []byte:
return basicnode.NewBytes(val), nil
case datamodel.Node:
return val, nil
case cid.Cid:
return LinkCid(val), nil
default:
}
builder := basicnode.Prototype__Any{}.NewBuilder()
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v", r)
res = nil
}
}()
anyAssemble(v)(builder)
return builder.Build(), nil
}
func anyAssemble(val any) qp.Assemble {
var rt reflect.Type
var rv reflect.Value
// support for recursive calls, staying in reflection land
if cast, ok := val.(reflect.Value); ok {
rt = cast.Type()
rv = cast
} else {
rt = reflect.TypeOf(val)
rv = reflect.ValueOf(val)
}
// we need to dereference in some cases, to get the real value type
if rt.Kind() == reflect.Ptr || rt.Kind() == reflect.Interface {
rv = rv.Elem()
rt = rv.Type()
}
switch rt.Kind() {
case reflect.Array:
if rt.Elem().Kind() == reflect.Uint8 {
panic("bytes array are not supported yet")
}
return qp.List(int64(rv.Len()), func(la datamodel.ListAssembler) {
for i := range rv.Len() {
qp.ListEntry(la, anyAssemble(rv.Index(i)))
}
})
case reflect.Slice:
if rt.Elem().Kind() == reflect.Uint8 {
return qp.Bytes(val.([]byte))
}
return qp.List(int64(rv.Len()), func(la datamodel.ListAssembler) {
for i := range rv.Len() {
qp.ListEntry(la, anyAssemble(rv.Index(i)))
}
})
case reflect.Map:
if rt.Key().Kind() != reflect.String {
break
}
// deterministic iteration
keys := rv.MapKeys()
sort.Slice(keys, func(i, j int) bool {
return keys[i].String() < keys[j].String()
})
return qp.Map(int64(rv.Len()), func(ma datamodel.MapAssembler) {
for _, key := range keys {
qp.MapEntry(ma, key.String(), anyAssemble(rv.MapIndex(key)))
}
})
case reflect.Bool:
return qp.Bool(rv.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return qp.Int(rv.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return qp.Int(int64(rv.Uint()))
case reflect.Float32, reflect.Float64:
return qp.Float(rv.Float())
case reflect.String:
return qp.String(rv.String())
case reflect.Struct:
if rt == reflect.TypeOf(cid.Cid{}) {
c := rv.Interface().(cid.Cid)
return qp.Link(cidlink.Link{Cid: c})
}
default:
}
panic(fmt.Sprintf("unsupported type %T", val))
}

View File

@@ -1,262 +0,0 @@
package literal
import (
"testing"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime/datamodel"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/printer"
"github.com/stretchr/testify/require"
)
func TestList(t *testing.T) {
n, err := List([]int{1, 2, 3})
require.NoError(t, err)
require.Equal(t, datamodel.Kind_List, n.Kind())
require.Equal(t, int64(3), n.Length())
require.Equal(t, `list{
0: int{1}
1: int{2}
2: int{3}
}`, printer.Sprint(n))
n, err = List([][]int{{1, 2, 3}, {4, 5, 6}})
require.NoError(t, err)
require.Equal(t, datamodel.Kind_List, n.Kind())
require.Equal(t, int64(2), n.Length())
require.Equal(t, `list{
0: list{
0: int{1}
1: int{2}
2: int{3}
}
1: list{
0: int{4}
1: int{5}
2: int{6}
}
}`, printer.Sprint(n))
}
func TestMap(t *testing.T) {
n, err := Map(map[string]any{
"bool": true,
"string": "foobar",
"bytes": []byte{1, 2, 3, 4},
"int": 1234,
"uint": uint(12345),
"float": 1.45,
"slice": []int{1, 2, 3},
"array": [2]int{1, 2},
"map": map[string]any{
"foo": "bar",
"foofoo": map[string]string{
"barbar": "foo",
},
},
"link": cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm"),
})
require.NoError(t, err)
v, err := n.LookupByString("bool")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Bool, v.Kind())
require.Equal(t, true, must(v.AsBool()))
v, err = n.LookupByString("string")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_String, v.Kind())
require.Equal(t, "foobar", must(v.AsString()))
v, err = n.LookupByString("bytes")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Bytes, v.Kind())
require.Equal(t, []byte{1, 2, 3, 4}, must(v.AsBytes()))
v, err = n.LookupByString("int")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Int, v.Kind())
require.Equal(t, int64(1234), must(v.AsInt()))
v, err = n.LookupByString("uint")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Int, v.Kind())
require.Equal(t, int64(12345), must(v.AsInt()))
v, err = n.LookupByString("float")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Float, v.Kind())
require.Equal(t, 1.45, must(v.AsFloat()))
v, err = n.LookupByString("slice")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_List, v.Kind())
require.Equal(t, int64(3), v.Length())
require.Equal(t, `list{
0: int{1}
1: int{2}
2: int{3}
}`, printer.Sprint(v))
v, err = n.LookupByString("array")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_List, v.Kind())
require.Equal(t, int64(2), v.Length())
require.Equal(t, `list{
0: int{1}
1: int{2}
}`, printer.Sprint(v))
v, err = n.LookupByString("map")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Map, v.Kind())
require.Equal(t, int64(2), v.Length())
require.Equal(t, `map{
string{"foo"}: string{"bar"}
string{"foofoo"}: map{
string{"barbar"}: string{"foo"}
}
}`, printer.Sprint(v))
v, err = n.LookupByString("link")
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Link, v.Kind())
asLink, err := v.AsLink()
require.NoError(t, err)
require.True(t, asLink.(cidlink.Link).Equals(cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm")))
}
func TestAny(t *testing.T) {
data := map[string]any{
"bool": true,
"string": "foobar",
"bytes": []byte{1, 2, 3, 4},
"int": 1234,
"uint": uint(12345),
"float": 1.45,
"slice": []int{1, 2, 3},
"array": [2]int{1, 2},
"map": map[string]any{
"foo": "bar",
"foofoo": map[string]string{
"barbar": "foo",
},
},
"link": cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm"),
"func": func() {},
}
v, err := Any(data["bool"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Bool, v.Kind())
require.Equal(t, true, must(v.AsBool()))
v, err = Any(data["string"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_String, v.Kind())
require.Equal(t, "foobar", must(v.AsString()))
v, err = Any(data["bytes"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Bytes, v.Kind())
require.Equal(t, []byte{1, 2, 3, 4}, must(v.AsBytes()))
v, err = Any(data["int"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Int, v.Kind())
require.Equal(t, int64(1234), must(v.AsInt()))
v, err = Any(data["uint"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Int, v.Kind())
require.Equal(t, int64(12345), must(v.AsInt()))
v, err = Any(data["float"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Float, v.Kind())
require.Equal(t, 1.45, must(v.AsFloat()))
v, err = Any(data["slice"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_List, v.Kind())
require.Equal(t, int64(3), v.Length())
require.Equal(t, `list{
0: int{1}
1: int{2}
2: int{3}
}`, printer.Sprint(v))
v, err = Any(data["array"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_List, v.Kind())
require.Equal(t, int64(2), v.Length())
require.Equal(t, `list{
0: int{1}
1: int{2}
}`, printer.Sprint(v))
v, err = Any(data["map"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Map, v.Kind())
require.Equal(t, int64(2), v.Length())
require.Equal(t, `map{
string{"foo"}: string{"bar"}
string{"foofoo"}: map{
string{"barbar"}: string{"foo"}
}
}`, printer.Sprint(v))
v, err = Any(data["link"])
require.NoError(t, err)
require.Equal(t, datamodel.Kind_Link, v.Kind())
asLink, err := v.AsLink()
require.NoError(t, err)
require.True(t, asLink.(cidlink.Link).Equals(cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm")))
v, err = Any(data["func"])
require.Error(t, err)
}
func BenchmarkAny(b *testing.B) {
b.Run("bool", func(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_, _ = Any(true)
}
})
b.Run("string", func(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_, _ = Any("foobar")
}
})
b.Run("bytes", func(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_, _ = Any([]byte{1, 2, 3, 4})
}
})
b.Run("map", func(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_, _ = Any(map[string]any{
"foo": "bar",
"foofoo": map[string]string{
"barbar": "foo",
},
})
}
})
}
func must[T any](t T, err error) T {
if err != nil {
panic(err)
}
return t
}

View File

@@ -1,277 +0,0 @@
package policy
import (
"cmp"
"fmt"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/must"
)
// Match determines if the IPLD node satisfies the policy.
// The first Statement failing to match is returned as well.
func (p Policy) Match(node datamodel.Node) (bool, Statement) {
for _, stmt := range p {
res, leaf := matchStatement(stmt, node)
switch res {
case matchResultNoData, matchResultFalse:
return false, leaf
case matchResultOptionalNoData, matchResultTrue:
// continue
}
}
return true, nil
}
// PartialMatch returns false IIF one non-optional Statement has the corresponding data and doesn't match.
// If the data is missing or the non-optional Statement is matching, true is returned.
//
// This allows performing the policy checking in multiple steps, and find immediately if a Statement already failed.
// A final call to Match is necessary to make sure that the policy is fully matched, with no missing data
// (apart from optional values).
//
// The first Statement failing to match is returned as well.
func (p Policy) PartialMatch(node datamodel.Node) (bool, Statement) {
for _, stmt := range p {
res, leaf := matchStatement(stmt, node)
switch res {
case matchResultFalse:
return false, leaf
case matchResultNoData, matchResultOptionalNoData, matchResultTrue:
// continue
}
}
return true, nil
}
type matchResult int8
const (
matchResultTrue matchResult = iota // statement has data and resolve to true
matchResultFalse // statement has data and resolve to false
matchResultNoData // statement has no data
matchResultOptionalNoData // statement has no data and is optional
)
// matchStatement evaluate the policy against the given ipld.Node and returns:
// - matchResultTrue: if the selector matched and the statement evaluated to true.
// - matchResultFalse: if the selector matched and the statement evaluated to false.
// - matchResultNoData: if the selector didn't match the expected data.
// For matchResultTrue and matchResultNoData, the leaf-most (innermost) statement failing to be true is returned,
// as well as the corresponding root-most encompassing statement.
func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Statement) {
var boolToRes = func(v bool) (matchResult, Statement) {
if v {
return matchResultTrue, nil
} else {
return matchResultFalse, cur
}
}
switch cur.Kind() {
case KindEqual:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(datamodel.DeepEqual(s.value, res))
}
case KindGreaterThan:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(isOrdered(s.value, res, gt))
}
case KindGreaterThanOrEqual:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(isOrdered(s.value, res, gte))
}
case KindLessThan:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(isOrdered(s.value, res, lt))
}
case KindLessThanOrEqual:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(isOrdered(s.value, res, lte))
}
case KindNot:
if s, ok := cur.(negation); ok {
res, leaf := matchStatement(s.statement, node)
switch res {
case matchResultNoData, matchResultOptionalNoData:
return res, leaf
case matchResultTrue:
return matchResultFalse, cur
case matchResultFalse:
return matchResultTrue, nil
}
}
case KindAnd:
if s, ok := cur.(connective); ok {
for _, cs := range s.statements {
res, leaf := matchStatement(cs, node)
switch res {
case matchResultNoData, matchResultOptionalNoData:
return res, leaf
case matchResultTrue:
// continue
case matchResultFalse:
return matchResultFalse, leaf
}
}
return matchResultTrue, nil
}
case KindOr:
if s, ok := cur.(connective); ok {
if len(s.statements) == 0 {
return matchResultTrue, nil
}
for _, cs := range s.statements {
res, leaf := matchStatement(cs, node)
switch res {
case matchResultNoData, matchResultOptionalNoData:
return res, leaf
case matchResultTrue:
return matchResultTrue, leaf
case matchResultFalse:
// continue
}
}
return matchResultFalse, cur
}
case KindLike:
if s, ok := cur.(wildcard); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
v, err := res.AsString()
if err != nil {
return matchResultFalse, cur // not a string
}
return boolToRes(s.pattern.Match(v))
}
case KindAll:
if s, ok := cur.(quantifier); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
}
if res == nil {
return matchResultOptionalNoData, nil
}
it := res.ListIterator()
if it == nil {
return matchResultFalse, cur // not a list
}
for !it.Done() {
_, v, err := it.Next()
if err != nil {
panic("should never happen")
}
matchRes, leaf := matchStatement(s.statement, v)
switch matchRes {
case matchResultNoData, matchResultOptionalNoData:
return matchRes, leaf
case matchResultTrue:
// continue
case matchResultFalse:
return matchResultFalse, leaf
}
}
return matchResultTrue, nil
}
case KindAny:
if s, ok := cur.(quantifier); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
}
if res == nil {
return matchResultOptionalNoData, nil
}
it := res.ListIterator()
if it == nil {
return matchResultFalse, cur // not a list
}
for !it.Done() {
_, v, err := it.Next()
if err != nil {
panic("should never happen")
}
matchRes, leaf := matchStatement(s.statement, v)
switch matchRes {
case matchResultNoData, matchResultOptionalNoData:
return matchRes, leaf
case matchResultTrue:
return matchResultTrue, nil
case matchResultFalse:
// continue
}
}
return matchResultFalse, cur
}
}
panic(fmt.Errorf("unimplemented statement kind: %s", cur.Kind()))
}
func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) bool) bool {
if expected.Kind() == ipld.Kind_Int && actual.Kind() == ipld.Kind_Int {
a := must.Int(actual)
b := must.Int(expected)
return satisfies(cmp.Compare(a, b))
}
if expected.Kind() == ipld.Kind_Float && actual.Kind() == ipld.Kind_Float {
a, err := actual.AsFloat()
if err != nil {
panic(fmt.Errorf("extracting node float: %w", err))
}
b, err := expected.AsFloat()
if err != nil {
panic(fmt.Errorf("extracting selector float: %w", err))
}
return satisfies(cmp.Compare(a, b))
}
return false
}
func gt(order int) bool { return order == 1 }
func gte(order int) bool { return order == 0 || order == 1 }
func lt(order int) bool { return order == -1 }
func lte(order int) bool { return order == 0 || order == -1 }

View File

@@ -1,958 +0,0 @@
package policy
import (
"fmt"
"testing"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/fluent/qp"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)
func TestMatch(t *testing.T) {
t.Run("equality", func(t *testing.T) {
t.Run("string", func(t *testing.T) {
nd := literal.String("test")
pol := MustConstruct(Equal(".", literal.String("test")))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".", literal.String("test2")))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".", literal.Int(138)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("int", func(t *testing.T) {
nd := literal.Int(138)
pol := MustConstruct(Equal(".", literal.Int(138)))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".", literal.Int(1138)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".", literal.String("138")))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("float", func(t *testing.T) {
nd := literal.Float(1.138)
pol := MustConstruct(Equal(".", literal.Float(1.138)))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".", literal.Float(11.38)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".", literal.String("138")))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("IPLD Link", func(t *testing.T) {
l0 := cidlink.Link{Cid: cid.MustParse("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")}
l1 := cidlink.Link{Cid: cid.MustParse("bafkreifau35r7vi37tvbvfy3hdwvgb4tlflqf7zcdzeujqcjk3rsphiwte")}
nd := literal.Link(l0)
pol := MustConstruct(Equal(".", literal.Link(l0)))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".", literal.Link(l1)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".", literal.String("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("string in map", func(t *testing.T) {
nd, _ := literal.Map(map[string]any{
"foo": "bar",
})
pol := MustConstruct(Equal(".foo", literal.String("bar")))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".[\"foo\"]", literal.String("bar")))
ok, leaf = pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".foo", literal.String("baz")))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".foobar", literal.String("bar")))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("string in list", func(t *testing.T) {
nd, _ := literal.List([]any{"foo"})
pol := MustConstruct(Equal(".[0]", literal.String("foo")))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".[1]", literal.String("foo")))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
})
t.Run("inequality", func(t *testing.T) {
t.Run("gt int", func(t *testing.T) {
nd := literal.Int(138)
pol := MustConstruct(GreaterThan(".", literal.Int(1)))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThan(".", literal.Int(138)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(GreaterThan(".", literal.Int(140)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("gte int", func(t *testing.T) {
nd := literal.Int(138)
pol := MustConstruct(GreaterThanOrEqual(".", literal.Int(1)))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThanOrEqual(".", literal.Int(138)))
ok, leaf = pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThanOrEqual(".", literal.Int(140)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("gt float", func(t *testing.T) {
nd := literal.Float(1.38)
pol := MustConstruct(GreaterThan(".", literal.Float(1)))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThan(".", literal.Float(2)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("gte float", func(t *testing.T) {
nd := literal.Float(1.38)
pol := MustConstruct(GreaterThanOrEqual(".", literal.Float(1)))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThanOrEqual(".", literal.Float(1.38)))
ok, leaf = pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThanOrEqual(".", literal.Float(2)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("lt int", func(t *testing.T) {
nd := literal.Int(138)
pol := MustConstruct(LessThan(".", literal.Int(1138)))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(LessThan(".", literal.Int(138)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(LessThan(".", literal.Int(100)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("lte int", func(t *testing.T) {
nd := literal.Int(138)
pol := MustConstruct(LessThanOrEqual(".", literal.Int(1138)))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(LessThanOrEqual(".", literal.Int(138)))
ok, leaf = pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(LessThanOrEqual(".", literal.Int(100)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
})
t.Run("negation", func(t *testing.T) {
nd := literal.Bool(false)
pol := MustConstruct(Not(Equal(".", literal.Bool(true))))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Not(Equal(".", literal.Bool(false))))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
t.Run("conjunction", func(t *testing.T) {
nd := literal.Int(138)
pol := MustConstruct(
And(
GreaterThan(".", literal.Int(1)),
LessThan(".", literal.Int(1138)),
),
)
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(
And(
GreaterThan(".", literal.Int(1)),
Equal(".", literal.Int(1138)),
),
)
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, MustConstruct(Equal(".", literal.Int(1138)))[0], leaf)
pol = MustConstruct(And())
ok, leaf = pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
})
t.Run("disjunction", func(t *testing.T) {
nd := literal.Int(138)
pol := MustConstruct(
Or(
GreaterThan(".", literal.Int(138)),
LessThan(".", literal.Int(1138)),
),
)
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(
Or(
GreaterThan(".", literal.Int(138)),
Equal(".", literal.Int(1138)),
),
)
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Or())
ok, leaf = pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
})
t.Run("wildcard", func(t *testing.T) {
pattern := `Alice\*, Bob*, Carol.`
for _, s := range []string{
"Alice*, Bob, Carol.",
"Alice*, Bob, Dan, Erin, Carol.",
"Alice*, Bob , Carol.",
"Alice*, Bob*, Carol.",
} {
func(s string) {
t.Run(fmt.Sprintf("pass %s", s), func(t *testing.T) {
nd := literal.String(s)
pol := MustConstruct(Like(".", pattern))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
})
}(s)
}
for _, s := range []string{
"Alice*, Bob, Carol",
"Alice*, Bob*, Carol!",
"Alice Cooper, Bob, Carol.",
"Alice, Bob, Carol.",
" Alice*, Bob, Carol. ",
} {
func(s string) {
t.Run(fmt.Sprintf("fail %s", s), func(t *testing.T) {
nd := literal.String(s)
pol := MustConstruct(Like(".", pattern))
ok, leaf := pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
}(s)
}
})
t.Run("quantification", func(t *testing.T) {
t.Run("all", func(t *testing.T) {
nd, _ := literal.List([]any{
map[string]int{"value": 5},
map[string]int{"value": 10},
map[string]int{"value": 20},
map[string]int{"value": 50},
map[string]int{"value": 100},
})
pol := MustConstruct(All(".[]", GreaterThan(".value", literal.Int(2))))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(All(".[]", GreaterThan(".value", literal.Int(20))))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, MustConstruct(GreaterThan(".value", literal.Int(20)))[0], leaf)
})
t.Run("any", func(t *testing.T) {
nd, _ := literal.List([]any{
map[string]int{"value": 5},
map[string]int{"value": 10},
map[string]int{"value": 20},
map[string]int{"value": 50},
map[string]int{"value": 100},
})
pol := MustConstruct(Any(".[]", GreaterThan(".value", literal.Int(60))))
ok, leaf := pol.Match(nd)
require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Any(".[]", GreaterThan(".value", literal.Int(100))))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
})
})
}
func TestPolicyExamples(t *testing.T) {
makeNode := func(data string) ipld.Node {
nd, err := ipld.Decode([]byte(data), dagjson.Decode)
require.NoError(t, err)
return nd
}
evaluate := func(statement string, data ipld.Node) bool {
// we need to wrap statement with [] to make them a policy
policy := fmt.Sprintf("[%s]", statement)
pol, err := FromDagJson(policy)
require.NoError(t, err)
res, _ := pol.Match(data)
return res
}
t.Run("And", func(t *testing.T) {
data := makeNode(`{ "name": "Katie", "age": 35, "nationalities": ["Canadian", "South African"] }`)
require.True(t, evaluate(`["and", []]`, data))
require.True(t, evaluate(`
["and", [
["==", ".name", "Katie"],
[">=", ".age", 21]
]]`, data))
require.False(t, evaluate(`
["and", [
["==", ".name", "Katie"],
[">=", ".age", 21],
["==", ".nationalities", ["American"]]
]]`, data))
})
t.Run("Or", func(t *testing.T) {
data := makeNode(`{ "name": "Katie", "age": 35, "nationalities": ["Canadian", "South African"] }`)
require.True(t, evaluate(`["or", []]`, data))
require.True(t, evaluate(`
["or", [
["==", ".name", "Katie"],
[">", ".age", 45]
]]
`, data))
})
t.Run("Not", func(t *testing.T) {
data := makeNode(`{ "name": "Katie", "nationalities": ["Canadian", "South African"] }`)
require.True(t, evaluate(`
["not",
["and", [
["==", ".name", "Katie"],
["==", ".nationalities", ["American"]]
]]
]
`, data))
})
t.Run("All", func(t *testing.T) {
data := makeNode(`{"a": [{"b": 1}, {"b": 2}, {"z": [7, 8, 9]}]}`)
require.False(t, evaluate(`["all", ".a", [">", ".b", 0]]`, data))
})
t.Run("Any", func(t *testing.T) {
data := makeNode(`{"a": [{"b": 1}, {"b": 2}, {"z": [7, 8, 9]}]}`)
require.True(t, evaluate(`["any", ".a", ["==", ".b", 2]]`, data))
})
}
func FuzzMatch(f *testing.F) {
// Policy + Data examples
f.Add([]byte(`[["==", ".status", "draft"]]`), []byte(`{"status": "draft"}`))
f.Add([]byte(`[["all", ".reviewer", ["like", ".email", "*@example.com"]]]`), []byte(`{"reviewer": [{"email": "alice@example.com"}, {"email": "bob@example.com"}]}`))
f.Add([]byte(`[["any", ".tags", ["or", [["==", ".", "news"], ["==", ".", "press"]]]]]`), []byte(`{"tags": ["news", "press"]}`))
f.Add([]byte(`[["==", ".name", "Alice"]]`), []byte(`{"name": "Alice"}`))
f.Add([]byte(`[[">", ".age", 30]]`), []byte(`{"age": 31}`))
f.Add([]byte(`[["<=", ".height", 180]]`), []byte(`{"height": 170}`))
f.Add([]byte(`[["not", ["==", ".status", "inactive"]]]`), []byte(`{"status": "active"}`))
f.Add([]byte(`[["and", [["==", ".role", "admin"], [">=", ".experience", 5]]]]`), []byte(`{"role": "admin", "experience": 6}`))
f.Add([]byte(`[["or", [["==", ".department", "HR"], ["==", ".department", "Finance"]]]]`), []byte(`{"department": "HR"}`))
f.Add([]byte(`[["like", ".email", "*@company.com"]]`), []byte(`{"email": "user@company.com"}`))
f.Add([]byte(`[["all", ".projects", [">", ".budget", 10000]]]`), []byte(`{"projects": [{"budget": 15000}, {"budget": 8000}]}`))
f.Add([]byte(`[["any", ".skills", ["==", ".", "Go"]]]`), []byte(`{"skills": ["Go", "Python", "JavaScript"]}`))
f.Add(
[]byte(`[["and", [
["==", ".name", "Bob"],
["or", [[">", ".age", 25],["==", ".status", "active"]]],
["all", ".tasks", ["==", ".completed", true]]
]]]`),
[]byte(`{
"name": "Bob",
"age": 26,
"status": "active",
"tasks": [{"completed": true}, {"completed": true}, {"completed": false}]
}`),
)
f.Fuzz(func(t *testing.T, policyBytes []byte, dataBytes []byte) {
policyNode, err := ipld.Decode(policyBytes, dagjson.Decode)
if err != nil {
t.Skip()
}
dataNode, err := ipld.Decode(dataBytes, dagjson.Decode)
if err != nil {
t.Skip()
}
// policy node -> policy object
policy, err := FromIPLD(policyNode)
if err != nil {
t.Skip()
}
_, _ = policy.Match(dataNode)
})
}
func TestOptionalSelectors(t *testing.T) {
tests := []struct {
name string
policy Policy
data map[string]any
expected bool
}{
{
name: "missing optional field returns true",
policy: MustConstruct(Equal(".field?", literal.String("value"))),
data: map[string]any{},
expected: true,
},
{
name: "present optional field with matching value returns true",
policy: MustConstruct(Equal(".field?", literal.String("value"))),
data: map[string]any{"field": "value"},
expected: true,
},
{
name: "present optional field with non-matching value returns false",
policy: MustConstruct(Equal(".field?", literal.String("value"))),
data: map[string]any{"field": "other"},
expected: false,
},
{
name: "missing non-optional field returns false",
policy: MustConstruct(Equal(".field", literal.String("value"))),
data: map[string]any{},
expected: false,
},
{
name: "nested missing non-optional field returns false",
policy: MustConstruct(Equal(".outer?.inner", literal.String("value"))),
data: map[string]any{"outer": map[string]any{}},
expected: false,
},
{
name: "completely missing nested optional path returns true",
policy: MustConstruct(Equal(".outer?.inner?", literal.String("value"))),
data: map[string]any{},
expected: true,
},
{
name: "partially present nested optional path with missing end returns true",
policy: MustConstruct(Equal(".outer?.inner?", literal.String("value"))),
data: map[string]any{"outer": map[string]any{}},
expected: true,
},
{
name: "optional array index returns true when array is empty",
policy: MustConstruct(Equal(".array[0]?", literal.String("value"))),
data: map[string]any{"array": []any{}},
expected: true,
},
{
name: "non-optional array index returns false when array is empty",
policy: MustConstruct(Equal(".array[0]", literal.String("value"))),
data: map[string]any{"array": []any{}},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
nb := basicnode.Prototype.Map.NewBuilder()
n, err := literal.Map(tt.data)
require.NoError(t, err)
err = nb.AssignNode(n)
require.NoError(t, err)
result, _ := tt.policy.Match(nb.Build())
require.Equal(t, tt.expected, result)
})
}
}
// The unique behaviour of PartialMatch is that it should return true for missing non-optional data (unlike Match).
func TestPartialMatch(t *testing.T) {
tests := []struct {
name string
policy Policy
data map[string]any
expectedMatch bool
expectedStmt Statement
}{
{
name: "returns true for missing non-optional field",
policy: MustConstruct(
Equal(".field", literal.String("value")),
),
data: map[string]any{},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns true when present data matches",
policy: MustConstruct(
Equal(".foo", literal.String("correct")),
Equal(".missing", literal.String("whatever")),
),
data: map[string]any{
"foo": "correct",
},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns false with failing statement for present but non-matching value",
policy: MustConstruct(
Equal(".foo", literal.String("value1")),
Equal(".bar", literal.String("value2")),
),
data: map[string]any{
"foo": "wrong",
"bar": "value2",
},
expectedMatch: false,
expectedStmt: MustConstruct(
Equal(".foo", literal.String("value1")),
)[0],
},
{
name: "continues past missing data until finding actual mismatch",
policy: MustConstruct(
Equal(".missing", literal.String("value")),
Equal(".present", literal.String("wrong")),
),
data: map[string]any{
"present": "actual",
},
expectedMatch: false,
expectedStmt: MustConstruct(
Equal(".present", literal.String("wrong")),
)[0],
},
// Optional fields
{
name: "returns false when optional field present but wrong",
policy: MustConstruct(
Equal(".field?", literal.String("value")),
),
data: map[string]any{
"field": "wrong",
},
expectedMatch: false,
expectedStmt: MustConstruct(
Equal(".field?", literal.String("value")),
)[0],
},
// Like pattern matching
{
name: "returns true for matching like pattern",
policy: MustConstruct(
Like(".pattern", "test*"),
),
data: map[string]any{
"pattern": "testing123",
},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns false for non-matching like pattern",
policy: MustConstruct(
Like(".pattern", "test*"),
),
data: map[string]any{
"pattern": "wrong123",
},
expectedMatch: false,
expectedStmt: MustConstruct(
Like(".pattern", "test*"),
)[0],
},
// Array quantifiers
{
name: "all matches when every element satisfies condition",
policy: MustConstruct(
All(".numbers", Equal(".", literal.Int(1))),
),
data: map[string]interface{}{
"numbers": []interface{}{1, 1, 1},
},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "all fails when any element doesn't satisfy",
policy: MustConstruct(
All(".numbers", Equal(".", literal.Int(1))),
),
data: map[string]interface{}{
"numbers": []interface{}{1, 2, 1},
},
expectedMatch: false,
expectedStmt: MustConstruct(
Equal(".", literal.Int(1)),
)[0],
},
{
name: "any succeeds when one element matches",
policy: MustConstruct(
Any(".numbers", Equal(".", literal.Int(2))),
),
data: map[string]interface{}{
"numbers": []interface{}{1, 2, 3},
},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "any fails when no elements match",
policy: MustConstruct(
Any(".numbers", Equal(".", literal.Int(4))),
),
data: map[string]interface{}{
"numbers": []interface{}{1, 2, 3},
},
expectedMatch: false,
expectedStmt: MustConstruct(
Any(".numbers", Equal(".", literal.Int(4))),
)[0],
},
// Complex nested case
{
name: "complex nested policy",
policy: MustConstruct(
And(
Equal(".required", literal.String("present")),
Equal(".optional?", literal.String("value")),
Any(".items",
And(
Equal(".name", literal.String("test")),
Like(".id", "ID*"),
),
),
),
),
data: map[string]any{
"required": "present",
"items": []any{
map[string]any{
"name": "wrong",
"id": "ID123",
},
map[string]any{
"name": "test",
"id": "ID456",
},
},
},
expectedMatch: true,
expectedStmt: nil,
},
// missing optional values for all the operators
{
name: "returns true for missing optional equal",
policy: MustConstruct(
Equal(".field?", literal.String("value")),
),
data: map[string]any{},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns true for missing optional like pattern",
policy: MustConstruct(
Like(".pattern?", "test*"),
),
data: map[string]any{},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns true for missing optional greater than",
policy: MustConstruct(
GreaterThan(".number?", literal.Int(5)),
),
data: map[string]any{},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns true for missing optional less than",
policy: MustConstruct(
LessThan(".number?", literal.Int(5)),
),
data: map[string]any{},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns true for missing optional array with all",
policy: MustConstruct(
All(".numbers?", Equal(".", literal.Int(1))),
),
data: map[string]any{},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns true for missing optional array with any",
policy: MustConstruct(
Any(".numbers?", Equal(".", literal.Int(1))),
),
data: map[string]any{},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns true for complex nested optional paths",
policy: MustConstruct(
And(
Equal(".required", literal.String("present")),
Any(".optional_array?",
And(
Equal(".name?", literal.String("test")),
Like(".id?", "ID*"),
),
),
),
),
data: map[string]any{
"required": "present",
},
expectedMatch: true,
expectedStmt: nil,
},
{
name: "returns true for partially present nested optional paths",
policy: MustConstruct(
And(
Equal(".required", literal.String("present")),
Any(".items",
And(
Equal(".name", literal.String("test")),
Like(".optional_id?", "ID*"),
),
),
),
),
data: map[string]any{
"required": "present",
"items": []any{
map[string]any{
"name": "test",
// optional_id is missing
},
},
},
expectedMatch: true,
expectedStmt: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
node, err := literal.Map(tt.data)
require.NoError(t, err)
match, stmt := tt.policy.PartialMatch(node)
require.Equal(t, tt.expectedMatch, match)
if tt.expectedStmt == nil {
require.Nil(t, stmt)
} else {
require.Equal(t, tt.expectedStmt, stmt)
}
})
}
}
// TestInvocationValidation applies the example policy to the second
// example arguments as defined in the [Validation] section of the
// invocation specification.
//
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
func TestInvocationValidationSpecExamples(t *testing.T) {
t.Parallel()
pol := MustConstruct(
Equal(".from", literal.String("alice@example.com")),
Any(".to", Like(".", "*@example.com")),
)
t.Run("with passing args", func(t *testing.T) {
t.Parallel()
argsNode, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
qp.MapEntry(ma, "from", qp.String("alice@example.com"))
qp.MapEntry(ma, "to", qp.List(2, func(la datamodel.ListAssembler) {
qp.ListEntry(la, qp.String("bob@example.com"))
qp.ListEntry(la, qp.String("carol@not.example.com"))
}))
qp.MapEntry(ma, "title", qp.String("Coffee"))
qp.MapEntry(ma, "body", qp.String("Still on for coffee"))
})
require.NoError(t, err)
exec, stmt := pol.Match(argsNode)
assert.True(t, exec)
assert.Nil(t, stmt)
})
t.Run("fails on recipients (second statement)", func(t *testing.T) {
t.Parallel()
argsNode, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
qp.MapEntry(ma, "from", qp.String("alice@example.com"))
qp.MapEntry(ma, "to", qp.List(2, func(la datamodel.ListAssembler) {
qp.ListEntry(la, qp.String("bob@null.com"))
qp.ListEntry(la, qp.String("carol@elsewhere.example.com"))
}))
qp.MapEntry(ma, "title", qp.String("Coffee"))
qp.MapEntry(ma, "body", qp.String("Still on for coffee"))
})
require.NoError(t, err)
exec, stmt := pol.Match(argsNode)
assert.False(t, exec)
assert.NotNil(t, stmt)
})
}

View File

@@ -1,246 +0,0 @@
package policy
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#policy
import (
"fmt"
"strings"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
selpkg "github.com/ucan-wg/go-ucan/pkg/policy/selector"
)
const (
KindEqual = "==" // implemented by equality
KindGreaterThan = ">" // implemented by equality
KindGreaterThanOrEqual = ">=" // implemented by equality
KindLessThan = "<" // implemented by equality
KindLessThanOrEqual = "<=" // implemented by equality
KindNot = "not" // implemented by negation
KindAnd = "and" // implemented by connective
KindOr = "or" // implemented by connective
KindLike = "like" // implemented by wildcard
KindAll = "all" // implemented by quantifier
KindAny = "any" // implemented by quantifier
)
type Policy []Statement
type Constructor func() (Statement, error)
func Construct(cstors ...Constructor) (Policy, error) {
stmts, err := assemble(cstors)
if err != nil {
return nil, err
}
return stmts, nil
}
func MustConstruct(cstors ...Constructor) Policy {
pol, err := Construct(cstors...)
if err != nil {
panic(err)
}
return pol
}
func (p Policy) String() string {
if len(p) == 0 {
return "[]"
}
childs := make([]string, len(p))
for i, statement := range p {
childs[i] = strings.ReplaceAll(statement.String(), "\n", "\n ")
}
return fmt.Sprintf("[\n %s\n]", strings.Join(childs, ",\n "))
}
type Statement interface {
Kind() string
String() string
}
type equality struct {
kind string
selector selpkg.Selector
value ipld.Node
}
func (e equality) Kind() string {
return e.kind
}
func (e equality) String() string {
child, err := ipld.Encode(e.value, dagjson.Encode)
if err != nil {
return "ERROR: INVALID VALUE"
}
return fmt.Sprintf(`["%s", "%s", %s]`, e.kind, e.selector, strings.ReplaceAll(string(child), "\n", "\n "))
}
func Equal(selector string, value ipld.Node) Constructor {
return func() (Statement, error) {
sel, err := selpkg.Parse(selector)
return equality{kind: KindEqual, selector: sel, value: value}, err
}
}
func GreaterThan(selector string, value ipld.Node) Constructor {
return func() (Statement, error) {
sel, err := selpkg.Parse(selector)
return equality{kind: KindGreaterThan, selector: sel, value: value}, err
}
}
func GreaterThanOrEqual(selector string, value ipld.Node) Constructor {
return func() (Statement, error) {
sel, err := selpkg.Parse(selector)
return equality{kind: KindGreaterThanOrEqual, selector: sel, value: value}, err
}
}
func LessThan(selector string, value ipld.Node) Constructor {
return func() (Statement, error) {
sel, err := selpkg.Parse(selector)
return equality{kind: KindLessThan, selector: sel, value: value}, err
}
}
func LessThanOrEqual(selector string, value ipld.Node) Constructor {
return func() (Statement, error) {
sel, err := selpkg.Parse(selector)
return equality{kind: KindLessThanOrEqual, selector: sel, value: value}, err
}
}
type negation struct {
statement Statement
}
func (n negation) Kind() string {
return KindNot
}
func (n negation) String() string {
child := n.statement.String()
return fmt.Sprintf(`["%s", "%s"]`, n.Kind(), strings.ReplaceAll(child, "\n", "\n "))
}
func Not(cstor Constructor) Constructor {
return func() (Statement, error) {
stmt, err := cstor()
return negation{statement: stmt}, err
}
}
type connective struct {
kind string
statements []Statement
}
func (c connective) Kind() string {
return c.kind
}
func (c connective) String() string {
childs := make([]string, len(c.statements))
for i, statement := range c.statements {
childs[i] = strings.ReplaceAll(statement.String(), "\n", "\n ")
}
return fmt.Sprintf("[\"%s\", [\n %s]]\n", c.kind, strings.Join(childs, ",\n "))
}
func And(cstors ...Constructor) Constructor {
return func() (Statement, error) {
stmts, err := assemble(cstors)
if err != nil {
return nil, err
}
return connective{kind: KindAnd, statements: stmts}, nil
}
}
func Or(cstors ...Constructor) Constructor {
return func() (Statement, error) {
stmts, err := assemble(cstors)
if err != nil {
return nil, err
}
return connective{kind: KindOr, statements: stmts}, nil
}
}
type wildcard struct {
selector selpkg.Selector
pattern glob
}
func (n wildcard) Kind() string {
return KindLike
}
func (n wildcard) String() string {
return fmt.Sprintf(`["%s", "%s", "%s"]`, n.Kind(), n.selector, n.pattern)
}
func Like(selector string, pattern string) Constructor {
return func() (Statement, error) {
g, err := parseGlob(pattern)
if err != nil {
return nil, err
}
sel, err := selpkg.Parse(selector)
return wildcard{selector: sel, pattern: g}, err
}
}
type quantifier struct {
kind string
selector selpkg.Selector
statement Statement
}
func (n quantifier) Kind() string {
return n.kind
}
func (n quantifier) String() string {
child := n.statement.String()
return fmt.Sprintf("[\"%s\", \"%s\",\n %s]", n.Kind(), n.selector, strings.ReplaceAll(child, "\n", "\n "))
}
func All(selector string, cstor Constructor) Constructor {
return func() (Statement, error) {
stmt, err := cstor()
if err != nil {
return nil, err
}
sel, err := selpkg.Parse(selector)
return quantifier{kind: KindAll, selector: sel, statement: stmt}, err
}
}
func Any(selector string, cstor Constructor) Constructor {
return func() (Statement, error) {
stmt, err := cstor()
if err != nil {
return nil, err
}
sel, err := selpkg.Parse(selector)
return quantifier{kind: KindAny, selector: sel, statement: stmt}, err
}
}
func assemble(cstors []Constructor) ([]Statement, error) {
stmts := make([]Statement, 0, len(cstors))
for _, cstor := range cstors {
stmt, err := cstor()
if err != nil {
return nil, err
}
stmts = append(stmts, stmt)
}
return stmts, nil
}

View File

@@ -1,91 +0,0 @@
package policy_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/policy"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)
func ExamplePolicy() {
pol := policy.MustConstruct(
policy.Equal(".status", literal.String("draft")),
policy.All(".reviewer",
policy.Like(".email", "*@example.com"),
),
policy.Any(".tags", policy.Or(
policy.Equal(".", literal.String("news")),
policy.Equal(".", literal.String("press")),
)),
)
fmt.Println(pol)
// Output:
// [
// ["==", ".status", "draft"],
// ["all", ".reviewer",
// ["like", ".email", "*@example.com"]],
// ["any", ".tags",
// ["or", [
// ["==", ".", "news"],
// ["==", ".", "press"]]]
// ]
// ]
}
func ExamplePolicy_accumulate() {
var statements []policy.Constructor
statements = append(statements, policy.Equal(".status", literal.String("draft")))
statements = append(statements, policy.All(".reviewer",
policy.Like(".email", "*@example.com"),
))
statements = append(statements, policy.Any(".tags", policy.Or(
policy.Equal(".", literal.String("news")),
policy.Equal(".", literal.String("press")),
)))
pol := policy.MustConstruct(statements...)
fmt.Println(pol)
// Output:
// [
// ["==", ".status", "draft"],
// ["all", ".reviewer",
// ["like", ".email", "*@example.com"]],
// ["any", ".tags",
// ["or", [
// ["==", ".", "news"],
// ["==", ".", "press"]]]
// ]
// ]
}
func TestConstruct(t *testing.T) {
pol, err := policy.Construct(
policy.Equal(".status", literal.String("draft")),
policy.All(".reviewer",
policy.Like(".email", "*@example.com"),
),
)
require.NoError(t, err)
require.NotNil(t, pol)
// check if errors cascade correctly
pol, err = policy.Construct(
policy.Equal(".status", literal.String("draft")),
policy.All(".reviewer", policy.Or(
policy.Like(".email", "*@example.com"),
policy.Like(".", "\\"), // invalid pattern
)),
)
require.Error(t, err)
require.Nil(t, pol)
}

View File

@@ -1,552 +0,0 @@
package selector
import (
"math"
"testing"
"github.com/stretchr/testify/require"
)
func TestParse(t *testing.T) {
t.Run("identity", func(t *testing.T) {
sel, err := Parse(".")
require.NoError(t, err)
require.Equal(t, 1, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
})
t.Run("dotted field name", func(t *testing.T) {
sel, err := Parse(".foo")
require.NoError(t, err)
require.Equal(t, 1, len(sel))
require.False(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Equal(t, sel[0].Field(), "foo")
require.Empty(t, sel[0].Index())
})
t.Run("explicit field", func(t *testing.T) {
sel, err := Parse(`.["foo"]`)
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Equal(t, sel[1].Field(), "foo")
require.Empty(t, sel[1].Index())
})
t.Run("iterator, collection value", func(t *testing.T) {
sel, err := Parse(".[]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.True(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
})
t.Run("index", func(t *testing.T) {
sel, err := Parse(".[138]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Equal(t, sel[1].Index(), 138)
})
t.Run("negative index", func(t *testing.T) {
sel, err := Parse(".[-138]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Equal(t, sel[1].Index(), -138)
})
t.Run("List slice", func(t *testing.T) {
sel, err := Parse(".[7:11]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{7, 11})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
sel, err = Parse(".[2:]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{2, math.MaxInt})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
sel, err = Parse(".[:42]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{math.MinInt, 42})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
sel, err = Parse(".[0:-2]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{0, -2})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
})
t.Run("optional identity", func(t *testing.T) {
sel, err := Parse(".?")
require.NoError(t, err)
require.Equal(t, 1, len(sel))
require.True(t, sel[0].Identity())
require.True(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
})
t.Run("optional dotted field name", func(t *testing.T) {
sel, err := Parse(".foo?")
require.NoError(t, err)
require.Equal(t, 1, len(sel))
require.False(t, sel[0].Identity())
require.True(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Equal(t, sel[0].Field(), "foo")
require.Empty(t, sel[0].Index())
})
t.Run("optional explicit field", func(t *testing.T) {
sel, err := Parse(`.["foo"]?`)
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Equal(t, sel[1].Field(), "foo")
require.Empty(t, sel[1].Index())
})
t.Run("optional iterator", func(t *testing.T) {
sel, err := Parse(".[]?")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.True(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
})
t.Run("optional index", func(t *testing.T) {
sel, err := Parse(".[138]?")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Equal(t, sel[1].Index(), 138)
})
t.Run("optional negative index", func(t *testing.T) {
sel, err := Parse(".[-138]?")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Equal(t, sel[1].Index(), -138)
})
t.Run("optional list slice", func(t *testing.T) {
sel, err := Parse(".[7:11]?")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{7, 11})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
sel, err = Parse(".[2:]?")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{2, math.MaxInt})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
sel, err = Parse(".[:42]?")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{math.MinInt, 42})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
sel, err = Parse(".[0:-2]?")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.True(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{0, -2})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
})
t.Run("idempotent optional", func(t *testing.T) {
sel, err := Parse(".foo???")
require.NoError(t, err)
require.Equal(t, 1, len(sel))
require.False(t, sel[0].Identity())
require.True(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Equal(t, sel[0].Field(), "foo")
require.Empty(t, sel[0].Index())
})
t.Run("deny multi dot", func(t *testing.T) {
_, err := Parse("..")
require.Error(t, err)
})
t.Run("nesting", func(t *testing.T) {
str := `.foo.["bar"].[138]?.baz[1:]`
sel, err := Parse(str)
require.NoError(t, err)
require.Equal(t, str, sel.String())
require.Equal(t, 7, len(sel))
require.False(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Equal(t, sel[0].Field(), "foo")
require.Empty(t, sel[0].Index())
require.True(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
require.False(t, sel[2].Identity())
require.False(t, sel[2].Optional())
require.False(t, sel[2].Iterator())
require.Empty(t, sel[2].Slice())
require.Equal(t, sel[2].Field(), "bar")
require.Empty(t, sel[2].Index())
require.True(t, sel[3].Identity())
require.False(t, sel[3].Optional())
require.False(t, sel[3].Iterator())
require.Empty(t, sel[3].Slice())
require.Empty(t, sel[3].Field())
require.Empty(t, sel[3].Index())
require.False(t, sel[4].Identity())
require.True(t, sel[4].Optional())
require.False(t, sel[4].Iterator())
require.Empty(t, sel[4].Slice())
require.Empty(t, sel[4].Field())
require.Equal(t, sel[4].Index(), 138)
require.False(t, sel[5].Identity())
require.False(t, sel[5].Optional())
require.False(t, sel[5].Iterator())
require.Empty(t, sel[5].Slice())
require.Equal(t, sel[5].Field(), "baz")
require.Empty(t, sel[5].Index())
require.False(t, sel[6].Identity())
require.False(t, sel[6].Optional())
require.False(t, sel[6].Iterator())
require.Equal(t, sel[6].Slice(), []int64{1, math.MaxInt})
require.Empty(t, sel[6].Field())
require.Empty(t, sel[6].Index())
})
t.Run("non dotted", func(t *testing.T) {
_, err := Parse("foo")
require.NotNil(t, err)
})
t.Run("non quoted", func(t *testing.T) {
_, err := Parse(".[foo]")
require.NotNil(t, err)
})
t.Run("slice with negative start and positive end", func(t *testing.T) {
sel, err := Parse(".[0:-2]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{0, -2})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
})
t.Run("slice with start greater than end", func(t *testing.T) {
sel, err := Parse(".[5:2]")
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{5, 2})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
})
t.Run("slice on string", func(t *testing.T) {
sel, err := Parse(`.["foo"].[1:3]`)
require.NoError(t, err)
require.Equal(t, 4, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Equal(t, sel[1].Field(), "foo")
require.Empty(t, sel[1].Index())
require.True(t, sel[2].Identity())
require.False(t, sel[2].Optional())
require.False(t, sel[2].Iterator())
require.Empty(t, sel[2].Slice())
require.Empty(t, sel[2].Field())
require.Empty(t, sel[2].Index())
require.False(t, sel[3].Identity())
require.False(t, sel[3].Optional())
require.False(t, sel[3].Iterator())
require.Equal(t, sel[3].Slice(), []int64{1, 3})
require.Empty(t, sel[3].Field())
require.Empty(t, sel[3].Index())
})
t.Run("slice on array", func(t *testing.T) {
sel, err := Parse(`.[1:3]`)
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Equal(t, sel[1].Slice(), []int64{1, 3})
require.Empty(t, sel[1].Field())
require.Empty(t, sel[1].Index())
})
t.Run("index on array", func(t *testing.T) {
sel, err := Parse(`.[1]`)
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Empty(t, sel[1].Field())
require.Equal(t, sel[1].Index(), 1)
})
t.Run("invalid slice on object", func(t *testing.T) {
_, err := Parse(`.["foo":"bar"]`)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid segment")
})
t.Run("index on object", func(t *testing.T) {
sel, err := Parse(`.["foo"]`)
require.NoError(t, err)
require.Equal(t, 2, len(sel))
require.True(t, sel[0].Identity())
require.False(t, sel[0].Optional())
require.False(t, sel[0].Iterator())
require.Empty(t, sel[0].Slice())
require.Empty(t, sel[0].Field())
require.Empty(t, sel[0].Index())
require.False(t, sel[1].Identity())
require.False(t, sel[1].Optional())
require.False(t, sel[1].Iterator())
require.Empty(t, sel[1].Slice())
require.Equal(t, sel[1].Field(), "foo")
require.Empty(t, sel[1].Index())
})
t.Run("slice with non-integer start", func(t *testing.T) {
_, err := Parse(".[foo:3]")
require.Error(t, err)
})
t.Run("slice with non-integer end", func(t *testing.T) {
_, err := Parse(".[1:bar]")
require.Error(t, err)
})
t.Run("index with non-integer", func(t *testing.T) {
_, err := Parse(".[foo]")
require.Error(t, err)
})
}

View File

@@ -1,326 +0,0 @@
package selector
import (
"fmt"
"math"
"strconv"
"strings"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/fluent/qp"
"github.com/ipld/go-ipld-prime/node/basicnode"
)
// Selector describes a UCAN policy selector, as specified here:
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#selectors
type Selector []segment
// Select perform the selection described by the selector on the input IPLD DAG.
// Select can return:
// - exactly one matched IPLD node
// - a resolutionerr error if not being able to resolve to a node
// - nil and no errors, if the selector couldn't match on an optional segment (with ?).
func (s Selector) Select(subject ipld.Node) (ipld.Node, error) {
return resolve(s, subject, nil)
}
func (s Selector) String() string {
var res strings.Builder
for _, seg := range s {
res.WriteString(seg.String())
}
return res.String()
}
type segment struct {
str string
identity bool
optional bool
iterator bool
slice []int64 // either 0-length or 2-length
field string
index int
}
// String returns the segment's string representation.
func (s segment) String() string {
return s.str
}
// Identity flags that this selector is the identity selector.
func (s segment) Identity() bool {
return s.identity
}
// Optional flags that this selector is optional.
func (s segment) Optional() bool {
return s.optional
}
// Iterator flags that this selector is an iterator segment.
func (s segment) Iterator() bool {
return s.iterator
}
// Slice flags that this segment targets a range of a slice.
func (s segment) Slice() []int64 {
return s.slice
}
// Field is the name of a field in a struct/map.
func (s segment) Field() string {
return s.field
}
// Index is an index of a slice.
func (s segment) Index() int {
return s.index
}
func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, error) {
errIfNotOptional := func(s segment, err error) error {
if !s.Optional() {
return err
}
return nil
}
cur := subject
for _, seg := range sel {
// 1st level: handle the different segment types (iterator, field, slice, index)
// 2nd level: handle different node kinds (list, map, string, bytes)
switch {
case seg.Identity():
continue
case seg.Iterator():
switch {
case cur == nil || cur.Kind() == datamodel.Kind_Null:
if seg.Optional() {
// build an empty list
n, _ := qp.BuildList(basicnode.Prototype.Any, 0, func(_ datamodel.ListAssembler) {})
return n, nil
}
return nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
case cur.Kind() == datamodel.Kind_List:
// iterators are no-op on list
continue
case cur.Kind() == datamodel.Kind_Map:
// iterators on maps collect the values
nd, err := qp.BuildList(basicnode.Prototype.Any, cur.Length(), func(l datamodel.ListAssembler) {
it := cur.MapIterator()
for !it.Done() {
_, v, err := it.Next()
if err != nil {
// recovered by BuildList
// Error is bubbled up, but should never occur as we already checked the type,
// and are using the iterator correctly.
// This is verified with fuzzing.
panic(err)
}
qp.ListEntry(l, qp.Node(v))
}
})
if err != nil {
panic("should never happen")
}
return nd, nil
default:
return nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
}
case seg.Field() != "":
at = append(at, seg.Field())
switch {
case cur == nil:
err := newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
return nil, errIfNotOptional(seg, err)
case cur.Kind() == datamodel.Kind_Map:
n, err := cur.LookupByString(seg.Field())
if err != nil {
// the only possible error is missing field as we already check the type
if seg.Optional() {
cur = nil
} else {
return nil, newResolutionError(fmt.Sprintf("object has no field named: %s", seg.Field()), at)
}
} else {
cur = n
}
default:
err := newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
return nil, errIfNotOptional(seg, err)
}
case len(seg.Slice()) > 0:
if cur == nil {
err := newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
return nil, errIfNotOptional(seg, err)
}
slice := seg.Slice()
switch cur.Kind() {
case datamodel.Kind_List:
start, end := resolveSliceIndices(slice, cur.Length())
sliced, err := qp.BuildList(basicnode.Prototype.Any, end-start, func(l datamodel.ListAssembler) {
for i := start; i < end; i++ {
item, err := cur.LookupByIndex(i)
if err != nil {
// recovered by BuildList
// Error is bubbled up, but should never occur as we already checked the type and boundaries
// This is verified with fuzzing.
panic(err)
}
qp.ListEntry(l, qp.Node(item))
}
})
if err != nil {
panic("should never happen")
}
cur = sliced
case datamodel.Kind_Bytes:
b, _ := cur.AsBytes()
start, end := resolveSliceIndices(slice, int64(len(b)))
cur = basicnode.NewBytes(b[start:end])
case datamodel.Kind_String:
str, _ := cur.AsString()
runes := []rune(str)
start, end := resolveSliceIndices(slice, int64(len(runes)))
cur = basicnode.NewString(string(runes[start:end]))
default:
return nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
}
default: // Index()
at = append(at, strconv.Itoa(seg.Index()))
if cur == nil {
err := newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
return nil, errIfNotOptional(seg, err)
}
idx := seg.Index()
switch cur.Kind() {
case datamodel.Kind_List:
if idx < 0 {
idx = int(cur.Length()) + idx
}
if idx < 0 || idx >= int(cur.Length()) {
err := newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
return nil, errIfNotOptional(seg, err)
}
cur, _ = cur.LookupByIndex(int64(idx))
case datamodel.Kind_Bytes:
b, _ := cur.AsBytes()
if idx < 0 {
idx = len(b) + idx
}
if idx < 0 || idx >= len(b) {
err := newResolutionError(fmt.Sprintf("index %d out of bounds for bytes of length %d", seg.Index(), len(b)), at)
return nil, errIfNotOptional(seg, err)
}
cur = basicnode.NewInt(int64(b[idx]))
default:
return nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
}
}
}
// segment exhausted, we return where we are
return cur, nil
}
// resolveSliceIndices resolves the start and end indices for slicing a list or byte array.
//
// It takes the slice indices from the selector segment and the length of the list or byte array,
// and returns the resolved start and end indices. Negative indices are supported.
//
// Parameters:
// - slice: The slice indices from the selector segment.
// - length: The length of the list or byte array being sliced.
//
// Returns:
// - start: The resolved start index for slicing.
// - end: The resolved **excluded** end index for slicing.
func resolveSliceIndices(slice []int64, length int64) (start int64, end int64) {
if len(slice) != 2 {
panic("should always be 2-length")
}
start, end = slice[0], slice[1]
// adjust boundaries
switch {
case slice[0] == math.MinInt:
start = 0
case slice[0] < 0:
start = length + slice[0]
}
switch {
case slice[1] == math.MaxInt:
end = length
case slice[1] < 0:
end = length + slice[1]
}
// backward iteration is not allowed, shortcut to an empty result
if start >= end {
start, end = 0, 0
}
// clamp out of bound
if start < 0 {
start = 0
}
if start > length {
start = length
}
if end > length {
end = length
}
return start, end
}
func kindString(n datamodel.Node) string {
if n == nil {
return "null"
}
return n.Kind().String()
}
type resolutionerr struct {
msg string
at []string
}
func (r resolutionerr) Name() string {
return "ResolutionError"
}
func (r resolutionerr) Message() string {
return fmt.Sprintf("can not resolve path: .%s", strings.Join(r.at, "."))
}
func (r resolutionerr) At() []string {
return r.at
}
func (r resolutionerr) Error() string {
return r.Message()
}
func newResolutionError(message string, at []string) error {
return resolutionerr{message, at}
}

View File

@@ -1,358 +0,0 @@
package selector
import (
"errors"
"strings"
"testing"
"github.com/ipld/go-ipld-prime"
"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/must"
basicnode "github.com/ipld/go-ipld-prime/node/basic"
"github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/stretchr/testify/require"
)
func TestSelect(t *testing.T) {
type name struct {
First string
Middle *string
Last string
}
type interest struct {
Name string
Outdoor bool
Experience int
}
type user struct {
Name name
Age int
Nationalities []string
Interests []interest
}
ts, err := ipld.LoadSchemaBytes([]byte(`
type User struct {
name Name
age Int
nationalities [String]
interests [Interest]
}
type Name struct {
first String
middle optional String
last String
}
type Interest struct {
name String
outdoor Bool
experience Int
}
`))
require.NoError(t, err)
typ := ts.TypeByName("User")
am := "Joan"
alice := user{
Name: name{First: "Alice", Middle: &am, Last: "Wonderland"},
Age: 24,
Nationalities: []string{"British"},
Interests: []interest{
{Name: "Cycling", Outdoor: true, Experience: 4},
{Name: "Chess", Outdoor: false, Experience: 2},
},
}
bob := user{
Name: name{First: "Bob", Last: "Builder"},
Age: 35,
Nationalities: []string{"Canadian", "South African"},
Interests: []interest{
{Name: "Snowboarding", Outdoor: true, Experience: 8},
{Name: "Reading", Outdoor: false, Experience: 25},
},
}
anode := bindnode.Wrap(&alice, typ)
bnode := bindnode.Wrap(&bob, typ)
t.Run("identity", func(t *testing.T) {
sel, err := Parse(".")
require.NoError(t, err)
res, err := sel.Select(anode)
require.NoError(t, err)
require.NotEmpty(t, res)
age := must.Int(must.Node(res.LookupByString("age")))
require.Equal(t, int64(alice.Age), age)
})
t.Run("nested property", func(t *testing.T) {
sel, err := Parse(".name.first")
require.NoError(t, err)
res, err := sel.Select(anode)
require.NoError(t, err)
require.NotEmpty(t, res)
name := must.String(res)
require.Equal(t, alice.Name.First, name)
res, err = sel.Select(bnode)
require.NoError(t, err)
require.NotEmpty(t, res)
name = must.String(res)
require.Equal(t, bob.Name.First, name)
})
t.Run("optional nested property", func(t *testing.T) {
sel, err := Parse(".name.middle?")
require.NoError(t, err)
res, err := sel.Select(anode)
require.NoError(t, err)
require.NotEmpty(t, res)
name := must.String(res)
require.Equal(t, *alice.Name.Middle, name)
res, err = sel.Select(bnode)
require.NoError(t, err)
require.Empty(t, res)
})
t.Run("not exists", func(t *testing.T) {
sel, err := Parse(".name.foo")
require.NoError(t, err)
res, err := sel.Select(anode)
require.Error(t, err)
require.Empty(t, res)
require.ErrorAs(t, err, &resolutionerr{}, "error should be a resolution error")
})
t.Run("optional not exists", func(t *testing.T) {
sel, err := Parse(".name.foo?")
require.NoError(t, err)
one, err := sel.Select(anode)
require.NoError(t, err)
require.Empty(t, one)
})
t.Run("iterator", func(t *testing.T) {
sel, err := Parse(".interests[]")
require.NoError(t, err)
res, err := sel.Select(anode)
require.NoError(t, err)
require.NotEmpty(t, res)
iname := must.String(must.Node(must.Node(res.LookupByIndex(0)).LookupByString("name")))
require.Equal(t, alice.Interests[0].Name, iname)
iname = must.String(must.Node(must.Node(res.LookupByIndex(1)).LookupByString("name")))
require.Equal(t, alice.Interests[1].Name, iname)
})
t.Run("slice on string", func(t *testing.T) {
sel, err := Parse(`.[1:3]`)
require.NoError(t, err)
node := basicnode.NewString("hello")
res, err := sel.Select(node)
require.NoError(t, err)
require.NotEmpty(t, res)
str, err := res.AsString()
require.NoError(t, err)
require.Equal(t, "el", str) // assert sliced substring
})
t.Run("out of bounds slicing", func(t *testing.T) {
node, err := qp.BuildList(basicnode.Prototype.Any, 3, func(la datamodel.ListAssembler) {
qp.ListEntry(la, qp.Int(1))
qp.ListEntry(la, qp.Int(2))
qp.ListEntry(la, qp.Int(3))
})
require.NoError(t, err)
sel, err := Parse(`.[10:20]`)
require.NoError(t, err)
res, err := sel.Select(node)
require.NoError(t, err)
require.NotEmpty(t, res)
require.Equal(t, int64(0), res.Length())
_, err = res.LookupByIndex(0)
require.ErrorIs(t, err, datamodel.ErrNotExists{}) // assert empty result for out of bounds slice
})
t.Run("backward slicing", func(t *testing.T) {
node, err := qp.BuildList(basicnode.Prototype.Any, 3, func(la datamodel.ListAssembler) {
qp.ListEntry(la, qp.Int(1))
qp.ListEntry(la, qp.Int(2))
qp.ListEntry(la, qp.Int(3))
})
require.NoError(t, err)
sel, err := Parse(`.[5:2]`)
require.NoError(t, err)
res, err := sel.Select(node)
require.NoError(t, err)
require.NotEmpty(t, res)
require.Equal(t, int64(0), res.Length())
_, err = res.LookupByIndex(0)
require.ErrorIs(t, err, datamodel.ErrNotExists{}) // assert empty result for backward slice
})
t.Run("slice with negative index", func(t *testing.T) {
node, err := qp.BuildList(basicnode.Prototype.Any, 3, func(la datamodel.ListAssembler) {
qp.ListEntry(la, qp.Int(1))
qp.ListEntry(la, qp.Int(2))
qp.ListEntry(la, qp.Int(3))
})
require.NoError(t, err)
sel, err := Parse(`.[0:-1]`)
require.NoError(t, err)
res, err := sel.Select(node)
require.NoError(t, err)
require.NotEmpty(t, res)
val, err := res.LookupByIndex(1)
require.NoError(t, err)
require.Equal(t, 2, int(must.Int(val))) // Assert sliced value at index 1
})
t.Run("slice on bytes", func(t *testing.T) {
sel, err := Parse(`.[1:3]`)
require.NoError(t, err)
node := basicnode.NewBytes([]byte{0x01, 0x02, 0x03, 0x04, 0x05})
res, err := sel.Select(node)
require.NoError(t, err)
require.NotEmpty(t, res)
bytes, err := res.AsBytes()
require.NoError(t, err)
require.Equal(t, []byte{0x02, 0x03}, bytes) // assert sliced bytes
})
t.Run("index on bytes", func(t *testing.T) {
sel, err := Parse(`.[2]`)
require.NoError(t, err)
node := basicnode.NewBytes([]byte{0x01, 0x02, 0x03, 0x04, 0x05})
res, err := sel.Select(node)
require.NoError(t, err)
require.NotEmpty(t, res)
val, err := res.AsInt()
require.NoError(t, err)
require.Equal(t, int64(0x03), val) // assert indexed byte value
})
t.Run("out of bounds slicing on bytes", func(t *testing.T) {
sel, err := Parse(`.[10:20]`)
require.NoError(t, err)
node := basicnode.NewBytes([]byte{0x01, 0x02, 0x03})
res, err := sel.Select(node)
require.NoError(t, err)
require.NotNil(t, res)
bytes, err := res.AsBytes()
require.NoError(t, err)
require.Empty(t, bytes) // assert empty result for out of bounds slice
})
t.Run("out of bounds indexing on bytes", func(t *testing.T) {
sel, err := Parse(`.[10]`)
require.NoError(t, err)
node := basicnode.NewBytes([]byte{0x01, 0x02, 0x03})
_, err = sel.Select(node)
require.Error(t, err)
require.Contains(t, err.Error(), "can not resolve path: .10") // assert error for out of bounds index
})
}
func FuzzParse(f *testing.F) {
selectorCorpus := []string{
`.`, `.[]`, `.[]?`, `.[][]?`, `.x`, `.["x"]`, `.[0]`, `.[-1]`, `.[0]`,
`.[0]`, `.[0:2]`, `.[1:]`, `.[:2]`, `.[0:2]`, `.[1:]`, `.x?`, `.x?`,
`.x?`, `.["x"]?`, `.length?`, `.[4]?`, `.[]`, `.[][]`, `.x`, `.x`, `.x`,
`.length`, `.[4]`,
}
for _, selector := range selectorCorpus {
f.Add(selector)
}
f.Fuzz(func(t *testing.T, selector string) {
// only look for panic()
_, _ = Parse(selector)
})
}
func FuzzParseAndSelect(f *testing.F) {
selectorCorpus := []string{
`.`, `.[]`, `.[]?`, `.[][]?`, `.x`, `.["x"]`, `.[0]`, `.[-1]`, `.[0]`,
`.[0]`, `.[0:2]`, `.[1:]`, `.[:2]`, `.[0:2]`, `.[1:]`, `.x?`, `.x?`,
`.x?`, `.["x"]?`, `.length?`, `.[4]?`, `.[]`, `.[][]`, `.x`, `.x`, `.x`,
`.length`, `.[4]`,
}
subjectCorpus := []string{
`{"x":1}`, `[1, 2]`, `null`, `[[1], 2, [3]]`, `{"x": 1 }`, `{"x": 1}`,
`[1, 2]`, `[1, 2]`, `"Hi"`, `{"/":{"bytes":"AAE"}`, `[0, 1, 2]`,
`[0, 1, 2]`, `[0, 1, 2]`, `"hello"`, `{"/":{"bytes":"AAEC"}}`, `{}`,
`null`, `[]`, `{}`, `[1, 2]`, `[0, 1]`, `null`, `[[1], 2, [3]]`, `{}`,
`null`, `[]`, `[1, 2]`, `[0, 1]`,
}
for i := 0; ; i++ {
switch {
case i < len(selectorCorpus) && i < len(subjectCorpus):
f.Add(selectorCorpus[i], subjectCorpus[i])
continue
case i > len(selectorCorpus):
f.Add("", subjectCorpus[i])
continue
case i > len(subjectCorpus):
f.Add(selectorCorpus[i], "")
continue
}
break
}
f.Fuzz(func(t *testing.T, selector, subject string) {
sel, err := Parse(selector)
if err != nil {
t.Skip()
}
np := basicnode.Prototype.Any
nb := np.NewBuilder()
err = dagjson.Decode(nb, strings.NewReader(subject))
if err != nil {
t.Skip()
}
node := nb.Build()
if node == nil {
t.Skip()
}
// look for panic()
_, err = sel.Select(node)
if err != nil && !errors.As(err, &resolutionerr{}) {
// not normal, we should only have resolution errors
t.Fatal(err)
}
})
}

View File

@@ -1,226 +0,0 @@
// Package delegation implements the UCAN [delegation] specification with
// an immutable Token type as well as methods to convert the Token to and
// from the [envelope]-enclosed, signed and DAG-CBOR-encoded form that
// should most commonly be used for transport and storage.
//
// [delegation]: https://github.com/ucan-wg/delegation/tree/v1_ipld
// [envelope]: https://github.com/ucan-wg/spec#envelope
package delegation
// TODO: change the "delegation" link above when the specification is merged
import (
"errors"
"fmt"
"time"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/meta"
"github.com/ucan-wg/go-ucan/pkg/policy"
"github.com/ucan-wg/go-ucan/token/internal/nonce"
"github.com/ucan-wg/go-ucan/token/internal/parse"
)
// Token is an immutable type that holds the fields of a UCAN delegation.
type Token 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 *meta.Meta
// "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
}
// New creates a validated Token from the provided parameters and options.
//
// When creating a delegated token, the Issuer's (iss) DID is assembled
// using the public key associated with the private key sent as the first
// parameter.
func New(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
tkn := &Token{
issuer: iss,
audience: aud,
subject: did.Undef,
command: cmd,
policy: pol,
meta: meta.NewMeta(),
nonce: nil,
}
for _, opt := range opts {
if err := opt(tkn); err != nil {
return nil, err
}
}
var err error
if len(tkn.nonce) == 0 {
tkn.nonce, err = nonce.Generate()
if err != nil {
return nil, err
}
}
if err := tkn.validate(); err != nil {
return nil, err
}
return tkn, nil
}
// Root creates a validated UCAN delegation Token from the provided
// parameters and options.
//
// When creating a root token, both the Issuer's (iss) and Subject's
// (sub) DIDs are assembled from the public key associated with the
// private key passed as the first argument.
func Root(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
opts = append(opts, WithSubject(iss))
return New(iss, aud, cmd, pol, opts...)
}
// Issuer returns the did.DID representing the Token's issuer.
func (t *Token) Issuer() did.DID {
return t.issuer
}
// Audience returns the did.DID representing the Token's audience.
func (t *Token) Audience() did.DID {
return t.audience
}
// Subject returns the did.DID representing the Token's subject.
//
// This field may be did.Undef for delegations that are [Powerlined] but
// must be equal to the value returned by the Issuer method for root
// tokens.
func (t *Token) Subject() did.DID {
return t.subject
}
// Command returns the capability's command.Command.
func (t *Token) Command() command.Command {
return t.command
}
// Policy returns the capability's policy.Policy.
func (t *Token) Policy() policy.Policy {
return t.policy
}
// Nonce returns the random Nonce encapsulated in this Token.
func (t *Token) Nonce() []byte {
return t.nonce
}
// Meta returns the Token's metadata.
func (t *Token) Meta() meta.ReadOnly {
return t.meta.ReadOnly()
}
// NotBefore returns the time at which the Token becomes "active".
func (t *Token) NotBefore() *time.Time {
return t.notBefore
}
// Expiration returns the time at which the Token expires.
func (t *Token) Expiration() *time.Time {
return t.expiration
}
// IsValidNow verifies that the token can be used at the current time, based on expiration or "not before" fields.
// This does NOT do any other kind of verifications.
func (t *Token) IsValidNow() bool {
return t.IsValidAt(time.Now())
}
// IsValidNow verifies that the token can be used at the given time, based on expiration or "not before" fields.
// This does NOT do any other kind of verifications.
func (t *Token) IsValidAt(ti time.Time) bool {
if t.expiration != nil && ti.After(*t.expiration) {
return false
}
if t.notBefore != nil && ti.Before(*t.notBefore) {
return false
}
return true
}
func (t *Token) validate() error {
var errs error
requiredDID := func(id did.DID, fieldname string) {
if !id.Defined() {
errs = errors.Join(errs, fmt.Errorf(`a valid did is required for %s: %s`, fieldname, id.String()))
}
}
requiredDID(t.issuer, "Issuer")
requiredDID(t.audience, "Audience")
if len(t.nonce) < 12 {
errs = errors.Join(errs, fmt.Errorf("token nonce too small"))
}
return errs
}
// tokenFromModel build a decoded view of the raw IPLD data.
// This function also serves as validation.
func tokenFromModel(m tokenPayloadModel) (*Token, error) {
var (
tkn Token
err error
)
tkn.issuer, err = did.Parse(m.Iss)
if err != nil {
return nil, fmt.Errorf("parse iss: %w", err)
}
if tkn.audience, err = did.Parse(m.Aud); err != nil {
return nil, fmt.Errorf("parse audience: %w", err)
}
if tkn.subject, err = parse.OptionalDID(m.Sub); err != nil {
return nil, fmt.Errorf("parse subject: %w", err)
}
if tkn.command, err = command.Parse(m.Cmd); err != nil {
return nil, fmt.Errorf("parse command: %w", err)
}
if tkn.policy, err = policy.FromIPLD(m.Pol); err != nil {
return nil, fmt.Errorf("parse policy: %w", err)
}
if len(m.Nonce) == 0 {
return nil, fmt.Errorf("nonce is required")
}
tkn.nonce = m.Nonce
tkn.meta = m.Meta
tkn.notBefore = parse.OptionalTimestamp(m.Nbf)
tkn.expiration = parse.OptionalTimestamp(m.Exp)
if err := tkn.validate(); err != nil {
return nil, err
}
return &tkn, nil
}

View File

@@ -1,209 +0,0 @@
package delegation_test
import (
"encoding/base64"
"testing"
"time"
"github.com/stretchr/testify/require"
"gotest.tools/v3/golden"
"github.com/ucan-wg/go-ucan/did/didtest"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/policy"
"github.com/ucan-wg/go-ucan/token/delegation"
)
const (
nonce = "6roDhGi0kiNriQAz7J3d+bOeoI/tj8ENikmQNbtjnD0"
subJectCmd = "/foo/bar"
subjectPol = `
[
[
"==",
".status",
"draft"
],
[
"all",
".reviewer",
[
"like",
".email",
"*@example.com"
]
],
[
"any",
".tags",
[
"or",
[
[
"==",
".",
"news"
],
[
"==",
".",
"press"
]
]
]
]
]
`
newCID = "zdpuAwa4qv3ncMDPeDoqVxjZy3JoyWsbqUzm94rdA1AvRFkkw"
rootCID = "zdpuAkgGmUp5JrXvehGuuw9JA8DLQKDaxtK3R8brDQQVC2i5X"
aesKey = "xQklMmNTnVrmaPBq/0pwV5fEwuv/iClF5HWak9MsgI8="
)
func TestConstructors(t *testing.T) {
t.Parallel()
cmd, err := command.Parse(subJectCmd)
require.NoError(t, err)
pol, err := policy.FromDagJson(subjectPol)
require.NoError(t, err)
exp, err := time.Parse(time.RFC3339, "2200-01-01T00:00:00Z")
require.NoError(t, err)
t.Run("New", func(t *testing.T) {
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
delegation.WithNonce([]byte(nonce)),
delegation.WithSubject(didtest.PersonaAlice.DID()),
delegation.WithExpiration(exp),
delegation.WithMeta("foo", "fooo"),
delegation.WithMeta("bar", "barr"),
)
require.NoError(t, err)
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
require.NoError(t, err)
golden.Assert(t, string(data), "new.dagjson")
})
t.Run("Root", func(t *testing.T) {
t.Parallel()
tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
delegation.WithNonce([]byte(nonce)),
delegation.WithExpiration(exp),
delegation.WithMeta("foo", "fooo"),
delegation.WithMeta("bar", "barr"),
)
require.NoError(t, err)
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
require.NoError(t, err)
golden.Assert(t, string(data), "root.dagjson")
})
}
func TestEncryptedMeta(t *testing.T) {
t.Parallel()
cmd, err := command.Parse(subJectCmd)
require.NoError(t, err)
pol, err := policy.FromDagJson(subjectPol)
require.NoError(t, err)
encryptionKey, err := base64.StdEncoding.DecodeString(aesKey)
require.NoError(t, err)
require.Len(t, encryptionKey, 32)
tests := []struct {
name string
key string
value string
expectError bool
}{
{
name: "simple string",
key: "secret1",
value: "hello world",
},
{
name: "empty string",
key: "secret2",
value: "",
},
{
name: "special characters",
key: "secret3",
value: "!@#$%^&*()_+-=[]{}|;:,.<>?",
},
{
name: "unicode characters",
key: "secret4",
value: "Hello, 世界! 👋",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
delegation.WithEncryptedMetaString(tt.key, tt.value, encryptionKey),
)
require.NoError(t, err)
data, err := tkn.ToDagCbor(didtest.PersonaAlice.PrivKey())
require.NoError(t, err)
decodedTkn, _, err := delegation.FromSealed(data)
require.NoError(t, err)
_, err = decodedTkn.Meta().GetString(tt.key)
require.Error(t, err)
decrypted, err := decodedTkn.Meta().GetEncryptedString(tt.key, encryptionKey)
require.NoError(t, err)
// Verify the decrypted value is equal to the original
require.Equal(t, tt.value, decrypted)
// Try to decrypt with wrong key
wrongKey := make([]byte, 32)
_, err = decodedTkn.Meta().GetEncryptedString(tt.key, wrongKey)
require.Error(t, err)
})
}
t.Run("multiple encrypted values in the same token", func(t *testing.T) {
values := map[string]string{
"secret1": "value1",
"secret2": "value2",
"secret3": "value3",
}
var opts []delegation.Option
for k, v := range values {
opts = append(opts, delegation.WithEncryptedMetaString(k, v, encryptionKey))
}
// Create token with multiple encrypted values
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, opts...)
require.NoError(t, err)
data, err := tkn.ToDagCbor(didtest.PersonaAlice.PrivKey())
require.NoError(t, err)
decodedTkn, _, err := delegation.FromSealed(data)
require.NoError(t, err)
for k, v := range values {
decrypted, err := decodedTkn.Meta().GetEncryptedString(k, encryptionKey)
require.NoError(t, err)
require.Equal(t, v, decrypted)
}
})
}

View File

@@ -1,5 +0,0 @@
# delegationtest
See the package documentation for instructions on how to use the generated
tokens as well as information on how to regenerate the code if changes have
been made.

View File

@@ -1,33 +0,0 @@
// Package delegationtest provides a set of pre-built delegation tokens
// for a variety of test cases.
//
// For all delegation tokens, the name of the delegation token is the
// Issuer appended with the Audience. The tokens are generated so that
// an invocation can be created for any didtest.Persona.
//
// Delegation proof-chain names contain each didtest.Persona name in
// order starting with the root delegation (which will always be generated
// by Alice). This is the opposite of the list of cic.Cids that represent the
// proof chain.
//
// For both the generated delegation tokens granted to Carol's Persona and
// the proof chains containing Carol's delegations to Dan, if there is no
// suffix, the proof chain will be deemed valid. If there is a suffix, it
// will consist of either the word "Valid" or "Invalid" and the name of the
// field that has been altered. Only optional fields will generate proof
// chains with Valid suffixes.
//
// If changes are made to the list of Personas included in the chain, or
// in the variants that are specified, the generated Go file and delegation
// tokens stored in the data/ directory should be regenerated by running
// the following command in this directory:
//
// cd generator && go run .
//
// Generated delegation Tokens are stored in the data/ directory and loaded
// into the delegation.Loader.
// Generated references to these tokens and the tokens themselves are
// created in the token_gen.go file. See /token/invocation/invocation_test.go
// for an example of how these delegation tokens and proof-chains can
// be used during testing.
package delegationtest

View File

@@ -1,234 +0,0 @@
package main
import (
"os"
"path/filepath"
"slices"
"time"
"github.com/dave/jennifer/jen"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/did/didtest"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/policy"
"github.com/ucan-wg/go-ucan/token/delegation"
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
)
const (
tokenNamePrefix = "Token"
proorChainNamePrefix = "Proof"
tokenExt = ".dagcbor"
)
var constantNonce = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}
type newDelegationParams struct {
privKey crypto.PrivKey
aud did.DID
sub did.DID
cmd command.Command
pol policy.Policy
opts []delegation.Option
}
type token struct {
name string
id cid.Cid
}
type proof struct {
name string
prf []cid.Cid
}
type acc struct {
name string
chain []cid.Cid
}
type variant struct {
name string
variant func(*newDelegationParams)
}
func noopVariant() variant {
return variant{
name: "",
variant: func(_ *newDelegationParams) {},
}
}
type generator struct {
dlgs []token
chains []proof
}
func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari variant) error {
acc.name += personas[0].Name()
proofName := acc.name
if len(vari.name) > 0 {
proofName += "_" + vari.name
}
g.createProofChain(proofName, acc.chain)
if len(personas) < 2 {
return nil
}
name := personas[0].Name() + personas[1].Name()
params := newDelegationParams{
privKey: personas[0].PrivKey(),
aud: personas[1].DID(),
cmd: delegationtest.NominalCommand,
pol: policy.Policy{},
opts: []delegation.Option{
delegation.WithSubject(didtest.PersonaAlice.DID()),
delegation.WithNonce(constantNonce),
},
}
// Create each nominal token and continue the chain
id, err := g.createDelegation(params, name, vari)
if err != nil {
return err
}
acc.chain = append(acc.chain, id)
err = g.chainPersonas(personas[1:], acc, vari)
if err != nil {
return err
}
// If the user is Carol, create variants for each invalid and/or optional
// parameter and also continue the chain
if personas[0] == didtest.PersonaCarol {
variants := []variant{
{name: "InvalidExpandedCommand", variant: func(p *newDelegationParams) {
p.cmd = delegationtest.ExpandedCommand
}},
{name: "ValidAttenuatedCommand", variant: func(p *newDelegationParams) {
p.cmd = delegationtest.AttenuatedCommand
}},
{name: "InvalidSubject", variant: func(p *newDelegationParams) {
p.opts = append(p.opts, delegation.WithSubject(didtest.PersonaBob.DID()))
}},
{name: "InvalidExpired", variant: func(p *newDelegationParams) {
// Note: this makes the generator not deterministic
p.opts = append(p.opts, delegation.WithExpiration(time.Now().Add(time.Second)))
}},
{name: "InvalidInactive", variant: func(p *newDelegationParams) {
nbf, err := time.Parse(time.RFC3339, "2070-01-01T00:00:00Z")
if err != nil {
panic(err)
}
p.opts = append(p.opts, delegation.WithNotBefore(nbf))
}},
}
// Start a branch in the recursion for each of the variants
for _, v := range variants {
id, err := g.createDelegation(params, name, v)
if err != nil {
return err
}
// replace the previous Carol token id with the one from the variant
acc.chain[len(acc.chain)-1] = id
err = g.chainPersonas(personas[1:], acc, v)
if err != nil {
return err
}
}
}
return nil
}
func (g *generator) createDelegation(params newDelegationParams, name string, vari variant) (cid.Cid, error) {
vari.variant(&params)
issDID, err := did.FromPrivKey(params.privKey)
if err != nil {
return cid.Undef, err
}
tkn, err := delegation.New(issDID, params.aud, params.cmd, params.pol, params.opts...)
if err != nil {
return cid.Undef, err
}
data, id, err := tkn.ToSealed(params.privKey)
if err != nil {
return cid.Undef, err
}
dlgName := tokenNamePrefix + name
if len(vari.name) > 0 {
dlgName += "_" + vari.name
}
err = os.WriteFile(filepath.Join("..", delegationtest.TokenDir, dlgName+tokenExt), data, 0o644)
if err != nil {
return cid.Undef, err
}
g.dlgs = append(g.dlgs, token{
name: dlgName,
id: id,
})
return id, nil
}
func (g *generator) createProofChain(name string, prf []cid.Cid) {
if len(prf) < 1 {
return
}
clone := make([]cid.Cid, len(prf))
copy(clone, prf)
g.chains = append(g.chains, proof{
name: proorChainNamePrefix + name,
prf: clone,
})
}
func (g *generator) writeGoFile() error {
file := jen.NewFile("delegationtest")
file.HeaderComment("Code generated by delegationtest - DO NOT EDIT.")
refs := map[cid.Cid]string{}
for _, d := range g.dlgs {
refs[d.id] = d.name + "CID"
file.Var().Defs(
jen.Id(d.name+"CID").Op("=").Qual("github.com/ipfs/go-cid", "MustParse").Call(jen.Lit(d.id.String())),
jen.Id(d.name).Op("=").Id("mustGetDelegation").Call(jen.Id(d.name+"CID")),
)
file.Line()
}
for _, c := range g.chains {
g := jen.CustomFunc(jen.Options{
Multi: true,
Separator: ",",
Close: "\n",
}, func(g *jen.Group) {
slices.Reverse(c.prf)
for _, p := range c.prf {
g.Id(refs[p])
}
})
file.Var().Id(c.name).Op("=").Index().Qual("github.com/ipfs/go-cid", "Cid").Values(g)
file.Line()
}
return file.Save("../token_gen.go")
}

View File

@@ -1,17 +0,0 @@
package main
import (
"github.com/ucan-wg/go-ucan/did/didtest"
)
func main() {
gen := &generator{}
err := gen.chainPersonas(didtest.Personas(), acc{}, noopVariant())
if err != nil {
panic(err)
}
err = gen.writeGoFile()
if err != nil {
panic(err)
}
}

View File

@@ -1,112 +0,0 @@
package delegationtest
import (
"embed"
"path/filepath"
"sync"
"github.com/ipfs/go-cid"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/token/delegation"
)
var (
// ExpandedCommand is the parent of the NominalCommand and represents
// the cases where the delegation proof-chain or invocation token tries
// to increase the privileges granted by the root delegation token.
// Execution of this command is generally prohibited in tests.
ExpandedCommand = command.MustParse("/expanded")
// NominalCommand is the command used for most test tokens and proof-
// chains. Execution of this command is generally allowed in tests.
NominalCommand = ExpandedCommand.Join("nominal")
// AttenuatedCommand is a sub-command of the NominalCommand. Execution
// of this command is generally allowed in tests.
AttenuatedCommand = NominalCommand.Join("attenuated")
)
// ProofEmpty provides an empty proof chain for testing purposes.
var ProofEmpty = []cid.Cid{}
const TokenDir = "data"
//go:embed data
var fs embed.FS
var _ delegation.Loader = (*delegationLoader)(nil)
type delegationLoader struct {
tokens map[cid.Cid]*delegation.Token
}
var (
once sync.Once
ldr delegation.Loader
)
// GetDelegationLoader returns a singleton instance of a test
// DelegationLoader containing all the tokens present in the data/
// directory.
func GetDelegationLoader() delegation.Loader {
once.Do(func() {
var err error
ldr, err = loadDelegations()
if err != nil {
panic(err)
}
})
return ldr
}
// GetDelegation implements invocation.DelegationLoader.
func (l *delegationLoader) GetDelegation(id cid.Cid) (*delegation.Token, error) {
tkn, ok := l.tokens[id]
if !ok {
return nil, delegation.ErrDelegationNotFound
}
return tkn, nil
}
func loadDelegations() (delegation.Loader, error) {
dirEntries, err := fs.ReadDir(TokenDir)
if err != nil {
return nil, err
}
tkns := make(map[cid.Cid]*delegation.Token, len(dirEntries))
for _, dirEntry := range dirEntries {
data, err := fs.ReadFile(filepath.Join(TokenDir, dirEntry.Name()))
if err != nil {
return nil, err
}
tkn, id, err := delegation.FromSealed(data)
if err != nil {
return nil, err
}
tkns[id] = tkn
}
return &delegationLoader{
tokens: tkns,
}, nil
}
// GetDelegation is a shortcut that gets (or creates) the DelegationLoader
// and attempts to return the token referenced by the provided CID.
func GetDelegation(id cid.Cid) (*delegation.Token, error) {
return GetDelegationLoader().GetDelegation(id)
}
func mustGetDelegation(id cid.Cid) *delegation.Token {
tkn, err := GetDelegation(id)
if err != nil {
panic(err)
}
return tkn
}

Some files were not shown because too many files have changed in this diff Show More