1 Commits

Author SHA1 Message Date
Fabio Bozzo
d3e2ac07fc fix(selector): tokenize utf-8 support 2024-10-22 11:12:42 +02:00
123 changed files with 1972 additions and 9541 deletions

View File

@@ -2,7 +2,7 @@ name: go continuous benchmark
on:
push:
branches:
- main
- v1
permissions:
contents: write
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v4
with:
go-version: "stable"
- name: Run benchmark

View File

@@ -7,14 +7,14 @@ 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
name: ${{ matrix.os}} (go ${{ matrix.go }})
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Go information
@@ -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, see [SPEC](pkg/container/SPEC.md)
- 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

@@ -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,171 +1,43 @@
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")
code, ok := map[pb.KeyType]multicodec.Code{
pb.KeyType_Ed25519: multicodec.Ed25519Pub,
pb.KeyType_RSA: multicodec.RsaPub,
pb.KeyType_Secp256k1: multicodec.Secp256k1Pub,
pb.KeyType_ECDSA: multicodec.Es256,
}[pubKey.Type()]
if !ok {
return Undef, errors.New("Blah")
}
if pubKey.Type() == pb.KeyType_ECDSA && code == Secp256k1 {
var err error
buf := varint.ToUvarint(uint64(code))
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))
pubBytes, err := pubKey.Raw()
if err != nil {
return Undef, err
}
return DID{
code: code,
bytes: string(append(varint.ToUvarint(uint64(code)), bytes...)),
str: string(append(buf, pubBytes...)),
code: uint64(code),
key: true,
}, 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 {
@@ -174,58 +46,3 @@ func ToPubKey(s string) (crypto.PubKey, error) {
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,17 +1,10 @@
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"
)
@@ -23,59 +16,9 @@ const (
func TestFromPubKey(t *testing.T) {
t.Parallel()
_, ecdsaP256, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P256(), rand.Reader)
id, err := did.FromPubKey(examplePubKey(t))
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)
})
require.Equal(t, exampleDID(t), id)
}
func TestToPubKey(t *testing.T) {

View File

@@ -1,9 +1,6 @@
package did
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"fmt"
"strings"
@@ -13,61 +10,113 @@ import (
varint "github.com/multiformats/go-varint"
)
// Signature algorithms from the [did:key specification]
const Prefix = "did:"
const KeyPrefix = "did:key:"
const DIDCore = 0x0d1d
const Ed25519 = 0xed
const RSA = uint64(multicodec.RsaPub)
var MethodOffset = varint.UvarintSize(uint64(DIDCore))
//
// [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
)
// [did:key format]: https://w3c-ccg.github.io/did-method-key/
type DID struct {
key bool
code uint64
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
func (d DID) Defined() bool {
return d.str != ""
}
// 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:"
func (d DID) Bytes() []byte {
if !d.Defined() {
return nil
}
return []byte(d.str)
}
if !strings.HasPrefix(str, keyPrefix) {
return Undef, fmt.Errorf("must start with 'did:key'")
func (d DID) Code() uint64 {
return d.code
}
func (d DID) DID() DID {
return d
}
func (d DID) Key() bool {
return d.key
}
func (d DID) PubKey() (crypto.PubKey, error) {
if !d.key {
return nil, fmt.Errorf("unsupported did type: %s", d.String())
}
baseCodec, bytes, err := mbase.Decode(str[len(keyPrefix):])
if err != nil {
return Undef, err
unmarshaler, ok := map[multicodec.Code]crypto.PubKeyUnmarshaller{
multicodec.Ed25519Pub: crypto.UnmarshalEd25519PublicKey,
multicodec.RsaPub: crypto.UnmarshalRsaPublicKey,
multicodec.Secp256k1Pub: crypto.UnmarshalSecp256k1PublicKey,
multicodec.Es256: crypto.UnmarshalECDSAPublicKey,
}[multicodec.Code(d.code)]
if !ok {
return nil, fmt.Errorf("unsupported multicodec: %d", d.code)
}
if baseCodec != mbase.Base58BTC {
return Undef, fmt.Errorf("not Base58BTC encoded")
return unmarshaler(d.Bytes()[varint.UvarintSize(d.code):])
}
// String formats the decentralized identity document (DID) as a string.
func (d DID) String() string {
if d.key {
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str))
return "did:key:" + key
}
return "did:" + d.str[MethodOffset:]
}
func Decode(bytes []byte) (DID, error) {
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)
if code == Ed25519 || code == RSA {
return DID{str: string(bytes), code: code, key: true}, nil
} else if code == DIDCore {
return DID{str: string(bytes)}, nil
}
return Undef, fmt.Errorf("unsupported DID encoding: 0x%x", code)
}
func Parse(str string) (DID, error) {
if !strings.HasPrefix(str, Prefix) {
return Undef, fmt.Errorf("must start with 'did:'")
}
if strings.HasPrefix(str, KeyPrefix) {
code, bytes, err := mbase.Decode(str[len(KeyPrefix):])
if err != nil {
return Undef, err
}
if code != mbase.Base58BTC {
return Undef, fmt.Errorf("not Base58BTC encoded")
}
return Decode(bytes)
}
buf := make([]byte, MethodOffset)
varint.PutUvarint(buf, DIDCore)
suffix, _ := strings.CutPrefix(str, Prefix)
buf = append(buf, suffix...)
return DID{str: string(buf), code: DIDCore}, nil
}
// MustParse is like Parse but panics instead of returning an error.
func MustParse(str string) DID {
did, err := Parse(str)
if err != nil {
@@ -75,69 +124,3 @@ func MustParse(str string) DID {
}
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
}
// 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)
}
codeSize := varint.UvarintSize(uint64(d.code))
return unmarshaler([]byte(d.bytes)[codeSize:])
}
// String formats the decentralized identity document (DID) as a string.
func (d DID) String() string {
if d == Undef {
return "(undefined)"
}
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes))
return "did:key:" + key
}
func ecdsaPubKeyUnmarshaler(curve elliptic.Curve) crypto.PubKeyUnmarshaller {
return func(data []byte) (crypto.PubKey, error) {
x, y := elliptic.UnmarshalCompressed(curve, data)
ecdsaPublicKey := &ecdsa.PublicKey{
Curve: curve,
X: x,
Y: y,
}
pkix, err := x509.MarshalPKIXPublicKey(ecdsaPublicKey)
if err != nil {
return nil, err
}
return crypto.UnmarshalECDSAPublicKey(pkix)
}
}
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)
}

View File

@@ -9,8 +9,12 @@ import (
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) {
@@ -25,17 +29,65 @@ func TestMustParseDIDKey(t *testing.T) {
})
}
func TestEquivalence(t *testing.T) {
undef0 := DID{}
undef1 := Undef
did0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
require.NoError(t, err)
did1, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
require.NoError(t, err)
require.True(t, undef0 == undef1)
require.False(t, undef0 == did0)
require.True(t, did0 == did1)
require.False(t, undef1 == did1)
func TestDecodeDIDKey(t *testing.T) {
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
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) {
u0 := DID{}
u1 := Undef
if u0 != u1 {
t.Fatalf("undef DID not equivalent")
}
d0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
if err != nil {
t.Fatalf("%v", err)
}
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,150 +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"
"github.com/libp2p/go-libp2p/core/crypto"
"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 + 1
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 {
res, ok := privKeys[p]
if !ok {
panic(fmt.Sprintf("Unknown persona: %v", p))
}
return res
}
func (p Persona) PrivKeyConfig() string {
res, ok := privKeyB64()[p]
if !ok {
panic(fmt.Sprintf("Unknown persona: %v", p))
}
return res
}
// 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() string {
pubKeyMar, err := crypto.MarshalPublicKey(p.PrivKey().GetPublic())
if err != nil {
panic(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,
}
}
// DidToName retrieve the persona's name from its DID.
func DidToName(d did.DID) string {
return map[did.DID]string{
PersonaAlice.DID(): "Alice",
PersonaBob.DID(): "Bob",
PersonaCarol.DID(): "Carol",
PersonaDan.DID(): "Dan",
PersonaErin.DID(): "Erin",
PersonaFrank.DID(): "Frank",
}[d]
}

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"
]
}
}
}

18
go.mod
View File

@@ -3,39 +3,31 @@ module github.com/ucan-wg/go-ucan
go 1.23
require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
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/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.10.0
golang.org/x/crypto v0.32.0
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/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // 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/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/sys v0.29.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

33
go.sum
View File

@@ -1,6 +1,5 @@
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/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=
@@ -10,8 +9,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3
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/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=
@@ -28,18 +25,6 @@ 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=
@@ -69,8 +54,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=
@@ -78,24 +61,21 @@ 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
@@ -106,7 +86,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
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=

View File

@@ -1,187 +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 (
"errors"
"fmt"
"iter"
"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/limits"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)
var ErrNotFound = errors.New("key not found in meta")
// 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{},
}
}
// GetNode retrieves a value as a raw IPLD node.
// Returns ErrNotFound if the given key is missing.
func (a *Args) GetNode(key string) (ipld.Node, error) {
v, ok := a.Values[key]
if !ok {
return nil, ErrNotFound
}
return v, nil
}
// 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
}
if err := limits.ValidateIntegerBoundsIPLD(node); err != nil {
return fmt.Errorf("value for key %q: %w", key, err)
}
a.Values[key] = node
a.Keys = append(a.Keys, key)
return nil
}
type Iterator interface {
Iter() iter.Seq2[string, ipld.Node]
}
// 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 Iterator) {
for key, value := range other.Iter() {
if _, ok := a.Values[key]; ok {
// don't overwrite
continue
}
a.Values[key] = value
a.Keys = append(a.Keys, key)
}
}
// Len return the number of arguments.
func (a *Args) Len() int {
return len(a.Keys)
}
// Iter iterates over the args key/values
func (a *Args) Iter() iter.Seq2[string, ipld.Node] {
return func(yield func(string, ipld.Node) bool) {
for _, key := range a.Keys {
if !yield(key, a.Values[key]) {
return
}
}
}
}
// 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
}
// Validate checks that all values in the Args are valid according to UCAN specs
func (a *Args) Validate() error {
for key, value := range a.Values {
if err := limits.ValidateIntegerBoundsIPLD(value); err != nil {
return fmt.Errorf("value for key %q: %w", key, err)
}
}
return nil
}

View File

@@ -1,297 +0,0 @@
package args_test
import (
"maps"
"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/node/basicnode"
"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/limits"
"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()))
}
func TestIterCloneEquals(t *testing.T) {
a := args.New()
require.NoError(t, a.Add("foo", "bar"))
require.NoError(t, a.Add("baz", 1234))
expected := map[string]ipld.Node{
"foo": basicnode.NewString("bar"),
"baz": basicnode.NewInt(1234),
}
// args -> iter
require.Equal(t, expected, maps.Collect(a.Iter()))
// readonly -> iter
ro := a.ReadOnly()
require.Equal(t, expected, maps.Collect(ro.Iter()))
// args -> clone -> iter
clone := a.Clone()
require.Equal(t, expected, maps.Collect(clone.Iter()))
// readonly -> WriteableClone -> iter
wclone := ro.WriteableClone()
require.Equal(t, expected, maps.Collect(wclone.Iter()))
require.True(t, a.Equals(wclone))
require.True(t, ro.Equals(wclone.ReadOnly()))
}
func TestInclude(t *testing.T) {
a1 := args.New()
require.NoError(t, a1.Add("samekey", "bar"))
require.NoError(t, a1.Add("baz", 1234))
a2 := args.New()
require.NoError(t, a2.Add("samekey", "othervalue")) // check no overwrite
require.NoError(t, a2.Add("otherkey", 1234))
a1.Include(a2)
require.Equal(t, map[string]ipld.Node{
"samekey": basicnode.NewString("bar"),
"baz": basicnode.NewInt(1234),
"otherkey": basicnode.NewInt(1234),
}, maps.Collect(a1.Iter()))
}
func TestArgsIntegerBounds(t *testing.T) {
t.Parallel()
tests := []struct {
name string
key string
val int64
wantErr string
}{
{
name: "valid int",
key: "valid",
val: 42,
},
{
name: "max safe integer",
key: "max",
val: limits.MaxInt53,
},
{
name: "min safe integer",
key: "min",
val: limits.MinInt53,
},
{
name: "exceeds max safe integer",
key: "tooBig",
val: limits.MaxInt53 + 1,
wantErr: "exceeds safe integer bounds",
},
{
name: "below min safe integer",
key: "tooSmall",
val: limits.MinInt53 - 1,
wantErr: "exceeds safe integer bounds",
},
{
name: "duplicate key",
key: "duplicate",
val: 42,
wantErr: "duplicate key",
},
}
a := args.New()
require.NoError(t, a.Add("duplicate", 1)) // tests duplicate key
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := a.Add(tt.key, tt.val)
if tt.wantErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.wantErr)
} else {
require.NoError(t, err)
val, err := a.GetNode(tt.key)
require.NoError(t, err)
i, err := val.AsInt()
require.NoError(t, err)
require.Equal(t, tt.val, i)
}
})
}
}
const (
argsSchema = "type Args { String : Any }"
argsName = "Args"
)
var (
once sync.Once
ts *schema.TypeSystem
errSchema error
)
func argsType() schema.Type {
once.Do(func() {
ts, errSchema = ipld.LoadSchemaBytes([]byte(argsSchema))
})
if errSchema != nil {
panic(errSchema)
}
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,71 +0,0 @@
package args
import (
"errors"
"github.com/ipld/go-ipld-prime"
)
// Builder allows the fluid construction of an Args.
type Builder struct {
args *Args
errs error
}
// NewBuilder returns a Builder which will assemble the Args.
func NewBuilder() *Builder {
return &Builder{
args: New(),
}
}
// Add inserts a new key/val into the Args being assembled while collecting
// any errors caused by duplicate keys.
func (b *Builder) Add(key string, val any) *Builder {
b.errs = errors.Join(b.errs, b.args.Add(key, val))
return b
}
// Build returns the assembled Args or an error containing a list of
// errors encountered while trying to build the Args.
func (b *Builder) Build() (*Args, error) {
if b.errs != nil {
return nil, b.errs
}
return b.args, nil
}
// BuildIPLD is the same as Build except it takes the additional step of
// converting the Args to an ipld.Node.
func (b *Builder) BuildIPLD() (ipld.Node, error) {
args, err := b.Build()
if err != nil {
return nil, err
}
return args.ToIPLD()
}
// MustBuild is the same as Build except it panics if an error occurs.
func (b *Builder) MustBuild() *Args {
args, err := b.Build()
if err != nil {
panic(b.errs)
}
return args
}
// MustBuildIPLD is the same as BuildIPLD except it panics if an error
// occurs.
func (b *Builder) MustBuildIPLD() ipld.Node {
node, err := b.BuildIPLD()
if err != nil {
panic(err)
}
return node
}

View File

@@ -1,81 +0,0 @@
package args_test
import (
"testing"
"github.com/ipld/go-ipld-prime"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/args"
)
func TestBuilder_XXX(t *testing.T) {
t.Parallel()
const (
keyOne = "key1"
valOne = "string"
keyTwo = "key2"
valTwo = 42
)
exp := args.New()
exp.Add(keyOne, valOne)
exp.Add(keyTwo, valTwo)
expNode, err := exp.ToIPLD()
require.NoError(t, err)
disjointKeys := args.NewBuilder().
Add(keyOne, valOne).
Add(keyTwo, valTwo)
duplicateKeys := args.NewBuilder().
Add(keyOne, valOne).
Add(keyTwo, valTwo).
Add(keyOne, "oh no!")
t.Run("MustBuild succeeds with disjoint keys", func(t *testing.T) {
t.Parallel()
var act *args.Args
require.NotPanics(t, func() {
act = disjointKeys.MustBuild()
})
assert.Equal(t, exp, act)
})
t.Run("MustBuild fails with duplicate keys", func(t *testing.T) {
t.Parallel()
var act *args.Args
require.Panics(t, func() {
act = duplicateKeys.MustBuild()
})
assert.Nil(t, act)
})
t.Run("MustBuildIPLD succeeds with disjoint keys", func(t *testing.T) {
t.Parallel()
var act ipld.Node
require.NotPanics(t, func() {
act = disjointKeys.MustBuildIPLD()
})
assert.Equal(t, expNode, act)
})
t.Run("MustBuildIPLD fails with duplicate keys", func(t *testing.T) {
t.Parallel()
var act ipld.Node
require.Panics(t, func() {
act = duplicateKeys.MustBuildIPLD()
})
assert.Nil(t, act)
})
}

View File

@@ -1,39 +0,0 @@
package args
import (
"iter"
"github.com/ipld/go-ipld-prime"
)
type ReadOnly struct {
args *Args
}
func (r ReadOnly) GetNode(key string) (ipld.Node, error) {
return r.args.GetNode(key)
}
func (r ReadOnly) Len() int {
return r.args.Len()
}
func (r ReadOnly) Iter() iter.Seq2[string, ipld.Node] {
return r.args.Iter()
}
func (r ReadOnly) ToIPLD() (ipld.Node, error) {
return r.args.ToIPLD()
}
func (r ReadOnly) Equals(other ReadOnly) bool {
return r.args.Equals(other.args)
}
func (r ReadOnly) String() string {
return r.args.String()
}
func (r ReadOnly) WriteableClone() *Args {
return r.args.Clone()
}

View File

@@ -98,34 +98,7 @@ func (c Command) Join(segments ...string) Command {
// 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
*/
return strings.Split(string(c), separator)
}
// String returns the composed representation the command. This is also

View File

@@ -73,21 +73,6 @@ func TestJoin(t *testing.T) {
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

@@ -1,109 +0,0 @@
# UCAN container Specification v0.1.0
## Editors
* [Michael Muré], [Consensys]
## Authors
* [Michael Muré], [Consensys]
* [Hugo Dias]
## Language
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [BCP 14] when, and only when, they appear in all capitals, as shown here.
# 0 Abstract
[User-Controlled Authorization Network (UCAN)][UCAN] is a trustless, secure, local-first, user-originated authorization and revocation scheme. This document describes a container format for transmitting one or more UCAN tokens as bytes, regardless of the transport.
# 1 Introduction
The UCAN spec itself is transport agnostic. This specification describes how to transfer one or more [UCAN] tokens bundled together, regardless of the transport.
# 2 Container format
## 2.1 Inner structure
UCAN tokens, regardless of their kind ([Delegation], [Invocation], [Revocation], [Promise]) MUST be first signed and serialized into DAG-CBOR bytes according to their respective specification. As the token's CID is not part of the serialized container, any CID returned by this operation is to be ignored.
All the tokens' bytes MUST be assembled in a [CBOR] array. The ordering of tokens in the array MUST NOT matter. This array SHOULD NOT have duplicate entries.
That array is then inserted as the value under the `ctn-v1` string key, in a CBOR map. There MUST NOT be other keys.
For clarity, the CBOR shape is given below:
```json
{
"ctn-v1": [
<token1 bytes>,
<token2 bytes>,
<token3 bytes>,
]
}
```
## 2.2 Serialisation
To serialize the container into bytes, the inner CBOR structure MUST then be serialized into bytes according to the CBOR specification. The resulting bytes MAY be compressed by a supported algorithm, then MAY be encoded with a supported base encoding.
The following compression algorithms are REQUIRED to be supported:
- [GZIP]
The following base encoding combinations are REQUIRED to be supported:
- base64, standard alphabet, padding
- base64, URL alphabet, no padding
The CBOR bytes MUST be prepended by a single byte header to indicate the selection combination of base encoding and compression. This header value MUST be set according to the following table:
| Header as hex | Header as ASCII | Base encoding | Compression |
|---------------|-----------------|-------------------------|----------------|
| 0x40 | @ | raw bytes | no compression |
| 0x42 | B | base64 std padding | no compression |
| 0x43 | C | base64 url (no padding) | no compression |
| 0x4D | M | raw bytes | gzip |
| 0x4F | O | base64 std padding | gzip |
| 0x50 | P | base64 url (no padding) | gzip |
For clarity, the resulting serialisation is in the form of `<header byte><cbor bytes, optionally compressed, optionally encoded>`.
# 3 FAQ
## 3.1 Why not include the UCAN CIDs?
Several attacks are possible if UCAN tokens aren't validated. If CIDs aren't validated, at least two attacks are possible: [privilege escalation] and [cache poisoning], as UCAN delegation proofs depends on a correct hash-linked structure.
By not including the CID in the container, the recipient is forced to hash (and thus validate) the CIDs for each token. If presented with a claimed CID paired with the token bytes, implementers could ignore CID validation, breaking a core part of the proof chain security model. Hash functions are very fast on a couple kilobytes of data so the overhead is still very low. It also significantly reduces the size of the container.
## 3.2 Why compress? Why not always compress?
Compression is a relatively demanding operation. As such, using it is a tradeoff between size on the wire and CPU/memory usage, both when writing and reading a container. The transport itself can make compression worthwhile or not: for example, HTTP/2 and HTTP/3 headers are already compressed, but HTTP/1 headers are not. This being highly contextual, the choice is left to the final implementer.
# 4 Implementation recommendations
## 4.1 Dissociate reader and writer
While it is tempting to write a single implementation to read and write a container, it is RECOMMENDED to separate the implementation into a reader and a writer. The writer can simply accept arbitrary tokens as bytes, while the reader provides a read-only view with convenient access functions.
# 5 Acknowledgments
Many thanks to all the [Fission] team and in particular to [Brooklyn Zelenka] for creating and pushing [UCAN] and other critical pieces like [WNFS], and generally being awesome and supportive people.
<!-- External Links -->
[BCP 14]: https://www.rfc-editor.org/info/bcp14
[Brooklyn Zelenka]: https://github.com/expede
[CBOR]: https://www.rfc-editor.org/rfc/rfc8949.html
[Consensys]: https://consensys.io/
[Delegation]: https://github.com/ucan-wg/delegation/tree/v1_ipld
[Fission]: https://fission.codes
[GZIP]: https://datatracker.ietf.org/doc/html/rfc1952
[Hugo Dias]: https://github.com/hugomrdias
[Invocation]: https://github.com/ucan-wg/invocation
[Michael Muré]: https://github.com/MichaelMure/
[Promise]: https://github.com/ucan-wg/promise/tree/v1-rc1
[Revocation]: https://github.com/ucan-wg/revocation/tree/first-draft
[UCAN]: https://github.com/ucan-wg/spec
[WNFS]: https://github.com/wnfs-wg
[cache poisoning]: https://en.wikipedia.org/wiki/Cache_poisoning
[privilede escalation]: https://en.wikipedia.org/wiki/Privilege_escalation

View File

@@ -40,13 +40,6 @@ func TestCarRoundTrip(t *testing.T) {
}
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)

View File

@@ -1,118 +0,0 @@
package container
import (
"compress/gzip"
"encoding/base64"
"errors"
"fmt"
"io"
)
const containerVersionTag = "ctn-v1"
type header byte
const (
headerRawBytes = header(0x40)
headerBase64StdPadding = header(0x42)
headerBase64URL = header(0x43)
headerRawBytesGzip = header(0x4D)
headerBase64StdPaddingGzip = header(0x4F)
headerBase64URLGzip = header(0x50)
)
func (h header) encoder(w io.Writer) *payloadWriter {
res := &payloadWriter{rawWriter: w, writer: w, header: h}
switch h {
case headerBase64StdPadding, headerBase64StdPaddingGzip:
b64Writer := base64.NewEncoder(base64.StdEncoding, res.writer)
res.writer = b64Writer
res.closers = append([]io.Closer{b64Writer}, res.closers...)
case headerBase64URL, headerBase64URLGzip:
b64Writer := base64.NewEncoder(base64.RawURLEncoding, res.writer)
res.writer = b64Writer
res.closers = append([]io.Closer{b64Writer}, res.closers...)
}
switch h {
case headerRawBytesGzip, headerBase64StdPaddingGzip, headerBase64URLGzip:
gzipWriter := gzip.NewWriter(res.writer)
res.writer = gzipWriter
res.closers = append([]io.Closer{gzipWriter}, res.closers...)
}
return res
}
func payloadDecoder(r io.Reader) (io.Reader, error) {
headerBuf := make([]byte, 1)
_, err := r.Read(headerBuf)
if err != nil {
return nil, err
}
h := header(headerBuf[0])
switch h {
case headerRawBytes,
headerBase64StdPadding,
headerBase64URL,
headerRawBytesGzip,
headerBase64StdPaddingGzip,
headerBase64URLGzip:
default:
return nil, fmt.Errorf("unknown container header")
}
switch h {
case headerBase64StdPadding, headerBase64StdPaddingGzip:
r = base64.NewDecoder(base64.StdEncoding, r)
case headerBase64URL, headerBase64URLGzip:
r = base64.NewDecoder(base64.RawURLEncoding, r)
}
switch h {
case headerRawBytesGzip, headerBase64StdPaddingGzip, headerBase64URLGzip:
gzipReader, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
r = gzipReader
}
return r, nil
}
var _ io.WriteCloser = &payloadWriter{}
// payloadWriter is tasked with two things:
// - prepend the header byte
// - call Close() on all the underlying io.Writer
type payloadWriter struct {
rawWriter io.Writer
writer io.Writer
header header
headerWrote bool
closers []io.Closer
}
func (w *payloadWriter) Write(p []byte) (n int, err error) {
if !w.headerWrote {
_, err := w.rawWriter.Write([]byte{byte(w.header)})
if err != nil {
return 0, err
}
w.headerWrote = true
}
return w.writer.Write(p)
}
func (w *payloadWriter) Close() error {
var errs error
for _, closer := range w.closers {
if err := closer.Close(); err != nil {
errs = errors.Join(errs, err)
}
}
return errs
}

View File

@@ -1,15 +1,13 @@
package container
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"iter"
"strings"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/cbor"
"github.com/ipld/go-ipld-prime/codec/dagcbor"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ucan-wg/go-ucan/token"
@@ -18,62 +16,83 @@ import (
)
var ErrNotFound = fmt.Errorf("not found")
var ErrMultipleInvocations = fmt.Errorf("multiple invocations")
// Reader is a token container reader. It exposes the tokens conveniently decoded.
type Reader map[cid.Cid]token.Token
// FromBytes decodes a container from a []byte
func FromBytes(data []byte) (Reader, error) {
return FromReader(bytes.NewReader(data))
// 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
}
// FromString decodes a container from a string
func FromString(s string) (Reader, error) {
return FromReader(strings.NewReader(s))
// 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 err != nil {
return nil, err
}
if tkn, ok := tkn.(*delegation.Token); ok {
return tkn, nil
}
return nil, fmt.Errorf("not a delegation token")
}
// FromReader decodes a container from an io.Reader.
func FromReader(r io.Reader) (Reader, error) {
payload, err := payloadDecoder(r)
// 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
}
n, err := ipld.DecodeStreaming(payload, cbor.Decode)
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")
if n.Kind() != datamodel.Kind_List {
return nil, fmt.Errorf("not a list")
}
// get the first (and only) key-value pair
it := n.MapIterator()
key, tokensNode, err := it.Next()
if err != nil {
return nil, err
}
ctn := make(Reader, n.Length())
version, err := key.AsString()
if err != nil {
return nil, fmt.Errorf("invalid container format: version must be string")
}
if version != containerVersionTag {
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()
it := n.ListIterator()
for !it.Done() {
_, val, err := it.Next()
if err != nil {
return nil, err
}
@@ -89,72 +108,8 @@ func FromReader(r io.Reader) (Reader, error) {
return ctn, nil
}
// 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.
// If not found, delegation.ErrDelegationNotFound is returned.
func (ctn Reader) GetDelegation(cid cid.Cid) (*delegation.Token, error) {
tkn, err := ctn.GetToken(cid)
if err != nil { // only ErrNotFound expected
return nil, delegation.ErrDelegationNotFound
}
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 a single invocation.Token.
// If none are found, ErrNotFound is returned.
// If more than one invocation exists, ErrMultipleInvocations is returned.
func (ctn Reader) GetInvocation() (*invocation.Token, error) {
var res *invocation.Token
for _, t := range ctn {
if inv, ok := t.(*invocation.Token); ok {
if res != nil {
return nil, ErrMultipleInvocations
}
res = inv
}
}
if res == nil {
return nil, ErrNotFound
}
return res, nil
}
// GetAllInvocations returns all the invocation.Token in the container.
func (ctn Reader) GetAllInvocations() iter.Seq2[cid.Cid, *invocation.Token] {
return func(yield func(cid.Cid, *invocation.Token) bool) {
for c, t := range ctn {
if t, ok := t.(*invocation.Token); ok {
if !yield(c, t) {
return
}
}
}
}
func FromCborBase64(r io.Reader) (Reader, error) {
return FromCbor(base64.NewDecoder(base64.StdEncoding, r))
}
func (ctn Reader) addToken(data []byte) error {

View File

@@ -22,22 +22,14 @@ import (
func TestContainerRoundTrip(t *testing.T) {
for _, tc := range []struct {
name string
expectedHeader header
writer any
name string
writer func(ctn Writer, w io.Writer) error
reader func(io.Reader) (Reader, error)
}{
{"Bytes", headerRawBytes, Writer.ToBytes},
{"BytesWriter", headerRawBytes, Writer.ToBytesWriter},
{"BytesGzipped", headerRawBytesGzip, Writer.ToBytesGzipped},
{"BytesGzippedWriter", headerRawBytesGzip, Writer.ToBytesGzippedWriter},
{"Base64StdPadding", headerBase64StdPadding, Writer.ToBase64StdPadding},
{"Base64StdPaddingWriter", headerBase64StdPadding, Writer.ToBase64StdPaddingWriter},
{"Base64StdPaddingGzipped", headerBase64StdPaddingGzip, Writer.ToBase64StdPaddingGzipped},
{"Base64StdPaddingGzippedWriter", headerBase64StdPaddingGzip, Writer.ToBase64StdPaddingGzippedWriter},
{"Base64URL", headerBase64URL, Writer.ToBase64URL},
{"Base64URLWriter", headerBase64URL, Writer.ToBase64URLWriter},
{"Base64URLGzip", headerBase64URLGzip, Writer.ToBase64URLGzip},
{"Base64URLGzipWriter", headerBase64URLGzip, Writer.ToBase64URLGzipWriter},
{"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)
@@ -47,53 +39,21 @@ func TestContainerRoundTrip(t *testing.T) {
for i := 0; i < 10; i++ {
dlg, c, data := randToken()
writer.AddSealed(data)
writer.AddSealed(c, data)
tokens[c] = dlg
dataSize += len(data)
}
var reader Reader
var serialLen int
buf := bytes.NewBuffer(nil)
switch fn := tc.writer.(type) {
case func(ctn Writer, w io.Writer) error:
buf := bytes.NewBuffer(nil)
err := fn(writer, buf)
require.NoError(t, err)
serialLen = buf.Len()
err := tc.writer(writer, buf)
require.NoError(t, err)
h, err := buf.ReadByte()
require.NoError(t, err)
require.Equal(t, byte(tc.expectedHeader), h)
err = buf.UnreadByte()
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 = FromReader(bytes.NewReader(buf.Bytes()))
require.NoError(t, err)
case func(ctn Writer) ([]byte, error):
b, err := fn(writer)
require.NoError(t, err)
serialLen = len(b)
require.Equal(t, byte(tc.expectedHeader), b[0])
reader, err = FromBytes(b)
require.NoError(t, err)
case func(ctn Writer) (string, error):
s, err := fn(writer)
require.NoError(t, err)
serialLen = len(s)
require.Equal(t, byte(tc.expectedHeader), s[0])
reader, err = FromString(s)
require.NoError(t, err)
}
t.Logf("data size %d, container size %d, overhead: %d%%, %d bytes",
dataSize, serialLen, int(float32(serialLen-dataSize)/float32(dataSize)*100.0), serialLen-dataSize)
reader, err := tc.reader(bytes.NewReader(buf.Bytes()))
require.NoError(t, err)
for c, dlg := range tokens {
tknRead, err := reader.GetToken(c)
@@ -138,18 +98,16 @@ func BenchmarkContainerSerialisation(b *testing.B) {
writer func(ctn Writer, w io.Writer) error
reader func(io.Reader) (Reader, error)
}{
{"Bytes", Writer.ToBytesWriter, FromReader},
{"BytesGzipped", Writer.ToBytesGzippedWriter, FromReader},
{"Base64StdPadding", Writer.ToBase64StdPaddingWriter, FromReader},
{"Base64StdPaddingGzipped", Writer.ToBase64StdPaddingGzippedWriter, FromReader},
{"Base64URL", Writer.ToBase64URLWriter, FromReader},
{"Base64URLGzip", Writer.ToBase64URLGzipWriter, FromReader},
{"car", Writer.ToCar, FromCar},
{"carBase64", Writer.ToCarBase64, FromCarBase64},
{"cbor", Writer.ToCbor, FromCbor},
{"cborBase64", Writer.ToCborBase64, FromCborBase64},
} {
writer := NewWriter()
for i := 0; i < 10; i++ {
_, _, data := randToken()
writer.AddSealed(data)
_, c, data := randToken()
writer.AddSealed(c, data)
}
buf := bytes.NewBuffer(nil)
@@ -172,6 +130,12 @@ func BenchmarkContainerSerialisation(b *testing.B) {
}
}
func randBytes(n int) []byte {
b := make([]byte, n)
_, _ = rand.Read(b)
return b
}
func randDID() (crypto.PrivKey, did.DID) {
privKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
@@ -186,7 +150,7 @@ func randDID() (crypto.PrivKey, did.DID) {
func randomString(length int) string {
b := make([]byte, length/2+1)
_, _ = rand.Read(b)
rand.Read(b)
return fmt.Sprintf("%x", b)[0:length]
}
@@ -202,12 +166,13 @@ func randToken() (*delegation.Token, cid.Cid, []byte) {
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.Root(iss, aud, cmd, pol, opts...)
t, err := delegation.New(priv, aud, cmd, pol, opts...)
if err != nil {
panic(err)
}
@@ -217,29 +182,3 @@ func randToken() (*delegation.Token, cid.Cid, []byte) {
}
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++ {
_, _, data := randToken()
writer.AddSealed(data)
}
data, err := writer.ToBytes()
require.NoError(f, err)
f.Add(data)
}
f.Fuzz(func(t *testing.T, data []byte) {
start := time.Now()
// search for panics
_, _ = FromBytes(data)
if time.Since(start) > 100*time.Millisecond {
panic("too long")
}
})
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
OH4sIAAAAAAAA/5zX+ZOUxR3HcY7FwhKToFJGFAU1ghzL8/RztpxzrzsHszuzcy1Ru5/unmd6dubZnXsGE9EtFVnFSCiNEFfFiCSKqIVI8ECBKGiJiMSTFdCYeGDK6GpMWcSUtUOq8ts++Qfev7zq+6nu+5lRys+riKuTYwdvSCw9NrDty6c/Wf668n73nFsqQ988uh1Z8rTNu9X6hkO33S/vG1i92+//ontN6/BubU54fntiUnXqrPNfo9p5bnTWd3PP3IhMt/z52L5i2UD5+aQnvVRsFVqFeQWjVdxsoDKp6SRDrsjS+hUNNZjNtFdJsa1CzUwCxSK8imjO7curtFHwBqy8US3hlBT08XQlkWeGkSPmfGZZ8zEqGLTWOzX9yyemGZli8X+b1XAxESdxALIVX6E3xkiY1coeswLDMOp1BVRgOrGnqxEtukoJo9fqub7fQD09Rmv3z/vRYtZaQT1lOs4olvH/nyU5WkIPmEAQkAYg5lhDqigxjeimJoqEMgC4JipMViURiCYTBQHLBHGZCkyRNcgYzVt5gwauDa67+uiirx+KuQZnjgB9/2H3noUzrj79rQdv27HpuSufnOrfvu3Myf98rfv5Tfds2OX9de9w92Mfm/m9s2eOe/etzPnd/1jdeqI8vvuVbYOb8Q2Ltky0CZRDlAfcJKqHGSklXclQxR+mPKbgcM5n5awkRYmwM2SltSQqjRYo3RFD/lDOnW/TYrxd7ayVQaQme10dVl3RPCardrQHkFJUvTUT2QCyk20CaQpUFF1CnFJJpDqVoGYSDKhuMJVDlaoqpIIMTIZVhcoMcCYAnSgC1GkT6BF9x2d9X19nTBw/+cgI0KO+/W+sk4cmDz7/7AM7D7s2+BdIFw0vGLv9yzm7Vv3h1QN/WZHwxy9Qj0evv2ftkX891BLy7V2cWjY+sGV9f+zYHh4eYxOoXE8E3BFPNB+q0Kw7XexLpfXeXiPrd5WC3N0Wj7iBFfeQMqi06aMFKtcMf9wRrbN4EKOa7KxYfRkQ0/3ecJ/LV/dnkl2OgEPSvIViNmQDyE62CSRDgWEBilwECpWwADVsKkQnhgQZ13QZMVWVITGRCLCmI8ANCWuiSiSqNIF+ExuqnnSds6b7zv4XRoDWzN08c6H1GByE86wXj+561L/q9pc+G1jy4/ya71uPb3kXLJmwe/jlHFu3+NhFK2/8Sce4sZtiLQezq/uVBx6OXnv3n0+zC+T1F5MuENTKFW8u0ld3GcVQNl0gZodSiCk01UkMV9SVauRNXR0tEOtkic5YKtXlDgKt4VFKuhWoGiku417g6wuUimZ7DbTJDAGv2waQnWwTCIka0XVJ4JCKVJGQLGqmIamaYaiQKwpVIFQMgZgUCQLSicqxTKCuyxLATaD4JZf/4mhvbf19y15uGwFyrodHo9vOP7kzObhklvuTfQ/j3IQLn5nz6dKNn0xo7H/nkQs+HT70VvLwmWzvm3c/Ofn7g3f91jvkb7/5vpWHHl8EX3/B7sSxiq9R5bkIsAJKJurhPj9z1GgDh3gy18ViyOP2sa5oUso4C9aoJ84fq/fKKm3LtRe8gWTe568V8z7sMCU1FnA7gJnOhXq9Lk+XaHTamTgb2SaQQIFEEaJcgLIqQEEQgaloGsKIMs5ESRYFQVYlU2UEEVnEXCUSA0zTBLUJJM/nb351ecu/h6dLlREg67y3b3be96Ky/p7L7jzeO9s5Y9Ka5fNxZHc8cVC6+K83iXPfO/GjL5ybrbZrWq4QPu64rfu7adMP7v/uyt/f0r7yJjBpkk2gvgQPOTusdDGqhOuKNxROw0ba29lFUjCbqNV1MSDWZZxgViRkjBbI8jUa1aKl0Y42qz3sTVt11dQSeo5WqsGKD1NY0sN6QS8aUhXaALKTPQWEFU0nKuMi0aGCDayoJjIwZjrGHMoEAUI1QTINxUAShCo3DCwTWdOZ0QRaAhdf1yIf7unm5atGgObsH8rs5FO2wH1r/zb84c8WrNLL179/za+G51WvkvaKXjguP3XmXWetHTix59zd32Te9h2ftGTbhU/dv3PvjcvAlBnjzrAJVOjMUKhKznS4jZR10ZWMVz1JZ5zWHbmuSqnglzpSMqPVuiPgkkcL1Butd1DQVQmGgByp0S5Xo5SLKXlfvD1QDolQZ2oxXIwFk42AUrEBZCfbBBJlSZFFReIIQZlqhgR1U0MSlilROBAMTACBAjYZMyjUich1w1B+2Dd4auIOnLPCY3wFNzxIc+tGgKqX5an27Qtn37C2d+ykS7XPnz30TSi9onDd2eeMK7N4/6x78XLQMnnlm6/mFlbIeRee9qWybfCjBceSi6YVdg2s7ZxgE6ga7sp29QGTJ+LOWAooJcsXJ9loQgvFmZOXOgNt7nox1jBRyAVGC8SNbK0zCyKmrDsDRrrhShLqqGQ1DKJyJVMvknY/jNV52hQbThtAdrJNIIgVSdBljUuCwCSgMxGbBhENJBLIBWYgAzCsAZNAwdAFXeZMVVX4wywKTaBZTxz9E9y6b0JPgu8YAZo9A258I6l75KfJM2Omb+T3zjDPTW393SUXnHjswB3DY05a7wQ7ux9fXjtjx4QxS6fPfmns3598sWf9lkvB83sOXrxytt1XXK5IHCHk1Tx6sc0fTPdlCjTBYmWHHNH8XR2s3khVjKqeFD085R71BeXrdVytWKqSqCaDMiehTm/Q4jCVrUcdrBKTOfR4vEGto01N2bkgG9lTF6RBwFQicAqJALGiaropCwggCWMuAyJThAxETUIA0CVV57qkUEVlOtSbQGMGJuaOTFzyx/FHjj81AvTqwIExd3vB9FuXrdHmrp7106G33/vgo9tP3zjF8t5ZOLz52q2bjt1x6+yTH6zQbmGpiasOJ5Xw+oU7n3v8YMuH77Vs/bbF7kcVmjBmuhuhVMyVUNWiSXg9XMs2yvUkpCnma8BKxlK0eqXT8o0WqBgpmI5YKuLvyLiUeqTsp42S6qiasUCjlFG9Xk1pr3ujpN0BLWoDyE62CSRpMtBFTeaipKqYyZJKTEwURGWkcVHAWDcY0TSTYipjjFUu6YL6w2P8v0AnP45M2du/fWgHeOWD/wQAAP//IuQ6Y1MQAAA=

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
PH4sIAAAAAAAA_5zWa5cUxR3HcVcRiejmuCII3siKwgnubnd1dVW1eIS5rzM9w8zs3HZETFd19_T07PTcZ2cH1KMgoB41YgIo3nM0G1GCRhCRiILIEaIhIEo0Bi9c9ewaFfEazYMd8iCPdvIGvk8-p_71e1RnZaujyt_e2_LQLYk5Fbb1QefFR9-_5LzWO59-9PfWoWV_nN_f177z06eH31j13Je8-Wpk35bTccKjtV_22MRfnz9DufuSg8Ntn02KzZtw83sLxj-mGE441FIoVZhidal9qTl8J9fJdRRZJz_IlIpaI2pavTKjDVxZR_5MQXa4aFIFgr-gMCrrOTWUqWNajNZERzin0GLRz7IxsTcfAgnGsqrRpedyXVQpMq2Wn5K68dmLWLpU-p9msRbQ-usuHEsGorZK0BbwxWzxvCdrhDK2vL23UMCRTM0RlStVxPK5vpsXMaWvj3Vee90i5Wq9s6r0VbRTWalC__-smtXKyu8MERNOwJSZkCACeAFgzkC6xGkYAhOLqkJEiBXJkJAuCUBUTMIA4KEgAEWzchbT5DXHfxwc7LiqNnPmRwdHgBa8NO6jDQ9uvW6wa7DHYetfP3DxO4s7JhybLa1YcerUV-Yae7aDww_P2_zjvH9euWqOOW3yjBWtO2__bKzLvfnR_enEpmaBMtFuy4qEsgrlqRMpPm86mql6apjI4V63V_bFgl5WT-USpQhDowVK2amiFmM8tEUsiRJcCeowHM9AvVQreLGE83LMkcBBI99dAE0ANZNtAGFewJjwkilBJPG8TphoKEQhmihKpsgBXuIVhXEGRRQpRGAmhzkRqIpOaQNo4afuoTWTj60cfjw5fQQoxy98ZPGmF3YsWLJo8voJ587Y9-X38geyLBq_nHXZIy9fPun5m1pebqVzv7J-NuaM08XEzYl3v3kmtfuCr2Yc-VAa3vtxS7NAnpKSKsBEzVaMphWdFB0kgNSEgHpwiPe7a0qaxYtVG_SVq_7RAhkey-asYmfUZ1XKMbGSr-Vo1UZ60rZyNV6Uy8l8MY_iES1ic0abAGom2wCCEqcLVFRNpEKBaghAaCBJUYgKOVNVGMQCUyXRQBqFTJWwKQkIIoZFQW8AvZV64bRDez_6flYbbmu8oDa_euu2yJB9w1vX3uO-YrZvobj2yK07V7-1YebjrRcgbsvS4fiyJ9_gpx4KvT5n_A7PTz-cqF_asuuaoTFnn2N-bbU2CaSHi0bJlrEi3kyh6NFrgqOu59VKOJ2t9Xj4sllT4wXqJdFQfzI2aiBvIGSG_Zls2IsDSdKblRV3QlQz2X7Z2evudVBk16QIM1yiCZsBaiLbAOIEnROQppu8AjgKiKqIhgQVwDQCTCKJkqCIBIqGpoicCilvcoxDWJewcPLE3TD0fHzb0y_BrtRd2gjQN3fd--36ju_CYzuV8adcsOeA95UvVv-09N1t05_bN7DllouWTN-18r4nTm-9Y8uZ2L02fj1XLizHlzyfrZ-x65Pt503b0uyJs0oDhpWuSh4vwFpJjlcDciKRCbtQTzmvxkWrKIWCUQm51FomO1ogKyMLyXSPx0xHxKrixwFT7a9XgzUUTuedXgDi8XKmgGJmJOwMNQHUTLYBhERGkE6YCTXEYYAYxxuE5xGnCbopAkWjAtCgbhCMKAUQmQqkvA4wJaQBpNyhPfDyK9Ijs-yOv40ArTv_hoMtSbB12NjtzD1Ef35ow47k_D3frn41f-2LX6YmdTxzzba3t180_7XFV5m-fz304tj2Td-t7Nx_oblszv7ctI2Hmz1xhayl1myOkhEOxUt2S_S4shFvMI7dIbseyPa4AlalJ8q6g2KhGBktUJGVfQEaSsXtsK7EnXnC6zIP84ZHrYUyGR9vcxXj9lwuaIvG7E0ANZM9-QcBHkNCNFMFEKhYFTRiSBoUOV0RTE3DKmWMCJKhijzgeJWakgoIwyLUuAZQSlq-Y177mBOSHt49AjRF-PBAe9d1H3ffcvyZczrHLAhP9X3jC5y797YFa-mNxYl_evDqo4fu_8NZh3d88un1R1f_5cfBf8_76p3tv_psyooll_k-nj2mWaC6b8BCmVrCytYjIFZKF1wytmUFKdijVuoCLIdrBV8dk3AxbIwWqJRAkpyRMnkY9ReLcipX8PR3u6RcrheykFINGCCc7-lJ25LMpjUB1Ez25IljDHFYpCYUiUYR4yVkUMKYIEiSiTDBSFcRgAajPAOU6ibjmShBToOwAXRf-6V31zceuXHdjHV9I0D7Jn7wgXzr4TVX_-PYw_mpLatO69_qW7R1Q-m8o9OcsGP2rM_f3lM_3n0_-aL18zWHV-eHzkik9r4_LriMt16a98Tm889sdiTASDqRwFLW3evj6_mU6i96cqInGEY4Ea0F1brlsPUXJSlCrcpogfRCzGll1YKRsqXrWEuVajq21wK5buKv5FxOV7wQy4Wz5Wwg3NRIaCbbABKopoi6BkymSSrTRCpSQ8UqEEQBmkTTBQp0VcSGBgAvYaiZCgQMUiz89wXdVtm7cNtjqvLmb5ZtHAHq2vfX9Uem2q-ZXTzw3oub0ruemj52f29hwuWeZ2_6_qylidcO5d9p3310-_wptTV3Dv1WXZzbuLsNDU2-dzDy7Sf84MxxTQKZAxWi5wO0Agci0WTOnStHklUpbPaHSi7JcsV9UbvH6_IHzHTVNeqRwLtlj6rV_T3ZehxGex2KLA3EXX4XhNmaGZdj-VjCRgy37HA4mhkJTWQbQBImhOkcbyIeQA1oiOcNBVBEdBWYlOc0IgBAREPnBYoABSavUAI0RhFuAM22T_nzs8NTTyz94YFVI0Bze_9-4utz3dvfW5B7fXL7uJWvr-1rO-uG5evusY_jlk-ynvzheOfE5IFIt4ieunAfO3bK-CWuX2w-0HbQP_fDi-8Y2NPsitNYLCfr2YLGEhrwg7LNoL3ZWKLSAzwDxNvNV_OA1L0oFVdToz5xuURswBupyoo7Ggua8Vg8kIjV5TLvcyCUDJsV7PXKuglTehn5mwBqJnvyBSEd6qpKTV5HCEOeU4mBCJSwpImmIkBd4BRGoUEUHUlMQ6amQVVVJB2CBpBwxRsH-9-cvnYnOfvd_wQAAP__hMJ751MQAAA

Binary file not shown.

View File

@@ -1,122 +1,61 @@
package container
import (
"bytes"
"encoding/base64"
"io"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/cbor"
"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 :-(
// Writer is a token container writer. It provides a convenient way to aggregate and serialize tokens together.
type Writer map[string]struct{}
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(data []byte) {
ctn[string(data)] = struct{}{}
func (ctn Writer) AddSealed(cid cid.Cid, data []byte) {
ctn[cid] = data
}
// ToBytes encode the container into raw bytes.
func (ctn Writer) ToBytes() ([]byte, error) {
return ctn.toBytes(headerRawBytes)
}
// ToBytesWriter is the same as ToBytes, but with an io.Writer.
func (ctn Writer) ToBytesWriter(w io.Writer) error {
return ctn.toWriter(headerRawBytes, w)
}
// ToBytesGzipped encode the container into gzipped bytes.
func (ctn Writer) ToBytesGzipped() ([]byte, error) {
return ctn.toBytes(headerRawBytesGzip)
}
// ToBytesGzippedWriter is the same as ToBytesGzipped, but with an io.Writer.
func (ctn Writer) ToBytesGzippedWriter(w io.Writer) error {
return ctn.toWriter(headerRawBytesGzip, w)
}
// ToBase64StdPadding encode the container into a base64 string, with standard encoding and padding.
func (ctn Writer) ToBase64StdPadding() (string, error) {
return ctn.toString(headerBase64StdPadding)
}
// ToBase64StdPaddingWriter is the same as ToBase64StdPadding, but with an io.Writer.
func (ctn Writer) ToBase64StdPaddingWriter(w io.Writer) error {
return ctn.toWriter(headerBase64StdPadding, w)
}
// ToBase64StdPaddingGzipped encode the container into a pre-gzipped base64 string, with standard encoding and padding.
func (ctn Writer) ToBase64StdPaddingGzipped() (string, error) {
return ctn.toString(headerBase64StdPaddingGzip)
}
// ToBase64StdPaddingGzippedWriter is the same as ToBase64StdPaddingGzipped, but with an io.Writer.
func (ctn Writer) ToBase64StdPaddingGzippedWriter(w io.Writer) error {
return ctn.toWriter(headerBase64StdPaddingGzip, w)
}
// ToBase64URL encode the container into base64 string, with URL-safe encoding and no padding.
func (ctn Writer) ToBase64URL() (string, error) {
return ctn.toString(headerBase64URL)
}
// ToBase64URLWriter is the same as ToBase64URL, but with an io.Writer.
func (ctn Writer) ToBase64URLWriter(w io.Writer) error {
return ctn.toWriter(headerBase64URL, w)
}
// ToBase64URL encode the container into pre-gzipped base64 string, with URL-safe encoding and no padding.
func (ctn Writer) ToBase64URLGzip() (string, error) {
return ctn.toString(headerBase64URLGzip)
}
// ToBase64URLWriter is the same as ToBase64URL, but with an io.Writer.
func (ctn Writer) ToBase64URLGzipWriter(w io.Writer) error {
return ctn.toWriter(headerBase64URLGzip, w)
}
func (ctn Writer) toBytes(header header) ([]byte, error) {
var buf bytes.Buffer
err := ctn.toWriter(header, &buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (ctn Writer) toString(header header) (string, error) {
var buf bytes.Buffer
err := ctn.toWriter(header, &buf)
if err != nil {
return "", err
}
return buf.String(), nil
}
func (ctn Writer) toWriter(header header, w io.Writer) (err error) {
encoder := header.encoder(w)
defer func() {
err = encoder.Close()
}()
node, err := qp.BuildMap(basicnode.Prototype.Any, 1, func(ma datamodel.MapAssembler) {
qp.MapEntry(ma, containerVersionTag, qp.List(int64(len(ctn)), func(la datamodel.ListAssembler) {
for data, _ := range ctn {
qp.ListEntry(la, qp.Bytes([]byte(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.BuildList(basicnode.Prototype.Any, 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(encoder, node, cbor.Encode)
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,18 +0,0 @@
package container
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestWriterDedup(t *testing.T) {
ctn := NewWriter()
_, _, sealed := randToken()
ctn.AddSealed(sealed)
require.Len(t, ctn, 1)
ctn.AddSealed(sealed)
require.Len(t, ctn, 1)
}

View File

@@ -3,28 +3,23 @@ package meta
import (
"errors"
"fmt"
"iter"
"sort"
"reflect"
"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/printer"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
"github.com/ucan-wg/go-ucan/pkg/secretbox"
)
var ErrNotFound = errors.New("key not found in meta")
var ErrUnsupported = errors.New("failure adding unsupported type to meta")
var ErrNotEncryptable = errors.New("value of this type cannot be encrypted")
var ErrNotFound = errors.New("key-value not found in meta")
// 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.
// 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
}
@@ -56,21 +51,6 @@ func (m *Meta) GetString(key string) (string, error) {
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 := secretbox.DecryptStringWithKey(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.
@@ -104,23 +84,9 @@ func (m *Meta) GetBytes(key string) ([]byte, error) {
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 := secretbox.DecryptStringWithKey(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 {
@@ -130,82 +96,33 @@ func (m *Meta) GetNode(key string) (ipld.Node, error) {
}
// Add adds a key/value pair in the meta set.
// Accepted types for val are any CBOR compatible type, or directly IPLD values.
// Accepted types for the value are: bool, string, int, int32, int64, []byte,
// and ipld.Node.
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.
// The ciphertext will be 40 bytes larger than the plaintext due to encryption overhead.
func (m *Meta) AddEncrypted(key string, val any, encryptionKey []byte) error {
var encrypted []byte
var err error
switch val := val.(type) {
case bool:
m.Values[key] = basicnode.NewBool(val)
case string:
encrypted, err = secretbox.EncryptWithKey([]byte(val), encryptionKey)
if err != nil {
return err
}
m.Values[key] = basicnode.NewString(val)
case int:
m.Values[key] = basicnode.NewInt(int64(val))
case int32:
m.Values[key] = basicnode.NewInt(int64(val))
case int64:
m.Values[key] = basicnode.NewInt(val)
case float32:
m.Values[key] = basicnode.NewFloat(float64(val))
case float64:
m.Values[key] = basicnode.NewFloat(val)
case []byte:
encrypted, err = secretbox.EncryptWithKey(val, encryptionKey)
if err != nil {
return err
}
m.Values[key] = basicnode.NewBytes(val)
case datamodel.Node:
m.Values[key] = val
default:
return ErrNotEncryptable
}
return m.Add(key, encrypted)
}
type Iterator interface {
Iter() iter.Seq2[string, ipld.Node]
}
// Include merges the provided meta into the existing one.
//
// If duplicate keys are encountered, the new value is silently dropped
// without causing an error.
func (m *Meta) Include(other Iterator) {
for key, value := range other.Iter() {
if _, ok := m.Values[key]; ok {
// don't overwrite
continue
}
m.Values[key] = value
m.Keys = append(m.Keys, key)
}
}
// Len returns the number of key/values.
func (m *Meta) Len() int {
return len(m.Values)
}
// Iter iterates over the meta key/values
func (m *Meta) Iter() iter.Seq2[string, ipld.Node] {
return func(yield func(string, ipld.Node) bool) {
for _, key := range m.Keys {
if !yield(key, m.Values[key]) {
return
}
}
return fmt.Errorf("%w: %s", ErrUnsupported, fqtn(val))
}
m.Keys = append(m.Keys, key)
return nil
}
// Equals tells if two Meta hold the same key/values.
@@ -225,41 +142,32 @@ func (m *Meta) Equals(other *Meta) bool {
}
func (m *Meta) String() string {
sort.Strings(m.Keys)
buf := strings.Builder{}
buf.WriteString("{")
var i int
for key, node := range m.Values {
buf.WriteString("\n\t")
if i > 0 {
buf.WriteString(", ")
}
i++
buf.WriteString(key)
buf.WriteString(": ")
buf.WriteString(strings.ReplaceAll(printer.Sprint(node), "\n", "\n\t"))
buf.WriteString(",")
buf.WriteString(":")
buf.WriteString(printer.Sprint(node))
}
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{meta: m}
}
func fqtn(val any) string {
var name string
// Clone makes a deep copy.
func (m *Meta) Clone() *Meta {
res := &Meta{
Keys: make([]string, len(m.Keys)),
Values: make(map[string]ipld.Node, len(m.Values)),
t := reflect.TypeOf(val)
for t.Kind() == reflect.Pointer {
name += "*"
t = t.Elem()
}
copy(res.Keys, m.Keys)
for k, v := range m.Values {
res.Values[k] = v
}
return res
return name + t.PkgPath() + "." + t.Name()
}

View File

@@ -1,15 +1,11 @@
package meta_test
import (
"crypto/rand"
"maps"
"testing"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/meta"
"gotest.tools/v3/assert"
)
func TestMeta_Add(t *testing.T) {
@@ -17,114 +13,11 @@ func TestMeta_Add(t *testing.T) {
type Unsupported struct{}
t.Run("error if not primitive or Node", func(t *testing.T) {
t.Run("error if not primative 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")
})
require.ErrorIs(t, err, meta.ErrUnsupported)
assert.ErrorContains(t, err, "*github.com/ucan-wg/go-ucan/pkg/meta_test.Unsupported")
})
}
func TestIterCloneEquals(t *testing.T) {
m := meta.NewMeta()
require.NoError(t, m.Add("foo", "bar"))
require.NoError(t, m.Add("baz", 1234))
expected := map[string]ipld.Node{
"foo": basicnode.NewString("bar"),
"baz": basicnode.NewInt(1234),
}
// meta -> iter
require.Equal(t, expected, maps.Collect(m.Iter()))
// readonly -> iter
ro := m.ReadOnly()
require.Equal(t, expected, maps.Collect(ro.Iter()))
// meta -> clone -> iter
clone := m.Clone()
require.Equal(t, expected, maps.Collect(clone.Iter()))
// readonly -> WriteableClone -> iter
wclone := ro.WriteableClone()
require.Equal(t, expected, maps.Collect(wclone.Iter()))
require.True(t, m.Equals(wclone))
require.True(t, ro.Equals(wclone.ReadOnly()))
}
func TestInclude(t *testing.T) {
m1 := meta.NewMeta()
require.NoError(t, m1.Add("samekey", "bar"))
require.NoError(t, m1.Add("baz", 1234))
m2 := meta.NewMeta()
require.NoError(t, m2.Add("samekey", "othervalue")) // check no overwrite
require.NoError(t, m2.Add("otherkey", 1234))
m1.Include(m2)
require.Equal(t, map[string]ipld.Node{
"samekey": basicnode.NewString("bar"),
"baz": basicnode.NewInt(1234),
"otherkey": basicnode.NewInt(1234),
}, maps.Collect(m1.Iter()))
}

View File

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

View File

@@ -9,15 +9,10 @@ import (
"github.com/ipld/go-ipld-prime/must"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ucan-wg/go-ucan/pkg/policy/limits"
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
)
func FromIPLD(node datamodel.Node) (Policy, error) {
if err := limits.ValidateIntegerBoundsIPLD(node); err != nil {
return nil, fmt.Errorf("policy contains integer values outside safe bounds: %w", err)
}
return statementsFromIPLD("/", node)
}
@@ -81,7 +76,7 @@ func statementFromIPLD(path string, node datamodel.Node) (Statement, error) {
}
case 3:
switch op {
case KindEqual, KindNotEqual, KindLessThan, KindLessThanOrEqual, KindGreaterThan, KindGreaterThanOrEqual:
case KindEqual, KindLessThan, KindLessThanOrEqual, KindGreaterThan, KindGreaterThanOrEqual:
sel, err := arg2AsSelector(op)
if err != nil {
return nil, err

View File

@@ -1,14 +1,11 @@
package policy
import (
"fmt"
"testing"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)
func TestIpldRoundTrip(t *testing.T) {
@@ -24,31 +21,10 @@ func TestIpldRoundTrip(t *testing.T) {
]
]`
// must contain all the operators
const allOps = `
[
["and", [
["==", ".foo1", ".bar1"],
["!=", ".foo2", ".bar2"]
]],
["or", [
[">", ".foo5", 5.2],
[">=", ".foo6", 6.2]
]],
["not", ["like", ".foo7", "*@example.com"]],
["all", ".foo8",
["<", ".foo3", 3]
],
["any", ".foo9",
["<=", ".foo4", 4]
]
]`
for _, tc := range []struct {
name, dagJsonStr string
}{
{"illustrativeExample", illustrativeExample},
{"allOps", allOps},
} {
nodes, err := ipld.Decode([]byte(tc.dagJsonStr), dagjson.Decode)
require.NoError(t, err)
@@ -65,19 +41,3 @@ func TestIpldRoundTrip(t *testing.T) {
require.JSONEq(t, tc.dagJsonStr, string(wroteAsDagJson))
}
}
func TestFoo(t *testing.T) {
fmt.Println(MustConstruct(
And(
Equal(".foo1", literal.String(".bar1")),
NotEqual(".foo2", literal.String(".bar2")),
),
Or(
GreaterThan(".foo5", literal.Float(5.2)),
GreaterThanOrEqual(".foo6", literal.Float(6.2)),
),
Not(Like(".foo7", "*@example.com")),
All(".foo8", LessThan(".foo3", literal.Int(3))),
Any(".foo9", LessThanOrEqual(".foo4", literal.Int(4))),
))
}

View File

@@ -1,49 +0,0 @@
package limits
import (
"fmt"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/must"
)
const (
// MaxInt53 represents the maximum safe integer in JavaScript (2^53 - 1)
MaxInt53 = 9007199254740991
// MinInt53 represents the minimum safe integer in JavaScript (-2^53 + 1)
MinInt53 = -9007199254740991
)
func ValidateIntegerBoundsIPLD(node ipld.Node) error {
switch node.Kind() {
case ipld.Kind_Int:
val := must.Int(node)
if val > MaxInt53 || val < MinInt53 {
return fmt.Errorf("integer value %d exceeds safe bounds", val)
}
case ipld.Kind_List:
it := node.ListIterator()
for !it.Done() {
_, v, err := it.Next()
if err != nil {
return err
}
if err := ValidateIntegerBoundsIPLD(v); err != nil {
return err
}
}
case ipld.Kind_Map:
it := node.MapIterator()
for !it.Done() {
_, v, err := it.Next()
if err != nil {
return err
}
if err := ValidateIntegerBoundsIPLD(v); err != nil {
return err
}
}
}
return nil
}

View File

@@ -1,82 +0,0 @@
package limits
import (
"testing"
"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/stretchr/testify/require"
)
func TestValidateIntegerBoundsIPLD(t *testing.T) {
buildMap := func() datamodel.Node {
nb := basicnode.Prototype.Any.NewBuilder()
qp.Map(1, func(ma datamodel.MapAssembler) {
qp.MapEntry(ma, "foo", qp.Int(MaxInt53+1))
})(nb)
return nb.Build()
}
buildList := func() datamodel.Node {
nb := basicnode.Prototype.Any.NewBuilder()
qp.List(1, func(la datamodel.ListAssembler) {
qp.ListEntry(la, qp.Int(MinInt53-1))
})(nb)
return nb.Build()
}
tests := []struct {
name string
input datamodel.Node
wantErr bool
}{
{
name: "valid int",
input: basicnode.NewInt(42),
wantErr: false,
},
{
name: "max safe int",
input: basicnode.NewInt(MaxInt53),
wantErr: false,
},
{
name: "min safe int",
input: basicnode.NewInt(MinInt53),
wantErr: false,
},
{
name: "above MaxInt53",
input: basicnode.NewInt(MaxInt53 + 1),
wantErr: true,
},
{
name: "below MinInt53",
input: basicnode.NewInt(MinInt53 - 1),
wantErr: true,
},
{
name: "nested map with invalid int",
input: buildMap(),
wantErr: true,
},
{
name: "nested list with invalid int",
input: buildList(),
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateIntegerBoundsIPLD(tt.input)
if tt.wantErr {
require.Error(t, err)
require.Contains(t, err.Error(), "exceeds safe bounds")
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -2,26 +2,47 @@
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"
"github.com/ucan-wg/go-ucan/pkg/policy/limits"
)
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 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 Link(link ipld.Link) ipld.Node {
nb := basicnode.Prototype.Link.NewBuilder()
nb.AssignLink(link)
return nb.Build()
}
func LinkCid(cid cid.Cid) ipld.Node {
return Link(cidlink.Link{Cid: cid})
@@ -32,174 +53,3 @@ func Null() ipld.Node {
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) {
// some fast path
switch val := v.(type) {
case bool:
return basicnode.NewBool(val), nil
case string:
return basicnode.NewString(val), nil
case int:
i := int64(val)
if i > limits.MaxInt53 || i < limits.MinInt53 {
return nil, fmt.Errorf("integer value %d exceeds safe integer bounds", i)
}
return basicnode.NewInt(i), 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:
if val > limits.MaxInt53 || val < limits.MinInt53 {
return nil, fmt.Errorf("integer value %d exceeds safe integer bounds", val)
}
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:
if val > uint64(limits.MaxInt53) {
return nil, fmt.Errorf("unsigned integer value %d exceeds safe integer bounds", val)
}
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:
i := rv.Int()
if i > limits.MaxInt53 || i < limits.MinInt53 {
panic(fmt.Sprintf("integer %d exceeds safe bounds", i))
}
return qp.Int(i)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
u := rv.Uint()
if u > limits.MaxInt53 {
panic(fmt.Sprintf("unsigned integer %d exceeds safe bounds", u))
}
return qp.Int(int64(u))
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,314 +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"
"github.com/ucan-wg/go-ucan/pkg/policy/limits"
)
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")))
_, 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 TestAnyAssembleIntegerOverflow(t *testing.T) {
tests := []struct {
name string
input interface{}
shouldErr bool
}{
{
name: "valid int",
input: 42,
shouldErr: false,
},
{
name: "max safe int",
input: limits.MaxInt53,
shouldErr: false,
},
{
name: "min safe int",
input: limits.MinInt53,
shouldErr: false,
},
{
name: "overflow int",
input: int64(limits.MaxInt53 + 1),
shouldErr: true,
},
{
name: "underflow int",
input: int64(limits.MinInt53 - 1),
shouldErr: true,
},
{
name: "overflow uint",
input: uint64(limits.MaxInt53 + 1),
shouldErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := Any(tt.input)
if tt.shouldErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func must[T any](t T, err error) T {
if err != nil {
panic(err)
}
return t
}

View File

@@ -3,7 +3,6 @@ package policy
import (
"cmp"
"fmt"
"math"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
@@ -11,272 +10,231 @@ import (
)
// 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) {
func (p Policy) Match(node datamodel.Node) bool {
for _, stmt := range p {
res, leaf := matchStatement(stmt, node)
switch res {
case matchResultNoData, matchResultFalse:
return false, leaf
case matchResultOptionalNoData, matchResultTrue:
// continue
ok := matchStatement(stmt, node)
if !ok {
return false
}
}
return true, nil
return true
}
// 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) {
// Filter performs a recursive filtering of the Statement, and prunes what doesn't match the given path
func (p Policy) Filter(path ...string) Policy {
var filtered Policy
for _, stmt := range p {
res, leaf := matchStatement(stmt, node)
switch res {
case matchResultFalse:
return false, leaf
case matchResultNoData, matchResultOptionalNoData, matchResultTrue:
// continue
newChild, remain := filter(stmt, path)
if newChild != nil && len(remain) == 0 {
filtered = append(filtered, newChild)
}
}
return true, nil
return filtered
}
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() {
func matchStatement(statement Statement, node ipld.Node) bool {
switch statement.Kind() {
case KindEqual:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if s, ok := statement.(equality); ok {
one, many, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
return false
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
if one != nil {
return datamodel.DeepEqual(s.value, one)
}
return boolToRes(datamodel.DeepEqual(s.value, res))
}
case KindNotEqual:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
if many != nil {
for _, n := range many {
if eq := datamodel.DeepEqual(s.value, n); eq {
return true
}
}
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(!datamodel.DeepEqual(s.value, res))
return false
}
case KindGreaterThan:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
if s, ok := statement.(equality); ok {
one, _, err := s.selector.Select(node)
if err != nil || one == nil {
return false
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(isOrdered(s.value, res, gt))
return isOrdered(s.value, one, gt)
}
case KindGreaterThanOrEqual:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
if s, ok := statement.(equality); ok {
one, _, err := s.selector.Select(node)
if err != nil || one == nil {
return false
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(isOrdered(s.value, res, gte))
return isOrdered(s.value, one, gte)
}
case KindLessThan:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
if s, ok := statement.(equality); ok {
one, _, err := s.selector.Select(node)
if err != nil || one == nil {
return false
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(isOrdered(s.value, res, lt))
return isOrdered(s.value, one, lt)
}
case KindLessThanOrEqual:
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
if s, ok := statement.(equality); ok {
one, _, err := s.selector.Select(node)
if err != nil || one == nil {
return false
}
if res == nil { // optional selector didn't match
return matchResultOptionalNoData, nil
}
return boolToRes(isOrdered(s.value, res, lte))
return isOrdered(s.value, one, 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
}
if s, ok := statement.(negation); ok {
return !matchStatement(s.statement, node)
}
case KindAnd:
if s, ok := cur.(connective); ok {
if s, ok := statement.(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
r := matchStatement(cs, node)
if !r {
return false
}
}
return matchResultTrue, nil
return true
}
case KindOr:
if s, ok := cur.(connective); ok {
if s, ok := statement.(connective); ok {
if len(s.statements) == 0 {
return matchResultTrue, nil
return true
}
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
r := matchStatement(cs, node)
if r {
return true
}
}
return matchResultFalse, cur
return false
}
case KindLike:
if s, ok := cur.(wildcard); ok {
res, err := s.selector.Select(node)
if s, ok := statement.(wildcard); ok {
one, _, err := s.selector.Select(node)
if err != nil || one == nil {
return false
}
v, err := one.AsString()
if err != nil {
return matchResultNoData, cur
return false
}
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))
return 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 s, ok := statement.(quantifier); ok {
_, many, err := s.selector.Select(node)
if err != nil || many == nil {
return false
}
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
for _, n := range many {
ok := matchStatement(s.statement, n)
if !ok {
return false
}
}
return matchResultTrue, nil
return true
}
case KindAny:
if s, ok := cur.(quantifier); ok {
res, err := s.selector.Select(node)
if s, ok := statement.(quantifier); ok {
one, many, err := s.selector.Select(node)
if err != nil {
return matchResultNoData, cur
return false
}
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
if one != nil {
ok := matchStatement(s.statement, one)
if ok {
return true
}
}
return matchResultFalse, cur
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", cur.Kind()))
panic(fmt.Errorf("unimplemented statement kind: %s", statement.Kind()))
}
// filter performs a recursive filtering of the Statement, and prunes what doesn't match the given path
func filter(stmt Statement, path []string) (Statement, []string) {
// For each kind, we do some of the following if it applies:
// - test the path against the selector, consuming segments
// - for terminal statements (equality, wildcard), require all the segments to have been consumed
// - recursively filter child (negation, quantifier) or children (connective) statements with the remaining path
switch stmt.(type) {
case equality:
match, remain := stmt.(equality).selector.MatchPath(path...)
if match && len(remain) == 0 {
return stmt, remain
}
return nil, nil
case negation:
newChild, remain := filter(stmt.(negation).statement, path)
if newChild != nil && len(remain) == 0 {
return negation{
statement: newChild,
}, nil
}
return nil, nil
case connective:
var newChildren []Statement
for _, child := range stmt.(connective).statements {
newChild, remain := filter(child, path)
if newChild != nil && len(remain) == 0 {
newChildren = append(newChildren, newChild)
}
}
if len(newChildren) == 0 {
return nil, nil
}
return connective{
kind: stmt.(connective).kind,
statements: newChildren,
}, nil
case wildcard:
match, remain := stmt.(wildcard).selector.MatchPath(path...)
if match && len(remain) == 0 {
return stmt, remain
}
return nil, nil
case quantifier:
match, remain := stmt.(quantifier).selector.MatchPath(path...)
if match && len(remain) == 0 {
return stmt, remain
}
if !match {
return nil, nil
}
newChild, remain := filter(stmt.(quantifier).statement, remain)
if newChild != nil && len(remain) == 0 {
return quantifier{
kind: stmt.(quantifier).kind,
selector: stmt.(quantifier).selector,
statement: newChild,
}, nil
}
return nil, nil
default:
panic(fmt.Errorf("unimplemented statement kind: %s", stmt.Kind()))
}
}
// isOrdered compares two IPLD nodes and returns true if they satisfy the given ordering function.
// It supports comparison of integers and floats, returning false for:
// - Nodes of different or unsupported kinds
// - Integer values outside JavaScript's safe integer bounds (±2^53-1)
// - Non-finite floating point values (NaN or ±Inf)
//
// The satisfies parameter is a function that interprets the comparison result:
// - For ">" it returns true when order is 1
// - For ">=" it returns true when order is 0 or 1
// - For "<" it returns true when order is -1
// - For "<=" it returns true when order is -1 or 0
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))
}
@@ -289,11 +247,6 @@ func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) b
if err != nil {
panic(fmt.Errorf("extracting selector float: %w", err))
}
if math.IsInf(a, 0) || math.IsNaN(a) || math.IsInf(b, 0) || math.IsNaN(b) {
return false
}
return satisfies(cmp.Compare(a, b))
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,6 @@ import (
const (
KindEqual = "==" // implemented by equality
KindNotEqual = "!=" // implemented by equality
KindGreaterThan = ">" // implemented by equality
KindGreaterThanOrEqual = ">=" // implemented by equality
KindLessThan = "<" // implemented by equality
@@ -88,13 +87,6 @@ func Equal(selector string, value ipld.Node) Constructor {
}
}
func NotEqual(selector string, value ipld.Node) Constructor {
return func() (Statement, error) {
sel, err := selpkg.Parse(selector)
return equality{kind: KindNotEqual, selector: sel, value: value}, err
}
}
func GreaterThan(selector string, value ipld.Node) Constructor {
return func() (Statement, error) {
sel, err := selpkg.Parse(selector)
@@ -133,7 +125,7 @@ func (n negation) Kind() string {
func (n negation) String() string {
child := n.statement.String()
return fmt.Sprintf(`["%s", %s]`, n.Kind(), strings.ReplaceAll(child, "\n", "\n "))
return fmt.Sprintf(`["%s", "%s"]`, n.Kind(), strings.ReplaceAll(child, "\n", "\n "))
}
func Not(cstor Constructor) Constructor {
@@ -157,7 +149,7 @@ func (c connective) String() string {
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 "))
return fmt.Sprintf("[\"%s\", [\n %s]]\n", c.kind, strings.Join(childs, ",\n "))
}
func And(cstors ...Constructor) Constructor {
@@ -216,7 +208,7 @@ func (n quantifier) Kind() string {
func (n quantifier) String() string {
child := n.statement.String()
return fmt.Sprintf("[\"%s\", \"%s\",\n %s\n]", n.Kind(), n.selector, strings.ReplaceAll(child, "\n", "\n "))
return fmt.Sprintf("[\"%s\", \"%s\",\n %s]", n.Kind(), n.selector, strings.ReplaceAll(child, "\n", "\n "))
}
func All(selector string, cstor Constructor) Constructor {

View File

@@ -28,47 +28,12 @@ func ExamplePolicy() {
// [
// ["==", ".status", "draft"],
// ["all", ".reviewer",
// ["like", ".email", "*@example.com"]
// ],
// ["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"]
// ]]
// ]
// ["==", ".", "press"]]]
// ]
// ]
}

View File

@@ -1,67 +0,0 @@
package policytest
import (
"github.com/ipld/go-ipld-prime"
"github.com/ucan-wg/go-ucan/pkg/args"
"github.com/ucan-wg/go-ucan/pkg/policy"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
)
// EmptyPolicy provides a Policy with no statements.
var EmptyPolicy = policy.Policy{}
// SpecPolicy provides a valid Policy containing the statements that are included
// in the second code block of the [Validation] section of the delegation specification.
//
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
var SpecPolicy = policy.MustConstruct(
policy.Equal(".from", literal.String("alice@example.com")),
policy.Any(".to", policy.Like(".", "*@example.com")),
)
// TODO: Replace the URL for [Validation] above when the delegation
// specification has been finished/merged.
// SpecValidArguments provides valid, instantiated Arguments containing
// the key/value pairs that are included in portion of the second code block
// of the [Validation] section of the delegation specification.
//
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
var SpecValidArguments = args.NewBuilder().
Add("from", "alice@example.com").
Add("to", []string{
"bob@example.com",
"carol@not.example.com",
}).
Add("title", "Coffee").
Add("body", "Still on for coffee").
MustBuild()
var specValidArgumentsIPLD = mustIPLD(SpecValidArguments)
// SpecInvalidArguments provides invalid, instantiated Arguments containing
// the key/value pairs that are included in portion of the second code block
// of the [Validation] section of the delegation specification.
//
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
var SpecInvalidArguments = args.NewBuilder().
Add("from", "alice@example.com").
Add("to", []string{
"bob@null.com",
"carol@elsewhere.example.com",
}).
Add("title", "Coffee").
Add("body", "Still on for coffee").
MustBuild()
var specInvalidArgumentsIPLD = mustIPLD(SpecInvalidArguments)
func mustIPLD(args *args.Args) ipld.Node {
node, err := args.ToIPLD()
if err != nil {
panic(err)
}
return node
}

View File

@@ -1,32 +0,0 @@
package policytest
import (
"testing"
"github.com/stretchr/testify/assert"
)
// 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()
t.Run("with passing args", func(t *testing.T) {
t.Parallel()
exec, stmt := SpecPolicy.Match(specValidArgumentsIPLD)
assert.True(t, exec)
assert.Nil(t, stmt)
})
t.Run("fails on recipients (second statement)", func(t *testing.T) {
t.Parallel()
exec, stmt := SpecPolicy.Match(specInvalidArgumentsIPLD)
assert.False(t, exec)
assert.NotNil(t, stmt)
})
}

View File

@@ -2,28 +2,17 @@ package selector
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
"github.com/ucan-wg/go-ucan/pkg/policy/limits"
"unicode/utf8"
)
var (
identity = Selector{segment{str: ".", identity: true}}
indexRegex = regexp.MustCompile(`^-?\d+$`)
sliceRegex = regexp.MustCompile(`^((\-?\d+:\-?\d*)|(\-?\d*:\-?\d+))$`)
// Field name requirements:
// - Must start with ASCII letter, Unicode letter, or underscore
// - Can contain:
// - ASCII letters (a-z, A-Z)
// - ASCII digits (0-9)
// - Unicode letters (\p{L})
// - Dollar sign ($)
// - Underscore (_)
// - Hyphen (-)
fieldRegex = regexp.MustCompile(`^\.[a-zA-Z_\p{L}][a-zA-Z0-9$_\p{L}\-]*$`)
fieldRegex = regexp.MustCompile(`^\.[a-zA-Z_]*?$`)
)
func Parse(str string) (Selector, error) {
@@ -34,10 +23,7 @@ func Parse(str string) (Selector, error) {
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
return identity, nil
}
col := 0
@@ -46,79 +32,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})
case seg == "[]":
case "[]":
sel = append(sel, segment{str: tok, optional: opt, iterator: true})
default:
if strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]") {
lookup := seg[1 : len(seg)-1]
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)
}
if idx > limits.MaxInt53 || idx < limits.MinInt53 {
return nil, newParseError(fmt.Sprintf("index %d exceeds safe integer bounds", idx), 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)
}
if i > limits.MaxInt53 || i < limits.MinInt53 {
return nil, newParseError(fmt.Sprintf("slice index %d exceeds safe integer bounds", i), 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)
}
if i > limits.MaxInt53 || i < limits.MinInt53 {
return nil, newParseError(fmt.Sprintf("slice index %d exceeds safe integer bounds", i), 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)
}
@@ -140,10 +103,10 @@ func tokenize(str string) []string {
ctx := ""
for col < len(str) {
char := string(str[col])
char, size := utf8.DecodeRuneInString(str[col:])
if char == "\"" && string(str[col-1]) != "\\" {
col++
if char == '"' && (col == 0 || str[col-1] != '\\') {
col += size
if ctx == "\"" {
ctx = ""
} else {
@@ -153,17 +116,17 @@ func tokenize(str string) []string {
}
if ctx == "\"" {
col++
col += size
continue
}
if char == "." || char == "[" {
if char == '.' || char == '[' {
if ofs < col {
toks = append(toks, str[ofs:col])
}
ofs = col
}
col++
col += size
}
if ofs < col && ctx != "\"" {
@@ -173,37 +136,37 @@ func tokenize(str string) []string {
return toks
}
type parseErr struct {
type parseerr struct {
msg string
src string
col int
tok string
}
func (p parseErr) Name() string {
func (p parseerr) Name() string {
return "ParseError"
}
func (p parseErr) Message() string {
func (p parseerr) Message() string {
return p.msg
}
func (p parseErr) Column() int {
func (p parseerr) Column() int {
return p.col
}
func (p parseErr) Error() string {
func (p parseerr) Error() string {
return p.msg
}
func (p parseErr) Source() string {
func (p parseerr) Source() string {
return p.src
}
func (p parseErr) Token() string {
func (p parseerr) Token() string {
return p.tok
}
func newParseError(message string, source string, column int, token string) error {
return parseErr{message, source, column, token}
return parseerr{message, source, column, token}
}

View File

@@ -1,641 +1,30 @@
package selector
import (
"fmt"
"math"
"testing"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/policy/limits"
)
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())
func TestTokenizeUTF8(t *testing.T) {
t.Run("simple UTF-8", func(t *testing.T) {
str := ".こんにちは[0]"
expected := []string{".", "こんにちは", "[0]"}
actual := tokenize(str)
require.Equal(t, expected, actual)
})
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())
sel, err = Parse(".foo_bar")
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_bar")
require.Empty(t, sel[0].Index())
sel, err = Parse(".foo-bar")
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-bar")
require.Empty(t, sel[0].Index())
sel, err = Parse(".foo*bar")
require.ErrorContains(t, err, "invalid segment")
t.Run("UTF-8 with quotes", func(t *testing.T) {
str := ".こんにちは[\"привет\"]"
expected := []string{".", "こんにちは", "[\"привет\"]"}
actual := tokenize(str)
require.Equal(t, expected, actual)
})
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)
})
t.Run("extended field names", func(t *testing.T) {
validFields := []string{
".basic",
".user_name",
".user-name",
".userName$special",
".αβγ", // Greek letters
".użytkownik", // Polish characters
".用户", // Chinese characters
".사용자", // Korean characters
"._private",
".number123",
".camelCase",
".snake_case",
".kebab-case",
".mixed_kebab-case",
".with$dollar",
".MIXED_Case_123",
".unicodeø",
}
for _, field := range validFields {
sel, err := Parse(field)
require.NoError(t, err, "field: %s", field)
require.NotNil(t, sel)
}
invalidFields := []string{
".123number", // Can't start with digit
".@special", // @ not allowed
".space name", // No spaces
".#hashtag", // No #
".name!", // No !
".{brackets}", // No brackets
".name/with/slashes", // No slashes
}
for _, field := range invalidFields {
sel, err := Parse(field)
require.Error(t, err, "field: %s", field)
require.Nil(t, sel)
}
})
t.Run("integer overflow", func(t *testing.T) {
sel, err := Parse(fmt.Sprintf(".[%d]", limits.MaxInt53+1))
require.Error(t, err)
require.Nil(t, sel)
sel, err = Parse(fmt.Sprintf(".[%d]", limits.MinInt53-1))
require.Error(t, err)
require.Nil(t, sel)
// Test slice overflow
sel, err = Parse(fmt.Sprintf(".[%d:42]", limits.MaxInt53+1))
require.Error(t, err)
require.Nil(t, sel)
sel, err = Parse(fmt.Sprintf(".[1:%d]", limits.MaxInt53+1))
require.Error(t, err)
require.Nil(t, sel)
t.Run("UTF-8 with escaped quotes", func(t *testing.T) {
str := ".こんにちは[\"привет \\\"мир\\\"\"]"
expected := []string{".", "こんにちは", "[\"привет \\\"мир\\\"\"]"}
actual := tokenize(str)
require.Equal(t, expected, actual)
})
}

View File

@@ -2,14 +2,12 @@ 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"
"github.com/ipld/go-ipld-prime/schema"
)
// Selector describes a UCAN policy selector, as specified here:
@@ -17,14 +15,17 @@ import (
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) {
// If no error, Select returns either one ipld.Node or a []ipld.Node.
func (s Selector) Select(subject ipld.Node) (ipld.Node, []ipld.Node, error) {
return resolve(s, subject, nil)
}
// MatchPath tells if the selector operates on the given (string only) path segments.
// It returns the segments that didn't get consumed by the matching.
func (s Selector) MatchPath(pathSegment ...string) (bool, []string) {
return matchPath(s, pathSegment)
}
func (s Selector) String() string {
var res strings.Builder
for _, seg := range s {
@@ -38,7 +39,7 @@ type segment struct {
identity bool
optional bool
iterator bool
slice []int64 // either 0-length or 2-length
slice []int
field string
index int
}
@@ -64,7 +65,7 @@ func (s segment) Iterator() bool {
}
// Slice flags that this segment targets a range of a slice.
func (s segment) Slice() []int64 {
func (s segment) Slice() []int {
return s.slice
}
@@ -78,168 +79,335 @@ 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
}
func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.Node, error) {
cur := subject
for _, seg := range sel {
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: // Index()
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
}
func matchPath(sel Selector, path []string) (bool, []string) {
for _, seg := range sel {
if len(path) == 0 {
return true, path
}
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)
}
// we have reached a [] iterator, it should have matched earlier
return false, nil
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)
// if exact match on the segment, we continue
if path[0] == seg.Field() {
path = path[1:]
continue
}
return false, nil
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)
}
case seg.Slice() != nil:
// we have reached a [<int>:<int>] slicing, it should have matched earlier
return false, nil
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)
}
// we have reached a [<int>] indexing, it should have matched earlier
return false, nil
}
}
// segment exhausted, we return where we are
return cur, nil
return true, path
}
// resolveSliceIndices resolves the start and end indices for slicing a list or byte array.
@@ -253,60 +421,29 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, error) {
//
// 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")
// - 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
}
}
}
start, end = slice[0], slice[1]
// adjust boundaries
switch {
case slice[0] == math.MinInt:
start = 0
case slice[0] < 0:
// Check for potential overflow before adding
if -slice[0] > length {
start = 0
} else {
start = length + slice[0]
if len(slice) > 1 {
end = int64(slice[1])
if end <= 0 {
end = length + end
if end < start {
end = start
}
}
}
switch {
case slice[1] == math.MaxInt:
end = length
case slice[1] < 0:
// Check for potential overflow before adding
if -slice[1] > length {
end = 0
} else {
end = length + slice[1]
}
}
// backward iteration is not allowed, shortcut to an empty result
if start >= end {
start, end = 0, 0
return
}
// clamp out of bound
if start < 0 {
start = 0
}
if start > length {
start = length
}
if end < 0 {
end = 0
}
if end > length {
end = length
}
return
return start, end
}
func kindString(n datamodel.Node) string {
@@ -316,27 +453,40 @@ func kindString(n datamodel.Node) string {
return n.Kind().String()
}
type resolutionErr struct {
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 {
func (r resolutionerr) Name() string {
return "ResolutionError"
}
func (r resolutionErr) Message() string {
func (r resolutionerr) Message() string {
return fmt.Sprintf("can not resolve path: .%s", strings.Join(r.at, "."))
}
func (r resolutionErr) At() []string {
func (r resolutionerr) At() []string {
return r.at
}
func (r resolutionErr) Error() string {
func (r resolutionerr) Error() string {
return r.Message()
}
func newResolutionError(message string, at []string) error {
return resolutionErr{message, at}
return resolutionerr{message, at}
}

View File

@@ -1,21 +1,252 @@
package selector
import (
"errors"
"math"
"fmt"
"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/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
@@ -82,11 +313,14 @@ func TestSelect(t *testing.T) {
sel, err := Parse(".")
require.NoError(t, err)
res, err := sel.Select(anode)
one, many, err := sel.Select(anode)
require.NoError(t, err)
require.NotEmpty(t, res)
require.NotEmpty(t, one)
require.Empty(t, many)
age := must.Int(must.Node(res.LookupByString("age")))
fmt.Println(printer.Sprint(one))
age := must.Int(must.Node(one.LookupByString("age")))
require.Equal(t, int64(alice.Age), age)
})
@@ -94,18 +328,24 @@ func TestSelect(t *testing.T) {
sel, err := Parse(".name.first")
require.NoError(t, err)
res, err := sel.Select(anode)
one, many, err := sel.Select(anode)
require.NoError(t, err)
require.NotEmpty(t, res)
require.NotEmpty(t, one)
require.Empty(t, many)
name := must.String(res)
fmt.Println(printer.Sprint(one))
name := must.String(one)
require.Equal(t, alice.Name.First, name)
res, err = sel.Select(bnode)
one, many, err = sel.Select(bnode)
require.NoError(t, err)
require.NotEmpty(t, res)
require.NotEmpty(t, one)
require.Empty(t, many)
name = must.String(res)
fmt.Println(printer.Sprint(one))
name = must.String(one)
require.Equal(t, bob.Name.First, name)
})
@@ -113,178 +353,108 @@ func TestSelect(t *testing.T) {
sel, err := Parse(".name.middle?")
require.NoError(t, err)
res, err := sel.Select(anode)
one, many, err := sel.Select(anode)
require.NoError(t, err)
require.NotEmpty(t, res)
require.NotEmpty(t, one)
require.Empty(t, many)
name := must.String(res)
fmt.Println(printer.Sprint(one))
name := must.String(one)
require.Equal(t, *alice.Name.Middle, name)
res, err = sel.Select(bnode)
one, many, err = sel.Select(bnode)
require.NoError(t, err)
require.Empty(t, res)
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)
res, err := sel.Select(anode)
one, many, err := sel.Select(anode)
require.Error(t, err)
require.Empty(t, res)
require.Empty(t, one)
require.Empty(t, many)
require.ErrorAs(t, err, &resolutionErr{}, "error should be a resolution error")
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, err := sel.Select(anode)
one, many, err := sel.Select(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)
res, err := sel.Select(anode)
one, many, err := sel.Select(anode)
require.NoError(t, err)
require.NotEmpty(t, res)
require.Empty(t, one)
require.NotEmpty(t, many)
iname := must.String(must.Node(must.Node(res.LookupByIndex(0)).LookupByString("name")))
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(must.Node(res.LookupByIndex(1)).LookupByString("name")))
iname = must.String(must.Node(many[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]`)
t.Run("map iterator", func(t *testing.T) {
sel, err := Parse(".interests[0][]")
require.NoError(t, err)
node := basicnode.NewString("hello")
res, err := sel.Select(node)
one, many, err := sel.Select(anode)
require.NoError(t, err)
require.NotEmpty(t, res)
require.Empty(t, one)
require.NotEmpty(t, many)
str, err := res.AsString()
require.NoError(t, err)
require.Equal(t, "el", str) // assert sliced substring
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])))
})
}
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))
func TestMatch(t *testing.T) {
for _, tc := range []struct {
sel string
path []string
want bool
remaining []string
}{
{sel: ".foo.bar", path: []string{"foo", "bar"}, want: true, remaining: []string{}},
{sel: ".foo.bar", path: []string{"foo"}, want: true, remaining: []string{}},
{sel: ".foo.bar", path: []string{"foo", "bar", "baz"}, want: true, remaining: []string{"baz"}},
{sel: ".foo.bar", path: []string{"foo", "faa"}, want: false},
{sel: ".foo.[]", path: []string{"foo", "faa"}, want: false},
{sel: ".foo.[]", path: []string{"foo"}, want: true, remaining: []string{}},
{sel: ".foo.bar?", path: []string{"foo"}, want: true, remaining: []string{}},
{sel: ".foo.bar?", path: []string{"foo", "bar"}, want: true, remaining: []string{}},
{sel: ".foo.bar?", path: []string{"foo", "baz"}, want: false},
} {
t.Run(tc.sel, func(t *testing.T) {
sel := MustParse(tc.sel)
res, remain := sel.MatchPath(tc.path...)
require.Equal(t, tc.want, res)
require.EqualValues(t, tc.remaining, remain)
})
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) {
@@ -350,64 +520,6 @@ func FuzzParseAndSelect(f *testing.F) {
}
// 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)
}
_, _, _ = sel.Select(node)
})
}
func TestResolveSliceIndices(t *testing.T) {
tests := []struct {
name string
slice []int64
length int64
wantStart int64
wantEnd int64
}{
{
name: "normal case",
slice: []int64{1, 3},
length: 5,
wantStart: 1,
wantEnd: 3,
},
{
name: "negative indices",
slice: []int64{-2, -1},
length: 5,
wantStart: 3,
wantEnd: 4,
},
{
name: "overflow protection negative start",
slice: []int64{math.MinInt64, 3},
length: 5,
wantStart: 0,
wantEnd: 3,
},
{
name: "overflow protection negative end",
slice: []int64{0, math.MinInt64},
length: 5,
wantStart: 0,
wantEnd: 0,
},
{
name: "max bounds",
slice: []int64{0, math.MaxInt64},
length: 5,
wantStart: 0,
wantEnd: 5,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
start, end := resolveSliceIndices(tt.slice, tt.length)
require.Equal(t, tt.wantStart, start)
require.Equal(t, tt.wantEnd, end)
})
}
}

View File

@@ -1,12 +1,15 @@
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"
@@ -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 := sel.Select(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 := sel.Select(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 := sel.Select(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

@@ -1,90 +0,0 @@
package secretbox
import (
"crypto/rand"
"errors"
"fmt"
"io"
"golang.org/x/crypto/nacl/secretbox"
)
const keySize = 32 // secretbox allows only 32-byte keys
var ErrShortCipherText = errors.New("ciphertext too short")
var ErrNoEncryptionKey = errors.New("encryption key is required")
var ErrInvalidKeySize = errors.New("invalid key size: must be 32 bytes")
var ErrZeroKey = errors.New("encryption key cannot be all zeros")
// GenerateKey generates a random 32-byte key to be used by EncryptWithKey and DecryptWithKey
func GenerateKey() ([]byte, error) {
key := make([]byte, keySize)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return nil, fmt.Errorf("failed to generate key: %w", err)
}
return key, nil
}
// EncryptWithKey encrypts data using NaCl's secretbox with the provided key.
// 40 bytes of overhead (24-byte nonce + 16-byte MAC) are added to the plaintext size.
func EncryptWithKey(data, key []byte) ([]byte, error) {
if err := validateKey(key); err != nil {
return nil, err
}
var secretKey [keySize]byte
copy(secretKey[:], key)
// Generate 24 bytes of random data as nonce
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
return nil, err
}
// Encrypt and authenticate data
encrypted := secretbox.Seal(nonce[:], data, &nonce, &secretKey)
return encrypted, nil
}
// DecryptStringWithKey decrypts data using secretbox with the provided key
func DecryptStringWithKey(data, key []byte) ([]byte, error) {
if err := validateKey(key); err != nil {
return nil, err
}
if len(data) < 24 {
return nil, ErrShortCipherText
}
var secretKey [keySize]byte
copy(secretKey[:], key)
var nonce [24]byte
copy(nonce[:], data[:24])
decrypted, ok := secretbox.Open(nil, data[24:], &nonce, &secretKey)
if !ok {
return nil, errors.New("decryption failed")
}
return decrypted, nil
}
func validateKey(key []byte) error {
if key == nil {
return ErrNoEncryptionKey
}
if len(key) != keySize {
return ErrInvalidKeySize
}
// check if key is all zeros
for _, b := range key {
if b != 0 {
return nil
}
}
return ErrZeroKey
}

View File

@@ -1,144 +0,0 @@
package secretbox
import (
"bytes"
"crypto/rand"
"testing"
"github.com/stretchr/testify/require"
)
func TestSecretBoxEncryption(t *testing.T) {
t.Parallel()
key := make([]byte, keySize) // generate 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, 16), // Only 32 bytes allowed now
wantErr: ErrInvalidKeySize,
},
{
name: "zero key returns error",
data: []byte("hello world"),
key: make([]byte, keySize),
wantErr: ErrZeroKey,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
encrypted, err := EncryptWithKey(tt.data, tt.key)
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
return
}
require.NoError(t, err)
// Verify encrypted data is different and includes nonce
require.Greater(t, len(encrypted), 24) // At least nonce size
if len(tt.data) > 0 {
require.NotEqual(t, tt.data, encrypted[24:]) // Ignore nonce prefix
}
decrypted, err := DecryptStringWithKey(encrypted, tt.key)
require.NoError(t, err)
require.True(t, bytes.Equal(tt.data, decrypted))
})
}
}
func TestDecryptionErrors(t *testing.T) {
t.Parallel()
key := make([]byte, keySize)
_, err := rand.Read(key)
require.NoError(t, err)
// Create valid encrypted data for tampering tests
validData := []byte("test message")
encrypted, err := EncryptWithKey(validData, key)
require.NoError(t, err)
tests := []struct {
name string
data []byte
key []byte
errMsg string
}{
{
name: "short ciphertext",
data: make([]byte, 23), // Less than nonce size
key: key,
errMsg: "ciphertext too short",
},
{
name: "invalid ciphertext",
data: make([]byte, 24), // Just nonce size
key: key,
errMsg: "decryption failed",
},
{
name: "tampered ciphertext",
data: tamperWithBytes(encrypted),
key: key,
errMsg: "decryption failed",
},
{
name: "missing key",
data: encrypted,
key: nil,
errMsg: "encryption key is required",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
_, err := DecryptStringWithKey(tt.data, tt.key)
require.Error(t, err)
require.Contains(t, err.Error(), tt.errMsg)
})
}
}
// tamperWithBytes modifies a byte in the encrypted data to simulate tampering
func tamperWithBytes(data []byte) []byte {
if len(data) < 25 { // Need at least nonce + 1 byte
return data
}
tampered := make([]byte, len(data))
copy(tampered, data)
tampered[24] ^= 0x01 // Modify first byte after nonce
return tampered
}

View File

@@ -10,18 +10,17 @@ package delegation
// TODO: change the "delegation" link above when the specification is merged
import (
"encoding/base64"
"crypto/rand"
"errors"
"fmt"
"strings"
"time"
"github.com/libp2p/go-libp2p/core/crypto"
"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.
@@ -46,15 +45,21 @@ type Token struct {
expiration *time.Time
}
// New creates a validated delegation Token from the provided parameters and options.
// This is typically used to delegate a given power to another agent.
// New creates a validated Token from the provided parameters and options.
//
// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on (subject)".
func New(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, sub did.DID, opts ...Option) (*Token, error) {
// When creating a delegated token, the Issuer's (iss) DID is assembed
// using the public key associated with the private key sent as the first
// parameter.
func New(privKey crypto.PrivKey, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
iss, err := did.FromPrivKey(privKey)
if err != nil {
return nil, err
}
tkn := &Token{
issuer: iss,
audience: aud,
subject: sub,
subject: did.Undef,
command: cmd,
policy: pol,
meta: meta.NewMeta(),
@@ -67,9 +72,8 @@ func New(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, sub d
}
}
var err error
if len(tkn.nonce) == 0 {
tkn.nonce, err = nonce.Generate()
tkn.nonce, err = generateNonce()
if err != nil {
return nil, err
}
@@ -82,27 +86,21 @@ func New(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, sub d
return tkn, nil
}
// Root creates a validated UCAN delegation Token from the provided parameters and options.
// This is typically used to create and give a power to an agent.
// Root creates a validated UCAN delegation Token from the provided
// parameters and options.
//
// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on itself".
func Root(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
return New(iss, aud, cmd, pol, iss, opts...)
}
// 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(privKey crypto.PrivKey, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
sub, err := did.FromPrivKey(privKey)
if err != nil {
return nil, err
}
// Powerline creates a validated UCAN delegation Token from the provided parameters and options.
//
// Powerline is a pattern for automatically delegating all future delegations to another agent regardless of Subject.
// This is a very powerful pattern, use it only if you understand it.
// Powerline delegations MUST NOT be used as the root delegation to a resource
//
// A very common use case for Powerline is providing a stable DID across multiple agents (e.g. representing a user with
// multiple devices). This enables the automatic sharing of authority across their devices without needing to share keys
// or set up a threshold scheme. It is also flexible, since a Powerline delegation MAY be revoked.
//
// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on anything".
func Powerline(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
return New(iss, aud, cmd, pol, did.Undef, opts...)
opts = append(opts, WithSubject(sub))
return New(privKey, aud, cmd, pol, opts...)
}
// Issuer returns the did.DID representing the Token's issuer.
@@ -140,8 +138,8 @@ func (t *Token) Nonce() []byte {
}
// Meta returns the Token's metadata.
func (t *Token) Meta() meta.ReadOnly {
return t.meta.ReadOnly()
func (t *Token) Meta() *meta.Meta {
return t.meta
}
// NotBefore returns the time at which the Token becomes "active".
@@ -154,50 +152,6 @@ 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) String() string {
var res strings.Builder
var kind string
switch {
case t.issuer == t.subject:
kind = " (root delegation)"
case t.subject == did.Undef:
kind = " (powerline delegation)"
default:
kind = " (normal delegation)"
}
res.WriteString(fmt.Sprintf("Issuer: %s\n", t.Issuer()))
res.WriteString(fmt.Sprintf("Audience: %s\n", t.Audience()))
res.WriteString(fmt.Sprintf("Subject: %s%s\n", t.Subject(), kind))
res.WriteString(fmt.Sprintf("Command: %s\n", t.Command()))
res.WriteString(fmt.Sprintf("Policy: %s\n", t.Policy()))
res.WriteString(fmt.Sprintf("Nonce: %s\n", base64.StdEncoding.EncodeToString(t.Nonce())))
res.WriteString(fmt.Sprintf("Meta: %s\n", t.Meta()))
res.WriteString(fmt.Sprintf("NotBefore: %v\n", t.NotBefore()))
res.WriteString(fmt.Sprintf("Expiration: %v", t.Expiration()))
return res.String()
}
func (t *Token) validate() error {
var errs error
@@ -230,19 +184,27 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
return nil, fmt.Errorf("parse iss: %w", err)
}
if tkn.audience, err = did.Parse(m.Aud); err != nil {
tkn.audience, err = did.Parse(m.Aud)
if 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 m.Sub != nil {
tkn.subject, err = did.Parse(*m.Sub)
if err != nil {
return nil, fmt.Errorf("parse subject: %w", err)
}
} else {
tkn.subject = did.Undef
}
if tkn.command, err = command.Parse(m.Cmd); err != nil {
tkn.command, err = command.Parse(m.Cmd)
if err != nil {
return nil, fmt.Errorf("parse command: %w", err)
}
if tkn.policy, err = policy.FromIPLD(m.Pol); err != nil {
tkn.policy, err = policy.FromIPLD(m.Pol)
if err != nil {
return nil, fmt.Errorf("parse policy: %w", err)
}
@@ -251,19 +213,16 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
}
tkn.nonce = m.Nonce
tkn.meta = m.Meta
if tkn.meta == nil {
tkn.meta = meta.NewMeta()
tkn.meta = &m.Meta
if m.Nbf != nil {
t := time.Unix(*m.Nbf, 0)
tkn.notBefore = &t
}
tkn.notBefore, err = parse.OptionalTimestamp(m.Nbf)
if err != nil {
return nil, fmt.Errorf("parse notBefore: %w", err)
}
tkn.expiration, err = parse.OptionalTimestamp(m.Exp)
if err != nil {
return nil, fmt.Errorf("parse expiration: %w", err)
if m.Exp != nil {
t := time.Unix(*m.Exp, 0)
tkn.expiration = &t
}
if err := tkn.validate(); err != nil {
@@ -272,3 +231,14 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
return &tkn, nil
}
// generateNonce creates a 12-byte random nonce.
// TODO: some crypto scheme require more, is that our case?
func generateNonce() ([]byte, error) {
res := make([]byte, 12)
_, err := rand.Read(res)
if err != nil {
return nil, err
}
return res, nil
}

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

View File

@@ -1,14 +1,14 @@
package delegation_test
import (
"encoding/base64"
"testing"
"time"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/stretchr/testify/require"
"gotest.tools/v3/golden"
"github.com/ucan-wg/go-ucan/did/didtest"
"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/token/delegation"
@@ -17,8 +17,16 @@ import (
const (
nonce = "6roDhGi0kiNriQAz7J3d+bOeoI/tj8ENikmQNbtjnD0"
subJectCmd = "/foo/bar"
subjectPol = `
AudiencePrivKeyCfg = "CAESQL1hvbXpiuk2pWr/XFbfHJcZNpJ7S90iTA3wSCTc/BPRneCwPnCZb6c0vlD6ytDWqaOt0HEOPYnqEpnzoBDprSM="
AudienceDID = "did:key:z6Mkq5YmbJcTrPExNDi26imrTCpKhepjBFBSHqrBDN2ArPkv"
issuerPrivKeyCfg = "CAESQLSql38oDmQXIihFFaYIjb73mwbPsc7MIqn4o8PN4kRNnKfHkw5gRP1IV9b6d0estqkZayGZ2vqMAbhRixjgkDU="
issuerDID = "did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2"
subjectPrivKeyCfg = "CAESQL9RtjZ4dQBeXtvDe53UyvslSd64kSGevjdNiA1IP+hey5i/3PfRXSuDr71UeJUo1fLzZ7mGldZCOZL3gsIQz5c="
subjectDID = "did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2"
subJectCmd = "/foo/bar"
subjectPol = `
[
[
"==",
@@ -56,12 +64,20 @@ const (
]
`
aesKey = "xQklMmNTnVrmaPBq/0pwV5fEwuv/iClF5HWak9MsgI8="
newCID = "zdpuAn9JgGPvnt2WCmTaKktZdbuvcVGTg9bUT5kQaufwUtZ6e"
rootCID = "zdpuAkgGmUp5JrXvehGuuw9JA8DLQKDaxtK3R8brDQQVC2i5X"
)
func TestConstructors(t *testing.T) {
t.Parallel()
privKey := privKey(t, issuerPrivKeyCfg)
aud, err := did.Parse(AudienceDID)
sub, err := did.Parse(subjectDID)
require.NoError(t, err)
cmd, err := command.Parse(subJectCmd)
require.NoError(t, err)
@@ -72,24 +88,27 @@ func TestConstructors(t *testing.T) {
require.NoError(t, err)
t.Run("New", func(t *testing.T) {
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, didtest.PersonaCarol.DID(),
tkn, err := delegation.New(privKey, aud, cmd, pol,
delegation.WithNonce([]byte(nonce)),
delegation.WithSubject(sub),
delegation.WithExpiration(exp),
delegation.WithMeta("foo", "fooo"),
delegation.WithMeta("bar", "barr"),
)
require.NoError(t, err)
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
data, err := tkn.ToDagJson(privKey)
require.NoError(t, err)
t.Log(string(data))
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,
tkn, err := delegation.Root(privKey, aud, cmd, pol,
delegation.WithNonce([]byte(nonce)),
delegation.WithExpiration(exp),
delegation.WithMeta("foo", "fooo"),
@@ -97,126 +116,21 @@ func TestConstructors(t *testing.T) {
)
require.NoError(t, err)
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
data, err := tkn.ToDagJson(privKey)
require.NoError(t, err)
t.Log(string(data))
golden.Assert(t, string(data), "root.dagjson")
})
t.Run("Powerline", func(t *testing.T) {
t.Parallel()
tkn, err := delegation.Powerline(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), "powerline.dagjson")
})
}
func TestEncryptedMeta(t *testing.T) {
t.Parallel()
cmd, err := command.Parse(subJectCmd)
require.NoError(t, err)
pol, err := policy.FromDagJson(subjectPol)
func privKey(t require.TestingT, privKeyCfg string) crypto.PrivKey {
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
require.NoError(t, err)
encryptionKey, err := base64.StdEncoding.DecodeString(aesKey)
privKey, err := crypto.UnmarshalPrivateKey(privKeyMar)
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.Root(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.Root(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)
}
})
return privKey
}

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,275 +0,0 @@
package main
import (
"bytes"
"fmt"
"go/format"
"os"
"path/filepath"
"slices"
"time"
"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/pkg/policy/policytest"
"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 // iss
aud did.DID
cmd command.Command
pol policy.Policy
sub did.DID
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: policytest.EmptyPolicy,
sub: didtest.PersonaAlice.DID(),
opts: []delegation.Option{
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.sub = 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))
}},
{name: "ValidExamplePolicy", variant: func(p *newDelegationParams) {
p.pol = policytest.SpecPolicy
}},
}
// 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.sub, 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 {
buf := bytes.NewBuffer(nil)
Println := func(a ...any) { _, _ = fmt.Fprintln(buf, a...) }
Printf := func(format string, a ...any) { _, _ = fmt.Fprintf(buf, format, a...) }
Println("// Code generated by delegationtest - DO NOT EDIT.")
Println()
Println("package delegationtest")
Println()
Println("import (")
Println("\t\"github.com/ipfs/go-cid\"")
Println()
Println("\t\"github.com/ucan-wg/go-ucan/token/delegation\"")
Println(")")
refs := make(map[cid.Cid]string, len(g.dlgs))
for _, d := range g.dlgs {
refs[d.id] = d.name + "CID"
Println()
Println("var (")
Printf("\t%sCID = cid.MustParse(\"%s\")\n", d.name, d.id.String())
Printf("\t%sSealed = mustGetBundle(%s).Sealed\n", d.name, d.name+"CID")
Printf("\t%sBundle = mustGetBundle(%s)\n", d.name, d.name+"CID")
Printf("\t%s = mustGetBundle(%s).Decoded\n", d.name, d.name+"CID")
Println(")")
}
Println()
Println("var AllTokens = []*delegation.Token{")
for _, d := range g.dlgs {
Printf("\t%s,\n", d.name)
}
Println("}")
Println()
Println("var AllBundles = []*delegation.Bundle{")
for _, d := range g.dlgs {
Printf("\t%sBundle,\n", d.name)
}
Println("}")
Println()
Println("var cidToName = map[cid.Cid]string{")
for _, d := range g.dlgs {
Printf("\t%sCID: \"%s\",\n", d.name, d.name)
}
Println("}")
for _, c := range g.chains {
Println()
Printf("var %s = []cid.Cid{\n", c.name)
for _, d := range slices.Backward(c.prf) {
Printf("\t%s,\n", refs[d])
}
Println("}")
}
out, err := format.Source(buf.Bytes())
if err != nil {
return err
}
return os.WriteFile("../token_gen.go", out, 0666)
}

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,115 +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 {
bundles map[cid.Cid]*delegation.Bundle
}
var (
once sync.Once
ldr *DelegationLoader
)
// GetDelegationLoader returns a singleton instance of a test
// DelegationLoader containing all the tokens present in the data/
// directory.
func GetDelegationLoader() *DelegationLoader {
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) {
bundle, ok := l.bundles[id]
if !ok {
return nil, delegation.ErrDelegationNotFound
}
return bundle.Decoded, nil
}
func loadDelegations() (*DelegationLoader, error) {
dirEntries, err := fs.ReadDir(TokenDir)
if err != nil {
return nil, err
}
bundles := make(map[cid.Cid]*delegation.Bundle, 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
}
bundles[id] = &delegation.Bundle{Cid: id, Decoded: tkn, Sealed: data}
}
return &DelegationLoader{
bundles: bundles,
}, 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 CidToName(id cid.Cid) string {
return cidToName[id]
}
func mustGetBundle(id cid.Cid) *delegation.Bundle {
bundle, ok := GetDelegationLoader().bundles[id]
if !ok {
panic(delegation.ErrDelegationNotFound)
}
return bundle
}

View File

@@ -1,404 +0,0 @@
// Code generated by delegationtest - DO NOT EDIT.
package delegationtest
import (
"github.com/ipfs/go-cid"
"github.com/ucan-wg/go-ucan/token/delegation"
)
var (
TokenAliceBobCID = cid.MustParse("bafyreicidrwvmac5lvjypucgityrtjsknojraio7ujjli4r5eyby66wjzm")
TokenAliceBobSealed = mustGetBundle(TokenAliceBobCID).Sealed
TokenAliceBobBundle = mustGetBundle(TokenAliceBobCID)
TokenAliceBob = mustGetBundle(TokenAliceBobCID).Decoded
)
var (
TokenBobCarolCID = cid.MustParse("bafyreihxv2uhq43oxllzs2xfvxst7wtvvvl7pohb2chcz6hjvfv2ntea5u")
TokenBobCarolSealed = mustGetBundle(TokenBobCarolCID).Sealed
TokenBobCarolBundle = mustGetBundle(TokenBobCarolCID)
TokenBobCarol = mustGetBundle(TokenBobCarolCID).Decoded
)
var (
TokenCarolDanCID = cid.MustParse("bafyreihclsgiroazq3heqdswvj2cafwqbpboicq7immo65scl7ahktpsdq")
TokenCarolDanSealed = mustGetBundle(TokenCarolDanCID).Sealed
TokenCarolDanBundle = mustGetBundle(TokenCarolDanCID)
TokenCarolDan = mustGetBundle(TokenCarolDanCID).Decoded
)
var (
TokenDanErinCID = cid.MustParse("bafyreicja6ihewy64p3ake56xukotafjlkh4uqep2qhj52en46zzfwby3e")
TokenDanErinSealed = mustGetBundle(TokenDanErinCID).Sealed
TokenDanErinBundle = mustGetBundle(TokenDanErinCID)
TokenDanErin = mustGetBundle(TokenDanErinCID).Decoded
)
var (
TokenErinFrankCID = cid.MustParse("bafyreicjlx3lobxm6hl5s4htd4ydwkkqeiou6rft4rnvulfdyoew565vka")
TokenErinFrankSealed = mustGetBundle(TokenErinFrankCID).Sealed
TokenErinFrankBundle = mustGetBundle(TokenErinFrankCID)
TokenErinFrank = mustGetBundle(TokenErinFrankCID).Decoded
)
var (
TokenCarolDan_InvalidExpandedCommandCID = cid.MustParse("bafyreid3m3pk53gqgp5rlzqhvpedbwsqbidqlp4yz64vknwbzj7bxrmsr4")
TokenCarolDan_InvalidExpandedCommandSealed = mustGetBundle(TokenCarolDan_InvalidExpandedCommandCID).Sealed
TokenCarolDan_InvalidExpandedCommandBundle = mustGetBundle(TokenCarolDan_InvalidExpandedCommandCID)
TokenCarolDan_InvalidExpandedCommand = mustGetBundle(TokenCarolDan_InvalidExpandedCommandCID).Decoded
)
var (
TokenDanErin_InvalidExpandedCommandCID = cid.MustParse("bafyreifn4sy5onwajx3kqvot5mib6m6xarzrqjozqbzgmzpmc5ox3g2uzm")
TokenDanErin_InvalidExpandedCommandSealed = mustGetBundle(TokenDanErin_InvalidExpandedCommandCID).Sealed
TokenDanErin_InvalidExpandedCommandBundle = mustGetBundle(TokenDanErin_InvalidExpandedCommandCID)
TokenDanErin_InvalidExpandedCommand = mustGetBundle(TokenDanErin_InvalidExpandedCommandCID).Decoded
)
var (
TokenErinFrank_InvalidExpandedCommandCID = cid.MustParse("bafyreidmpgd36jznmq42bs34o4qi3fcbrsh4idkg6ejahudejzwb76fwxe")
TokenErinFrank_InvalidExpandedCommandSealed = mustGetBundle(TokenErinFrank_InvalidExpandedCommandCID).Sealed
TokenErinFrank_InvalidExpandedCommandBundle = mustGetBundle(TokenErinFrank_InvalidExpandedCommandCID)
TokenErinFrank_InvalidExpandedCommand = mustGetBundle(TokenErinFrank_InvalidExpandedCommandCID).Decoded
)
var (
TokenCarolDan_ValidAttenuatedCommandCID = cid.MustParse("bafyreiekhtm237vyapk3c6voeb5lnz54crebqdqi3x4wn4u4cbrrhzsqfe")
TokenCarolDan_ValidAttenuatedCommandSealed = mustGetBundle(TokenCarolDan_ValidAttenuatedCommandCID).Sealed
TokenCarolDan_ValidAttenuatedCommandBundle = mustGetBundle(TokenCarolDan_ValidAttenuatedCommandCID)
TokenCarolDan_ValidAttenuatedCommand = mustGetBundle(TokenCarolDan_ValidAttenuatedCommandCID).Decoded
)
var (
TokenDanErin_ValidAttenuatedCommandCID = cid.MustParse("bafyreicrvzqferyy7rgo75l5rn6r2nl7zyeexxjmu3dm4ff7rn2coblj4y")
TokenDanErin_ValidAttenuatedCommandSealed = mustGetBundle(TokenDanErin_ValidAttenuatedCommandCID).Sealed
TokenDanErin_ValidAttenuatedCommandBundle = mustGetBundle(TokenDanErin_ValidAttenuatedCommandCID)
TokenDanErin_ValidAttenuatedCommand = mustGetBundle(TokenDanErin_ValidAttenuatedCommandCID).Decoded
)
var (
TokenErinFrank_ValidAttenuatedCommandCID = cid.MustParse("bafyreie6fhspk53kplcc2phla3e7z7fzldlbmmpuwk6nbow5q6s2zjmw2q")
TokenErinFrank_ValidAttenuatedCommandSealed = mustGetBundle(TokenErinFrank_ValidAttenuatedCommandCID).Sealed
TokenErinFrank_ValidAttenuatedCommandBundle = mustGetBundle(TokenErinFrank_ValidAttenuatedCommandCID)
TokenErinFrank_ValidAttenuatedCommand = mustGetBundle(TokenErinFrank_ValidAttenuatedCommandCID).Decoded
)
var (
TokenCarolDan_InvalidSubjectCID = cid.MustParse("bafyreifgksz6756if42tnc6rqsnbaa2u3fdrveo7ek44lnj2d64d5sw26u")
TokenCarolDan_InvalidSubjectSealed = mustGetBundle(TokenCarolDan_InvalidSubjectCID).Sealed
TokenCarolDan_InvalidSubjectBundle = mustGetBundle(TokenCarolDan_InvalidSubjectCID)
TokenCarolDan_InvalidSubject = mustGetBundle(TokenCarolDan_InvalidSubjectCID).Decoded
)
var (
TokenDanErin_InvalidSubjectCID = cid.MustParse("bafyreibdwew5nypsxrm4fq73wu6hw3lgwwiolj3bi33xdrbgcf3ogm6fty")
TokenDanErin_InvalidSubjectSealed = mustGetBundle(TokenDanErin_InvalidSubjectCID).Sealed
TokenDanErin_InvalidSubjectBundle = mustGetBundle(TokenDanErin_InvalidSubjectCID)
TokenDanErin_InvalidSubject = mustGetBundle(TokenDanErin_InvalidSubjectCID).Decoded
)
var (
TokenErinFrank_InvalidSubjectCID = cid.MustParse("bafyreicr364mj3n7x4iyhcksxypelktcqkkw3ptg7ggxtqegw3p3mr6zc4")
TokenErinFrank_InvalidSubjectSealed = mustGetBundle(TokenErinFrank_InvalidSubjectCID).Sealed
TokenErinFrank_InvalidSubjectBundle = mustGetBundle(TokenErinFrank_InvalidSubjectCID)
TokenErinFrank_InvalidSubject = mustGetBundle(TokenErinFrank_InvalidSubjectCID).Decoded
)
var (
TokenCarolDan_InvalidExpiredCID = cid.MustParse("bafyreifyzm5jkx2sfu5awyndg3dn5zlg7sq5hssgfatafk62kiiilapnqe")
TokenCarolDan_InvalidExpiredSealed = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Sealed
TokenCarolDan_InvalidExpiredBundle = mustGetBundle(TokenCarolDan_InvalidExpiredCID)
TokenCarolDan_InvalidExpired = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Decoded
)
var (
TokenDanErin_InvalidExpiredCID = cid.MustParse("bafyreihhnisabmkofuk3qaw37leijxqjaz5or6v2cufjxwzdkvuvv2dzbq")
TokenDanErin_InvalidExpiredSealed = mustGetBundle(TokenDanErin_InvalidExpiredCID).Sealed
TokenDanErin_InvalidExpiredBundle = mustGetBundle(TokenDanErin_InvalidExpiredCID)
TokenDanErin_InvalidExpired = mustGetBundle(TokenDanErin_InvalidExpiredCID).Decoded
)
var (
TokenErinFrank_InvalidExpiredCID = cid.MustParse("bafyreigeokaziviwm5kzmkpwesj3gta5k7zrd62x4a746fnrnkhvatwbna")
TokenErinFrank_InvalidExpiredSealed = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Sealed
TokenErinFrank_InvalidExpiredBundle = mustGetBundle(TokenErinFrank_InvalidExpiredCID)
TokenErinFrank_InvalidExpired = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Decoded
)
var (
TokenCarolDan_InvalidInactiveCID = cid.MustParse("bafyreicea5y2nvlitvxijkupeavtg23i7ktjk3uejnaquguurzptiabk4u")
TokenCarolDan_InvalidInactiveSealed = mustGetBundle(TokenCarolDan_InvalidInactiveCID).Sealed
TokenCarolDan_InvalidInactiveBundle = mustGetBundle(TokenCarolDan_InvalidInactiveCID)
TokenCarolDan_InvalidInactive = mustGetBundle(TokenCarolDan_InvalidInactiveCID).Decoded
)
var (
TokenDanErin_InvalidInactiveCID = cid.MustParse("bafyreifsgqzkmxj2vexuts3z766mwcjreiisjg2jykyzf7tbj5sclutpvq")
TokenDanErin_InvalidInactiveSealed = mustGetBundle(TokenDanErin_InvalidInactiveCID).Sealed
TokenDanErin_InvalidInactiveBundle = mustGetBundle(TokenDanErin_InvalidInactiveCID)
TokenDanErin_InvalidInactive = mustGetBundle(TokenDanErin_InvalidInactiveCID).Decoded
)
var (
TokenErinFrank_InvalidInactiveCID = cid.MustParse("bafyreifbfegon24c6dndiqyktahzs65vhyasrygbw7nhsvojn6distsdre")
TokenErinFrank_InvalidInactiveSealed = mustGetBundle(TokenErinFrank_InvalidInactiveCID).Sealed
TokenErinFrank_InvalidInactiveBundle = mustGetBundle(TokenErinFrank_InvalidInactiveCID)
TokenErinFrank_InvalidInactive = mustGetBundle(TokenErinFrank_InvalidInactiveCID).Decoded
)
var (
TokenCarolDan_ValidExamplePolicyCID = cid.MustParse("bafyreibtfrp2njnkjrcuhxd4ebaecmpcql5knek2h2j2fjzu2sij2tv6ei")
TokenCarolDan_ValidExamplePolicySealed = mustGetBundle(TokenCarolDan_ValidExamplePolicyCID).Sealed
TokenCarolDan_ValidExamplePolicyBundle = mustGetBundle(TokenCarolDan_ValidExamplePolicyCID)
TokenCarolDan_ValidExamplePolicy = mustGetBundle(TokenCarolDan_ValidExamplePolicyCID).Decoded
)
var (
TokenDanErin_ValidExamplePolicyCID = cid.MustParse("bafyreidxfwbkzujpu7ivulkc7b6ff4cpbzrkeklmxqvyhhmkmym5b45e2e")
TokenDanErin_ValidExamplePolicySealed = mustGetBundle(TokenDanErin_ValidExamplePolicyCID).Sealed
TokenDanErin_ValidExamplePolicyBundle = mustGetBundle(TokenDanErin_ValidExamplePolicyCID)
TokenDanErin_ValidExamplePolicy = mustGetBundle(TokenDanErin_ValidExamplePolicyCID).Decoded
)
var (
TokenErinFrank_ValidExamplePolicyCID = cid.MustParse("bafyreiatkvtvgakqcrdk6vgrv7tbq5rbeiqct52ep4plcftp2agffjyvp4")
TokenErinFrank_ValidExamplePolicySealed = mustGetBundle(TokenErinFrank_ValidExamplePolicyCID).Sealed
TokenErinFrank_ValidExamplePolicyBundle = mustGetBundle(TokenErinFrank_ValidExamplePolicyCID)
TokenErinFrank_ValidExamplePolicy = mustGetBundle(TokenErinFrank_ValidExamplePolicyCID).Decoded
)
var AllTokens = []*delegation.Token{
TokenAliceBob,
TokenBobCarol,
TokenCarolDan,
TokenDanErin,
TokenErinFrank,
TokenCarolDan_InvalidExpandedCommand,
TokenDanErin_InvalidExpandedCommand,
TokenErinFrank_InvalidExpandedCommand,
TokenCarolDan_ValidAttenuatedCommand,
TokenDanErin_ValidAttenuatedCommand,
TokenErinFrank_ValidAttenuatedCommand,
TokenCarolDan_InvalidSubject,
TokenDanErin_InvalidSubject,
TokenErinFrank_InvalidSubject,
TokenCarolDan_InvalidExpired,
TokenDanErin_InvalidExpired,
TokenErinFrank_InvalidExpired,
TokenCarolDan_InvalidInactive,
TokenDanErin_InvalidInactive,
TokenErinFrank_InvalidInactive,
TokenCarolDan_ValidExamplePolicy,
TokenDanErin_ValidExamplePolicy,
TokenErinFrank_ValidExamplePolicy,
}
var AllBundles = []*delegation.Bundle{
TokenAliceBobBundle,
TokenBobCarolBundle,
TokenCarolDanBundle,
TokenDanErinBundle,
TokenErinFrankBundle,
TokenCarolDan_InvalidExpandedCommandBundle,
TokenDanErin_InvalidExpandedCommandBundle,
TokenErinFrank_InvalidExpandedCommandBundle,
TokenCarolDan_ValidAttenuatedCommandBundle,
TokenDanErin_ValidAttenuatedCommandBundle,
TokenErinFrank_ValidAttenuatedCommandBundle,
TokenCarolDan_InvalidSubjectBundle,
TokenDanErin_InvalidSubjectBundle,
TokenErinFrank_InvalidSubjectBundle,
TokenCarolDan_InvalidExpiredBundle,
TokenDanErin_InvalidExpiredBundle,
TokenErinFrank_InvalidExpiredBundle,
TokenCarolDan_InvalidInactiveBundle,
TokenDanErin_InvalidInactiveBundle,
TokenErinFrank_InvalidInactiveBundle,
TokenCarolDan_ValidExamplePolicyBundle,
TokenDanErin_ValidExamplePolicyBundle,
TokenErinFrank_ValidExamplePolicyBundle,
}
var cidToName = map[cid.Cid]string{
TokenAliceBobCID: "TokenAliceBob",
TokenBobCarolCID: "TokenBobCarol",
TokenCarolDanCID: "TokenCarolDan",
TokenDanErinCID: "TokenDanErin",
TokenErinFrankCID: "TokenErinFrank",
TokenCarolDan_InvalidExpandedCommandCID: "TokenCarolDan_InvalidExpandedCommand",
TokenDanErin_InvalidExpandedCommandCID: "TokenDanErin_InvalidExpandedCommand",
TokenErinFrank_InvalidExpandedCommandCID: "TokenErinFrank_InvalidExpandedCommand",
TokenCarolDan_ValidAttenuatedCommandCID: "TokenCarolDan_ValidAttenuatedCommand",
TokenDanErin_ValidAttenuatedCommandCID: "TokenDanErin_ValidAttenuatedCommand",
TokenErinFrank_ValidAttenuatedCommandCID: "TokenErinFrank_ValidAttenuatedCommand",
TokenCarolDan_InvalidSubjectCID: "TokenCarolDan_InvalidSubject",
TokenDanErin_InvalidSubjectCID: "TokenDanErin_InvalidSubject",
TokenErinFrank_InvalidSubjectCID: "TokenErinFrank_InvalidSubject",
TokenCarolDan_InvalidExpiredCID: "TokenCarolDan_InvalidExpired",
TokenDanErin_InvalidExpiredCID: "TokenDanErin_InvalidExpired",
TokenErinFrank_InvalidExpiredCID: "TokenErinFrank_InvalidExpired",
TokenCarolDan_InvalidInactiveCID: "TokenCarolDan_InvalidInactive",
TokenDanErin_InvalidInactiveCID: "TokenDanErin_InvalidInactive",
TokenErinFrank_InvalidInactiveCID: "TokenErinFrank_InvalidInactive",
TokenCarolDan_ValidExamplePolicyCID: "TokenCarolDan_ValidExamplePolicy",
TokenDanErin_ValidExamplePolicyCID: "TokenDanErin_ValidExamplePolicy",
TokenErinFrank_ValidExamplePolicyCID: "TokenErinFrank_ValidExamplePolicy",
}
var ProofAliceBob = []cid.Cid{
TokenAliceBobCID,
}
var ProofAliceBobCarol = []cid.Cid{
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDan = []cid.Cid{
TokenCarolDanCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErin = []cid.Cid{
TokenDanErinCID,
TokenCarolDanCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErinFrank = []cid.Cid{
TokenErinFrankCID,
TokenDanErinCID,
TokenCarolDanCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDan_InvalidExpandedCommand = []cid.Cid{
TokenCarolDan_InvalidExpandedCommandCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErin_InvalidExpandedCommand = []cid.Cid{
TokenDanErin_InvalidExpandedCommandCID,
TokenCarolDan_InvalidExpandedCommandCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErinFrank_InvalidExpandedCommand = []cid.Cid{
TokenErinFrank_InvalidExpandedCommandCID,
TokenDanErin_InvalidExpandedCommandCID,
TokenCarolDan_InvalidExpandedCommandCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDan_ValidAttenuatedCommand = []cid.Cid{
TokenCarolDan_ValidAttenuatedCommandCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErin_ValidAttenuatedCommand = []cid.Cid{
TokenDanErin_ValidAttenuatedCommandCID,
TokenCarolDan_ValidAttenuatedCommandCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErinFrank_ValidAttenuatedCommand = []cid.Cid{
TokenErinFrank_ValidAttenuatedCommandCID,
TokenDanErin_ValidAttenuatedCommandCID,
TokenCarolDan_ValidAttenuatedCommandCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDan_InvalidSubject = []cid.Cid{
TokenCarolDan_InvalidSubjectCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErin_InvalidSubject = []cid.Cid{
TokenDanErin_InvalidSubjectCID,
TokenCarolDan_InvalidSubjectCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErinFrank_InvalidSubject = []cid.Cid{
TokenErinFrank_InvalidSubjectCID,
TokenDanErin_InvalidSubjectCID,
TokenCarolDan_InvalidSubjectCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDan_InvalidExpired = []cid.Cid{
TokenCarolDan_InvalidExpiredCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErin_InvalidExpired = []cid.Cid{
TokenDanErin_InvalidExpiredCID,
TokenCarolDan_InvalidExpiredCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErinFrank_InvalidExpired = []cid.Cid{
TokenErinFrank_InvalidExpiredCID,
TokenDanErin_InvalidExpiredCID,
TokenCarolDan_InvalidExpiredCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDan_InvalidInactive = []cid.Cid{
TokenCarolDan_InvalidInactiveCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErin_InvalidInactive = []cid.Cid{
TokenDanErin_InvalidInactiveCID,
TokenCarolDan_InvalidInactiveCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErinFrank_InvalidInactive = []cid.Cid{
TokenErinFrank_InvalidInactiveCID,
TokenDanErin_InvalidInactiveCID,
TokenCarolDan_InvalidInactiveCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDan_ValidExamplePolicy = []cid.Cid{
TokenCarolDan_ValidExamplePolicyCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErin_ValidExamplePolicy = []cid.Cid{
TokenDanErin_ValidExamplePolicyCID,
TokenCarolDan_ValidExamplePolicyCID,
TokenBobCarolCID,
TokenAliceBobCID,
}
var ProofAliceBobCarolDanErinFrank_ValidExamplePolicy = []cid.Cid{
TokenErinFrank_ValidExamplePolicyCID,
TokenDanErin_ValidExamplePolicyCID,
TokenCarolDan_ValidExamplePolicyCID,
TokenBobCarolCID,
TokenAliceBobCID,
}

View File

@@ -1,30 +0,0 @@
package delegationtest_test
import (
"testing"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/token/delegation"
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
)
func TestGetDelegation(t *testing.T) {
t.Run("passes with valid CID", func(t *testing.T) {
t.Parallel()
tkn, err := delegationtest.GetDelegation(delegationtest.TokenAliceBobCID)
require.NoError(t, err)
assert.NotZero(t, tkn)
})
t.Run("fails with unknown CID", func(t *testing.T) {
t.Parallel()
tkn, err := delegationtest.GetDelegation(cid.Undef)
require.ErrorIs(t, err, delegation.ErrDelegationNotFound)
assert.Nil(t, tkn)
})
}

View File

@@ -2,6 +2,7 @@ package delegation_test
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
@@ -12,8 +13,9 @@ import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagcbor"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/ucan-wg/go-ucan/did/didtest"
"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"
@@ -24,7 +26,15 @@ import (
// The following example shows how to create a delegation.Token with
// distinct DIDs for issuer (iss), audience (aud) and subject (sub).
func ExampleNew() {
fmt.Println("issDid:", didtest.PersonaBob.DID().String())
issPriv, issPub, err := crypto.GenerateEd25519Key(rand.Reader)
printThenPanicOnErr(err)
issDid, err := did.FromPubKey(issPub)
printThenPanicOnErr(err)
fmt.Println("issDid:", issDid)
audDid := did.MustParse(AudienceDID)
subDid := did.MustParse(subjectDID)
// The command defines the shape of the arguments that will be evaluated against the policy
cmd := command.MustParse("/foo/bar")
@@ -41,7 +51,8 @@ func ExampleNew() {
)),
)
tkn, err := delegation.New(didtest.PersonaBob.DID(), didtest.PersonaCarol.DID(), cmd, pol, didtest.PersonaAlice.DID(),
tkn, err := delegation.New(issPriv, audDid, cmd, pol,
delegation.WithSubject(subDid),
delegation.WithExpirationIn(time.Hour),
delegation.WithNotBeforeIn(time.Minute),
delegation.WithMeta("foo", "bar"),
@@ -50,91 +61,101 @@ func ExampleNew() {
printThenPanicOnErr(err)
// "Seal", meaning encode and wrap into a signed envelope.
data, id, err := tkn.ToSealed(didtest.PersonaBob.PrivKey())
data, id, err := tkn.ToSealed(issPriv)
printThenPanicOnErr(err)
printCIDAndSealed(id, data)
// Example output:
//
// issDid: did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU
// issDid: did:key:z6MkhVFznPeR572rTK51UjoTNpnF8cxuWfPm9oBMPr7y8ABe
//
// CID (base58BTC): zdpuAsqfZkgg2jgZyob23sq1J9xwtf9PHgt1PsskVCMq7Vvxk
// CID (base58BTC): zdpuAv6g2eJSc4RJwEpmooGLVK4wJ4CZpnM92tPVYt5jtMoLW
//
// DAG-CBOR (base64) out: glhA5rvl8uKmDVGvAVSt4m/0MGiXl9dZwljJJ9m2qHCoIB617l26UvMxyH5uvN9hM7ozfVATiq4mLhoGgm9IGnEEAqJhaEQ07QFxc3VjYW4vZGxnQDEuMC4wLXJjLjGpY2F1ZHg4ZGlkOmtleTp6Nk1rcTVZbWJKY1RyUEV4TkRpMjZpbXJUQ3BLaGVwakJGQlNIcXJCRE4yQXJQa3ZjY21kaC9mb28vYmFyY2V4cBpnDWzqY2lzc3g4ZGlkOmtleTp6Nk1raFZGem5QZVI1NzJyVEs1MVVqb1ROcG5GOGN4dVdmUG05b0JNUHI3eThBQmVjbmJmGmcNXxZjcG9sg4NiPT1nLnN0YXR1c2VkcmFmdINjYWxsaS5yZXZpZXdlcoNkbGlrZWYuZW1haWxtKkBleGFtcGxlLmNvbYNjYW55ZS50YWdzgmJvcoKDYj09YS5kbmV3c4NiPT1hLmVwcmVzc2NzdWJ4OGRpZDprZXk6ejZNa3RBMXVCZENwcTR1SkJxRTlqak1pTHl4WkJnOWE2eGdQUEtKak1xc3M2WmMyZG1ldGGiY2Jhehh7Y2Zvb2NiYXJlbm9uY2VMu0HMgJ5Y+M84I/66
//
// DAG-CBOR (base64) out: lhAOnjc0bPptlI5MxRBrIK3YmAP1CxKfXOPkz6MHt/UJCx2gCN+6gXZX2N+BIJvmy8XmAO5sT2GYimiV7HlJH1AA6JhaEQ07QFxc3VjYW4vZGxnQDEuMC4wLXJjLjGpY2F1ZHg4ZGlkOmtleTp6Nk1rZ3VwY2hoNUh3dUhhaFM3WXN5RThiTHVhMU1yOHAyaUtOUmh5dlN2UkFzOW5jY21kaC9mb28vYmFyY2V4cBpnROP/Y2lzc3g4ZGlkOmtleTp6Nk1rdkpQbUVaWlliZ2l3MW91VDFvb3VUc1RGQkhKU3RzOW9waFZzTmdjUm1ZeFVjbmJmGmdE1itjcG9sg4NiPT1nLnN0YXR1c2VkcmFmdINjYWxsaS5yZXZpZXdlcoNkbGlrZWYuZW1haWxtKkBleGFtcGxlLmNvbYNjYW55ZS50YWdzgmJvcoKDYj09YS5kbmV3c4NiPT1hLmVwcmVzc2NzdWJ4OGRpZDprZXk6ejZNa3V1a2syc2tEWExRbjdOSzNFaDlqTW5kWWZ2REJ4eGt0Z3BpZEpBcWI3TTNwZG1ldGGiY2Jhehh7Y2Zvb2NiYXJlbm9uY2VMv+Diy6GExIuM1eX4
// Converted to DAG-JSON out:
// [
// {
// [
// {
// "/": {
// "bytes": "5rvl8uKmDVGvAVSt4m/0MGiXl9dZwljJJ9m2qHCoIB617l26UvMxyH5uvN9hM7ozfVATiq4mLhoGgm9IGnEEAg"
// }
// },
// {
// "h": {
// "/": {
// "bytes": "5rvl8uKmDVGvAVSt4m/0MGiXl9dZwljJJ9m2qHCoIB617l26UvMxyH5uvN9hM7ozfVATiq4mLhoGgm9IGnEEAg"
// "bytes": "NO0BcQ"
// }
// },
// {
// "h": {
// "ucan/dlg@1.0.0-rc.1": {
// "aud": "did:key:z6Mkq5YmbJcTrPExNDi26imrTCpKhepjBFBSHqrBDN2ArPkv",
// "cmd": "/foo/bar",
// "exp": 1728933098,
// "iss": "did:key:z6MkhVFznPeR572rTK51UjoTNpnF8cxuWfPm9oBMPr7y8ABe",
// "meta": {
// "baz": 123,
// "foo": "bar"
// },
// "nbf": 1728929558,
// "nonce": {
// "/": {
// "bytes": "NO0BcQ"
// "bytes": "u0HMgJ5Y+M84I/66"
// }
// },
// "ucan/dlg@1.0.0-rc.1": {
// "aud": "did:key:z6Mkq5YmbJcTrPExNDi26imrTCpKhepjBFBSHqrBDN2ArPkv",
// "cmd": "/foo/bar",
// "exp": 1728933098,
// "iss": "did:key:z6MkhVFznPeR572rTK51UjoTNpnF8cxuWfPm9oBMPr7y8ABe",
// "meta": {
// "baz": 123,
// "foo": "bar"
// },
// "nbf": 1728929558,
// "nonce": {
// "/": {
// "bytes": "u0HMgJ5Y+M84I/66"
// }
// },
// "pol": [
// "pol": [
// [
// "==",
// ".status",
// "draft"
// ],
// [
// "all",
// ".reviewer",
// [
// "==",
// ".status",
// "draft"
// ],
// "like",
// ".email",
// "*@example.com"
// ]
// ],
// [
// "any",
// ".tags",
// [
// "all",
// ".reviewer",
// "or",
// [
// "like",
// ".email",
// "*@example.com"
// ]
// ],
// [
// "any",
// ".tags",
// [
// "or",
// [
// [
// "==",
// ".",
// "news"
// ],
// [
// "==",
// ".",
// "press"
// ]
// "==",
// ".",
// "news"
// ],
// [
// "==",
// ".",
// "press"
// ]
// ]
// ]
// ],
// "sub": "did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2"
// }
// ]
// ],
// "sub": "did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2"
// }
// ]
// }
// ]
}
// The following example shows how to create a UCAN root delegation.Token
// - a delegation.Token with the subject (sub) set to the value of issuer
// (iss).
func ExampleRoot() {
issPriv, issPub, err := crypto.GenerateEd25519Key(rand.Reader)
printThenPanicOnErr(err)
issDid, err := did.FromPubKey(issPub)
printThenPanicOnErr(err)
fmt.Println("issDid:", issDid)
audDid := did.MustParse(AudienceDID)
// The command defines the shape of the arguments that will be evaluated against the policy
cmd := command.MustParse("/foo/bar")
@@ -150,7 +171,7 @@ func ExampleRoot() {
)),
)
tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
tkn, err := delegation.Root(issPriv, audDid, cmd, pol,
delegation.WithExpirationIn(time.Hour),
delegation.WithNotBeforeIn(time.Minute),
delegation.WithMeta("foo", "bar"),
@@ -159,7 +180,7 @@ func ExampleRoot() {
printThenPanicOnErr(err)
// "Seal", meaning encode and wrap into a signed envelope.
data, id, err := tkn.ToSealed(didtest.PersonaAlice.PrivKey())
data, id, err := tkn.ToSealed(issPriv)
printThenPanicOnErr(err)
printCIDAndSealed(id, data)
@@ -168,82 +189,82 @@ func ExampleRoot() {
//
// issDid: did:key:z6MknWJqz17Y4AfsXSJUFKomuBR4GTkViM7kJYutzTMkCyFF
//
// CID (base58BTC): zdpuAkwYz8nY7uU8j3F6wVTfFY1VEoExwvUAYBEwRWfTozddE
// CID (base58BTC): zdpuAwLojgfvFCbjz2FsKrvN1khDQ9mFGT6b6pxjMfz73Roed
//
// DAG-CBOR (base64) out: glhAVpW67FJ+myNi+azvnw2jivuiqXTuMrDZI2Qdaa8jE1Oi3mkjnm7DyqSQGADcomcuDslMWKmJ+OIyvbPG5PtSA6JhaEQ07QFxc3VjYW4vZGxnQDEuMC4wLXJjLjGpY2F1ZHg4ZGlkOmtleTp6Nk1rdkpQbUVaWlliZ2l3MW91VDFvb3VUc1RGQkhKU3RzOW9waFZzTmdjUm1ZeFVjY21kaC9mb28vYmFyY2V4cBpnROVoY2lzc3g4ZGlkOmtleTp6Nk1rdXVrazJza0RYTFFuN05LM0VoOWpNbmRZZnZEQnh4a3RncGlkSkFxYjdNM3BjbmJmGmdE15RjcG9sg4NiPT1nLnN0YXR1c2VkcmFmdINjYWxsaS5yZXZpZXdlcoNkbGlrZWYuZW1haWxtKkBleGFtcGxlLmNvbYNjYW55ZS50YWdzgmJvcoKDYj09YS5kbmV3c4NiPT1hLmVwcmVzc2NzdWJ4OGRpZDprZXk6ejZNa3V1a2syc2tEWExRbjdOSzNFaDlqTW5kWWZ2REJ4eGt0Z3BpZEpBcWI3TTNwZG1ldGGiY2Jhehh7Y2Zvb2NiYXJlbm9uY2VMwzDc03WBciJIGPWG
// DAG-CBOR (base64) out: glhA6dBhbhhGE36CW22OxjOEIAqdDmBqCNsAhCRljnBdXd7YrVOUG+bnXGCIwd4dTGgpEdmY06PFIl7IXKXCh/ESBqJhaEQ07QFxc3VjYW4vZGxnQDEuMC4wLXJjLjGpY2F1ZHg4ZGlkOmtleTp6Nk1rcTVZbWJKY1RyUEV4TkRpMjZpbXJUQ3BLaGVwakJGQlNIcXJCRE4yQXJQa3ZjY21kaC9mb28vYmFyY2V4cBpnDW0wY2lzc3g4ZGlkOmtleTp6Nk1rbldKcXoxN1k0QWZzWFNKVUZLb211QlI0R1RrVmlNN2tKWXV0elRNa0N5RkZjbmJmGmcNX1xjcG9sg4NiPT1nLnN0YXR1c2VkcmFmdINjYWxsaS5yZXZpZXdlcoNkbGlrZWYuZW1haWxtKkBleGFtcGxlLmNvbYNjYW55ZS50YWdzgmJvcoKDYj09YS5kbmV3c4NiPT1hLmVwcmVzc2NzdWJ4OGRpZDprZXk6ejZNa25XSnF6MTdZNEFmc1hTSlVGS29tdUJSNEdUa1ZpTTdrSll1dHpUTWtDeUZGZG1ldGGiY2Jhehh7Y2Zvb2NiYXJlbm9uY2VMJOsjYi1Pq3OIB0La
//
// Converted to DAG-JSON out:
// [
// {
// [
// {
// "/": {
// "bytes": "6dBhbhhGE36CW22OxjOEIAqdDmBqCNsAhCRljnBdXd7YrVOUG+bnXGCIwd4dTGgpEdmY06PFIl7IXKXCh/ESBg"
// }
// },
// {
// "h": {
// "/": {
// "bytes": "VpW67FJ+myNi+azvnw2jivuiqXTuMrDZI2Qdaa8jE1Oi3mkjnm7DyqSQGADcomcuDslMWKmJ+OIyvbPG5PtSAw"
// "bytes": "NO0BcQ"
// }
// },
// {
// "h": {
// "ucan/dlg@1.0.0-rc.1": {
// "aud": "did:key:z6Mkq5YmbJcTrPExNDi26imrTCpKhepjBFBSHqrBDN2ArPkv",
// "cmd": "/foo/bar",
// "exp": 1728933168,
// "iss": "did:key:z6MknWJqz17Y4AfsXSJUFKomuBR4GTkViM7kJYutzTMkCyFF",
// "meta": {
// "baz": 123,
// "foo": "bar"
// },
// "nbf": 1728929628,
// "nonce": {
// "/": {
// "bytes": "NO0BcQ"
// "bytes": "JOsjYi1Pq3OIB0La"
// }
// },
// "ucan/dlg@1.0.0-rc.1": {
// "aud": "did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU",
// "cmd": "/foo/bar",
// "exp": 1732568424,
// "iss": "did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p",
// "meta": {
// "baz": 123,
// "foo": "bar"
// },
// "nbf": 1732564884,
// "nonce": {
// "/": {
// "bytes": "wzDc03WBciJIGPWG"
// }
// },
// "pol": [
// "pol": [
// [
// "==",
// ".status",
// "draft"
// ],
// [
// "all",
// ".reviewer",
// [
// "==",
// ".status",
// "draft"
// ],
// "like",
// ".email",
// "*@example.com"
// ]
// ],
// [
// "any",
// ".tags",
// [
// "all",
// ".reviewer",
// "or",
// [
// "like",
// ".email",
// "*@example.com"
// ]
// ],
// [
// "any",
// ".tags",
// [
// "or",
// [
// [
// "==",
// ".",
// "news"
// ],
// [
// "==",
// ".",
// "press"
// ]
// "==",
// ".",
// "news"
// ],
// [
// "==",
// ".",
// "press"
// ]
// ]
// ]
// ],
// "sub": "did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p"
// }
// ]
// ],
// "sub": "did:key:z6MknWJqz17Y4AfsXSJUFKomuBR4GTkViM7kJYutzTMkCyFF"
// }
// ]
// }
// ]
}
// The following example demonstrates how to get a delegation.Token from
// a DAG-CBOR []byte.
func ExampleFromSealed() {
func ExampleToken_FromSealed() {
const cborBase64 = "glhAmnAkgfjAx4SA5pzJmtaHRJtTGNpF1y6oqb4yhGoM2H2EUGbBYT4rVDjMKBgCjhdGHjipm00L8iR5SsQh3sIEBaJhaEQ07QFxc3VjYW4vZGxnQDEuMC4wLXJjLjGoY2F1ZHg4ZGlkOmtleTp6Nk1rcTVZbWJKY1RyUEV4TkRpMjZpbXJUQ3BLaGVwakJGQlNIcXJCRE4yQXJQa3ZjY21kaC9mb28vYmFyY2V4cPZjaXNzeDhkaWQ6a2V5Ono2TWtwem4ybjNaR1QyVmFxTUdTUUMzdHptelY0VFM5UzcxaUZzRFhFMVdub05IMmNwb2yDg2I9PWcuc3RhdHVzZWRyYWZ0g2NhbGxpLnJldmlld2Vyg2RsaWtlZi5lbWFpbG0qQGV4YW1wbGUuY29tg2NhbnllLnRhZ3OCYm9ygoNiPT1hLmRuZXdzg2I9PWEuZXByZXNzY3N1Yng4ZGlkOmtleTp6Nk1rdEExdUJkQ3BxNHVKQnFFOWpqTWlMeXhaQmc5YTZ4Z1BQS0pqTXFzczZaYzJkbWV0YaBlbm9uY2VMAAECAwQFBgcICQoL"
cborBytes, err := base64.StdEncoding.DecodeString(cborBase64)
@@ -272,14 +293,12 @@ func ExampleFromSealed() {
// Policy (pol): [
// ["==", ".status", "draft"],
// ["all", ".reviewer",
// ["like", ".email", "*@example.com"]
// ],
// ["like", ".email", "*@example.com"]],
// ["any", ".tags",
// ["or", [
// ["==", ".", "news"],
// ["==", ".", "press"]
// ]]
// ]
// ["==", ".", "press"]]]
// ]
// ]
// Nonce (nonce): 000102030405060708090a0b
// Meta (meta): {}

View File

@@ -1,7 +1,6 @@
package delegation
import (
"fmt"
"io"
"github.com/ipfs/go-cid"
@@ -194,16 +193,8 @@ func FromIPLD(node datamodel.Node) (*Token, error) {
}
func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
// sanity check that privKey and issuer are matching
issPub, err := t.issuer.PubKey()
if err != nil {
return nil, err
}
if !issPub.Equals(privKey.GetPublic()) {
return nil, fmt.Errorf("private key doesn't match the issuer")
}
var sub *string
if t.subject != did.Undef {
s := t.subject.String()
sub = &s
@@ -233,15 +224,10 @@ func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
Cmd: t.command.String(),
Pol: pol,
Nonce: t.nonce,
Meta: t.meta,
Meta: *t.meta,
Nbf: nbf,
Exp: exp,
}
// seems like it's a requirement to have a null meta if there are no values?
if len(model.Meta.Keys) == 0 {
model.Meta = nil
}
return envelope.ToIPLD(privKey, model)
}

View File

@@ -3,6 +3,8 @@ package delegation
import (
"fmt"
"time"
"github.com/ucan-wg/go-ucan/did"
)
// Option is a type that allows optional fields to be set during the
@@ -42,24 +44,6 @@ func WithMeta(key string, val any) Option {
}
}
// WithEncryptedMetaString adds a key/value pair in the "meta" field.
// The string value is encrypted with the given key.
// The ciphertext will be 40 bytes larger than the plaintext due to encryption overhead.
func WithEncryptedMetaString(key, val string, encryptionKey []byte) Option {
return func(t *Token) error {
return t.meta.AddEncrypted(key, val, encryptionKey)
}
}
// WithEncryptedMetaBytes adds a key/value pair in the "meta" field.
// The []byte value is encrypted with the given key.
// The ciphertext will be 40 bytes larger than the plaintext due to encryption overhead.
func WithEncryptedMetaBytes(key string, val, encryptionKey []byte) Option {
return func(t *Token) error {
return t.meta.AddEncrypted(key, val, encryptionKey)
}
}
// WithNotBefore set's the Token's optional "notBefore" field to the value
// of the provided time.Time.
func WithNotBefore(nbf time.Time) Option {
@@ -83,6 +67,20 @@ func WithNotBeforeIn(nbf time.Duration) Option {
}
}
// WithSubject sets the Tokens's optional "subject" field to the value of
// provided did.DID.
//
// This Option should only be used with the New constructor - since
// Subject is a required parameter when creating a Token via the Root
// constructor, any value provided via this Option will be silently
// overwritten.
func WithSubject(sub did.DID) Option {
return func(t *Token) error {
t.subject = sub
return nil
}
}
// WithNonce sets the Token's nonce with the given value.
// If this option is not used, a random 12-byte nonce is generated for this required field.
func WithNonce(nonce []byte) Option {

View File

@@ -26,17 +26,17 @@ const Tag = "ucan/dlg@1.0.0-rc.1"
var schemaBytes []byte
var (
once sync.Once
ts *schema.TypeSystem
errSchema error
once sync.Once
ts *schema.TypeSystem
err error
)
func mustLoadSchema() *schema.TypeSystem {
once.Do(func() {
ts, errSchema = ipld.LoadSchemaBytes(schemaBytes)
ts, err = ipld.LoadSchemaBytes(schemaBytes)
})
if errSchema != nil {
panic(fmt.Errorf("failed to load IPLD schema: %s", errSchema))
if err != nil {
panic(fmt.Errorf("failed to load IPLD schema: %s", err))
}
return ts
}
@@ -66,7 +66,7 @@ type tokenPayloadModel struct {
Nonce []byte
// Arbitrary Metadata
Meta *meta.Meta
Meta meta.Meta
// "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
// optional: can be nil

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