Compare commits
4 Commits
v1
...
v1-policy-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfd5f00618 | ||
|
|
d66b8e40ec | ||
|
|
18820f5e9d | ||
|
|
ddaa67ed7d |
34
.github/workflows/bench.yml
vendored
34
.github/workflows/bench.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: go continuous benchmark
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v1
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
deployments: write
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Run Go continuous benchmark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "stable"
|
||||
- name: Run benchmark
|
||||
run: go test -v ./... -bench=. -run=xxx -benchmem | tee output.txt
|
||||
|
||||
- name: Store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
with:
|
||||
name: Go Benchmark
|
||||
tool: 'go'
|
||||
output-file-path: output.txt
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Push and deploy GitHub pages branch automatically
|
||||
auto-push: true
|
||||
# Show alert with commit comment on detecting possible performance regression
|
||||
alert-threshold: '200%'
|
||||
comment-on-alert: true
|
||||
4
.github/workflows/gotest.yml
vendored
4
.github/workflows/gotest.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ "ubuntu" ]
|
||||
go: [ "1.22.x", "1.23.x", ]
|
||||
go: [ "1.21.x", "1.22.x", "1.23.x", ]
|
||||
env:
|
||||
COVERAGES: ""
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
@@ -22,6 +22,6 @@ jobs:
|
||||
go version
|
||||
go env
|
||||
- name: Run tests
|
||||
run: go test -v ./... -tags jwx_es256k
|
||||
run: go test -v ./...
|
||||
- name: Check formatted
|
||||
run: gofmt -l .
|
||||
@@ -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
|
||||
|
||||
|
||||
77
Readme.md
77
Readme.md
@@ -1,77 +0,0 @@
|
||||
<div align="center">
|
||||
<a href="https://github.com/ucan-wg/go-ucan" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/ucan-wg/go-ucan/v1/assets/logo.png" alt="go-ucan Logo" height="250"></img>
|
||||
</a>
|
||||
|
||||
<h1 align="center">go-ucan</h1>
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/UCAN-v1.0.0--rc.1-blue" alt="UCAN v1.0.0-rc.1">
|
||||
<a href="https://github.com/ucan-wg/go-ucan/tags">
|
||||
<img alt="GitHub Tag" src="https://img.shields.io/github/v/tag/ucan-wg/go-ucan">
|
||||
</a>
|
||||
<a href="https://github.com/ucan-wg/go-ucan/actions?query=">
|
||||
<img src="https://github.com/ucan-wg/go-ucan/actions/workflows/gotest.yml/badge.svg" alt="Build Status">
|
||||
</a>
|
||||
<a href="https://ucan-wg.github.io/go-ucan/dev/bench/">
|
||||
<img alt="Go benchmarks" src="https://img.shields.io/badge/Benchmarks-go-blue">
|
||||
</a>
|
||||
<a href="https://github.com/ucan-wg/go-ucan/blob/v1/LICENSE.md">
|
||||
<img alt="Apache 2.0 + MIT License" src="https://img.shields.io/badge/License-Apache--2.0+MIT-green">
|
||||
</a>
|
||||
<a href="https://pkg.go.dev/github.com/ucan-wg/go-ucan">
|
||||
<img src="https://img.shields.io/badge/Docs-godoc-blue" alt="Docs">
|
||||
</a>
|
||||
<a href="https://discord.gg/JSyFG6XgVM">
|
||||
<img src="https://img.shields.io/static/v1?label=Discord&message=join%20us!&color=mediumslateblue" alt="Discord">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
This is a go library to help the next generation of web and decentralized applications make use
|
||||
of UCANs in their authorization flows.
|
||||
|
||||
User Controlled Authorization Networks (UCANs) are a way of doing authorization where users are fully in control. OAuth is designed for a centralized world, UCAN is the distributed user controlled version.
|
||||
|
||||
## Resources
|
||||
|
||||
### Specifications
|
||||
|
||||
The UCAN specification is separated in multiple sub-spec:
|
||||
- [Main specification](https://github.com/ucan-wg/spec)
|
||||
- [Delegation](https://github.com/ucan-wg/delegation/tree/v1_ipld)
|
||||
- [Invocation](https://github.com/ucan-wg/invocation)
|
||||
|
||||
Not implemented yet:
|
||||
- [Revocation](https://github.com/ucan-wg/revocation/tree/first-draft)
|
||||
- [Promise](https://github.com/ucan-wg/promise/tree/v1-rc1)
|
||||
|
||||
### Talks
|
||||
|
||||
- [Decentralizing Auth, and UCAN Too - Brooklyn Zelenka (2023)](https://www.youtube.com/watch?v=MuHfrqw9gQA)
|
||||
- [What's New in UCAN 1.0 - Brooklyn Zelenka (2024)](https://www.youtube.com/watch?v=-uohQzZcwF4)
|
||||
|
||||
## Status
|
||||
|
||||
`go-ucan` currently support the required parts of the UCAN specification: the main specification, delegation and invocation.
|
||||
|
||||
Besides that, `go-ucan` also includes:
|
||||
- a simplified [DID](https://www.w3.org/TR/did-core/) and [did-key](https://w3c-ccg.github.io/did-method-key/) implementation
|
||||
- a [token container](https://github.com/ucan-wg/go-ucan/tree/v1/pkg/container) with CBOR and CAR format, to package and carry tokens together
|
||||
- support for encrypted values in token's metadata
|
||||
|
||||
## Getting Help
|
||||
|
||||
For usage questions, usecases, or issues reach out to us in our `go-ucan`
|
||||
[Discord channel](https://discord.gg/3EHEQ6M8BC).
|
||||
|
||||
We would be happy to try to answer your question or try opening a new issue on
|
||||
Github.
|
||||
|
||||
## UCAN Gopher
|
||||
|
||||
Artwork by [Bruno Monts](https://www.instagram.com/bruno_monts). Thank you [Renee French](http://reneefrench.blogspot.com/) for creating the [Go Gopher](https://blog.golang.org/gopher)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the double license [Apache 2.0 + MIT](https://github.com/ucan-wg/go-ucan/blob/v1/LICENSE.md).
|
||||
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 125 KiB |
@@ -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",
|
||||
```
|
||||
213
did/crypto.go
213
did/crypto.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
180
did/did.go
180
did/did.go
@@ -1,9 +1,6 @@
|
||||
package did
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -13,128 +10,109 @@ 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
|
||||
}
|
||||
|
||||
// Parse returns the DID from the string representation or an error if
|
||||
// the prefix and method are incorrect, if an unknown encryption algorithm
|
||||
// is specified or if the method-specific-identifier's bytes don't
|
||||
// represent a public key for the specified encryption algorithm.
|
||||
func Parse(str string) (DID, error) {
|
||||
const keyPrefix = "did:key:"
|
||||
|
||||
if !strings.HasPrefix(str, keyPrefix) {
|
||||
return Undef, fmt.Errorf("must start with 'did:key'")
|
||||
}
|
||||
|
||||
baseCodec, bytes, err := mbase.Decode(str[len(keyPrefix):])
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
if baseCodec != mbase.Base58BTC {
|
||||
return Undef, fmt.Errorf("not Base58BTC encoded")
|
||||
}
|
||||
code, _, err := varint.FromUvarint(bytes)
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
switch multicodec.Code(code) {
|
||||
case Ed25519, P256, Secp256k1, RSA:
|
||||
return DID{bytes: string(bytes), code: multicodec.Code(code)}, nil
|
||||
default:
|
||||
return Undef, fmt.Errorf("unsupported did:key multicodec: 0x%x", code)
|
||||
}
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics instead of returning an error.
|
||||
func MustParse(str string) DID {
|
||||
did, err := Parse(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return did
|
||||
}
|
||||
|
||||
// Defined tells if the DID is defined, not equal to Undef.
|
||||
func (d DID) Defined() bool {
|
||||
return d.code != 0 || len(d.bytes) > 0
|
||||
return d.str != ""
|
||||
}
|
||||
|
||||
func (d DID) Bytes() []byte {
|
||||
if !d.Defined() {
|
||||
return nil
|
||||
}
|
||||
return []byte(d.str)
|
||||
}
|
||||
|
||||
func (d DID) Code() uint64 {
|
||||
return d.code
|
||||
}
|
||||
|
||||
func (d DID) DID() DID {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d DID) Key() bool {
|
||||
return d.key
|
||||
}
|
||||
|
||||
// PubKey returns the public key encapsulated by the did:key.
|
||||
func (d DID) PubKey() (crypto.PubKey, error) {
|
||||
if !d.key {
|
||||
return nil, fmt.Errorf("unsupported did type: %s", d.String())
|
||||
}
|
||||
|
||||
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]
|
||||
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)
|
||||
}
|
||||
|
||||
codeSize := varint.UvarintSize(uint64(d.code))
|
||||
return unmarshaler([]byte(d.bytes)[codeSize:])
|
||||
return unmarshaler(d.Bytes()[varint.UvarintSize(d.code):])
|
||||
}
|
||||
|
||||
// String formats the decentralized identity document (DID) as a string.
|
||||
func (d DID) String() string {
|
||||
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes))
|
||||
return "did:key:" + key
|
||||
if d.key {
|
||||
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str))
|
||||
return "did:key:" + key
|
||||
}
|
||||
return "did:" + d.str[MethodOffset:]
|
||||
}
|
||||
|
||||
func ecdsaPubKeyUnmarshaler(curve elliptic.Curve) crypto.PubKeyUnmarshaller {
|
||||
return func(data []byte) (crypto.PubKey, error) {
|
||||
x, y := elliptic.UnmarshalCompressed(curve, data)
|
||||
func Decode(bytes []byte) (DID, error) {
|
||||
code, _, err := varint.FromUvarint(bytes)
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
if code == Ed25519 || 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)
|
||||
}
|
||||
|
||||
ecdsaPublicKey := &ecdsa.PublicKey{
|
||||
Curve: curve,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
func Parse(str string) (DID, error) {
|
||||
if !strings.HasPrefix(str, Prefix) {
|
||||
return Undef, fmt.Errorf("must start with 'did:'")
|
||||
}
|
||||
|
||||
pkix, err := x509.MarshalPKIXPublicKey(ecdsaPublicKey)
|
||||
if strings.HasPrefix(str, KeyPrefix) {
|
||||
code, bytes, err := mbase.Decode(str[len(KeyPrefix):])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Undef, err
|
||||
}
|
||||
|
||||
return crypto.UnmarshalECDSAPublicKey(pkix)
|
||||
if code != mbase.Base58BTC {
|
||||
return Undef, fmt.Errorf("not Base58BTC encoded")
|
||||
}
|
||||
return Decode(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func rsaPubKeyUnmarshaller(data []byte) (crypto.PubKey, error) {
|
||||
rsaPublicKey, err := x509.ParsePKCS1PublicKey(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkix, err := x509.MarshalPKIXPublicKey(rsaPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return crypto.UnmarshalRsaPublicKey(pkix)
|
||||
|
||||
buf := make([]byte, MethodOffset)
|
||||
varint.PutUvarint(buf, DIDCore)
|
||||
suffix, _ := strings.CutPrefix(str, Prefix)
|
||||
buf = append(buf, suffix...)
|
||||
return DID{str: string(buf), code: DIDCore}, nil
|
||||
}
|
||||
|
||||
@@ -2,40 +2,78 @@ package did
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseDIDKey(t *testing.T) {
|
||||
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
||||
d, err := Parse(str)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, str, d.String())
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if d.String() != str {
|
||||
t.Fatalf("expected %v to equal %v", d.String(), str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustParseDIDKey(t *testing.T) {
|
||||
func TestDecodeDIDKey(t *testing.T) {
|
||||
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
||||
require.NotPanics(t, func() {
|
||||
d := MustParse(str)
|
||||
require.Equal(t, str, d.String())
|
||||
})
|
||||
str = "did:key:z7Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
||||
require.Panics(t, func() {
|
||||
MustParse(str)
|
||||
})
|
||||
d0, err := Parse(str)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
d1, err := Decode(d0.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if d1.String() != str {
|
||||
t.Fatalf("expected %v to equal %v", d1.String(), str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDIDWeb(t *testing.T) {
|
||||
str := "did:web:up.web3.storage"
|
||||
d, err := Parse(str)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if d.String() != str {
|
||||
t.Fatalf("expected %v to equal %v", d.String(), str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeDIDWeb(t *testing.T) {
|
||||
str := "did:web:up.web3.storage"
|
||||
d0, err := Parse(str)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
d1, err := Decode(d0.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if d1.String() != str {
|
||||
t.Fatalf("expected %v to equal %v", d1.String(), str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEquivalence(t *testing.T) {
|
||||
undef0 := DID{}
|
||||
undef1 := Undef
|
||||
u0 := DID{}
|
||||
u1 := Undef
|
||||
if u0 != u1 {
|
||||
t.Fatalf("undef DID not equivalent")
|
||||
}
|
||||
|
||||
did0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
||||
require.NoError(t, err)
|
||||
did1, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
||||
require.NoError(t, err)
|
||||
d0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
require.True(t, undef0 == undef1)
|
||||
require.False(t, undef0 == did0)
|
||||
require.True(t, did0 == did1)
|
||||
require.False(t, undef1 == did1)
|
||||
d1, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
if d0 != d1 {
|
||||
t.Fatalf("two equivalent DID not equivalent")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
// Package didtest provides Personas that can be used for testing. Each
|
||||
// Persona has a name, crypto.PrivKey and associated crypto.PubKey and
|
||||
// did.DID.
|
||||
package didtest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
)
|
||||
|
||||
const (
|
||||
alicePrivKeyB64 = "CAESQHdNJLBBiuc1AdwPHBkubB2KS1p0cv2JEF7m8tfwtrcm5ajaYPm+XmVCmtcHOF2lGDlmaiDA7emfwD3IrcyES0M="
|
||||
bobPrivKeyB64 = "CAESQHBz+AIop1g+9iBDj+ufUc/zm9/ry7c6kDFO8Wl/D0+H63V9hC6s9l4npf3pYEFCjBtlR0AMNWMoFQKSlYNKo20="
|
||||
carolPrivKeyB64 = "CAESQPrCgkcHnYFXDT9AlAydhPECBEivEuuVx9dJxLjVvDTmJIVNivfzg6H4mAiPfYS+5ryVVUZTHZBzvMuvvvG/Ks0="
|
||||
danPrivKeyB64 = "CAESQCgNhzofKhC+7hW6x+fNd7iMPtQHeEmKRhhlduf/I7/TeOEFYAEflbJ0sAhMeDJ/HQXaAvsWgHEbJ3ZLhP8q2B0="
|
||||
erinPrivKeyB64 = "CAESQKhCJo5UBpQcthko8DKMFsbdZ+qqQ5oc01CtLCqrE90dF2GfRlrMmot3WPHiHGCmEYi5ZMEHuiSI095e/6O4Bpw="
|
||||
frankPrivKeyB64 = "CAESQDlXPKsy3jHh7OWTWQqyZF95Ueac5DKo7xD0NOBE5F2BNr1ZVxRmJ2dBELbOt8KP9sOACcO9qlCB7uMA1UQc7sk="
|
||||
)
|
||||
|
||||
// Persona is a generic participant used for cryptographic testing.
|
||||
type Persona int
|
||||
|
||||
// The provided Personas were selected from the first few generic
|
||||
// participants listed in this [table].
|
||||
//
|
||||
// [table]: https://en.wikipedia.org/wiki/Alice_and_Bob#Cryptographic_systems
|
||||
const (
|
||||
PersonaAlice Persona = iota
|
||||
PersonaBob
|
||||
PersonaCarol
|
||||
PersonaDan
|
||||
PersonaErin
|
||||
PersonaFrank
|
||||
)
|
||||
|
||||
var privKeys map[Persona]crypto.PrivKey
|
||||
|
||||
func init() {
|
||||
privKeys = make(map[Persona]crypto.PrivKey, 6)
|
||||
for persona, privKeyCfg := range privKeyB64() {
|
||||
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
privKey, err := crypto.UnmarshalPrivateKey(privKeyMar)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
privKeys[persona] = privKey
|
||||
}
|
||||
}
|
||||
|
||||
// DID returns a did.DID based on the Persona's Ed25519 public key.
|
||||
func (p Persona) DID() did.DID {
|
||||
d, err := did.FromPrivKey(p.PrivKey())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Name returns the username of the Persona.
|
||||
func (p Persona) Name() string {
|
||||
name, ok := map[Persona]string{
|
||||
PersonaAlice: "Alice",
|
||||
PersonaBob: "Bob",
|
||||
PersonaCarol: "Carol",
|
||||
PersonaDan: "Dan",
|
||||
PersonaErin: "Erin",
|
||||
PersonaFrank: "Frank",
|
||||
}[p]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Unknown persona: %v", p))
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// PrivKey returns the Ed25519 private key for the Persona.
|
||||
func (p Persona) PrivKey() crypto.PrivKey {
|
||||
return privKeys[p]
|
||||
}
|
||||
|
||||
// PubKey returns the Ed25519 public key for the Persona.
|
||||
func (p Persona) PubKey() crypto.PubKey {
|
||||
return p.PrivKey().GetPublic()
|
||||
}
|
||||
|
||||
// PubKeyConfig returns the marshaled and encoded Ed25519 public key
|
||||
// for the Persona.
|
||||
func (p Persona) PubKeyConfig(t *testing.T) string {
|
||||
pubKeyMar, err := crypto.MarshalPublicKey(p.PrivKey().GetPublic())
|
||||
require.NoError(t, err)
|
||||
|
||||
return crypto.ConfigEncodeKey(pubKeyMar)
|
||||
}
|
||||
|
||||
func privKeyB64() map[Persona]string {
|
||||
return map[Persona]string{
|
||||
PersonaAlice: alicePrivKeyB64,
|
||||
PersonaBob: bobPrivKeyB64,
|
||||
PersonaCarol: carolPrivKeyB64,
|
||||
PersonaDan: danPrivKeyB64,
|
||||
PersonaErin: erinPrivKeyB64,
|
||||
PersonaFrank: frankPrivKeyB64,
|
||||
}
|
||||
}
|
||||
|
||||
// Personas returns an (alphabetically) ordered list of the defined
|
||||
// Persona values.
|
||||
func Personas() []Persona {
|
||||
return []Persona{
|
||||
PersonaAlice,
|
||||
PersonaBob,
|
||||
PersonaCarol,
|
||||
PersonaDan,
|
||||
PersonaErin,
|
||||
PersonaFrank,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
18
go.mod
@@ -1,15 +1,13 @@
|
||||
module github.com/ucan-wg/go-ucan
|
||||
|
||||
go 1.23
|
||||
go 1.22
|
||||
|
||||
toolchain go1.22.4
|
||||
|
||||
require (
|
||||
github.com/dave/jennifer v1.7.1
|
||||
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
|
||||
@@ -20,25 +18,19 @@ require (
|
||||
|
||||
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/crypto v0.25.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
26
go.sum
26
go.sum
@@ -1,8 +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/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
||||
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||
@@ -12,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=
|
||||
@@ -30,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=
|
||||
@@ -71,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=
|
||||
@@ -80,9 +61,6 @@ github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hg
|
||||
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
@@ -102,13 +80,11 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
|
||||
137
pkg/args/args.go
137
pkg/args/args.go
@@ -1,137 +0,0 @@
|
||||
// Package args provides the type that represents the Arguments passed to
|
||||
// a command within an invocation.Token as well as a convenient Add method
|
||||
// to incrementally build the underlying map.
|
||||
package args
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
"github.com/ipld/go-ipld-prime/printer"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
)
|
||||
|
||||
// Args are the Command's arguments when an invocation Token is processed by the executor.
|
||||
// This also serves as a way to construct the underlying IPLD data with minimum allocations
|
||||
// and transformations, while hiding the IPLD complexity from the caller.
|
||||
type Args struct {
|
||||
// This type must be compatible with the IPLD type represented by the IPLD
|
||||
// schema { String : Any }.
|
||||
|
||||
Keys []string
|
||||
Values map[string]ipld.Node
|
||||
}
|
||||
|
||||
// New returns a pointer to an initialized Args value.
|
||||
func New() *Args {
|
||||
return &Args{
|
||||
Values: map[string]ipld.Node{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add inserts a key/value pair in the Args set.
|
||||
//
|
||||
// Accepted types for val are any CBOR compatible type, or directly IPLD values.
|
||||
func (a *Args) Add(key string, val any) error {
|
||||
if _, ok := a.Values[key]; ok {
|
||||
return fmt.Errorf("duplicate key %q", key)
|
||||
}
|
||||
|
||||
node, err := literal.Any(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Values[key] = node
|
||||
a.Keys = append(a.Keys, key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Include merges the provided arguments into the existing arguments.
|
||||
//
|
||||
// If duplicate keys are encountered, the new value is silently dropped
|
||||
// without causing an error.
|
||||
func (a *Args) Include(other *Args) {
|
||||
for _, key := range other.Keys {
|
||||
if _, ok := a.Values[key]; ok {
|
||||
// don't overwrite
|
||||
continue
|
||||
}
|
||||
a.Values[key] = other.Values[key]
|
||||
a.Keys = append(a.Keys, key)
|
||||
}
|
||||
}
|
||||
|
||||
// ToIPLD wraps an instance of an Args with an ipld.Node.
|
||||
func (a *Args) ToIPLD() (ipld.Node, error) {
|
||||
sort.Strings(a.Keys)
|
||||
|
||||
return qp.BuildMap(basicnode.Prototype.Any, int64(len(a.Keys)), func(ma datamodel.MapAssembler) {
|
||||
for _, key := range a.Keys {
|
||||
qp.MapEntry(ma, key, qp.Node(a.Values[key]))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Equals tells if two Args hold the same values.
|
||||
func (a *Args) Equals(other *Args) bool {
|
||||
if len(a.Keys) != len(other.Keys) {
|
||||
return false
|
||||
}
|
||||
if len(a.Values) != len(other.Values) {
|
||||
return false
|
||||
}
|
||||
for _, key := range a.Keys {
|
||||
if !ipld.DeepEqual(a.Values[key], other.Values[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *Args) String() string {
|
||||
sort.Strings(a.Keys)
|
||||
|
||||
buf := strings.Builder{}
|
||||
buf.WriteString("{")
|
||||
|
||||
for _, key := range a.Keys {
|
||||
buf.WriteString("\n\t")
|
||||
buf.WriteString(key)
|
||||
buf.WriteString(": ")
|
||||
buf.WriteString(strings.ReplaceAll(printer.Sprint(a.Values[key]), "\n", "\n\t"))
|
||||
buf.WriteString(",")
|
||||
}
|
||||
|
||||
if len(a.Keys) > 0 {
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
buf.WriteString("}")
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ReadOnly returns a read-only version of Args.
|
||||
func (a *Args) ReadOnly() ReadOnly {
|
||||
return ReadOnly{args: a}
|
||||
}
|
||||
|
||||
// Clone makes a deep copy.
|
||||
func (a *Args) Clone() *Args {
|
||||
res := &Args{
|
||||
Keys: make([]string, len(a.Keys)),
|
||||
Values: make(map[string]ipld.Node, len(a.Values)),
|
||||
}
|
||||
copy(res.Keys, a.Keys)
|
||||
for k, v := range a.Values {
|
||||
res.Values[k] = v
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
package args_test
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/schema"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
)
|
||||
|
||||
func TestArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
intKey = "intKey"
|
||||
mapKey = "mapKey"
|
||||
nilKey = "nilKey"
|
||||
boolKey = "boolKey"
|
||||
linkKey = "linkKey"
|
||||
listKey = "listKey"
|
||||
nodeKey = "nodeKey"
|
||||
uintKey = "uintKey"
|
||||
bytesKey = "bytesKey"
|
||||
floatKey = "floatKey"
|
||||
stringKey = "stringKey"
|
||||
)
|
||||
|
||||
const (
|
||||
expIntVal = int64(-42)
|
||||
expBoolVal = true
|
||||
expUintVal = uint(42)
|
||||
expStringVal = "stringVal"
|
||||
)
|
||||
|
||||
var (
|
||||
expMapVal = map[string]string{"keyOne": "valOne", "keyTwo": "valTwo"}
|
||||
// expNilVal = (map[string]string)(nil)
|
||||
expLinkVal = cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm")
|
||||
expListVal = []string{"elem1", "elem2", "elem3"}
|
||||
expNodeVal = literal.String("nodeVal")
|
||||
expBytesVal = []byte{0xde, 0xad, 0xbe, 0xef}
|
||||
expFloatVal = 42.0
|
||||
)
|
||||
|
||||
argsIn := args.New()
|
||||
|
||||
for _, a := range []struct {
|
||||
key string
|
||||
val any
|
||||
}{
|
||||
{key: intKey, val: expIntVal},
|
||||
{key: mapKey, val: expMapVal},
|
||||
// {key: nilKey, val: expNilVal},
|
||||
{key: boolKey, val: expBoolVal},
|
||||
{key: linkKey, val: expLinkVal},
|
||||
{key: listKey, val: expListVal},
|
||||
{key: uintKey, val: expUintVal},
|
||||
{key: nodeKey, val: expNodeVal},
|
||||
{key: bytesKey, val: expBytesVal},
|
||||
{key: floatKey, val: expFloatVal},
|
||||
{key: stringKey, val: expStringVal},
|
||||
} {
|
||||
require.NoError(t, argsIn.Add(a.key, a.val))
|
||||
}
|
||||
|
||||
// Round-trip to DAG-CBOR
|
||||
argsOut := roundTripThroughDAGCBOR(t, argsIn)
|
||||
assert.ElementsMatch(t, argsIn.Keys, argsOut.Keys)
|
||||
assert.Equal(t, argsIn.Values, argsOut.Values)
|
||||
|
||||
actMapVal := map[string]string{}
|
||||
mit := argsOut.Values[mapKey].MapIterator()
|
||||
|
||||
for !mit.Done() {
|
||||
k, v, err := mit.Next()
|
||||
require.NoError(t, err)
|
||||
ks := must(k.AsString())
|
||||
vs := must(v.AsString())
|
||||
actMapVal[ks] = vs
|
||||
}
|
||||
|
||||
actListVal := []string{}
|
||||
lit := argsOut.Values[listKey].ListIterator()
|
||||
|
||||
for !lit.Done() {
|
||||
_, v, err := lit.Next()
|
||||
require.NoError(t, err)
|
||||
vs := must(v.AsString())
|
||||
|
||||
actListVal = append(actListVal, vs)
|
||||
}
|
||||
|
||||
assert.Equal(t, expIntVal, must(argsOut.Values[intKey].AsInt()))
|
||||
assert.Equal(t, expMapVal, actMapVal) // TODO: special accessor
|
||||
// TODO: the nil map comes back empty (but the right type)
|
||||
// assert.Equal(t, expNilVal, actNilVal)
|
||||
assert.Equal(t, expBoolVal, must(argsOut.Values[boolKey].AsBool()))
|
||||
assert.Equal(t, expLinkVal.String(), must(argsOut.Values[linkKey].AsLink()).(datamodel.Link).String()) // TODO: special accessor
|
||||
assert.Equal(t, expListVal, actListVal) // TODO: special accessor
|
||||
assert.Equal(t, expNodeVal, argsOut.Values[nodeKey])
|
||||
assert.Equal(t, expUintVal, uint(must(argsOut.Values[uintKey].AsInt())))
|
||||
assert.Equal(t, expBytesVal, must(argsOut.Values[bytesKey].AsBytes()))
|
||||
assert.Equal(t, expFloatVal, must(argsOut.Values[floatKey].AsFloat()))
|
||||
assert.Equal(t, expStringVal, must(argsOut.Values[stringKey].AsString()))
|
||||
}
|
||||
|
||||
func TestArgs_Include(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argsIn := args.New()
|
||||
require.NoError(t, argsIn.Add("key1", "val1"))
|
||||
require.NoError(t, argsIn.Add("key2", "val2"))
|
||||
|
||||
argsOther := args.New()
|
||||
require.NoError(t, argsOther.Add("key2", "valOther")) // This should not overwrite key2 above
|
||||
require.NoError(t, argsOther.Add("key3", "val3"))
|
||||
require.NoError(t, argsOther.Add("key4", "val4"))
|
||||
|
||||
argsIn.Include(argsOther)
|
||||
|
||||
assert.Len(t, argsIn.Values, 4)
|
||||
assert.Equal(t, "val1", must(argsIn.Values["key1"].AsString()))
|
||||
assert.Equal(t, "val2", must(argsIn.Values["key2"].AsString()))
|
||||
assert.Equal(t, "val3", must(argsIn.Values["key3"].AsString()))
|
||||
assert.Equal(t, "val4", must(argsIn.Values["key4"].AsString()))
|
||||
}
|
||||
|
||||
const (
|
||||
argsSchema = "type Args { String : Any }"
|
||||
argsName = "Args"
|
||||
)
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
ts *schema.TypeSystem
|
||||
err error
|
||||
)
|
||||
|
||||
func argsType() schema.Type {
|
||||
once.Do(func() {
|
||||
ts, err = ipld.LoadSchemaBytes([]byte(argsSchema))
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ts.TypeByName(argsName)
|
||||
}
|
||||
|
||||
func roundTripThroughDAGCBOR(t *testing.T, argsIn *args.Args) *args.Args {
|
||||
t.Helper()
|
||||
|
||||
node, err := argsIn.ToIPLD()
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := ipld.Encode(node, dagcbor.Encode)
|
||||
require.NoError(t, err)
|
||||
|
||||
var argsOut args.Args
|
||||
_, err = ipld.Unmarshal(data, dagcbor.Decode, &argsOut, argsType())
|
||||
require.NoError(t, err)
|
||||
|
||||
return &argsOut
|
||||
}
|
||||
|
||||
func must[T any](t T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package args
|
||||
|
||||
import "github.com/ipld/go-ipld-prime"
|
||||
|
||||
type ReadOnly struct {
|
||||
args *Args
|
||||
}
|
||||
|
||||
func (r ReadOnly) ToIPLD() (ipld.Node, error) {
|
||||
return r.args.ToIPLD()
|
||||
}
|
||||
|
||||
func (r ReadOnly) Equals(other *Args) bool {
|
||||
return r.args.Equals(other)
|
||||
}
|
||||
|
||||
func (r ReadOnly) String() string {
|
||||
return r.args.String()
|
||||
}
|
||||
|
||||
func (r ReadOnly) WriteableClone() *Args {
|
||||
return r.args.Clone()
|
||||
}
|
||||
@@ -16,12 +16,14 @@ var _ fmt.Stringer = (*Command)(nil)
|
||||
// by one or more slash-separated Segments of lowercase characters.
|
||||
//
|
||||
// [Command]: https://github.com/ucan-wg/spec#command
|
||||
type Command string
|
||||
type Command struct {
|
||||
segments []string
|
||||
}
|
||||
|
||||
// New creates a validated command from the provided list of segment strings.
|
||||
// An error is returned if an invalid Command would be formed
|
||||
func New(segments ...string) Command {
|
||||
return Top().Join(segments...)
|
||||
return Command{segments: segments}
|
||||
}
|
||||
|
||||
// Parse verifies that the provided string contains the required
|
||||
@@ -31,20 +33,20 @@ func New(segments ...string) Command {
|
||||
// [segment structure]: https://github.com/ucan-wg/spec#segment-structure
|
||||
func Parse(s string) (Command, error) {
|
||||
if !strings.HasPrefix(s, "/") {
|
||||
return "", ErrRequiresLeadingSlash
|
||||
return Command{}, ErrRequiresLeadingSlash
|
||||
}
|
||||
|
||||
if len(s) > 1 && strings.HasSuffix(s, "/") {
|
||||
return "", ErrDisallowsTrailingSlash
|
||||
return Command{}, ErrDisallowsTrailingSlash
|
||||
}
|
||||
|
||||
if s != strings.ToLower(s) {
|
||||
return "", ErrRequiresLowercase
|
||||
return Command{}, ErrRequiresLowercase
|
||||
}
|
||||
|
||||
// The leading slash will result in the first element from strings.Split
|
||||
// being an empty string which is removed as strings.Join will ignore it.
|
||||
return Command(s), nil
|
||||
return Command{strings.Split(s, "/")[1:]}, nil
|
||||
}
|
||||
|
||||
// MustParse is the same as Parse, but panic() if the parsing fail.
|
||||
@@ -56,14 +58,14 @@ func MustParse(s string) Command {
|
||||
return c
|
||||
}
|
||||
|
||||
// Top is the most powerful capability.
|
||||
// [Top] is the most powerful capability.
|
||||
//
|
||||
// This function returns a Command that is a wildcard and therefore represents the
|
||||
// most powerful ability. As such, it should be handled with care and used sparingly.
|
||||
//
|
||||
// [Top]: https://github.com/ucan-wg/spec#-aka-top
|
||||
func Top() Command {
|
||||
return Command(separator)
|
||||
return New()
|
||||
}
|
||||
|
||||
// IsValid returns true if the provided string is a valid UCAN command.
|
||||
@@ -75,61 +77,17 @@ func IsValid(s string) bool {
|
||||
// Join appends segments to the end of this command using the required
|
||||
// segment separator.
|
||||
func (c Command) Join(segments ...string) Command {
|
||||
size := 0
|
||||
for _, s := range segments {
|
||||
size += len(s)
|
||||
}
|
||||
if size == 0 {
|
||||
return c
|
||||
}
|
||||
buf := make([]byte, 0, len(c)+size+len(segments))
|
||||
buf = append(buf, []byte(c)...)
|
||||
for _, s := range segments {
|
||||
if s != "" {
|
||||
if len(buf) > 1 {
|
||||
buf = append(buf, separator...)
|
||||
}
|
||||
buf = append(buf, []byte(s)...)
|
||||
}
|
||||
}
|
||||
return Command(buf)
|
||||
return Command{append(c.segments, segments...)}
|
||||
}
|
||||
|
||||
// Segments returns the ordered segments that comprise the Command as a
|
||||
// slice of strings.
|
||||
func (c Command) Segments() []string {
|
||||
if c == separator {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(string(c), separator)[1:]
|
||||
}
|
||||
|
||||
// Covers returns true if the command is identical or a parent of the given other command.
|
||||
func (c Command) Covers(other Command) bool {
|
||||
// fast-path, equivalent to the code below (verified with fuzzing)
|
||||
if !strings.HasPrefix(string(other), string(c)) {
|
||||
return false
|
||||
}
|
||||
return c == separator || len(c) == len(other) || other[len(c)] == separator[0]
|
||||
|
||||
/* -------
|
||||
|
||||
otherSegments := other.Segments()
|
||||
if len(otherSegments) < len(c.Segments()) {
|
||||
return false
|
||||
}
|
||||
for i, s := range c.Segments() {
|
||||
if otherSegments[i] != s {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
*/
|
||||
return c.segments
|
||||
}
|
||||
|
||||
// String returns the composed representation the command. This is also
|
||||
// the required wire representation (before IPLD encoding occurs.)
|
||||
func (c Command) String() string {
|
||||
return string(c)
|
||||
return "/" + strings.Join(c.segments, "/")
|
||||
}
|
||||
|
||||
@@ -13,81 +13,73 @@ func TestTop(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsValidCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("succeeds when", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testcase := range validTestcases(t) {
|
||||
testcase := testcase
|
||||
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.True(t, command.IsValid(testcase.inp))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails when", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testcase := range invalidTestcases(t) {
|
||||
testcase := testcase
|
||||
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.False(t, command.IsValid(testcase.inp))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
require.Equal(t, command.Top(), command.New())
|
||||
require.Equal(t, "/foo", command.New("foo").String())
|
||||
require.Equal(t, "/foo/bar", command.New("foo", "bar").String())
|
||||
require.Equal(t, "/foo/bar/baz", command.New("foo", "bar/baz").String())
|
||||
}
|
||||
|
||||
func TestParseCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("succeeds when", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testcase := range validTestcases(t) {
|
||||
testcase := testcase
|
||||
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cmd, err := command.Parse("/elem0/elem1/elem2")
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, cmd)
|
||||
require.NotNil(t, cmd)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails when", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testcase := range invalidTestcases(t) {
|
||||
testcase := testcase
|
||||
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cmd, err := command.Parse(testcase.inp)
|
||||
require.ErrorIs(t, err, testcase.err)
|
||||
require.Zero(t, cmd)
|
||||
require.Equal(t, command.Command{}, cmd)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEquality(t *testing.T) {
|
||||
require.True(t, command.MustParse("/foo/bar/baz") == command.MustParse("/foo/bar/baz"))
|
||||
require.False(t, command.MustParse("/foo/bar/baz") == command.MustParse("/foo/bar/bazz"))
|
||||
require.False(t, command.MustParse("/foo/bar") == command.MustParse("/foo/bar/baz"))
|
||||
}
|
||||
|
||||
func TestJoin(t *testing.T) {
|
||||
require.Equal(t, "/foo", command.Top().Join("foo").String())
|
||||
require.Equal(t, "/foo/bar", command.Top().Join("foo/bar").String())
|
||||
require.Equal(t, "/foo/bar", command.Top().Join("foo", "bar").String())
|
||||
require.Equal(t, "/faz/boz/foo/bar", command.MustParse("/faz/boz").Join("foo/bar").String())
|
||||
require.Equal(t, "/faz/boz/foo/bar", command.MustParse("/faz/boz").Join("foo", "bar").String())
|
||||
}
|
||||
|
||||
func TestSegments(t *testing.T) {
|
||||
require.Empty(t, command.Top().Segments())
|
||||
require.Equal(t, []string{"foo", "bar", "baz"}, command.MustParse("/foo/bar/baz").Segments())
|
||||
}
|
||||
|
||||
func TestCovers(t *testing.T) {
|
||||
require.True(t, command.MustParse("/foo/bar/baz").Covers(command.MustParse("/foo/bar/baz")))
|
||||
require.True(t, command.MustParse("/foo/bar").Covers(command.MustParse("/foo/bar/baz")))
|
||||
require.False(t, command.MustParse("/foo/bar/baz").Covers(command.MustParse("/foo/bar")))
|
||||
require.True(t, command.MustParse("/").Covers(command.MustParse("/foo")))
|
||||
require.True(t, command.MustParse("/").Covers(command.MustParse("/foo/bar/baz")))
|
||||
require.False(t, command.MustParse("/foo").Covers(command.MustParse("/foo00")))
|
||||
require.False(t, command.MustParse("/foo/bar").Covers(command.MustParse("/foo/bar00")))
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
name string
|
||||
inp string
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
# Token container
|
||||
|
||||
## Why do I need that?
|
||||
|
||||
Some common situation asks to package multiple tokens together:
|
||||
- calling a service requires sending an invocation, alongside the matching delegations
|
||||
- sending a series of revocations
|
||||
- \<insert your application specific scenario here>
|
||||
|
||||
The UCAN specification defines how a single token is serialized (envelope with signature, IPLD encoded as Dag-cbor), but it's entirely left open how to package multiple tokens together. To be clear, this is a correct thing to do for a specification, as different ways equally valid to solve that problem exists and can coexist. Any wire format holding a list of bytes would do (cbor, json, csv ...).
|
||||
|
||||
**go-ucan** however, provide an opinionated implementation, which may or may not work in your situation.
|
||||
|
||||
Some experiment has been done over which format is appropriate, and two have been selected:
|
||||
- **DAG-CBOR** of a list of bytes, as a low overhead option
|
||||
- **CAR** file, as a somewhat common ways to cary arbitrary blocks of data
|
||||
|
||||
Notably, **compression is not included**, even though it does work reasonably well. This is because your transport medium might already do it, or should.
|
||||
|
||||
## Wire format consideration
|
||||
|
||||
Several possible formats have been explored:
|
||||
- CAR files (binary or base64)
|
||||
- DAG-CBOR (binary or base64)
|
||||
|
||||
Additionally, gzip and deflate compression has been experimented with.
|
||||
|
||||
Below are the results in terms of storage used, as percentage and byte overhead over the raw tokens:
|
||||
|
||||
| Token count | car | carBase64 | carGzip | carGzipBase64 | cbor | cborBase64 | cborGzip | cborGzipBase64 | cborFlate | cborFlateBase64 |
|
||||
|-------------|-----|-----------|---------|---------------|------|------------|----------|----------------|-----------|-----------------|
|
||||
| 1 | 15 | 54 | 7 | 42 | 0 | 35 | \-8 | 22 | \-12 | 16 |
|
||||
| 2 | 12 | 49 | \-12 | 15 | 0 | 34 | \-25 | 0 | \-28 | \-3 |
|
||||
| 3 | 11 | 48 | \-21 | 4 | 0 | 34 | \-32 | \-10 | \-34 | \-11 |
|
||||
| 4 | 10 | 47 | \-26 | \-1 | 0 | 34 | \-36 | \-15 | \-37 | \-17 |
|
||||
| 5 | 10 | 47 | \-28 | \-4 | 0 | 34 | \-38 | \-18 | \-40 | \-20 |
|
||||
| 6 | 10 | 47 | \-30 | \-7 | 0 | 34 | \-40 | \-20 | \-40 | \-20 |
|
||||
| 7 | 10 | 46 | \-31 | \-8 | 0 | 34 | \-41 | \-21 | \-42 | \-22 |
|
||||
| 8 | 9 | 46 | \-32 | \-10 | 0 | 34 | \-42 | \-22 | \-42 | \-23 |
|
||||
| 9 | 9 | 46 | \-33 | \-11 | 0 | 34 | \-43 | \-23 | \-43 | \-24 |
|
||||
| 10 | 9 | 46 | \-34 | \-12 | 0 | 34 | \-43 | \-25 | \-44 | \-25 |
|
||||
|
||||

|
||||
|
||||
| Token count | car | carBase64 | carGzip | carGzipBase64 | cbor | cborBase64 | cborGzip | cborGzipBase64 | cborFlate | cborFlateBase64 |
|
||||
|-------------|-----|-----------|---------|---------------|------|------------|----------|----------------|-----------|-----------------|
|
||||
| 1 | 64 | 226 | 29 | 178 | 4 | 146 | \-35 | 94 | \-52 | 70 |
|
||||
| 2 | 102 | 412 | \-107 | 128 | 7 | 288 | \-211 | 0 | \-234 | \-32 |
|
||||
| 3 | 140 | 602 | \-270 | 58 | 10 | 430 | \-405 | \-126 | \-429 | \-146 |
|
||||
| 4 | 178 | 792 | \-432 | \-28 | 13 | 572 | \-602 | \-252 | \-617 | \-288 |
|
||||
| 5 | 216 | 978 | \-582 | \-94 | 16 | 714 | \-805 | \-386 | \-839 | \-418 |
|
||||
| 6 | 254 | 1168 | \-759 | \-176 | 19 | 856 | \-1001 | \-508 | \-1018 | \-520 |
|
||||
| 7 | 292 | 1358 | \-908 | \-246 | 22 | 998 | \-1204 | \-634 | \-1229 | \-650 |
|
||||
| 8 | 330 | 1544 | \-1085 | \-332 | 25 | 1140 | \-1398 | \-756 | \-1423 | \-792 |
|
||||
| 9 | 368 | 1734 | \-1257 | \-414 | 28 | 1282 | \-1614 | \-894 | \-1625 | \-930 |
|
||||
| 10 | 406 | 1924 | \-1408 | \-508 | 31 | 1424 | \-1804 | \-1040 | \-1826 | \-1060 |
|
||||
|
||||

|
||||
|
||||
Following is the performance aspect, with CPU usage and memory allocation:
|
||||
|
||||
| | Write ns/op | Read ns/op | Write B/op | Read B/op | Write allocs/op | Read allocs/op |
|
||||
|-----------------|-------------|------------|------------|-----------|-----------------|----------------|
|
||||
| car | 8451 | 1474630 | 17928 | 149437 | 59 | 2631 |
|
||||
| carBase64 | 16750 | 1437678 | 24232 | 151502 | 61 | 2633 |
|
||||
| carGzip | 320253 | 1581412 | 823887 | 192272 | 76 | 2665 |
|
||||
| carGzipBase64 | 343305 | 1486269 | 828782 | 198543 | 77 | 2669 |
|
||||
| cbor | 6419 | 1301554 | 16368 | 138891 | 25 | 2534 |
|
||||
| cborBase64 | 12860 | 1386728 | 20720 | 140962 | 26 | 2536 |
|
||||
| cborGzip | 310106 | 1379146 | 822742 | 182003 | 42 | 2585 |
|
||||
| cborGzipBase64 | 317001 | 1462548 | 827640 | 189283 | 43 | 2594 |
|
||||
| cborFlate | 327112 | 1555007 | 822473 | 181537 | 40 | 2591 |
|
||||
| cborFlateBase64 | 311276 | 1456562 | 826042 | 188665 | 41 | 2596 |
|
||||
|
||||
(BEWARE: logarithmic scale)
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Conclusion:
|
||||
- CAR files are heavy for this usage, notably because they carry the CIDs of the tokens
|
||||
- compression works quite well and warrants its usage even with a single token
|
||||
- DAG-CBOR outperform CAR files everywhere, and comes with a tiny ~3 bytes per token overhead.
|
||||
|
||||
**Formats beside DAG-CBOR and CAR, with or without base64, have been removed. They are in the git history though.**
|
||||
@@ -1,263 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
)
|
||||
|
||||
/*
|
||||
Note: below is essentially a re-implementation of the CAR file v1 read and write.
|
||||
This exists here for two reasons:
|
||||
- go-car's API forces to go through an IPLD getter or through a blockstore API
|
||||
- generally, go-car is a very complex and large dependency
|
||||
*/
|
||||
|
||||
// EmptyCid is a "zero" Cid: zero-length "identity" multihash with "raw" codec
|
||||
// It can be used to have at least one root in a CARv1 file (making it legal), yet
|
||||
// denote that it can be ignored.
|
||||
var EmptyCid = cid.MustParse([]byte{01, 55, 00, 00})
|
||||
|
||||
type carBlock struct {
|
||||
c cid.Cid
|
||||
data []byte
|
||||
}
|
||||
|
||||
// writeCar writes a CARv1 file containing the blocks from the iterator.
|
||||
// If no roots are provided, a single EmptyCid is used as root to make the file
|
||||
// spec compliant.
|
||||
func writeCar(w io.Writer, roots []cid.Cid, blocks iter.Seq2[carBlock, error]) error {
|
||||
if len(roots) == 0 {
|
||||
roots = []cid.Cid{EmptyCid}
|
||||
}
|
||||
h := carHeader{
|
||||
Roots: roots,
|
||||
Version: 1,
|
||||
}
|
||||
hb, err := h.Write()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ldWrite(w, hb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for block, err := range blocks {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ldWrite(w, block.c.Bytes(), block.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readCar reads a CARv1 file from the reader, and return a block iterator.
|
||||
// Roots are ignored.
|
||||
func readCar(r io.Reader) (roots []cid.Cid, blocks iter.Seq2[carBlock, error], err error) {
|
||||
br := bufio.NewReader(r)
|
||||
|
||||
hb, err := ldRead(br)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
h, err := readHeader(hb)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if h.Version != 1 {
|
||||
return nil, nil, fmt.Errorf("invalid car version: %d", h.Version)
|
||||
}
|
||||
|
||||
return h.Roots, func(yield func(block carBlock, err error) bool) {
|
||||
for {
|
||||
block, err := readBlock(br)
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
if !yield(carBlock{}, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !yield(block, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// readBlock reads a section from the reader and decode a (cid+data) block.
|
||||
func readBlock(r *bufio.Reader) (carBlock, error) {
|
||||
raw, err := ldRead(r)
|
||||
if err != nil {
|
||||
return carBlock{}, err
|
||||
}
|
||||
|
||||
n, c, err := cid.CidFromReader(bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
return carBlock{}, err
|
||||
}
|
||||
data := raw[n:]
|
||||
|
||||
// integrity check
|
||||
hashed, err := c.Prefix().Sum(data)
|
||||
if err != nil {
|
||||
return carBlock{}, err
|
||||
}
|
||||
|
||||
if !hashed.Equals(c) {
|
||||
return carBlock{}, fmt.Errorf("mismatch in content integrity, name: %s, data: %s", c, hashed)
|
||||
}
|
||||
|
||||
return carBlock{c: c, data: data}, nil
|
||||
}
|
||||
|
||||
// maxAllowedSectionSize dictates the maximum number of bytes that a CARv1 header
|
||||
// or section is allowed to occupy without causing a decode to error.
|
||||
// This cannot be supplied as an option, only adjusted as a global. You should
|
||||
// use v2#NewReader instead since it allows for options to be passed in.
|
||||
var maxAllowedSectionSize uint = 32 << 20 // 32MiB
|
||||
|
||||
// ldRead performs a length-delimited read of a section from the reader.
|
||||
// A section is composed of an uint length followed by the data.
|
||||
func ldRead(r *bufio.Reader) ([]byte, error) {
|
||||
if _, err := r.Peek(1); err != nil { // no more blocks, likely clean io.EOF
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l, err := binary.ReadUvarint(r)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil, io.ErrUnexpectedEOF // don't silently pretend this is a clean EOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if l == 0 {
|
||||
return nil, fmt.Errorf("invalid zero size section")
|
||||
}
|
||||
|
||||
if l > uint64(maxAllowedSectionSize) { // Don't OOM
|
||||
return nil, fmt.Errorf("malformed car; header is bigger than MaxAllowedSectionSize")
|
||||
}
|
||||
|
||||
buf := make([]byte, l)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
if err == io.EOF {
|
||||
// we should be able to read the promised bytes, this is not normal
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// ldWrite performs a length-delimited write of a section on the writer.
|
||||
// A section is composed of an uint length followed by the data.
|
||||
func ldWrite(w io.Writer, d ...[]byte) error {
|
||||
var sum uint64
|
||||
for _, s := range d {
|
||||
sum += uint64(len(s))
|
||||
}
|
||||
|
||||
buf := make([]byte, 8)
|
||||
n := binary.PutUvarint(buf, sum)
|
||||
_, err := w.Write(buf[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range d {
|
||||
_, err = w.Write(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type carHeader struct {
|
||||
Roots []cid.Cid
|
||||
Version uint64
|
||||
}
|
||||
|
||||
const rootsKey = "roots"
|
||||
const versionKey = "version"
|
||||
|
||||
func readHeader(data []byte) (*carHeader, error) {
|
||||
var header carHeader
|
||||
|
||||
nd, err := ipld.Decode(data, dagcbor.Decode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nd.Length() != 2 {
|
||||
return nil, fmt.Errorf("malformed car header")
|
||||
}
|
||||
rootsNd, err := nd.LookupByString(rootsKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed car header")
|
||||
}
|
||||
it := rootsNd.ListIterator()
|
||||
if it == nil {
|
||||
return nil, fmt.Errorf("malformed car header")
|
||||
}
|
||||
header.Roots = make([]cid.Cid, 0, rootsNd.Length())
|
||||
for !it.Done() {
|
||||
_, nd, err := it.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lk, err := nd.AsLink()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch lk := lk.(type) {
|
||||
case cidlink.Link:
|
||||
header.Roots = append(header.Roots, lk.Cid)
|
||||
default:
|
||||
return nil, fmt.Errorf("malformed car header")
|
||||
}
|
||||
}
|
||||
versionNd, err := nd.LookupByString(versionKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed car header")
|
||||
}
|
||||
version, err := versionNd.AsInt()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed car header")
|
||||
}
|
||||
header.Version = uint64(version)
|
||||
return &header, nil
|
||||
}
|
||||
|
||||
func (ch *carHeader) Write() ([]byte, error) {
|
||||
nd, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
|
||||
qp.MapEntry(ma, rootsKey, qp.List(int64(len(ch.Roots)), func(la datamodel.ListAssembler) {
|
||||
for _, root := range ch.Roots {
|
||||
qp.ListEntry(la, qp.Link(cidlink.Link{Cid: root}))
|
||||
}
|
||||
}))
|
||||
qp.MapEntry(ma, versionKey, qp.Int(1))
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ipld.Encode(nd, dagcbor.Encode)
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCarRoundTrip(t *testing.T) {
|
||||
// this car file is a complex and legal CARv1 file
|
||||
original, err := os.ReadFile("testdata/sample-v1.car")
|
||||
require.NoError(t, err)
|
||||
|
||||
roots, it, err := readCar(bytes.NewReader(original))
|
||||
require.NoError(t, err)
|
||||
|
||||
var blks []carBlock
|
||||
for blk, err := range it {
|
||||
require.NoError(t, err)
|
||||
blks = append(blks, blk)
|
||||
}
|
||||
|
||||
require.Len(t, blks, 1049)
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
err = writeCar(buf, roots, func(yield func(carBlock, error) bool) {
|
||||
for _, blk := range blks {
|
||||
if !yield(blk, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Bytes equal after the round-trip
|
||||
require.Equal(t, original, buf.Bytes())
|
||||
}
|
||||
|
||||
func FuzzCarRoundTrip(f *testing.F) {
|
||||
// Note: this fuzzing is somewhat broken.
|
||||
// After some time, the fuzzer discover that a varint can be serialized in different
|
||||
// ways that lead to the same integer value. This means that the CAR format can have
|
||||
// multiple legal binary representation for the exact same data, which is what we are
|
||||
// trying to detect here. Ideally, the format would be stricter, but that's how things
|
||||
// are.
|
||||
|
||||
example, err := os.ReadFile("testdata/sample-v1.car")
|
||||
require.NoError(f, err)
|
||||
|
||||
f.Add(example)
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
roots, blocksIter, err := readCar(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
// skip invalid binary
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
// reading all the blocks, which force reading and verifying the full file
|
||||
var blocks []carBlock
|
||||
for block, err := range blocksIter {
|
||||
if err != nil {
|
||||
// error reading, invalid data
|
||||
t.Skip()
|
||||
}
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = writeCar(&buf, roots, func(yield func(carBlock, error) bool) {
|
||||
for _, blk := range blocks {
|
||||
if !yield(blk, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// test if the round-trip produce a byte-equal CAR
|
||||
require.Equal(t, data, buf.Bytes())
|
||||
})
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,161 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/token"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
)
|
||||
|
||||
var ErrNotFound = fmt.Errorf("not found")
|
||||
|
||||
// Reader is a token container reader. It exposes the tokens conveniently decoded.
|
||||
type Reader map[cid.Cid]token.Token
|
||||
|
||||
// GetToken returns an arbitrary decoded token, from its CID.
|
||||
// If not found, ErrNotFound is returned.
|
||||
func (ctn Reader) GetToken(cid cid.Cid) (token.Token, error) {
|
||||
tkn, ok := ctn[cid]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return tkn, nil
|
||||
}
|
||||
|
||||
// GetDelegation is the same as GetToken but only return a delegation.Token, with the right type.
|
||||
func (ctn Reader) GetDelegation(cid cid.Cid) (*delegation.Token, error) {
|
||||
tkn, err := ctn.GetToken(cid)
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return nil, delegation.ErrDelegationNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tkn, ok := tkn.(*delegation.Token); ok {
|
||||
return tkn, nil
|
||||
}
|
||||
return nil, delegation.ErrDelegationNotFound
|
||||
}
|
||||
|
||||
// GetAllDelegations returns all the delegation.Token in the container.
|
||||
func (ctn Reader) GetAllDelegations() iter.Seq2[cid.Cid, *delegation.Token] {
|
||||
return func(yield func(cid.Cid, *delegation.Token) bool) {
|
||||
for c, t := range ctn {
|
||||
if t, ok := t.(*delegation.Token); ok {
|
||||
if !yield(c, t) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetInvocation returns the first found invocation.Token.
|
||||
// If none are found, ErrNotFound is returned.
|
||||
func (ctn Reader) GetInvocation() (*invocation.Token, error) {
|
||||
for _, t := range ctn {
|
||||
if inv, ok := t.(*invocation.Token); ok {
|
||||
return inv, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
func FromCar(r io.Reader) (Reader, error) {
|
||||
_, it, err := readCar(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctn := make(Reader)
|
||||
|
||||
for block, err := range it {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ctn.addToken(block.data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ctn, nil
|
||||
}
|
||||
|
||||
func FromCarBase64(r io.Reader) (Reader, error) {
|
||||
return FromCar(base64.NewDecoder(base64.StdEncoding, r))
|
||||
}
|
||||
|
||||
func FromCbor(r io.Reader) (Reader, error) {
|
||||
n, err := ipld.DecodeStreaming(r, dagcbor.Decode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n.Kind() != datamodel.Kind_Map {
|
||||
return nil, fmt.Errorf("invalid container format: expected map")
|
||||
}
|
||||
if n.Length() != 1 {
|
||||
return nil, fmt.Errorf("invalid container format: expected single version key")
|
||||
}
|
||||
|
||||
// get the first (and only) key-value pair
|
||||
it := n.MapIterator()
|
||||
key, tokensNode, err := it.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version, err := key.AsString()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid container format: version must be string")
|
||||
}
|
||||
if version != currentContainerVersion {
|
||||
return nil, fmt.Errorf("unsupported container version: %s", version)
|
||||
}
|
||||
|
||||
if tokensNode.Kind() != datamodel.Kind_List {
|
||||
return nil, fmt.Errorf("invalid container format: tokens must be a list")
|
||||
}
|
||||
|
||||
ctn := make(Reader, tokensNode.Length())
|
||||
it2 := tokensNode.ListIterator()
|
||||
for !it2.Done() {
|
||||
_, val, err := it2.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := val.AsBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ctn.addToken(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ctn, nil
|
||||
}
|
||||
|
||||
func FromCborBase64(r io.Reader) (Reader, error) {
|
||||
return FromCbor(base64.NewDecoder(base64.StdEncoding, r))
|
||||
}
|
||||
|
||||
func (ctn Reader) addToken(data []byte) error {
|
||||
tkn, c, err := token.FromSealed(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctn[c] = tkn
|
||||
return nil
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
)
|
||||
|
||||
func TestContainerRoundTrip(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
writer func(ctn Writer, w io.Writer) error
|
||||
reader func(io.Reader) (Reader, error)
|
||||
}{
|
||||
{"car", Writer.ToCar, FromCar},
|
||||
{"carBase64", Writer.ToCarBase64, FromCarBase64},
|
||||
{"cbor", Writer.ToCbor, FromCbor},
|
||||
{"cborBase64", Writer.ToCborBase64, FromCborBase64},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tokens := make(map[cid.Cid]*delegation.Token)
|
||||
var dataSize int
|
||||
|
||||
writer := NewWriter()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
dlg, c, data := randToken()
|
||||
writer.AddSealed(c, data)
|
||||
tokens[c] = dlg
|
||||
dataSize += len(data)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
err := tc.writer(writer, buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("data size %d", dataSize)
|
||||
t.Logf("container overhead: %d%%, %d bytes", int(float32(buf.Len()-dataSize)/float32(dataSize)*100.0), buf.Len()-dataSize)
|
||||
|
||||
reader, err := tc.reader(bytes.NewReader(buf.Bytes()))
|
||||
require.NoError(t, err)
|
||||
|
||||
for c, dlg := range tokens {
|
||||
tknRead, err := reader.GetToken(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
// require.Equal fails as time.Time holds a wall time that is going to be
|
||||
// different, even if it represents the same event.
|
||||
// We need to do the following instead.
|
||||
|
||||
dlgRead := tknRead.(*delegation.Token)
|
||||
require.Equal(t, dlg.Issuer(), dlgRead.Issuer())
|
||||
require.Equal(t, dlg.Audience(), dlgRead.Audience())
|
||||
require.Equal(t, dlg.Subject(), dlgRead.Subject())
|
||||
require.Equal(t, dlg.Command(), dlgRead.Command())
|
||||
require.Equal(t, dlg.Policy(), dlgRead.Policy())
|
||||
require.Equal(t, dlg.Nonce(), dlgRead.Nonce())
|
||||
require.True(t, dlg.Meta().Equals(dlgRead.Meta()))
|
||||
if dlg.NotBefore() != nil {
|
||||
// within 1s as the original value gets truncated to seconds when serialized
|
||||
require.WithinDuration(t, *dlg.NotBefore(), *dlgRead.NotBefore(), time.Second)
|
||||
}
|
||||
if dlg.Expiration() != nil {
|
||||
// within 1s as the original value gets truncated to seconds when serialized
|
||||
require.WithinDuration(t, *dlg.Expiration(), *dlgRead.Expiration(), time.Second)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkContainerSerialisation(b *testing.B) {
|
||||
var duration strings.Builder
|
||||
var allocByte strings.Builder
|
||||
var allocCount strings.Builder
|
||||
|
||||
for _, builder := range []strings.Builder{duration, allocByte, allocCount} {
|
||||
builder.WriteString("car\tcarBase64\tcarGzip\tcarGzipBase64\tcbor\tcborBase64\tcborGzip\tcborGzipBase64\tcborFlate\tcborFlateBase64\n")
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
writer func(ctn Writer, w io.Writer) error
|
||||
reader func(io.Reader) (Reader, error)
|
||||
}{
|
||||
{"car", Writer.ToCar, FromCar},
|
||||
{"carBase64", Writer.ToCarBase64, FromCarBase64},
|
||||
{"cbor", Writer.ToCbor, FromCbor},
|
||||
{"cborBase64", Writer.ToCborBase64, FromCborBase64},
|
||||
} {
|
||||
writer := NewWriter()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, c, data := randToken()
|
||||
writer.AddSealed(c, data)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_ = tc.writer(writer, buf)
|
||||
|
||||
b.Run(tc.name+"_write", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_ = tc.writer(writer, buf)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(tc.name+"_read", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = tc.reader(bytes.NewReader(buf.Bytes()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func randDID() (crypto.PrivKey, did.DID) {
|
||||
privKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
d, err := did.FromPrivKey(privKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return privKey, d
|
||||
}
|
||||
|
||||
func randomString(length int) string {
|
||||
b := make([]byte, length/2+1)
|
||||
_, _ = rand.Read(b)
|
||||
return fmt.Sprintf("%x", b)[0:length]
|
||||
}
|
||||
|
||||
func randToken() (*delegation.Token, cid.Cid, []byte) {
|
||||
priv, iss := randDID()
|
||||
_, aud := randDID()
|
||||
cmd := command.New("foo", "bar")
|
||||
pol := policy.MustConstruct(
|
||||
policy.All(".[]",
|
||||
policy.GreaterThan(".value", literal.Int(2)),
|
||||
),
|
||||
)
|
||||
|
||||
opts := []delegation.Option{
|
||||
delegation.WithExpiration(time.Now().Add(time.Hour)),
|
||||
delegation.WithSubject(iss),
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
opts = append(opts, delegation.WithMeta(randomString(8), randomString(10)))
|
||||
}
|
||||
|
||||
t, err := delegation.New(iss, aud, cmd, pol, opts...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b, c, err := t.ToSealed(priv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t, c, b
|
||||
}
|
||||
|
||||
func FuzzContainerRead(f *testing.F) {
|
||||
// Generate a corpus
|
||||
for tokenCount := 0; tokenCount < 10; tokenCount++ {
|
||||
writer := NewWriter()
|
||||
for i := 0; i < tokenCount; i++ {
|
||||
_, c, data := randToken()
|
||||
writer.AddSealed(c, data)
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := writer.ToCbor(buf)
|
||||
require.NoError(f, err)
|
||||
|
||||
f.Add(buf.Bytes())
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
start := time.Now()
|
||||
|
||||
// search for panics
|
||||
_, _ = FromCbor(bytes.NewReader(data))
|
||||
|
||||
if time.Since(start) > 100*time.Millisecond {
|
||||
panic("too long")
|
||||
}
|
||||
})
|
||||
}
|
||||
BIN
pkg/container/testdata/sample-v1.car
vendored
BIN
pkg/container/testdata/sample-v1.car
vendored
Binary file not shown.
@@ -1,65 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
)
|
||||
|
||||
// TODO: should we have a multibase to wrap the cbor? but there is no reader/write in go-multibase :-(
|
||||
|
||||
const currentContainerVersion = "ctn-v1"
|
||||
|
||||
// Writer is a token container writer. It provides a convenient way to aggregate and serialize tokens together.
|
||||
type Writer map[cid.Cid][]byte
|
||||
|
||||
func NewWriter() Writer {
|
||||
return make(Writer)
|
||||
}
|
||||
|
||||
// AddSealed includes a "sealed" token (serialized with a ToSealed* function) in the container.
|
||||
func (ctn Writer) AddSealed(cid cid.Cid, data []byte) {
|
||||
ctn[cid] = data
|
||||
}
|
||||
|
||||
func (ctn Writer) ToCar(w io.Writer) error {
|
||||
return writeCar(w, nil, func(yield func(carBlock, error) bool) {
|
||||
for c, bytes := range ctn {
|
||||
if !yield(carBlock{c: c, data: bytes}, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (ctn Writer) ToCarBase64(w io.Writer) error {
|
||||
w2 := base64.NewEncoder(base64.StdEncoding, w)
|
||||
defer w2.Close()
|
||||
return ctn.ToCar(w2)
|
||||
}
|
||||
|
||||
func (ctn Writer) ToCbor(w io.Writer) error {
|
||||
node, err := qp.BuildMap(basicnode.Prototype.Any, 1, func(ma datamodel.MapAssembler) {
|
||||
qp.MapEntry(ma, currentContainerVersion, qp.List(int64(len(ctn)), func(la datamodel.ListAssembler) {
|
||||
for _, bytes := range ctn {
|
||||
qp.ListEntry(la, qp.Bytes(bytes))
|
||||
}
|
||||
}))
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ipld.EncodeStreaming(w, node, dagcbor.Encode)
|
||||
}
|
||||
|
||||
func (ctn Writer) ToCborBase64(w io.Writer) error {
|
||||
w2 := base64.NewEncoder(base64.StdEncoding, w)
|
||||
defer w2.Close()
|
||||
return ctn.ToCbor(w2)
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// KeySize represents valid AES key sizes
|
||||
type KeySize int
|
||||
|
||||
const (
|
||||
KeySize128 KeySize = 16 // AES-128
|
||||
KeySize192 KeySize = 24 // AES-192
|
||||
KeySize256 KeySize = 32 // AES-256 (recommended)
|
||||
)
|
||||
|
||||
// IsValid returns true if the key size is valid for AES
|
||||
func (ks KeySize) IsValid() bool {
|
||||
switch ks {
|
||||
case KeySize128, KeySize192, KeySize256:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var ErrShortCipherText = errors.New("ciphertext too short")
|
||||
var ErrNoEncryptionKey = errors.New("encryption key is required")
|
||||
var ErrInvalidKeySize = errors.New("invalid key size: must be 16, 24, or 32 bytes")
|
||||
var ErrZeroKey = errors.New("encryption key cannot be all zeros")
|
||||
|
||||
// GenerateKey generates a random AES key of default size KeySize256 (32 bytes).
|
||||
// Returns an error if the specified size is invalid or if key generation fails.
|
||||
func GenerateKey() ([]byte, error) {
|
||||
return GenerateKeyWithSize(KeySize256)
|
||||
}
|
||||
|
||||
// GenerateKeyWithSize generates a random AES key of the specified size.
|
||||
// Returns an error if the specified size is invalid or if key generation fails.
|
||||
func GenerateKeyWithSize(size KeySize) ([]byte, error) {
|
||||
if !size.IsValid() {
|
||||
return nil, ErrInvalidKeySize
|
||||
}
|
||||
|
||||
key := make([]byte, size)
|
||||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate AES key: %w", err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// EncryptWithAESKey encrypts data using AES-GCM with the provided key.
|
||||
// The key must be 16, 24, or 32 bytes long (for AES-128, AES-192, or AES-256).
|
||||
// Returns the encrypted data with the nonce prepended, or an error if encryption fails.
|
||||
func EncryptWithAESKey(data, key []byte) ([]byte, error) {
|
||||
if err := validateAESKey(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gcm.Seal(nonce, nonce, data, nil), nil
|
||||
}
|
||||
|
||||
// DecryptStringWithAESKey decrypts data that was encrypted with EncryptWithAESKey.
|
||||
// The key must match the one used for encryption.
|
||||
// Expects the input to have a prepended nonce.
|
||||
// Returns the decrypted data or an error if decryption fails.
|
||||
func DecryptStringWithAESKey(data, key []byte) ([]byte, error) {
|
||||
if err := validateAESKey(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) < gcm.NonceSize() {
|
||||
return nil, ErrShortCipherText
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
|
||||
decrypted, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decrypted, nil
|
||||
}
|
||||
|
||||
func validateAESKey(key []byte) error {
|
||||
if key == nil {
|
||||
return ErrNoEncryptionKey
|
||||
}
|
||||
|
||||
if !KeySize(len(key)).IsValid() {
|
||||
return ErrInvalidKeySize
|
||||
}
|
||||
|
||||
// check if key is all zeros
|
||||
for _, b := range key {
|
||||
if b != 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrZeroKey
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAESEncryption(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key := make([]byte, 32) // generated random 32-byte key
|
||||
_, errKey := rand.Read(key)
|
||||
require.NoError(t, errKey)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
key []byte
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "valid encryption/decryption",
|
||||
data: []byte("hello world"),
|
||||
key: key,
|
||||
},
|
||||
{
|
||||
name: "nil key returns error",
|
||||
data: []byte("hello world"),
|
||||
key: nil,
|
||||
wantErr: ErrNoEncryptionKey,
|
||||
},
|
||||
{
|
||||
name: "empty data",
|
||||
data: []byte{},
|
||||
key: key,
|
||||
},
|
||||
{
|
||||
name: "invalid key size",
|
||||
data: []byte("hello world"),
|
||||
key: make([]byte, 31),
|
||||
wantErr: ErrInvalidKeySize,
|
||||
},
|
||||
{
|
||||
name: "zero key returns error",
|
||||
data: []byte("hello world"),
|
||||
key: make([]byte, 32),
|
||||
wantErr: ErrZeroKey,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
encrypted, err := EncryptWithAESKey(tt.data, tt.key)
|
||||
if tt.wantErr != nil {
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
decrypted, err := DecryptStringWithAESKey(encrypted, tt.key)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tt.key == nil {
|
||||
require.Equal(t, tt.data, encrypted)
|
||||
require.Equal(t, tt.data, decrypted)
|
||||
} else {
|
||||
require.NotEqual(t, tt.data, encrypted)
|
||||
require.True(t, bytes.Equal(tt.data, decrypted))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptionErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key := make([]byte, 32)
|
||||
_, err := rand.Read(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
key []byte
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "short ciphertext",
|
||||
data: []byte("short"),
|
||||
key: key,
|
||||
errMsg: "ciphertext too short",
|
||||
},
|
||||
{
|
||||
name: "invalid ciphertext",
|
||||
data: make([]byte, 16), // just nonce size
|
||||
key: key,
|
||||
errMsg: "message authentication failed",
|
||||
},
|
||||
{
|
||||
name: "missing key",
|
||||
data: []byte("<22>`M<><4D><EFBFBD>l\u001AIF<49>\u0012<31><32><EFBFBD>=h<>?<3F>c<EFBFBD> <20><>\u0012<31><32><EFBFBD><EFBFBD>\u001C<31>\u0018Ƽ(g"),
|
||||
key: nil,
|
||||
errMsg: "encryption key is required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := DecryptStringWithAESKey(tt.data, tt.key)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.errMsg)
|
||||
})
|
||||
}
|
||||
}
|
||||
151
pkg/meta/meta.go
151
pkg/meta/meta.go
@@ -3,26 +3,21 @@ package meta
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"reflect"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/printer"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/meta/internal/crypto"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -54,21 +49,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 := crypto.DecryptStringWithAESKey(v, encryptionKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(decrypted), nil
|
||||
}
|
||||
|
||||
// GetInt64 retrieves a value as an int64.
|
||||
// Returns ErrNotFound if the given key is missing.
|
||||
// Returns datamodel.ErrWrongKind if the value has the wrong type.
|
||||
@@ -102,21 +82,6 @@ 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 := crypto.DecryptStringWithAESKey(v, encryptionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decrypted, nil
|
||||
}
|
||||
|
||||
// GetNode retrieves a value as a raw IPLD node.
|
||||
// Returns ErrNotFound if the given key is missing.
|
||||
// Returns datamodel.ErrWrongKind if the value has the wrong type.
|
||||
@@ -129,85 +94,43 @@ 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)
|
||||
switch val := val.(type) {
|
||||
case bool:
|
||||
m.Values[key] = basicnode.NewBool(val)
|
||||
case string:
|
||||
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:
|
||||
m.Values[key] = basicnode.NewBytes(val)
|
||||
case datamodel.Node:
|
||||
m.Values[key] = val
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrUnsupported, fqtn(val))
|
||||
}
|
||||
|
||||
node, err := literal.Any(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Keys = append(m.Keys, key)
|
||||
m.Values[key] = node
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddEncrypted adds a key/value pair in the meta set.
|
||||
// The value is encrypted with the given encryptionKey.
|
||||
// Accepted types for the value are: string, []byte.
|
||||
func (m *Meta) AddEncrypted(key string, val any, encryptionKey []byte) error {
|
||||
var encrypted []byte
|
||||
var err error
|
||||
func fqtn(val any) string {
|
||||
var name string
|
||||
|
||||
switch val := val.(type) {
|
||||
case string:
|
||||
encrypted, err = crypto.EncryptWithAESKey([]byte(val), encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case []byte:
|
||||
encrypted, err = crypto.EncryptWithAESKey(val, encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return ErrNotEncryptable
|
||||
t := reflect.TypeOf(val)
|
||||
for t.Kind() == reflect.Pointer {
|
||||
name += "*"
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
return m.Add(key, encrypted)
|
||||
}
|
||||
|
||||
// Equals tells if two Meta hold the same key/values.
|
||||
func (m *Meta) Equals(other *Meta) bool {
|
||||
if len(m.Keys) != len(other.Keys) {
|
||||
return false
|
||||
}
|
||||
if len(m.Values) != len(other.Values) {
|
||||
return false
|
||||
}
|
||||
for _, key := range m.Keys {
|
||||
if !ipld.DeepEqual(m.Values[key], other.Values[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *Meta) String() string {
|
||||
buf := strings.Builder{}
|
||||
buf.WriteString("{")
|
||||
|
||||
for key, node := range m.Values {
|
||||
buf.WriteString("\n\t")
|
||||
buf.WriteString(key)
|
||||
buf.WriteString(": ")
|
||||
buf.WriteString(strings.ReplaceAll(printer.Sprint(node), "\n", "\n\t"))
|
||||
buf.WriteString(",")
|
||||
}
|
||||
|
||||
if len(m.Values) > 0 {
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
buf.WriteString("}")
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ReadOnly returns a read-only version of Meta.
|
||||
func (m *Meta) ReadOnly() ReadOnly {
|
||||
return ReadOnly{m: m}
|
||||
return name + t.PkgPath() + "." + t.Name()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package meta_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestMeta_Add(t *testing.T) {
|
||||
@@ -14,64 +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")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
)
|
||||
|
||||
// ReadOnly wraps a Meta into a read-only facade.
|
||||
type ReadOnly struct {
|
||||
m *Meta
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetBool(key string) (bool, error) {
|
||||
return r.m.GetBool(key)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetString(key string) (string, error) {
|
||||
return r.m.GetString(key)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetEncryptedString(key string, encryptionKey []byte) (string, error) {
|
||||
return r.m.GetEncryptedString(key, encryptionKey)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetInt64(key string) (int64, error) {
|
||||
return r.m.GetInt64(key)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetFloat64(key string) (float64, error) {
|
||||
return r.m.GetFloat64(key)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetBytes(key string) ([]byte, error) {
|
||||
return r.m.GetBytes(key)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetEncryptedBytes(key string, encryptionKey []byte) ([]byte, error) {
|
||||
return r.m.GetEncryptedBytes(key, encryptionKey)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetNode(key string) (ipld.Node, error) {
|
||||
return r.m.GetNode(key)
|
||||
}
|
||||
|
||||
func (r ReadOnly) Equals(other ReadOnly) bool {
|
||||
return r.m.Equals(other.m)
|
||||
}
|
||||
|
||||
func (r ReadOnly) String() string {
|
||||
return r.m.String()
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func statementFromIPLD(path string, node datamodel.Node) (Statement, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return negation{statement: statement}, nil
|
||||
return Not(statement), nil
|
||||
|
||||
case KindAnd, KindOr:
|
||||
arg2, _ := node.LookupByIndex(1)
|
||||
@@ -93,11 +93,11 @@ func statementFromIPLD(path string, node datamodel.Node) (Statement, error) {
|
||||
if pattern.Kind() != datamodel.Kind_String {
|
||||
return nil, ErrNotAString(combinePath(path, op, 2))
|
||||
}
|
||||
g, err := parseGlob(must.String(pattern))
|
||||
res, err := Like(sel, must.String(pattern))
|
||||
if err != nil {
|
||||
return nil, ErrInvalidPattern(combinePath(path, op, 2), err)
|
||||
}
|
||||
return wildcard{selector: sel, pattern: g}, nil
|
||||
return res, nil
|
||||
|
||||
case KindAll, KindAny:
|
||||
sel, err := arg2AsSelector(op)
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
// Package literal holds a collection of functions to create IPLD types to use in policies, selector and args.
|
||||
package literal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
)
|
||||
|
||||
var Bool = basicnode.NewBool
|
||||
var Int = basicnode.NewInt
|
||||
var Float = basicnode.NewFloat
|
||||
var String = basicnode.NewString
|
||||
var Bytes = basicnode.NewBytes
|
||||
var Link = basicnode.NewLink
|
||||
func Node(n ipld.Node) ipld.Node {
|
||||
return n
|
||||
}
|
||||
|
||||
func LinkCid(cid cid.Cid) ipld.Node {
|
||||
return Link(cidlink.Link{Cid: cid})
|
||||
func Link(cid ipld.Link) ipld.Node {
|
||||
nb := basicnode.Prototype.Link.NewBuilder()
|
||||
nb.AssignLink(cid)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Bool(val bool) ipld.Node {
|
||||
nb := basicnode.Prototype.Bool.NewBuilder()
|
||||
nb.AssignBool(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Int(val int64) ipld.Node {
|
||||
nb := basicnode.Prototype.Int.NewBuilder()
|
||||
nb.AssignInt(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Float(val float64) ipld.Node {
|
||||
nb := basicnode.Prototype.Float.NewBuilder()
|
||||
nb.AssignFloat(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func String(val string) ipld.Node {
|
||||
nb := basicnode.Prototype.String.NewBuilder()
|
||||
nb.AssignString(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Bytes(val []byte) ipld.Node {
|
||||
nb := basicnode.Prototype.Bytes.NewBuilder()
|
||||
nb.AssignBytes(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Null() ipld.Node {
|
||||
@@ -30,158 +50,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) {
|
||||
// TODO: handle uint overflow below
|
||||
|
||||
// some fast path
|
||||
switch val := v.(type) {
|
||||
case bool:
|
||||
return basicnode.NewBool(val), nil
|
||||
case string:
|
||||
return basicnode.NewString(val), nil
|
||||
case int:
|
||||
return basicnode.NewInt(int64(val)), nil
|
||||
case int8:
|
||||
return basicnode.NewInt(int64(val)), nil
|
||||
case int16:
|
||||
return basicnode.NewInt(int64(val)), nil
|
||||
case int32:
|
||||
return basicnode.NewInt(int64(val)), nil
|
||||
case int64:
|
||||
return basicnode.NewInt(val), nil
|
||||
case uint:
|
||||
return basicnode.NewInt(int64(val)), nil
|
||||
case uint8:
|
||||
return basicnode.NewInt(int64(val)), nil
|
||||
case uint16:
|
||||
return basicnode.NewInt(int64(val)), nil
|
||||
case uint32:
|
||||
return basicnode.NewInt(int64(val)), nil
|
||||
case uint64:
|
||||
return basicnode.NewInt(int64(val)), nil
|
||||
case float32:
|
||||
return basicnode.NewFloat(float64(val)), nil
|
||||
case float64:
|
||||
return basicnode.NewFloat(val), nil
|
||||
case []byte:
|
||||
return basicnode.NewBytes(val), nil
|
||||
case datamodel.Node:
|
||||
return val, nil
|
||||
case cid.Cid:
|
||||
return LinkCid(val), nil
|
||||
default:
|
||||
}
|
||||
|
||||
builder := basicnode.Prototype__Any{}.NewBuilder()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("%v", r)
|
||||
res = nil
|
||||
}
|
||||
}()
|
||||
|
||||
anyAssemble(v)(builder)
|
||||
|
||||
return builder.Build(), nil
|
||||
}
|
||||
|
||||
func anyAssemble(val any) qp.Assemble {
|
||||
var rt reflect.Type
|
||||
var rv reflect.Value
|
||||
|
||||
// support for recursive calls, staying in reflection land
|
||||
if cast, ok := val.(reflect.Value); ok {
|
||||
rt = cast.Type()
|
||||
rv = cast
|
||||
} else {
|
||||
rt = reflect.TypeOf(val)
|
||||
rv = reflect.ValueOf(val)
|
||||
}
|
||||
|
||||
// we need to dereference in some cases, to get the real value type
|
||||
if rt.Kind() == reflect.Ptr || rt.Kind() == reflect.Interface {
|
||||
rv = rv.Elem()
|
||||
rt = rv.Type()
|
||||
}
|
||||
|
||||
switch rt.Kind() {
|
||||
case reflect.Array:
|
||||
if rt.Elem().Kind() == reflect.Uint8 {
|
||||
panic("bytes array are not supported yet")
|
||||
}
|
||||
return qp.List(int64(rv.Len()), func(la datamodel.ListAssembler) {
|
||||
for i := range rv.Len() {
|
||||
qp.ListEntry(la, anyAssemble(rv.Index(i)))
|
||||
}
|
||||
})
|
||||
case reflect.Slice:
|
||||
if rt.Elem().Kind() == reflect.Uint8 {
|
||||
return qp.Bytes(val.([]byte))
|
||||
}
|
||||
return qp.List(int64(rv.Len()), func(la datamodel.ListAssembler) {
|
||||
for i := range rv.Len() {
|
||||
qp.ListEntry(la, anyAssemble(rv.Index(i)))
|
||||
}
|
||||
})
|
||||
case reflect.Map:
|
||||
if rt.Key().Kind() != reflect.String {
|
||||
break
|
||||
}
|
||||
// deterministic iteration
|
||||
keys := rv.MapKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].String() < keys[j].String()
|
||||
})
|
||||
return qp.Map(int64(rv.Len()), func(ma datamodel.MapAssembler) {
|
||||
for _, key := range keys {
|
||||
qp.MapEntry(ma, key.String(), anyAssemble(rv.MapIndex(key)))
|
||||
}
|
||||
})
|
||||
case reflect.Bool:
|
||||
return qp.Bool(rv.Bool())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return qp.Int(rv.Int())
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return qp.Int(int64(rv.Uint()))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return qp.Float(rv.Float())
|
||||
case reflect.String:
|
||||
return qp.String(rv.String())
|
||||
case reflect.Struct:
|
||||
if rt == reflect.TypeOf(cid.Cid{}) {
|
||||
c := rv.Interface().(cid.Cid)
|
||||
return qp.Link(cidlink.Link{Cid: c})
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported type %T", val))
|
||||
}
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
package literal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
||||
"github.com/ipld/go-ipld-prime/printer"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
n, err := List([]int{1, 2, 3})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_List, n.Kind())
|
||||
require.Equal(t, int64(3), n.Length())
|
||||
require.Equal(t, `list{
|
||||
0: int{1}
|
||||
1: int{2}
|
||||
2: int{3}
|
||||
}`, printer.Sprint(n))
|
||||
|
||||
n, err = List([][]int{{1, 2, 3}, {4, 5, 6}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_List, n.Kind())
|
||||
require.Equal(t, int64(2), n.Length())
|
||||
require.Equal(t, `list{
|
||||
0: list{
|
||||
0: int{1}
|
||||
1: int{2}
|
||||
2: int{3}
|
||||
}
|
||||
1: list{
|
||||
0: int{4}
|
||||
1: int{5}
|
||||
2: int{6}
|
||||
}
|
||||
}`, printer.Sprint(n))
|
||||
}
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
n, err := Map(map[string]any{
|
||||
"bool": true,
|
||||
"string": "foobar",
|
||||
"bytes": []byte{1, 2, 3, 4},
|
||||
"int": 1234,
|
||||
"uint": uint(12345),
|
||||
"float": 1.45,
|
||||
"slice": []int{1, 2, 3},
|
||||
"array": [2]int{1, 2},
|
||||
"map": map[string]any{
|
||||
"foo": "bar",
|
||||
"foofoo": map[string]string{
|
||||
"barbar": "foo",
|
||||
},
|
||||
},
|
||||
"link": cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
v, err := n.LookupByString("bool")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Bool, v.Kind())
|
||||
require.Equal(t, true, must(v.AsBool()))
|
||||
|
||||
v, err = n.LookupByString("string")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_String, v.Kind())
|
||||
require.Equal(t, "foobar", must(v.AsString()))
|
||||
|
||||
v, err = n.LookupByString("bytes")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Bytes, v.Kind())
|
||||
require.Equal(t, []byte{1, 2, 3, 4}, must(v.AsBytes()))
|
||||
|
||||
v, err = n.LookupByString("int")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Int, v.Kind())
|
||||
require.Equal(t, int64(1234), must(v.AsInt()))
|
||||
|
||||
v, err = n.LookupByString("uint")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Int, v.Kind())
|
||||
require.Equal(t, int64(12345), must(v.AsInt()))
|
||||
|
||||
v, err = n.LookupByString("float")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Float, v.Kind())
|
||||
require.Equal(t, 1.45, must(v.AsFloat()))
|
||||
|
||||
v, err = n.LookupByString("slice")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_List, v.Kind())
|
||||
require.Equal(t, int64(3), v.Length())
|
||||
require.Equal(t, `list{
|
||||
0: int{1}
|
||||
1: int{2}
|
||||
2: int{3}
|
||||
}`, printer.Sprint(v))
|
||||
|
||||
v, err = n.LookupByString("array")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_List, v.Kind())
|
||||
require.Equal(t, int64(2), v.Length())
|
||||
require.Equal(t, `list{
|
||||
0: int{1}
|
||||
1: int{2}
|
||||
}`, printer.Sprint(v))
|
||||
|
||||
v, err = n.LookupByString("map")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Map, v.Kind())
|
||||
require.Equal(t, int64(2), v.Length())
|
||||
require.Equal(t, `map{
|
||||
string{"foo"}: string{"bar"}
|
||||
string{"foofoo"}: map{
|
||||
string{"barbar"}: string{"foo"}
|
||||
}
|
||||
}`, printer.Sprint(v))
|
||||
|
||||
v, err = n.LookupByString("link")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Link, v.Kind())
|
||||
asLink, err := v.AsLink()
|
||||
require.NoError(t, err)
|
||||
require.True(t, asLink.(cidlink.Link).Equals(cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm")))
|
||||
}
|
||||
|
||||
func TestAny(t *testing.T) {
|
||||
data := map[string]any{
|
||||
"bool": true,
|
||||
"string": "foobar",
|
||||
"bytes": []byte{1, 2, 3, 4},
|
||||
"int": 1234,
|
||||
"uint": uint(12345),
|
||||
"float": 1.45,
|
||||
"slice": []int{1, 2, 3},
|
||||
"array": [2]int{1, 2},
|
||||
"map": map[string]any{
|
||||
"foo": "bar",
|
||||
"foofoo": map[string]string{
|
||||
"barbar": "foo",
|
||||
},
|
||||
},
|
||||
"link": cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm"),
|
||||
"func": func() {},
|
||||
}
|
||||
|
||||
v, err := Any(data["bool"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Bool, v.Kind())
|
||||
require.Equal(t, true, must(v.AsBool()))
|
||||
|
||||
v, err = Any(data["string"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_String, v.Kind())
|
||||
require.Equal(t, "foobar", must(v.AsString()))
|
||||
|
||||
v, err = Any(data["bytes"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Bytes, v.Kind())
|
||||
require.Equal(t, []byte{1, 2, 3, 4}, must(v.AsBytes()))
|
||||
|
||||
v, err = Any(data["int"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Int, v.Kind())
|
||||
require.Equal(t, int64(1234), must(v.AsInt()))
|
||||
|
||||
v, err = Any(data["uint"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Int, v.Kind())
|
||||
require.Equal(t, int64(12345), must(v.AsInt()))
|
||||
|
||||
v, err = Any(data["float"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Float, v.Kind())
|
||||
require.Equal(t, 1.45, must(v.AsFloat()))
|
||||
|
||||
v, err = Any(data["slice"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_List, v.Kind())
|
||||
require.Equal(t, int64(3), v.Length())
|
||||
require.Equal(t, `list{
|
||||
0: int{1}
|
||||
1: int{2}
|
||||
2: int{3}
|
||||
}`, printer.Sprint(v))
|
||||
|
||||
v, err = Any(data["array"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_List, v.Kind())
|
||||
require.Equal(t, int64(2), v.Length())
|
||||
require.Equal(t, `list{
|
||||
0: int{1}
|
||||
1: int{2}
|
||||
}`, printer.Sprint(v))
|
||||
|
||||
v, err = Any(data["map"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Map, v.Kind())
|
||||
require.Equal(t, int64(2), v.Length())
|
||||
require.Equal(t, `map{
|
||||
string{"foo"}: string{"bar"}
|
||||
string{"foofoo"}: map{
|
||||
string{"barbar"}: string{"foo"}
|
||||
}
|
||||
}`, printer.Sprint(v))
|
||||
|
||||
v, err = Any(data["link"])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, datamodel.Kind_Link, v.Kind())
|
||||
asLink, err := v.AsLink()
|
||||
require.NoError(t, err)
|
||||
require.True(t, asLink.(cidlink.Link).Equals(cid.MustParse("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm")))
|
||||
|
||||
v, err = Any(data["func"])
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func BenchmarkAny(b *testing.B) {
|
||||
b.Run("bool", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Any(true)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("string", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Any("foobar")
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("bytes", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Any([]byte{1, 2, 3, 4})
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("map", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = Any(map[string]any{
|
||||
"foo": "bar",
|
||||
"foofoo": map[string]string{
|
||||
"barbar": "foo",
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func must[T any](t T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
@@ -7,246 +7,170 @@ import (
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/must"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||
)
|
||||
|
||||
// Match determines if the IPLD node satisfies the policy.
|
||||
// The first Statement failing to match is returned as well.
|
||||
func (p Policy) Match(node datamodel.Node) (bool, Statement) {
|
||||
for _, stmt := range p {
|
||||
res, leaf := matchStatement(stmt, node)
|
||||
switch res {
|
||||
case matchResultNoData, matchResultFalse:
|
||||
return false, leaf
|
||||
case matchResultOptionalNoData, matchResultTrue:
|
||||
// continue
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
func (p Policy) Filter(sel selector.Selector) Policy {
|
||||
return p.FilterWithMatcher(sel, selector.SegmentEquals)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// FilterWithMatcher extracts a subset of the policy related to the specified selector,
|
||||
// by matching each segment using the given selector.SegmentMatcher.
|
||||
func (p Policy) FilterWithMatcher(sel selector.Selector, matcher selector.SegmentMatcher) 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
|
||||
if stmt.Selector().Matches(sel, matcher) {
|
||||
filtered = append(filtered, stmt)
|
||||
}
|
||||
}
|
||||
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
|
||||
// Match determines if the IPLD node matches the policy document.
|
||||
func (p Policy) Match(node datamodel.Node) bool {
|
||||
for _, stmt := range p {
|
||||
ok := matchStatement(stmt, node)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch cur.Kind() {
|
||||
return true
|
||||
}
|
||||
|
||||
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 := selector.Select(s.selector, 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))
|
||||
if many != nil {
|
||||
for _, n := range many {
|
||||
if eq := datamodel.DeepEqual(s.value, n); eq {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 := selector.Select(s.selector, 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 := selector.Select(s.selector, 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 := selector.Select(s.selector, 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 := selector.Select(s.selector, 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 := selector.Select(s.selector, 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 := selector.Select(s.selector, 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 := selector.Select(s.selector, 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()))
|
||||
}
|
||||
|
||||
func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) bool) bool {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,13 +3,9 @@ package policy
|
||||
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#policy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
|
||||
selpkg "github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,43 +24,14 @@ const (
|
||||
|
||||
type Policy []Statement
|
||||
|
||||
type Constructor func() (Statement, error)
|
||||
|
||||
func Construct(cstors ...Constructor) (Policy, error) {
|
||||
stmts, err := assemble(cstors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stmts, nil
|
||||
}
|
||||
|
||||
func MustConstruct(cstors ...Constructor) Policy {
|
||||
pol, err := Construct(cstors...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pol
|
||||
}
|
||||
|
||||
func (p Policy) String() string {
|
||||
if len(p) == 0 {
|
||||
return "[]"
|
||||
}
|
||||
childs := make([]string, len(p))
|
||||
for i, statement := range p {
|
||||
childs[i] = strings.ReplaceAll(statement.String(), "\n", "\n ")
|
||||
}
|
||||
return fmt.Sprintf("[\n %s\n]", strings.Join(childs, ",\n "))
|
||||
}
|
||||
|
||||
type Statement interface {
|
||||
Kind() string
|
||||
String() string
|
||||
Selector() selector.Selector
|
||||
}
|
||||
|
||||
type equality struct {
|
||||
kind string
|
||||
selector selpkg.Selector
|
||||
selector selector.Selector
|
||||
value ipld.Node
|
||||
}
|
||||
|
||||
@@ -72,47 +39,28 @@ func (e equality) Kind() string {
|
||||
return e.kind
|
||||
}
|
||||
|
||||
func (e equality) String() string {
|
||||
child, err := ipld.Encode(e.value, dagjson.Encode)
|
||||
if err != nil {
|
||||
return "ERROR: INVALID VALUE"
|
||||
}
|
||||
return fmt.Sprintf(`["%s", "%s", %s]`, e.kind, e.selector, strings.ReplaceAll(string(child), "\n", "\n "))
|
||||
func (e equality) Selector() selector.Selector {
|
||||
return e.selector
|
||||
}
|
||||
|
||||
func Equal(selector string, value ipld.Node) Constructor {
|
||||
return func() (Statement, error) {
|
||||
sel, err := selpkg.Parse(selector)
|
||||
return equality{kind: KindEqual, selector: sel, value: value}, err
|
||||
}
|
||||
func Equal(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{kind: KindEqual, selector: selector, value: value}
|
||||
}
|
||||
|
||||
func GreaterThan(selector string, value ipld.Node) Constructor {
|
||||
return func() (Statement, error) {
|
||||
sel, err := selpkg.Parse(selector)
|
||||
return equality{kind: KindGreaterThan, selector: sel, value: value}, err
|
||||
}
|
||||
func GreaterThan(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{kind: KindGreaterThan, selector: selector, value: value}
|
||||
}
|
||||
|
||||
func GreaterThanOrEqual(selector string, value ipld.Node) Constructor {
|
||||
return func() (Statement, error) {
|
||||
sel, err := selpkg.Parse(selector)
|
||||
return equality{kind: KindGreaterThanOrEqual, selector: sel, value: value}, err
|
||||
}
|
||||
func GreaterThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{kind: KindGreaterThanOrEqual, selector: selector, value: value}
|
||||
}
|
||||
|
||||
func LessThan(selector string, value ipld.Node) Constructor {
|
||||
return func() (Statement, error) {
|
||||
sel, err := selpkg.Parse(selector)
|
||||
return equality{kind: KindLessThan, selector: sel, value: value}, err
|
||||
}
|
||||
func LessThan(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{kind: KindLessThan, selector: selector, value: value}
|
||||
}
|
||||
|
||||
func LessThanOrEqual(selector string, value ipld.Node) Constructor {
|
||||
return func() (Statement, error) {
|
||||
sel, err := selpkg.Parse(selector)
|
||||
return equality{kind: KindLessThanOrEqual, selector: sel, value: value}, err
|
||||
}
|
||||
func LessThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
|
||||
return equality{kind: KindLessThanOrEqual, selector: selector, value: value}
|
||||
}
|
||||
|
||||
type negation struct {
|
||||
@@ -123,16 +71,12 @@ func (n negation) Kind() string {
|
||||
return KindNot
|
||||
}
|
||||
|
||||
func (n negation) String() string {
|
||||
child := n.statement.String()
|
||||
return fmt.Sprintf(`["%s", "%s"]`, n.Kind(), strings.ReplaceAll(child, "\n", "\n "))
|
||||
func (n negation) Selector() selector.Selector {
|
||||
return n.statement.Selector()
|
||||
}
|
||||
|
||||
func Not(cstor Constructor) Constructor {
|
||||
return func() (Statement, error) {
|
||||
stmt, err := cstor()
|
||||
return negation{statement: stmt}, err
|
||||
}
|
||||
func Not(stmt Statement) Statement {
|
||||
return negation{statement: stmt}
|
||||
}
|
||||
|
||||
type connective struct {
|
||||
@@ -144,36 +88,25 @@ func (c connective) Kind() string {
|
||||
return c.kind
|
||||
}
|
||||
|
||||
func (c connective) String() string {
|
||||
childs := make([]string, len(c.statements))
|
||||
for i, statement := range c.statements {
|
||||
childs[i] = strings.ReplaceAll(statement.String(), "\n", "\n ")
|
||||
func (c connective) Selector() selector.Selector {
|
||||
// assuming the first statement's selector is representative
|
||||
if len(c.statements) > 0 {
|
||||
return c.statements[0].Selector()
|
||||
}
|
||||
return fmt.Sprintf("[\"%s\", [\n %s]]\n", c.kind, strings.Join(childs, ",\n "))
|
||||
|
||||
return selector.Selector{}
|
||||
}
|
||||
|
||||
func And(cstors ...Constructor) Constructor {
|
||||
return func() (Statement, error) {
|
||||
stmts, err := assemble(cstors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connective{kind: KindAnd, statements: stmts}, nil
|
||||
}
|
||||
func And(stmts ...Statement) Statement {
|
||||
return connective{kind: KindAnd, statements: stmts}
|
||||
}
|
||||
|
||||
func Or(cstors ...Constructor) Constructor {
|
||||
return func() (Statement, error) {
|
||||
stmts, err := assemble(cstors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connective{kind: KindOr, statements: stmts}, nil
|
||||
}
|
||||
func Or(stmts ...Statement) Statement {
|
||||
return connective{kind: KindOr, statements: stmts}
|
||||
}
|
||||
|
||||
type wildcard struct {
|
||||
selector selpkg.Selector
|
||||
selector selector.Selector
|
||||
pattern glob
|
||||
}
|
||||
|
||||
@@ -181,24 +114,22 @@ func (n wildcard) Kind() string {
|
||||
return KindLike
|
||||
}
|
||||
|
||||
func (n wildcard) String() string {
|
||||
return fmt.Sprintf(`["%s", "%s", "%s"]`, n.Kind(), n.selector, n.pattern)
|
||||
func (n wildcard) Selector() selector.Selector {
|
||||
return n.selector
|
||||
}
|
||||
|
||||
func Like(selector string, pattern string) Constructor {
|
||||
return func() (Statement, error) {
|
||||
g, err := parseGlob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sel, err := selpkg.Parse(selector)
|
||||
return wildcard{selector: sel, pattern: g}, err
|
||||
func Like(selector selector.Selector, pattern string) (Statement, error) {
|
||||
g, err := parseGlob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wildcard{selector: selector, pattern: g}, nil
|
||||
}
|
||||
|
||||
type quantifier struct {
|
||||
kind string
|
||||
selector selpkg.Selector
|
||||
selector selector.Selector
|
||||
statement Statement
|
||||
}
|
||||
|
||||
@@ -206,41 +137,14 @@ func (n quantifier) Kind() string {
|
||||
return n.kind
|
||||
}
|
||||
|
||||
func (n quantifier) String() string {
|
||||
child := n.statement.String()
|
||||
return fmt.Sprintf("[\"%s\", \"%s\",\n %s]", n.Kind(), n.selector, strings.ReplaceAll(child, "\n", "\n "))
|
||||
func (n quantifier) Selector() selector.Selector {
|
||||
return n.selector
|
||||
}
|
||||
|
||||
func All(selector string, cstor Constructor) Constructor {
|
||||
return func() (Statement, error) {
|
||||
stmt, err := cstor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sel, err := selpkg.Parse(selector)
|
||||
return quantifier{kind: KindAll, selector: sel, statement: stmt}, err
|
||||
}
|
||||
func All(selector selector.Selector, statement Statement) Statement {
|
||||
return quantifier{kind: KindAll, selector: selector, statement: statement}
|
||||
}
|
||||
|
||||
func Any(selector string, cstor Constructor) Constructor {
|
||||
return func() (Statement, error) {
|
||||
stmt, err := cstor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sel, err := selpkg.Parse(selector)
|
||||
return quantifier{kind: KindAny, selector: sel, statement: stmt}, err
|
||||
}
|
||||
}
|
||||
|
||||
func assemble(cstors []Constructor) ([]Statement, error) {
|
||||
stmts := make([]Statement, 0, len(cstors))
|
||||
for _, cstor := range cstors {
|
||||
stmt, err := cstor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmts = append(stmts, stmt)
|
||||
}
|
||||
return stmts, nil
|
||||
func Any(selector selector.Selector, statement Statement) Statement {
|
||||
return quantifier{kind: KindAny, selector: selector, statement: statement}
|
||||
}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
package policy_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
)
|
||||
|
||||
func ExamplePolicy() {
|
||||
pol := policy.MustConstruct(
|
||||
policy.Equal(".status", literal.String("draft")),
|
||||
policy.All(".reviewer",
|
||||
policy.Like(".email", "*@example.com"),
|
||||
),
|
||||
policy.Any(".tags", policy.Or(
|
||||
policy.Equal(".", literal.String("news")),
|
||||
policy.Equal(".", literal.String("press")),
|
||||
)),
|
||||
)
|
||||
|
||||
fmt.Println(pol)
|
||||
|
||||
// Output:
|
||||
// [
|
||||
// ["==", ".status", "draft"],
|
||||
// ["all", ".reviewer",
|
||||
// ["like", ".email", "*@example.com"]],
|
||||
// ["any", ".tags",
|
||||
// ["or", [
|
||||
// ["==", ".", "news"],
|
||||
// ["==", ".", "press"]]]
|
||||
// ]
|
||||
// ]
|
||||
}
|
||||
|
||||
func ExamplePolicy_accumulate() {
|
||||
var statements []policy.Constructor
|
||||
|
||||
statements = append(statements, policy.Equal(".status", literal.String("draft")))
|
||||
|
||||
statements = append(statements, policy.All(".reviewer",
|
||||
policy.Like(".email", "*@example.com"),
|
||||
))
|
||||
|
||||
statements = append(statements, policy.Any(".tags", policy.Or(
|
||||
policy.Equal(".", literal.String("news")),
|
||||
policy.Equal(".", literal.String("press")),
|
||||
)))
|
||||
|
||||
pol := policy.MustConstruct(statements...)
|
||||
|
||||
fmt.Println(pol)
|
||||
|
||||
// Output:
|
||||
// [
|
||||
// ["==", ".status", "draft"],
|
||||
// ["all", ".reviewer",
|
||||
// ["like", ".email", "*@example.com"]],
|
||||
// ["any", ".tags",
|
||||
// ["or", [
|
||||
// ["==", ".", "news"],
|
||||
// ["==", ".", "press"]]]
|
||||
// ]
|
||||
// ]
|
||||
}
|
||||
|
||||
func TestConstruct(t *testing.T) {
|
||||
pol, err := policy.Construct(
|
||||
policy.Equal(".status", literal.String("draft")),
|
||||
policy.All(".reviewer",
|
||||
policy.Like(".email", "*@example.com"),
|
||||
),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pol)
|
||||
|
||||
// check if errors cascade correctly
|
||||
pol, err = policy.Construct(
|
||||
policy.Equal(".status", literal.String("draft")),
|
||||
policy.All(".reviewer", policy.Or(
|
||||
policy.Like(".email", "*@example.com"),
|
||||
policy.Like(".", "\\"), // invalid pattern
|
||||
)),
|
||||
)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, pol)
|
||||
}
|
||||
@@ -2,18 +2,10 @@ package selector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
indexRegex = regexp.MustCompile(`^-?\d+$`)
|
||||
sliceRegex = regexp.MustCompile(`^((\-?\d+:\-?\d*)|(\-?\d*:\-?\d+))$`)
|
||||
fieldRegex = regexp.MustCompile(`^\.[a-zA-Z_]*?$`)
|
||||
)
|
||||
|
||||
func Parse(str string) (Selector, error) {
|
||||
if len(str) == 0 {
|
||||
return nil, newParseError("empty selector", str, 0, "")
|
||||
@@ -21,12 +13,6 @@ func Parse(str string) (Selector, error) {
|
||||
if string(str[0]) != "." {
|
||||
return nil, newParseError("selector must start with identity segment '.'", str, 0, string(str[0]))
|
||||
}
|
||||
if str == "." {
|
||||
return Selector{segment{str: ".", identity: true}}, nil
|
||||
}
|
||||
if str == ".?" {
|
||||
return Selector{segment{str: ".?", identity: true, optional: true}}, nil
|
||||
}
|
||||
|
||||
col := 0
|
||||
var sel Selector
|
||||
@@ -34,70 +20,56 @@ func Parse(str string) (Selector, error) {
|
||||
seg := tok
|
||||
opt := strings.HasSuffix(tok, "?")
|
||||
if opt {
|
||||
seg = strings.TrimRight(tok, "?")
|
||||
seg = tok[0 : len(tok)-1]
|
||||
}
|
||||
switch {
|
||||
case seg == ".":
|
||||
switch seg {
|
||||
case ".":
|
||||
if len(sel) > 0 && sel[len(sel)-1].Identity() {
|
||||
return nil, newParseError("selector contains unsupported recursive descent segment: '..'", str, col, tok)
|
||||
}
|
||||
sel = append(sel, segment{str: ".", identity: true})
|
||||
sel = append(sel, Identity)
|
||||
case "[]":
|
||||
sel = append(sel, segment{tok, false, opt, true, nil, "", 0})
|
||||
default:
|
||||
if strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]") {
|
||||
lookup := seg[1 : len(seg)-1]
|
||||
|
||||
case seg == "[]":
|
||||
sel = append(sel, segment{str: tok, optional: opt, iterator: true})
|
||||
|
||||
case strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]"):
|
||||
lookup := seg[1 : len(seg)-1]
|
||||
|
||||
switch {
|
||||
// index, [123]
|
||||
case indexRegex.MatchString(lookup):
|
||||
idx, err := strconv.Atoi(lookup)
|
||||
if err != nil {
|
||||
return nil, newParseError("invalid index", str, col, tok)
|
||||
}
|
||||
sel = append(sel, segment{str: tok, optional: opt, index: idx})
|
||||
|
||||
// explicit field, ["abcd"]
|
||||
case strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\""):
|
||||
fieldName := lookup[1 : len(lookup)-1]
|
||||
if strings.Contains(fieldName, ":") {
|
||||
if indexRegex.MatchString(lookup) { // index
|
||||
idx, err := strconv.Atoi(lookup)
|
||||
if err != nil {
|
||||
return nil, newParseError("invalid index", str, col, tok)
|
||||
}
|
||||
sel = append(sel, segment{str: tok, optional: opt, index: idx})
|
||||
} else if strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\"") { // explicit field
|
||||
sel = append(sel, segment{str: tok, optional: opt, field: lookup[1 : len(lookup)-1]})
|
||||
} else if sliceRegex.MatchString(lookup) { // slice [3:5] or [:5] or [3:]
|
||||
var rng []int
|
||||
splt := strings.Split(lookup, ":")
|
||||
if splt[0] == "" {
|
||||
rng = append(rng, 0)
|
||||
} else {
|
||||
i, err := strconv.Atoi(splt[0])
|
||||
if err != nil {
|
||||
return nil, newParseError("invalid slice index", str, col, tok)
|
||||
}
|
||||
rng = append(rng, i)
|
||||
}
|
||||
if splt[1] != "" {
|
||||
i, err := strconv.Atoi(splt[1])
|
||||
if err != nil {
|
||||
return nil, newParseError("invalid slice index", str, col, tok)
|
||||
}
|
||||
rng = append(rng, i)
|
||||
}
|
||||
sel = append(sel, segment{str: tok, optional: opt, slice: rng})
|
||||
} else {
|
||||
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
|
||||
}
|
||||
sel = append(sel, segment{str: tok, optional: opt, field: fieldName})
|
||||
|
||||
// slice [3:5] or [:5] or [3:], also negative numbers
|
||||
case sliceRegex.MatchString(lookup):
|
||||
var rng [2]int64
|
||||
splt := strings.Split(lookup, ":")
|
||||
if splt[0] == "" {
|
||||
rng[0] = math.MinInt
|
||||
} else {
|
||||
i, err := strconv.ParseInt(splt[0], 10, 0)
|
||||
if err != nil {
|
||||
return nil, newParseError("invalid slice index", str, col, tok)
|
||||
}
|
||||
rng[0] = i
|
||||
}
|
||||
if splt[1] == "" {
|
||||
rng[1] = math.MaxInt
|
||||
} else {
|
||||
i, err := strconv.ParseInt(splt[1], 10, 0)
|
||||
if err != nil {
|
||||
return nil, newParseError("invalid slice index", str, col, tok)
|
||||
}
|
||||
rng[1] = i
|
||||
}
|
||||
sel = append(sel, segment{str: tok, optional: opt, slice: rng[:]})
|
||||
|
||||
default:
|
||||
} else if fieldRegex.MatchString(seg) {
|
||||
sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]})
|
||||
} else {
|
||||
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
|
||||
}
|
||||
|
||||
case fieldRegex.MatchString(seg):
|
||||
sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]})
|
||||
default:
|
||||
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
|
||||
}
|
||||
col += len(tok)
|
||||
}
|
||||
|
||||
@@ -1,552 +0,0 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Run("identity", func(t *testing.T) {
|
||||
sel, err := Parse(".")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
})
|
||||
|
||||
t.Run("dotted field name", func(t *testing.T) {
|
||||
sel, err := Parse(".foo")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(sel))
|
||||
require.False(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Equal(t, sel[0].Field(), "foo")
|
||||
require.Empty(t, sel[0].Index())
|
||||
})
|
||||
|
||||
t.Run("explicit field", func(t *testing.T) {
|
||||
sel, err := Parse(`.["foo"]`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Equal(t, sel[1].Field(), "foo")
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("iterator, collection value", func(t *testing.T) {
|
||||
sel, err := Parse(".[]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.True(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("index", func(t *testing.T) {
|
||||
sel, err := Parse(".[138]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Equal(t, sel[1].Index(), 138)
|
||||
})
|
||||
|
||||
t.Run("negative index", func(t *testing.T) {
|
||||
sel, err := Parse(".[-138]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Equal(t, sel[1].Index(), -138)
|
||||
})
|
||||
|
||||
t.Run("List slice", func(t *testing.T) {
|
||||
sel, err := Parse(".[7:11]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{7, 11})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
|
||||
sel, err = Parse(".[2:]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{2, math.MaxInt})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
|
||||
sel, err = Parse(".[:42]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{math.MinInt, 42})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
|
||||
sel, err = Parse(".[0:-2]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{0, -2})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("optional identity", func(t *testing.T) {
|
||||
sel, err := Parse(".?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.True(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
})
|
||||
|
||||
t.Run("optional dotted field name", func(t *testing.T) {
|
||||
sel, err := Parse(".foo?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(sel))
|
||||
require.False(t, sel[0].Identity())
|
||||
require.True(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Equal(t, sel[0].Field(), "foo")
|
||||
require.Empty(t, sel[0].Index())
|
||||
})
|
||||
|
||||
t.Run("optional explicit field", func(t *testing.T) {
|
||||
sel, err := Parse(`.["foo"]?`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Equal(t, sel[1].Field(), "foo")
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("optional iterator", func(t *testing.T) {
|
||||
sel, err := Parse(".[]?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.True(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("optional index", func(t *testing.T) {
|
||||
sel, err := Parse(".[138]?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Equal(t, sel[1].Index(), 138)
|
||||
})
|
||||
|
||||
t.Run("optional negative index", func(t *testing.T) {
|
||||
sel, err := Parse(".[-138]?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Equal(t, sel[1].Index(), -138)
|
||||
})
|
||||
|
||||
t.Run("optional list slice", func(t *testing.T) {
|
||||
sel, err := Parse(".[7:11]?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{7, 11})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
|
||||
sel, err = Parse(".[2:]?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{2, math.MaxInt})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
|
||||
sel, err = Parse(".[:42]?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{math.MinInt, 42})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
|
||||
sel, err = Parse(".[0:-2]?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{0, -2})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("idempotent optional", func(t *testing.T) {
|
||||
sel, err := Parse(".foo???")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(sel))
|
||||
require.False(t, sel[0].Identity())
|
||||
require.True(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Equal(t, sel[0].Field(), "foo")
|
||||
require.Empty(t, sel[0].Index())
|
||||
})
|
||||
|
||||
t.Run("deny multi dot", func(t *testing.T) {
|
||||
_, err := Parse("..")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("nesting", func(t *testing.T) {
|
||||
str := `.foo.["bar"].[138]?.baz[1:]`
|
||||
sel, err := Parse(str)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, str, sel.String())
|
||||
require.Equal(t, 7, len(sel))
|
||||
require.False(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Equal(t, sel[0].Field(), "foo")
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.True(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
require.False(t, sel[2].Identity())
|
||||
require.False(t, sel[2].Optional())
|
||||
require.False(t, sel[2].Iterator())
|
||||
require.Empty(t, sel[2].Slice())
|
||||
require.Equal(t, sel[2].Field(), "bar")
|
||||
require.Empty(t, sel[2].Index())
|
||||
require.True(t, sel[3].Identity())
|
||||
require.False(t, sel[3].Optional())
|
||||
require.False(t, sel[3].Iterator())
|
||||
require.Empty(t, sel[3].Slice())
|
||||
require.Empty(t, sel[3].Field())
|
||||
require.Empty(t, sel[3].Index())
|
||||
require.False(t, sel[4].Identity())
|
||||
require.True(t, sel[4].Optional())
|
||||
require.False(t, sel[4].Iterator())
|
||||
require.Empty(t, sel[4].Slice())
|
||||
require.Empty(t, sel[4].Field())
|
||||
require.Equal(t, sel[4].Index(), 138)
|
||||
require.False(t, sel[5].Identity())
|
||||
require.False(t, sel[5].Optional())
|
||||
require.False(t, sel[5].Iterator())
|
||||
require.Empty(t, sel[5].Slice())
|
||||
require.Equal(t, sel[5].Field(), "baz")
|
||||
require.Empty(t, sel[5].Index())
|
||||
require.False(t, sel[6].Identity())
|
||||
require.False(t, sel[6].Optional())
|
||||
require.False(t, sel[6].Iterator())
|
||||
require.Equal(t, sel[6].Slice(), []int64{1, math.MaxInt})
|
||||
require.Empty(t, sel[6].Field())
|
||||
require.Empty(t, sel[6].Index())
|
||||
})
|
||||
|
||||
t.Run("non dotted", func(t *testing.T) {
|
||||
_, err := Parse("foo")
|
||||
require.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("non quoted", func(t *testing.T) {
|
||||
_, err := Parse(".[foo]")
|
||||
require.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("slice with negative start and positive end", func(t *testing.T) {
|
||||
sel, err := Parse(".[0:-2]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{0, -2})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("slice with start greater than end", func(t *testing.T) {
|
||||
sel, err := Parse(".[5:2]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{5, 2})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("slice on string", func(t *testing.T) {
|
||||
sel, err := Parse(`.["foo"].[1:3]`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 4, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Equal(t, sel[1].Field(), "foo")
|
||||
require.Empty(t, sel[1].Index())
|
||||
require.True(t, sel[2].Identity())
|
||||
require.False(t, sel[2].Optional())
|
||||
require.False(t, sel[2].Iterator())
|
||||
require.Empty(t, sel[2].Slice())
|
||||
require.Empty(t, sel[2].Field())
|
||||
require.Empty(t, sel[2].Index())
|
||||
require.False(t, sel[3].Identity())
|
||||
require.False(t, sel[3].Optional())
|
||||
require.False(t, sel[3].Iterator())
|
||||
require.Equal(t, sel[3].Slice(), []int64{1, 3})
|
||||
require.Empty(t, sel[3].Field())
|
||||
require.Empty(t, sel[3].Index())
|
||||
})
|
||||
|
||||
t.Run("slice on array", func(t *testing.T) {
|
||||
sel, err := Parse(`.[1:3]`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Equal(t, sel[1].Slice(), []int64{1, 3})
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("index on array", func(t *testing.T) {
|
||||
sel, err := Parse(`.[1]`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Equal(t, sel[1].Index(), 1)
|
||||
})
|
||||
|
||||
t.Run("invalid slice on object", func(t *testing.T) {
|
||||
_, err := Parse(`.["foo":"bar"]`)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid segment")
|
||||
})
|
||||
|
||||
t.Run("index on object", func(t *testing.T) {
|
||||
sel, err := Parse(`.["foo"]`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Equal(t, sel[1].Field(), "foo")
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("slice with non-integer start", func(t *testing.T) {
|
||||
_, err := Parse(".[foo:3]")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("slice with non-integer end", func(t *testing.T) {
|
||||
_, err := Parse(".[1:bar]")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("index with non-integer", func(t *testing.T) {
|
||||
_, err := Parse(".[foo]")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -2,29 +2,19 @@ package selector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"regexp"
|
||||
"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:
|
||||
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#selectors
|
||||
type Selector []segment
|
||||
|
||||
// Select perform the selection described by the selector on the input IPLD DAG.
|
||||
// Select can return:
|
||||
// - exactly one matched IPLD node
|
||||
// - a resolutionerr error if not being able to resolve to a node
|
||||
// - nil and no errors, if the selector couldn't match on an optional segment (with ?).
|
||||
func (s Selector) Select(subject ipld.Node) (ipld.Node, error) {
|
||||
return resolve(s, subject, nil)
|
||||
}
|
||||
|
||||
func (s Selector) String() string {
|
||||
var res strings.Builder
|
||||
for _, seg := range s {
|
||||
@@ -33,16 +23,81 @@ func (s Selector) String() string {
|
||||
return res.String()
|
||||
}
|
||||
|
||||
// Matches checks if the selector matches another selector.
|
||||
func (s Selector) Matches(other Selector, matcher SegmentMatcher) bool {
|
||||
if len(s) != len(other) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, seg := range s {
|
||||
if !matcher(seg, other[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var Identity = segment{".", true, false, false, nil, "", 0}
|
||||
|
||||
var (
|
||||
indexRegex = regexp.MustCompile(`^-?\d+$`)
|
||||
sliceRegex = regexp.MustCompile(`^((\-?\d+:\-?\d*)|(\-?\d*:\-?\d+))$`)
|
||||
fieldRegex = regexp.MustCompile(`^\.[a-zA-Z_]*?$`)
|
||||
)
|
||||
|
||||
type segment struct {
|
||||
str string
|
||||
identity bool
|
||||
optional bool
|
||||
iterator bool
|
||||
slice []int64 // either 0-length or 2-length
|
||||
slice []int
|
||||
field string
|
||||
index int
|
||||
}
|
||||
|
||||
// NewFieldSegment creates a new segment for a field.
|
||||
func NewFieldSegment(field string) segment {
|
||||
return segment{
|
||||
str: fmt.Sprintf(".%s", field),
|
||||
field: field,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIndexSegment creates a new segment for an index.
|
||||
func NewIndexSegment(index int) segment {
|
||||
return segment{
|
||||
str: fmt.Sprintf("[%d]", index),
|
||||
index: index,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSliceSegment creates a new segment for a slice.
|
||||
func NewSliceSegment(slice []int) segment {
|
||||
return segment{
|
||||
str: fmt.Sprintf("[%d:%d]", slice[0], slice[1]),
|
||||
slice: slice,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIteratorSegment creates a new segment for an iterator.
|
||||
func NewIteratorSegment() segment {
|
||||
return segment{
|
||||
str: "*",
|
||||
iterator: true,
|
||||
}
|
||||
}
|
||||
|
||||
type SegmentMatcher func(s1, s2 segment) bool
|
||||
|
||||
var SegmentEquals SegmentMatcher = func(s1, s2 segment) bool {
|
||||
return s1.str == s2.str
|
||||
}
|
||||
|
||||
var SegmentStartsWith SegmentMatcher = func(s1, s2 segment) bool {
|
||||
return strings.HasPrefix(s1.str, s2.str)
|
||||
}
|
||||
|
||||
// String returns the segment's string representation.
|
||||
func (s segment) String() string {
|
||||
return s.str
|
||||
@@ -64,7 +119,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 +133,308 @@ 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
|
||||
}
|
||||
// Select uses a selector to extract an IPLD node or set of nodes from the
|
||||
// passed subject node.
|
||||
func Select(sel Selector, subject ipld.Node) (ipld.Node, []ipld.Node, error) {
|
||||
return resolve(sel, subject, nil)
|
||||
}
|
||||
|
||||
func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.Node, error) {
|
||||
cur := subject
|
||||
for _, 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.Identity():
|
||||
continue
|
||||
|
||||
case seg.Iterator():
|
||||
switch {
|
||||
case cur == nil || cur.Kind() == datamodel.Kind_Null:
|
||||
if 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
|
||||
// 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)
|
||||
}
|
||||
return 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
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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 {
|
||||
// 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)
|
||||
return nil, nil, 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)
|
||||
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())
|
||||
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 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, newResolutionError(fmt.Sprintf("object has no field named: %s", seg.Field()), at)
|
||||
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 {
|
||||
cur = n
|
||||
}
|
||||
|
||||
default:
|
||||
err := newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
||||
return nil, errIfNotOptional(seg, err)
|
||||
}
|
||||
|
||||
case len(seg.Slice()) > 0:
|
||||
if cur == nil {
|
||||
err := newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
|
||||
return nil, errIfNotOptional(seg, err)
|
||||
}
|
||||
|
||||
slice := seg.Slice()
|
||||
|
||||
switch cur.Kind() {
|
||||
case datamodel.Kind_List:
|
||||
start, end := resolveSliceIndices(slice, cur.Length())
|
||||
sliced, err := qp.BuildList(basicnode.Prototype.Any, end-start, func(l datamodel.ListAssembler) {
|
||||
for i := start; i < end; i++ {
|
||||
item, err := cur.LookupByIndex(i)
|
||||
if err != nil {
|
||||
// recovered by BuildList
|
||||
// Error is bubbled up, but should never occur as we already checked the type and boundaries
|
||||
// This is verified with fuzzing.
|
||||
panic(err)
|
||||
switch cur.Kind() {
|
||||
case datamodel.Kind_List:
|
||||
if end > cur.Length() {
|
||||
end = cur.Length()
|
||||
}
|
||||
qp.ListEntry(l, qp.Node(item))
|
||||
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])
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
panic("should never happen")
|
||||
}
|
||||
cur = sliced
|
||||
|
||||
case datamodel.Kind_Bytes:
|
||||
b, _ := cur.AsBytes()
|
||||
start, end := resolveSliceIndices(slice, int64(len(b)))
|
||||
cur = basicnode.NewBytes(b[start:end])
|
||||
|
||||
case datamodel.Kind_String:
|
||||
str, _ := cur.AsString()
|
||||
runes := []rune(str)
|
||||
start, end := resolveSliceIndices(slice, int64(len(runes)))
|
||||
cur = basicnode.NewString(string(runes[start:end]))
|
||||
|
||||
default:
|
||||
return nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
|
||||
}
|
||||
|
||||
default: // Index()
|
||||
at = append(at, strconv.Itoa(seg.Index()))
|
||||
|
||||
default:
|
||||
at = append(at, fmt.Sprintf("%d", 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 seg.Optional() {
|
||||
cur = nil
|
||||
} else {
|
||||
return nil, nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
||||
}
|
||||
if idx < 0 || idx >= int(cur.Length()) {
|
||||
err := newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
|
||||
return nil, errIfNotOptional(seg, err)
|
||||
} 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)
|
||||
}
|
||||
cur, _ = cur.LookupByIndex(int64(idx))
|
||||
|
||||
case datamodel.Kind_Bytes:
|
||||
b, _ := cur.AsBytes()
|
||||
if idx < 0 {
|
||||
idx = len(b) + idx
|
||||
}
|
||||
if idx < 0 || idx >= len(b) {
|
||||
err := newResolutionError(fmt.Sprintf("index %d out of bounds for bytes of length %d", seg.Index(), len(b)), at)
|
||||
return nil, errIfNotOptional(seg, err)
|
||||
}
|
||||
cur = basicnode.NewInt(int64(b[idx]))
|
||||
|
||||
default:
|
||||
return nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// segment exhausted, we return where we are
|
||||
return cur, nil
|
||||
return cur, nil, nil
|
||||
}
|
||||
|
||||
// resolveSliceIndices resolves the start and end indices for slicing a list or byte array.
|
||||
@@ -253,41 +448,26 @@ 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:
|
||||
start = length + slice[0]
|
||||
}
|
||||
switch {
|
||||
case slice[1] == math.MaxInt:
|
||||
end = length
|
||||
case slice[1] < 0:
|
||||
end = length + slice[1]
|
||||
}
|
||||
|
||||
// backward iteration is not allowed, shortcut to an empty result
|
||||
if start >= end {
|
||||
start, end = 0, 0
|
||||
}
|
||||
// clamp out of bound
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > length {
|
||||
start = length
|
||||
}
|
||||
if end > length {
|
||||
end = length
|
||||
if len(slice) > 1 {
|
||||
end = int64(slice[1])
|
||||
if end <= 0 {
|
||||
end = length + end
|
||||
if end < start {
|
||||
end = start
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return start, end
|
||||
@@ -300,6 +480,19 @@ func kindString(n datamodel.Node) string {
|
||||
return n.Kind().String()
|
||||
}
|
||||
|
||||
func isMissing(err error) bool {
|
||||
if _, ok := err.(datamodel.ErrNotExists); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := err.(schema.ErrNoSuchField); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := err.(schema.ErrInvalidKey); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type resolutionerr struct {
|
||||
msg string
|
||||
at []string
|
||||
|
||||
@@ -1,20 +1,252 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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
|
||||
@@ -81,11 +313,14 @@ func TestSelect(t *testing.T) {
|
||||
sel, err := Parse(".")
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := sel.Select(anode)
|
||||
one, many, err := Select(sel, 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)
|
||||
})
|
||||
|
||||
@@ -93,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 := Select(sel, 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 = Select(sel, 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)
|
||||
})
|
||||
|
||||
@@ -112,177 +353,81 @@ func TestSelect(t *testing.T) {
|
||||
sel, err := Parse(".name.middle?")
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := sel.Select(anode)
|
||||
one, many, err := Select(sel, 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 = Select(sel, 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 := Select(sel, 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 := Select(sel, anode)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, one)
|
||||
require.Empty(t, many)
|
||||
})
|
||||
|
||||
t.Run("iterator", func(t *testing.T) {
|
||||
sel, err := Parse(".interests[]")
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := sel.Select(anode)
|
||||
one, many, err := Select(sel, 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 := Select(sel, 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))
|
||||
}
|
||||
|
||||
t.Run("out of bounds slicing", func(t *testing.T) {
|
||||
node, err := qp.BuildList(basicnode.Prototype.Any, 3, func(la datamodel.ListAssembler) {
|
||||
qp.ListEntry(la, qp.Int(1))
|
||||
qp.ListEntry(la, qp.Int(2))
|
||||
qp.ListEntry(la, qp.Int(3))
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
sel, err := Parse(`.[10:20]`)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := sel.Select(node)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res)
|
||||
require.Equal(t, int64(0), res.Length())
|
||||
|
||||
_, err = res.LookupByIndex(0)
|
||||
require.ErrorIs(t, err, datamodel.ErrNotExists{}) // assert empty result for out of bounds slice
|
||||
})
|
||||
|
||||
t.Run("backward slicing", func(t *testing.T) {
|
||||
node, err := qp.BuildList(basicnode.Prototype.Any, 3, func(la datamodel.ListAssembler) {
|
||||
qp.ListEntry(la, qp.Int(1))
|
||||
qp.ListEntry(la, qp.Int(2))
|
||||
qp.ListEntry(la, qp.Int(3))
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
sel, err := Parse(`.[5:2]`)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := sel.Select(node)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res)
|
||||
require.Equal(t, int64(0), res.Length())
|
||||
|
||||
_, err = res.LookupByIndex(0)
|
||||
require.ErrorIs(t, err, datamodel.ErrNotExists{}) // assert empty result for backward slice
|
||||
})
|
||||
|
||||
t.Run("slice with negative index", func(t *testing.T) {
|
||||
node, err := qp.BuildList(basicnode.Prototype.Any, 3, func(la datamodel.ListAssembler) {
|
||||
qp.ListEntry(la, qp.Int(1))
|
||||
qp.ListEntry(la, qp.Int(2))
|
||||
qp.ListEntry(la, qp.Int(3))
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
sel, err := Parse(`.[0:-1]`)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := sel.Select(node)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res)
|
||||
|
||||
val, err := res.LookupByIndex(1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, int(must.Int(val))) // Assert sliced value at index 1
|
||||
})
|
||||
|
||||
t.Run("slice on bytes", func(t *testing.T) {
|
||||
sel, err := Parse(`.[1:3]`)
|
||||
require.NoError(t, err)
|
||||
|
||||
node := basicnode.NewBytes([]byte{0x01, 0x02, 0x03, 0x04, 0x05})
|
||||
res, err := sel.Select(node)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res)
|
||||
|
||||
bytes, err := res.AsBytes()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{0x02, 0x03}, bytes) // assert sliced bytes
|
||||
})
|
||||
|
||||
t.Run("index on bytes", func(t *testing.T) {
|
||||
sel, err := Parse(`.[2]`)
|
||||
require.NoError(t, err)
|
||||
|
||||
node := basicnode.NewBytes([]byte{0x01, 0x02, 0x03, 0x04, 0x05})
|
||||
res, err := sel.Select(node)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res)
|
||||
|
||||
val, err := res.AsInt()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0x03), val) // assert indexed byte value
|
||||
})
|
||||
|
||||
t.Run("out of bounds slicing on bytes", func(t *testing.T) {
|
||||
sel, err := Parse(`.[10:20]`)
|
||||
require.NoError(t, err)
|
||||
|
||||
node := basicnode.NewBytes([]byte{0x01, 0x02, 0x03})
|
||||
res, err := sel.Select(node)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
|
||||
bytes, err := res.AsBytes()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, bytes) // assert empty result for out of bounds slice
|
||||
})
|
||||
|
||||
t.Run("out of bounds indexing on bytes", func(t *testing.T) {
|
||||
sel, err := Parse(`.[10]`)
|
||||
require.NoError(t, err)
|
||||
|
||||
node := basicnode.NewBytes([]byte{0x01, 0x02, 0x03})
|
||||
_, err = sel.Select(node)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "can not resolve path: .10") // assert error for out of bounds index
|
||||
require.Equal(t, alice.Interests[0].Name, must.String(many[0]))
|
||||
require.Equal(t, alice.Interests[0].Experience, int(must.Int(many[2])))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -349,10 +494,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)
|
||||
}
|
||||
_, _, _ = Select(sel, node)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 := selector.Select(sel, makeNode(t, tc.Input))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
require.NotEqual(t, node != nil, len(nodes) > 0) // XOR (only one of node or nodes should be set)
|
||||
|
||||
// make an IPLD List node from a []datamodel.Node
|
||||
if node == nil {
|
||||
nb := basicnode.Prototype.List.NewBuilder()
|
||||
la, err := nb.BeginList(int64(len(nodes)))
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, n := range nodes {
|
||||
// TODO: This code is probably not needed if the Select operation properly prunes nil values - e.g.: Optional Iterator
|
||||
if n == nil {
|
||||
n = datamodel.Null
|
||||
}
|
||||
|
||||
require.NoError(t, la.AssembleValue().AssignNode(n))
|
||||
}
|
||||
require.NoError(t, la.Finish())
|
||||
|
||||
node = nb.Build()
|
||||
}
|
||||
|
||||
exp := makeNode(t, tc.Output)
|
||||
require.True(t, ipld.DeepEqual(exp, res))
|
||||
equalIPLD(t, exp, node)
|
||||
})
|
||||
}
|
||||
|
||||
// No error and return null, as optional
|
||||
// null
|
||||
for _, testcase := range []Testcase{
|
||||
{Name: "Optional Missing Key", Selector: `.x?`, Input: `{}`},
|
||||
{Name: "Optional Null Key", Selector: `.x?`, Input: `null`},
|
||||
@@ -73,15 +97,19 @@ func TestSupportedForms(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// attempt to select
|
||||
res, err := sel.Select(makeNode(t, tc.Input))
|
||||
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, res)
|
||||
// TODO: should Select return a single node which is sometimes a list or null?
|
||||
// require.Equal(t, datamodel.Null, node)
|
||||
assert.Nil(t, node)
|
||||
assert.Empty(t, nodes)
|
||||
})
|
||||
}
|
||||
|
||||
// fail to select and return an error
|
||||
// error
|
||||
for _, testcase := range []Testcase{
|
||||
{Name: "Null Iterator", Selector: `.[]`, Input: `null`},
|
||||
{Name: "Nested Iterator", Selector: `.[][]`, Input: `[[1], 2, [3]]`},
|
||||
{Name: "Missing Key", Selector: `.x`, Input: `{}`},
|
||||
{Name: "Null Key", Selector: `.x`, Input: `null`},
|
||||
{Name: "Array Key", Selector: `.x`, Input: `[]`},
|
||||
@@ -96,13 +124,31 @@ func TestSupportedForms(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// attempt to select
|
||||
res, err := sel.Select(makeNode(t, tc.Input))
|
||||
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
|
||||
require.Error(t, err)
|
||||
require.Nil(t, res)
|
||||
assert.Nil(t, node)
|
||||
assert.Empty(t, nodes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func equalIPLD(t *testing.T, expected datamodel.Node, actual datamodel.Node) bool {
|
||||
t.Helper()
|
||||
|
||||
exp, act := &bytes.Buffer{}, &bytes.Buffer{}
|
||||
if err := dagjson.Encode(expected, exp); err != nil {
|
||||
return assert.Fail(t, "Failed to encode json for expected IPLD node")
|
||||
}
|
||||
|
||||
if err := dagjson.Encode(actual, act); err != nil {
|
||||
return assert.Fail(t, "Failed to encode JSON for actual IPLD node")
|
||||
}
|
||||
|
||||
require.JSONEq(t, exp.String(), act.String())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func makeNode(t *testing.T, dagJsonInput string) ipld.Node {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
package delegation_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/golden"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did/didtest"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
)
|
||||
|
||||
const (
|
||||
nonce = "6roDhGi0kiNriQAz7J3d+bOeoI/tj8ENikmQNbtjnD0"
|
||||
|
||||
subJectCmd = "/foo/bar"
|
||||
subjectPol = `
|
||||
[
|
||||
[
|
||||
"==",
|
||||
".status",
|
||||
"draft"
|
||||
],
|
||||
[
|
||||
"all",
|
||||
".reviewer",
|
||||
[
|
||||
"like",
|
||||
".email",
|
||||
"*@example.com"
|
||||
]
|
||||
],
|
||||
[
|
||||
"any",
|
||||
".tags",
|
||||
[
|
||||
"or",
|
||||
[
|
||||
[
|
||||
"==",
|
||||
".",
|
||||
"news"
|
||||
],
|
||||
[
|
||||
"==",
|
||||
".",
|
||||
"press"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
`
|
||||
|
||||
newCID = "zdpuAwa4qv3ncMDPeDoqVxjZy3JoyWsbqUzm94rdA1AvRFkkw"
|
||||
rootCID = "zdpuAkgGmUp5JrXvehGuuw9JA8DLQKDaxtK3R8brDQQVC2i5X"
|
||||
|
||||
aesKey = "xQklMmNTnVrmaPBq/0pwV5fEwuv/iClF5HWak9MsgI8="
|
||||
)
|
||||
|
||||
func TestConstructors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cmd, err := command.Parse(subJectCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
pol, err := policy.FromDagJson(subjectPol)
|
||||
require.NoError(t, err)
|
||||
|
||||
exp, err := time.Parse(time.RFC3339, "2200-01-01T00:00:00Z")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("New", func(t *testing.T) {
|
||||
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
|
||||
delegation.WithNonce([]byte(nonce)),
|
||||
delegation.WithSubject(didtest.PersonaAlice.DID()),
|
||||
delegation.WithExpiration(exp),
|
||||
delegation.WithMeta("foo", "fooo"),
|
||||
delegation.WithMeta("bar", "barr"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
|
||||
require.NoError(t, err)
|
||||
|
||||
golden.Assert(t, string(data), "new.dagjson")
|
||||
})
|
||||
|
||||
t.Run("Root", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
|
||||
delegation.WithNonce([]byte(nonce)),
|
||||
delegation.WithExpiration(exp),
|
||||
delegation.WithMeta("foo", "fooo"),
|
||||
delegation.WithMeta("bar", "barr"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
|
||||
require.NoError(t, err)
|
||||
|
||||
golden.Assert(t, string(data), "root.dagjson")
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncryptedMeta(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cmd, err := command.Parse(subJectCmd)
|
||||
require.NoError(t, err)
|
||||
pol, err := policy.FromDagJson(subjectPol)
|
||||
require.NoError(t, err)
|
||||
|
||||
encryptionKey, err := base64.StdEncoding.DecodeString(aesKey)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, encryptionKey, 32)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
value string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple string",
|
||||
key: "secret1",
|
||||
value: "hello world",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
key: "secret2",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "special characters",
|
||||
key: "secret3",
|
||||
value: "!@#$%^&*()_+-=[]{}|;:,.<>?",
|
||||
},
|
||||
{
|
||||
name: "unicode characters",
|
||||
key: "secret4",
|
||||
value: "Hello, 世界! 👋",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
|
||||
delegation.WithEncryptedMetaString(tt.key, tt.value, encryptionKey),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := tkn.ToDagCbor(didtest.PersonaAlice.PrivKey())
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedTkn, _, err := delegation.FromSealed(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = decodedTkn.Meta().GetString(tt.key)
|
||||
require.Error(t, err)
|
||||
|
||||
decrypted, err := decodedTkn.Meta().GetEncryptedString(tt.key, encryptionKey)
|
||||
require.NoError(t, err)
|
||||
// Verify the decrypted value is equal to the original
|
||||
require.Equal(t, tt.value, decrypted)
|
||||
|
||||
// Try to decrypt with wrong key
|
||||
wrongKey := make([]byte, 32)
|
||||
_, err = decodedTkn.Meta().GetEncryptedString(tt.key, wrongKey)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("multiple encrypted values in the same token", func(t *testing.T) {
|
||||
values := map[string]string{
|
||||
"secret1": "value1",
|
||||
"secret2": "value2",
|
||||
"secret3": "value3",
|
||||
}
|
||||
var opts []delegation.Option
|
||||
for k, v := range values {
|
||||
opts = append(opts, delegation.WithEncryptedMetaString(k, v, encryptionKey))
|
||||
}
|
||||
|
||||
// Create token with multiple encrypted values
|
||||
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := tkn.ToDagCbor(didtest.PersonaAlice.PrivKey())
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedTkn, _, err := delegation.FromSealed(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
for k, v := range values {
|
||||
decrypted, err := decodedTkn.Meta().GetEncryptedString(k, encryptionKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, v, decrypted)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -1,234 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/dave/jennifer/jen"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/did/didtest"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
|
||||
)
|
||||
|
||||
const (
|
||||
tokenNamePrefix = "Token"
|
||||
proorChainNamePrefix = "Proof"
|
||||
tokenExt = ".dagcbor"
|
||||
)
|
||||
|
||||
var constantNonce = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}
|
||||
|
||||
type newDelegationParams struct {
|
||||
privKey crypto.PrivKey
|
||||
aud did.DID
|
||||
sub did.DID
|
||||
cmd command.Command
|
||||
pol policy.Policy
|
||||
opts []delegation.Option
|
||||
}
|
||||
|
||||
type token struct {
|
||||
name string
|
||||
id cid.Cid
|
||||
}
|
||||
|
||||
type proof struct {
|
||||
name string
|
||||
prf []cid.Cid
|
||||
}
|
||||
|
||||
type acc struct {
|
||||
name string
|
||||
chain []cid.Cid
|
||||
}
|
||||
|
||||
type variant struct {
|
||||
name string
|
||||
variant func(*newDelegationParams)
|
||||
}
|
||||
|
||||
func noopVariant() variant {
|
||||
return variant{
|
||||
name: "",
|
||||
variant: func(_ *newDelegationParams) {},
|
||||
}
|
||||
}
|
||||
|
||||
type generator struct {
|
||||
dlgs []token
|
||||
chains []proof
|
||||
}
|
||||
|
||||
func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari variant) error {
|
||||
acc.name += personas[0].Name()
|
||||
|
||||
proofName := acc.name
|
||||
if len(vari.name) > 0 {
|
||||
proofName += "_" + vari.name
|
||||
}
|
||||
g.createProofChain(proofName, acc.chain)
|
||||
|
||||
if len(personas) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := personas[0].Name() + personas[1].Name()
|
||||
|
||||
params := newDelegationParams{
|
||||
privKey: personas[0].PrivKey(),
|
||||
aud: personas[1].DID(),
|
||||
cmd: delegationtest.NominalCommand,
|
||||
pol: policy.Policy{},
|
||||
opts: []delegation.Option{
|
||||
delegation.WithSubject(didtest.PersonaAlice.DID()),
|
||||
delegation.WithNonce(constantNonce),
|
||||
},
|
||||
}
|
||||
|
||||
// Create each nominal token and continue the chain
|
||||
id, err := g.createDelegation(params, name, vari)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acc.chain = append(acc.chain, id)
|
||||
err = g.chainPersonas(personas[1:], acc, vari)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the user is Carol, create variants for each invalid and/or optional
|
||||
// parameter and also continue the chain
|
||||
if personas[0] == didtest.PersonaCarol {
|
||||
variants := []variant{
|
||||
{name: "InvalidExpandedCommand", variant: func(p *newDelegationParams) {
|
||||
p.cmd = delegationtest.ExpandedCommand
|
||||
}},
|
||||
{name: "ValidAttenuatedCommand", variant: func(p *newDelegationParams) {
|
||||
p.cmd = delegationtest.AttenuatedCommand
|
||||
}},
|
||||
{name: "InvalidSubject", variant: func(p *newDelegationParams) {
|
||||
p.opts = append(p.opts, delegation.WithSubject(didtest.PersonaBob.DID()))
|
||||
}},
|
||||
{name: "InvalidExpired", variant: func(p *newDelegationParams) {
|
||||
// Note: this makes the generator not deterministic
|
||||
p.opts = append(p.opts, delegation.WithExpiration(time.Now().Add(time.Second)))
|
||||
}},
|
||||
{name: "InvalidInactive", variant: func(p *newDelegationParams) {
|
||||
nbf, err := time.Parse(time.RFC3339, "2070-01-01T00:00:00Z")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.opts = append(p.opts, delegation.WithNotBefore(nbf))
|
||||
}},
|
||||
}
|
||||
|
||||
// Start a branch in the recursion for each of the variants
|
||||
for _, v := range variants {
|
||||
id, err := g.createDelegation(params, name, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// replace the previous Carol token id with the one from the variant
|
||||
acc.chain[len(acc.chain)-1] = id
|
||||
err = g.chainPersonas(personas[1:], acc, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *generator) createDelegation(params newDelegationParams, name string, vari variant) (cid.Cid, error) {
|
||||
vari.variant(¶ms)
|
||||
|
||||
issDID, err := did.FromPrivKey(params.privKey)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
tkn, err := delegation.New(issDID, params.aud, params.cmd, params.pol, params.opts...)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
data, id, err := tkn.ToSealed(params.privKey)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
dlgName := tokenNamePrefix + name
|
||||
if len(vari.name) > 0 {
|
||||
dlgName += "_" + vari.name
|
||||
}
|
||||
|
||||
err = os.WriteFile(filepath.Join("..", delegationtest.TokenDir, dlgName+tokenExt), data, 0o644)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
g.dlgs = append(g.dlgs, token{
|
||||
name: dlgName,
|
||||
id: id,
|
||||
})
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (g *generator) createProofChain(name string, prf []cid.Cid) {
|
||||
if len(prf) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
clone := make([]cid.Cid, len(prf))
|
||||
copy(clone, prf)
|
||||
|
||||
g.chains = append(g.chains, proof{
|
||||
name: proorChainNamePrefix + name,
|
||||
prf: clone,
|
||||
})
|
||||
}
|
||||
|
||||
func (g *generator) writeGoFile() error {
|
||||
file := jen.NewFile("delegationtest")
|
||||
file.HeaderComment("Code generated by delegationtest - DO NOT EDIT.")
|
||||
|
||||
refs := map[cid.Cid]string{}
|
||||
|
||||
for _, d := range g.dlgs {
|
||||
refs[d.id] = d.name + "CID"
|
||||
|
||||
file.Var().Defs(
|
||||
jen.Id(d.name+"CID").Op("=").Qual("github.com/ipfs/go-cid", "MustParse").Call(jen.Lit(d.id.String())),
|
||||
jen.Id(d.name).Op("=").Id("mustGetDelegation").Call(jen.Id(d.name+"CID")),
|
||||
)
|
||||
file.Line()
|
||||
}
|
||||
|
||||
for _, c := range g.chains {
|
||||
g := jen.CustomFunc(jen.Options{
|
||||
Multi: true,
|
||||
Separator: ",",
|
||||
Close: "\n",
|
||||
}, func(g *jen.Group) {
|
||||
slices.Reverse(c.prf)
|
||||
for _, p := range c.prf {
|
||||
g.Id(refs[p])
|
||||
}
|
||||
})
|
||||
|
||||
file.Var().Id(c.name).Op("=").Index().Qual("github.com/ipfs/go-cid", "Cid").Values(g)
|
||||
file.Line()
|
||||
}
|
||||
|
||||
return file.Save("../token_gen.go")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package delegationtest
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
)
|
||||
|
||||
var (
|
||||
// ExpandedCommand is the parent of the NominalCommand and represents
|
||||
// the cases where the delegation proof-chain or invocation token tries
|
||||
// to increase the privileges granted by the root delegation token.
|
||||
// Execution of this command is generally prohibited in tests.
|
||||
ExpandedCommand = command.MustParse("/expanded")
|
||||
|
||||
// NominalCommand is the command used for most test tokens and proof-
|
||||
// chains. Execution of this command is generally allowed in tests.
|
||||
NominalCommand = ExpandedCommand.Join("nominal")
|
||||
|
||||
// AttenuatedCommand is a sub-command of the NominalCommand. Execution
|
||||
// of this command is generally allowed in tests.
|
||||
AttenuatedCommand = NominalCommand.Join("attenuated")
|
||||
)
|
||||
|
||||
// ProofEmpty provides an empty proof chain for testing purposes.
|
||||
var ProofEmpty = []cid.Cid{}
|
||||
|
||||
const TokenDir = "data"
|
||||
|
||||
//go:embed data
|
||||
var fs embed.FS
|
||||
|
||||
var _ delegation.Loader = (*delegationLoader)(nil)
|
||||
|
||||
type delegationLoader struct {
|
||||
tokens map[cid.Cid]*delegation.Token
|
||||
}
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
ldr delegation.Loader
|
||||
)
|
||||
|
||||
// GetDelegationLoader returns a singleton instance of a test
|
||||
// DelegationLoader containing all the tokens present in the data/
|
||||
// directory.
|
||||
func GetDelegationLoader() delegation.Loader {
|
||||
once.Do(func() {
|
||||
var err error
|
||||
ldr, err = loadDelegations()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
return ldr
|
||||
}
|
||||
|
||||
// GetDelegation implements invocation.DelegationLoader.
|
||||
func (l *delegationLoader) GetDelegation(id cid.Cid) (*delegation.Token, error) {
|
||||
tkn, ok := l.tokens[id]
|
||||
if !ok {
|
||||
return nil, delegation.ErrDelegationNotFound
|
||||
}
|
||||
|
||||
return tkn, nil
|
||||
}
|
||||
|
||||
func loadDelegations() (delegation.Loader, error) {
|
||||
dirEntries, err := fs.ReadDir(TokenDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkns := make(map[cid.Cid]*delegation.Token, len(dirEntries))
|
||||
|
||||
for _, dirEntry := range dirEntries {
|
||||
data, err := fs.ReadFile(filepath.Join(TokenDir, dirEntry.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkn, id, err := delegation.FromSealed(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkns[id] = tkn
|
||||
}
|
||||
|
||||
return &delegationLoader{
|
||||
tokens: tkns,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDelegation is a shortcut that gets (or creates) the DelegationLoader
|
||||
// and attempts to return the token referenced by the provided CID.
|
||||
func GetDelegation(id cid.Cid) (*delegation.Token, error) {
|
||||
return GetDelegationLoader().GetDelegation(id)
|
||||
}
|
||||
|
||||
func mustGetDelegation(id cid.Cid) *delegation.Token {
|
||||
tkn, err := GetDelegation(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tkn
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
// Code generated by delegationtest - DO NOT EDIT.
|
||||
|
||||
package delegationtest
|
||||
|
||||
import gocid "github.com/ipfs/go-cid"
|
||||
|
||||
var (
|
||||
TokenAliceBobCID = gocid.MustParse("bafyreicidrwvmac5lvjypucgityrtjsknojraio7ujjli4r5eyby66wjzm")
|
||||
TokenAliceBob = mustGetDelegation(TokenAliceBobCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenBobCarolCID = gocid.MustParse("bafyreihxv2uhq43oxllzs2xfvxst7wtvvvl7pohb2chcz6hjvfv2ntea5u")
|
||||
TokenBobCarol = mustGetDelegation(TokenBobCarolCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDanCID = gocid.MustParse("bafyreihclsgiroazq3heqdswvj2cafwqbpboicq7immo65scl7ahktpsdq")
|
||||
TokenCarolDan = mustGetDelegation(TokenCarolDanCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErinCID = gocid.MustParse("bafyreicja6ihewy64p3ake56xukotafjlkh4uqep2qhj52en46zzfwby3e")
|
||||
TokenDanErin = mustGetDelegation(TokenDanErinCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrankCID = gocid.MustParse("bafyreicjlx3lobxm6hl5s4htd4ydwkkqeiou6rft4rnvulfdyoew565vka")
|
||||
TokenErinFrank = mustGetDelegation(TokenErinFrankCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDan_InvalidExpandedCommandCID = gocid.MustParse("bafyreid3m3pk53gqgp5rlzqhvpedbwsqbidqlp4yz64vknwbzj7bxrmsr4")
|
||||
TokenCarolDan_InvalidExpandedCommand = mustGetDelegation(TokenCarolDan_InvalidExpandedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErin_InvalidExpandedCommandCID = gocid.MustParse("bafyreifn4sy5onwajx3kqvot5mib6m6xarzrqjozqbzgmzpmc5ox3g2uzm")
|
||||
TokenDanErin_InvalidExpandedCommand = mustGetDelegation(TokenDanErin_InvalidExpandedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrank_InvalidExpandedCommandCID = gocid.MustParse("bafyreidmpgd36jznmq42bs34o4qi3fcbrsh4idkg6ejahudejzwb76fwxe")
|
||||
TokenErinFrank_InvalidExpandedCommand = mustGetDelegation(TokenErinFrank_InvalidExpandedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDan_ValidAttenuatedCommandCID = gocid.MustParse("bafyreiekhtm237vyapk3c6voeb5lnz54crebqdqi3x4wn4u4cbrrhzsqfe")
|
||||
TokenCarolDan_ValidAttenuatedCommand = mustGetDelegation(TokenCarolDan_ValidAttenuatedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErin_ValidAttenuatedCommandCID = gocid.MustParse("bafyreicrvzqferyy7rgo75l5rn6r2nl7zyeexxjmu3dm4ff7rn2coblj4y")
|
||||
TokenDanErin_ValidAttenuatedCommand = mustGetDelegation(TokenDanErin_ValidAttenuatedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrank_ValidAttenuatedCommandCID = gocid.MustParse("bafyreie6fhspk53kplcc2phla3e7z7fzldlbmmpuwk6nbow5q6s2zjmw2q")
|
||||
TokenErinFrank_ValidAttenuatedCommand = mustGetDelegation(TokenErinFrank_ValidAttenuatedCommandCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDan_InvalidSubjectCID = gocid.MustParse("bafyreifgksz6756if42tnc6rqsnbaa2u3fdrveo7ek44lnj2d64d5sw26u")
|
||||
TokenCarolDan_InvalidSubject = mustGetDelegation(TokenCarolDan_InvalidSubjectCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErin_InvalidSubjectCID = gocid.MustParse("bafyreibdwew5nypsxrm4fq73wu6hw3lgwwiolj3bi33xdrbgcf3ogm6fty")
|
||||
TokenDanErin_InvalidSubject = mustGetDelegation(TokenDanErin_InvalidSubjectCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrank_InvalidSubjectCID = gocid.MustParse("bafyreicr364mj3n7x4iyhcksxypelktcqkkw3ptg7ggxtqegw3p3mr6zc4")
|
||||
TokenErinFrank_InvalidSubject = mustGetDelegation(TokenErinFrank_InvalidSubjectCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDan_InvalidExpiredCID = gocid.MustParse("bafyreigenypixaxvhzlry5rjnywvjyl4xvzlzxz2ui74uzys7qdhos4bbu")
|
||||
TokenCarolDan_InvalidExpired = mustGetDelegation(TokenCarolDan_InvalidExpiredCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErin_InvalidExpiredCID = gocid.MustParse("bafyreifvnfb7zqocpdysedcvjkb4y7tqfuziuqjhbbdoay4zg33pwpbzqi")
|
||||
TokenDanErin_InvalidExpired = mustGetDelegation(TokenDanErin_InvalidExpiredCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrank_InvalidExpiredCID = gocid.MustParse("bafyreicvydzt3obkqx7krmoi3zu4tlirlksibxfks5jc7vlvjxjamv2764")
|
||||
TokenErinFrank_InvalidExpired = mustGetDelegation(TokenErinFrank_InvalidExpiredCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenCarolDan_InvalidInactiveCID = gocid.MustParse("bafyreicea5y2nvlitvxijkupeavtg23i7ktjk3uejnaquguurzptiabk4u")
|
||||
TokenCarolDan_InvalidInactive = mustGetDelegation(TokenCarolDan_InvalidInactiveCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenDanErin_InvalidInactiveCID = gocid.MustParse("bafyreifsgqzkmxj2vexuts3z766mwcjreiisjg2jykyzf7tbj5sclutpvq")
|
||||
TokenDanErin_InvalidInactive = mustGetDelegation(TokenDanErin_InvalidInactiveCID)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenErinFrank_InvalidInactiveCID = gocid.MustParse("bafyreifbfegon24c6dndiqyktahzs65vhyasrygbw7nhsvojn6distsdre")
|
||||
TokenErinFrank_InvalidInactive = mustGetDelegation(TokenErinFrank_InvalidInactiveCID)
|
||||
)
|
||||
|
||||
var ProofAliceBob = []gocid.Cid{
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarol = []gocid.Cid{
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDan = []gocid.Cid{
|
||||
TokenCarolDanCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErin = []gocid.Cid{
|
||||
TokenDanErinCID,
|
||||
TokenCarolDanCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrank = []gocid.Cid{
|
||||
TokenErinFrankCID,
|
||||
TokenDanErinCID,
|
||||
TokenCarolDanCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDan_InvalidExpandedCommand = []gocid.Cid{
|
||||
TokenCarolDan_InvalidExpandedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErin_InvalidExpandedCommand = []gocid.Cid{
|
||||
TokenDanErin_InvalidExpandedCommandCID,
|
||||
TokenCarolDan_InvalidExpandedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrank_InvalidExpandedCommand = []gocid.Cid{
|
||||
TokenErinFrank_InvalidExpandedCommandCID,
|
||||
TokenDanErin_InvalidExpandedCommandCID,
|
||||
TokenCarolDan_InvalidExpandedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDan_ValidAttenuatedCommand = []gocid.Cid{
|
||||
TokenCarolDan_ValidAttenuatedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErin_ValidAttenuatedCommand = []gocid.Cid{
|
||||
TokenDanErin_ValidAttenuatedCommandCID,
|
||||
TokenCarolDan_ValidAttenuatedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrank_ValidAttenuatedCommand = []gocid.Cid{
|
||||
TokenErinFrank_ValidAttenuatedCommandCID,
|
||||
TokenDanErin_ValidAttenuatedCommandCID,
|
||||
TokenCarolDan_ValidAttenuatedCommandCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDan_InvalidSubject = []gocid.Cid{
|
||||
TokenCarolDan_InvalidSubjectCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErin_InvalidSubject = []gocid.Cid{
|
||||
TokenDanErin_InvalidSubjectCID,
|
||||
TokenCarolDan_InvalidSubjectCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrank_InvalidSubject = []gocid.Cid{
|
||||
TokenErinFrank_InvalidSubjectCID,
|
||||
TokenDanErin_InvalidSubjectCID,
|
||||
TokenCarolDan_InvalidSubjectCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDan_InvalidExpired = []gocid.Cid{
|
||||
TokenCarolDan_InvalidExpiredCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErin_InvalidExpired = []gocid.Cid{
|
||||
TokenDanErin_InvalidExpiredCID,
|
||||
TokenCarolDan_InvalidExpiredCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrank_InvalidExpired = []gocid.Cid{
|
||||
TokenErinFrank_InvalidExpiredCID,
|
||||
TokenDanErin_InvalidExpiredCID,
|
||||
TokenCarolDan_InvalidExpiredCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDan_InvalidInactive = []gocid.Cid{
|
||||
TokenCarolDan_InvalidInactiveCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErin_InvalidInactive = []gocid.Cid{
|
||||
TokenDanErin_InvalidInactiveCID,
|
||||
TokenCarolDan_InvalidInactiveCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
|
||||
var ProofAliceBobCarolDanErinFrank_InvalidInactive = []gocid.Cid{
|
||||
TokenErinFrank_InvalidInactiveCID,
|
||||
TokenDanErin_InvalidInactiveCID,
|
||||
TokenCarolDan_InvalidInactiveCID,
|
||||
TokenBobCarolCID,
|
||||
TokenAliceBobCID,
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
package delegation_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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/codec/dagjson"
|
||||
|
||||
"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/literal"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||
)
|
||||
|
||||
// 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())
|
||||
|
||||
// The command defines the shape of the arguments that will be evaluated against the policy
|
||||
cmd := command.MustParse("/foo/bar")
|
||||
|
||||
// The policy defines what is allowed to do.
|
||||
pol := policy.MustConstruct(
|
||||
policy.Equal(".status", literal.String("draft")),
|
||||
policy.All(".reviewer",
|
||||
policy.Like(".email", "*@example.com"),
|
||||
),
|
||||
policy.Any(".tags", policy.Or(
|
||||
policy.Equal(".", literal.String("news")),
|
||||
policy.Equal(".", literal.String("press")),
|
||||
)),
|
||||
)
|
||||
|
||||
tkn, err := delegation.New(didtest.PersonaBob.DID(), didtest.PersonaCarol.DID(), cmd, pol,
|
||||
delegation.WithSubject(didtest.PersonaAlice.DID()),
|
||||
delegation.WithExpirationIn(time.Hour),
|
||||
delegation.WithNotBeforeIn(time.Minute),
|
||||
delegation.WithMeta("foo", "bar"),
|
||||
delegation.WithMeta("baz", 123),
|
||||
)
|
||||
printThenPanicOnErr(err)
|
||||
|
||||
// "Seal", meaning encode and wrap into a signed envelope.
|
||||
data, id, err := tkn.ToSealed(didtest.PersonaBob.PrivKey())
|
||||
printThenPanicOnErr(err)
|
||||
|
||||
printCIDAndSealed(id, data)
|
||||
|
||||
// Example output:
|
||||
//
|
||||
// issDid: did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU
|
||||
//
|
||||
// CID (base58BTC): zdpuAsqfZkgg2jgZyob23sq1J9xwtf9PHgt1PsskVCMq7Vvxk
|
||||
//
|
||||
// DAG-CBOR (base64) out: lhAOnjc0bPptlI5MxRBrIK3YmAP1CxKfXOPkz6MHt/UJCx2gCN+6gXZX2N+BIJvmy8XmAO5sT2GYimiV7HlJH1AA6JhaEQ07QFxc3VjYW4vZGxnQDEuMC4wLXJjLjGpY2F1ZHg4ZGlkOmtleTp6Nk1rZ3VwY2hoNUh3dUhhaFM3WXN5RThiTHVhMU1yOHAyaUtOUmh5dlN2UkFzOW5jY21kaC9mb28vYmFyY2V4cBpnROP/Y2lzc3g4ZGlkOmtleTp6Nk1rdkpQbUVaWlliZ2l3MW91VDFvb3VUc1RGQkhKU3RzOW9waFZzTmdjUm1ZeFVjbmJmGmdE1itjcG9sg4NiPT1nLnN0YXR1c2VkcmFmdINjYWxsaS5yZXZpZXdlcoNkbGlrZWYuZW1haWxtKkBleGFtcGxlLmNvbYNjYW55ZS50YWdzgmJvcoKDYj09YS5kbmV3c4NiPT1hLmVwcmVzc2NzdWJ4OGRpZDprZXk6ejZNa3V1a2syc2tEWExRbjdOSzNFaDlqTW5kWWZ2REJ4eGt0Z3BpZEpBcWI3TTNwZG1ldGGiY2Jhehh7Y2Zvb2NiYXJlbm9uY2VMv+Diy6GExIuM1eX4
|
||||
// Converted to DAG-JSON out:
|
||||
// [
|
||||
// {
|
||||
// "/": {
|
||||
// "bytes": "5rvl8uKmDVGvAVSt4m/0MGiXl9dZwljJJ9m2qHCoIB617l26UvMxyH5uvN9hM7ozfVATiq4mLhoGgm9IGnEEAg"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "h": {
|
||||
// "/": {
|
||||
// "bytes": "NO0BcQ"
|
||||
// }
|
||||
// },
|
||||
// "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": [
|
||||
// [
|
||||
// "==",
|
||||
// ".status",
|
||||
// "draft"
|
||||
// ],
|
||||
// [
|
||||
// "all",
|
||||
// ".reviewer",
|
||||
// [
|
||||
// "like",
|
||||
// ".email",
|
||||
// "*@example.com"
|
||||
// ]
|
||||
// ],
|
||||
// [
|
||||
// "any",
|
||||
// ".tags",
|
||||
// [
|
||||
// "or",
|
||||
// [
|
||||
// [
|
||||
// "==",
|
||||
// ".",
|
||||
// "news"
|
||||
// ],
|
||||
// [
|
||||
// "==",
|
||||
// ".",
|
||||
// "press"
|
||||
// ]
|
||||
// ]
|
||||
// ]
|
||||
// ]
|
||||
// ],
|
||||
// "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() {
|
||||
// The command defines the shape of the arguments that will be evaluated against the policy
|
||||
cmd := command.MustParse("/foo/bar")
|
||||
|
||||
// The policy defines what is allowed to do.
|
||||
pol := policy.MustConstruct(
|
||||
policy.Equal(".status", literal.String("draft")),
|
||||
policy.All(".reviewer",
|
||||
policy.Like(".email", "*@example.com"),
|
||||
),
|
||||
policy.Any(".tags", policy.Or(
|
||||
policy.Equal(".", literal.String("news")),
|
||||
policy.Equal(".", literal.String("press")),
|
||||
)),
|
||||
)
|
||||
|
||||
tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
|
||||
delegation.WithExpirationIn(time.Hour),
|
||||
delegation.WithNotBeforeIn(time.Minute),
|
||||
delegation.WithMeta("foo", "bar"),
|
||||
delegation.WithMeta("baz", 123),
|
||||
)
|
||||
printThenPanicOnErr(err)
|
||||
|
||||
// "Seal", meaning encode and wrap into a signed envelope.
|
||||
data, id, err := tkn.ToSealed(didtest.PersonaAlice.PrivKey())
|
||||
printThenPanicOnErr(err)
|
||||
|
||||
printCIDAndSealed(id, data)
|
||||
|
||||
// Example output:
|
||||
//
|
||||
// issDid: did:key:z6MknWJqz17Y4AfsXSJUFKomuBR4GTkViM7kJYutzTMkCyFF
|
||||
//
|
||||
// CID (base58BTC): zdpuAkwYz8nY7uU8j3F6wVTfFY1VEoExwvUAYBEwRWfTozddE
|
||||
//
|
||||
// DAG-CBOR (base64) out: glhAVpW67FJ+myNi+azvnw2jivuiqXTuMrDZI2Qdaa8jE1Oi3mkjnm7DyqSQGADcomcuDslMWKmJ+OIyvbPG5PtSA6JhaEQ07QFxc3VjYW4vZGxnQDEuMC4wLXJjLjGpY2F1ZHg4ZGlkOmtleTp6Nk1rdkpQbUVaWlliZ2l3MW91VDFvb3VUc1RGQkhKU3RzOW9waFZzTmdjUm1ZeFVjY21kaC9mb28vYmFyY2V4cBpnROVoY2lzc3g4ZGlkOmtleTp6Nk1rdXVrazJza0RYTFFuN05LM0VoOWpNbmRZZnZEQnh4a3RncGlkSkFxYjdNM3BjbmJmGmdE15RjcG9sg4NiPT1nLnN0YXR1c2VkcmFmdINjYWxsaS5yZXZpZXdlcoNkbGlrZWYuZW1haWxtKkBleGFtcGxlLmNvbYNjYW55ZS50YWdzgmJvcoKDYj09YS5kbmV3c4NiPT1hLmVwcmVzc2NzdWJ4OGRpZDprZXk6ejZNa3V1a2syc2tEWExRbjdOSzNFaDlqTW5kWWZ2REJ4eGt0Z3BpZEpBcWI3TTNwZG1ldGGiY2Jhehh7Y2Zvb2NiYXJlbm9uY2VMwzDc03WBciJIGPWG
|
||||
//
|
||||
// Converted to DAG-JSON out:
|
||||
// [
|
||||
// {
|
||||
// "/": {
|
||||
// "bytes": "VpW67FJ+myNi+azvnw2jivuiqXTuMrDZI2Qdaa8jE1Oi3mkjnm7DyqSQGADcomcuDslMWKmJ+OIyvbPG5PtSAw"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "h": {
|
||||
// "/": {
|
||||
// "bytes": "NO0BcQ"
|
||||
// }
|
||||
// },
|
||||
// "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": [
|
||||
// [
|
||||
// "==",
|
||||
// ".status",
|
||||
// "draft"
|
||||
// ],
|
||||
// [
|
||||
// "all",
|
||||
// ".reviewer",
|
||||
// [
|
||||
// "like",
|
||||
// ".email",
|
||||
// "*@example.com"
|
||||
// ]
|
||||
// ],
|
||||
// [
|
||||
// "any",
|
||||
// ".tags",
|
||||
// [
|
||||
// "or",
|
||||
// [
|
||||
// [
|
||||
// "==",
|
||||
// ".",
|
||||
// "news"
|
||||
// ],
|
||||
// [
|
||||
// "==",
|
||||
// ".",
|
||||
// "press"
|
||||
// ]
|
||||
// ]
|
||||
// ]
|
||||
// ]
|
||||
// ],
|
||||
// "sub": "did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
}
|
||||
|
||||
// The following example demonstrates how to get a delegation.Token from
|
||||
// a DAG-CBOR []byte.
|
||||
func ExampleFromSealed() {
|
||||
const cborBase64 = "glhAmnAkgfjAx4SA5pzJmtaHRJtTGNpF1y6oqb4yhGoM2H2EUGbBYT4rVDjMKBgCjhdGHjipm00L8iR5SsQh3sIEBaJhaEQ07QFxc3VjYW4vZGxnQDEuMC4wLXJjLjGoY2F1ZHg4ZGlkOmtleTp6Nk1rcTVZbWJKY1RyUEV4TkRpMjZpbXJUQ3BLaGVwakJGQlNIcXJCRE4yQXJQa3ZjY21kaC9mb28vYmFyY2V4cPZjaXNzeDhkaWQ6a2V5Ono2TWtwem4ybjNaR1QyVmFxTUdTUUMzdHptelY0VFM5UzcxaUZzRFhFMVdub05IMmNwb2yDg2I9PWcuc3RhdHVzZWRyYWZ0g2NhbGxpLnJldmlld2Vyg2RsaWtlZi5lbWFpbG0qQGV4YW1wbGUuY29tg2NhbnllLnRhZ3OCYm9ygoNiPT1hLmRuZXdzg2I9PWEuZXByZXNzY3N1Yng4ZGlkOmtleTp6Nk1rdEExdUJkQ3BxNHVKQnFFOWpqTWlMeXhaQmc5YTZ4Z1BQS0pqTXFzczZaYzJkbWV0YaBlbm9uY2VMAAECAwQFBgcICQoL"
|
||||
|
||||
cborBytes, err := base64.StdEncoding.DecodeString(cborBase64)
|
||||
printThenPanicOnErr(err)
|
||||
|
||||
tkn, c, err := delegation.FromSealed(cborBytes)
|
||||
printThenPanicOnErr(err)
|
||||
|
||||
fmt.Println("CID (base58BTC):", envelope.CIDToBase58BTC(c))
|
||||
fmt.Println("Issuer (iss):", tkn.Issuer().String())
|
||||
fmt.Println("Audience (aud):", tkn.Audience().String())
|
||||
fmt.Println("Subject (sub):", tkn.Subject().String())
|
||||
fmt.Println("Command (cmd):", tkn.Command().String())
|
||||
fmt.Println("Policy (pol):", tkn.Policy().String())
|
||||
fmt.Println("Nonce (nonce):", hex.EncodeToString(tkn.Nonce()))
|
||||
fmt.Println("Meta (meta):", tkn.Meta().String())
|
||||
fmt.Println("NotBefore (nbf):", tkn.NotBefore())
|
||||
fmt.Println("Expiration (exp):", tkn.Expiration())
|
||||
|
||||
// Output:
|
||||
// CID (base58BTC): zdpuAw26pFuvZa2Z9YAtpZZnWN6VmnRFr7Z8LVY5c7RVWoxGY
|
||||
// Issuer (iss): did:key:z6Mkpzn2n3ZGT2VaqMGSQC3tzmzV4TS9S71iFsDXE1WnoNH2
|
||||
// Audience (aud): did:key:z6Mkq5YmbJcTrPExNDi26imrTCpKhepjBFBSHqrBDN2ArPkv
|
||||
// Subject (sub): did:key:z6MktA1uBdCpq4uJBqE9jjMiLyxZBg9a6xgPPKJjMqss6Zc2
|
||||
// Command (cmd): /foo/bar
|
||||
// Policy (pol): [
|
||||
// ["==", ".status", "draft"],
|
||||
// ["all", ".reviewer",
|
||||
// ["like", ".email", "*@example.com"]],
|
||||
// ["any", ".tags",
|
||||
// ["or", [
|
||||
// ["==", ".", "news"],
|
||||
// ["==", ".", "press"]]]
|
||||
// ]
|
||||
// ]
|
||||
// Nonce (nonce): 000102030405060708090a0b
|
||||
// Meta (meta): {}
|
||||
// NotBefore (nbf): <nil>
|
||||
// Expiration (exp): <nil>
|
||||
}
|
||||
|
||||
func printCIDAndSealed(id cid.Cid, data []byte) {
|
||||
fmt.Println("CID (base58BTC):", envelope.CIDToBase58BTC(id))
|
||||
fmt.Println("DAG-CBOR (base64) out:", base64.StdEncoding.EncodeToString(data))
|
||||
fmt.Println("Converted to DAG-JSON out:")
|
||||
|
||||
node, err := ipld.Decode(data, dagcbor.Decode)
|
||||
printThenPanicOnErr(err)
|
||||
|
||||
rawJSON, err := ipld.Encode(node, dagjson.Encode)
|
||||
printThenPanicOnErr(err)
|
||||
|
||||
prettyJSON := &bytes.Buffer{}
|
||||
err = json.Indent(prettyJSON, rawJSON, "", "\t")
|
||||
printThenPanicOnErr(err)
|
||||
|
||||
fmt.Println(prettyJSON.String())
|
||||
}
|
||||
|
||||
func printThenPanicOnErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package delegation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
// ErrDelegationNotFound is returned if a delegation token is not found
|
||||
var ErrDelegationNotFound = fmt.Errorf("delegation not found")
|
||||
|
||||
// Loader is a delegation token loader.
|
||||
type Loader interface {
|
||||
// GetDelegation returns the delegation.Token matching the given CID.
|
||||
// If not found, ErrDelegationNotFound is returned.
|
||||
GetDelegation(cid cid.Cid) (*Token, error)
|
||||
}
|
||||
1
token/delegation/testdata/new.dagjson
vendored
1
token/delegation/testdata/new.dagjson
vendored
@@ -1 +0,0 @@
|
||||
[{"/":{"bytes":"BBabgnWqd+cjwG1td0w9BudNocmUwoR89RMZTqZHk3osCXEI/bOkko0zTvlusaE4EMBBeSzZDKzjvunLBfdiBg"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]],"sub":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p"}}]
|
||||
1
token/delegation/testdata/root.dagjson
vendored
1
token/delegation/testdata/root.dagjson
vendored
@@ -1 +0,0 @@
|
||||
[{"/":{"bytes":"BBabgnWqd+cjwG1td0w9BudNocmUwoR89RMZTqZHk3osCXEI/bOkko0zTvlusaE4EMBBeSzZDKzjvunLBfdiBg"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]],"sub":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p"}}]
|
||||
@@ -1,19 +0,0 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||
)
|
||||
|
||||
type Info = envelope.Info
|
||||
|
||||
// Inspect inspects the given token IPLD representation and extract some envelope facts.
|
||||
func Inspect(node datamodel.Node) (Info, error) {
|
||||
return envelope.Inspect(node)
|
||||
}
|
||||
|
||||
// FindTag inspect the given token IPLD representation and extract the token tag.
|
||||
func FindTag(node datamodel.Node) (string, error) {
|
||||
return envelope.FindTag(node)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime/codec"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
)
|
||||
|
||||
type Token interface {
|
||||
Marshaller
|
||||
|
||||
// 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.
|
||||
IsValidNow() bool
|
||||
// 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.
|
||||
IsValidAt(t time.Time) bool
|
||||
}
|
||||
|
||||
type Marshaller interface {
|
||||
// ToSealed wraps the token in an envelope, generates the signature, encodes
|
||||
// the result to DAG-CBOR and calculates the CID of the resulting binary data.
|
||||
ToSealed(privKey crypto.PrivKey) ([]byte, cid.Cid, error)
|
||||
// ToSealedWriter is the same as ToSealed but accepts an io.Writer.
|
||||
ToSealedWriter(w io.Writer, privKey crypto.PrivKey) (cid.Cid, error)
|
||||
// Encode marshals a Token to the format specified by the provided codec.Encoder.
|
||||
Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error)
|
||||
// EncodeWriter is the same as Encode, but accepts an io.Writer.
|
||||
EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.Encoder) error
|
||||
// ToDagCbor marshals the Token to the DAG-CBOR format.
|
||||
ToDagCbor(privKey crypto.PrivKey) ([]byte, error)
|
||||
// ToDagCborWriter is the same as ToDagCbor, but it accepts an io.Writer.
|
||||
ToDagCborWriter(w io.Writer, privKey crypto.PrivKey) error
|
||||
// ToDagJson marshals the Token to the DAG-JSON format.
|
||||
ToDagJson(privKey crypto.PrivKey) ([]byte, error)
|
||||
// ToDagJsonWriter is the same as ToDagJson, but it accepts an io.Writer.
|
||||
ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package nonce
|
||||
|
||||
import "crypto/rand"
|
||||
|
||||
// TODO: some crypto scheme require more, is that our case?
|
||||
//
|
||||
// The spec mention:
|
||||
// The REQUIRED nonce parameter nonce MAY be any value.
|
||||
// A randomly generated string is RECOMMENDED to provide a unique UCAN, though it MAY
|
||||
// also be a monotonically increasing count of the number of links in the hash chain.
|
||||
// This field helps prevent replay attacks and ensures a unique CID per delegation.
|
||||
// The iss, aud, and exp fields together will often ensure that UCANs are unique,
|
||||
// but adding the nonce ensures uniqueness.
|
||||
//
|
||||
// The recommended size of the nonce differs by key type. In many cases, a random
|
||||
// 12-byte nonce is sufficient. If uncertain, check the nonce in your DID's crypto suite.
|
||||
//
|
||||
// 12 bytes is 10^28, 16 bytes is 10^38. Both sounds like a lot of random to achieve
|
||||
// those goals, but maybe the crypto voodoo require more.
|
||||
//
|
||||
// The rust implementation use 16 bytes nonce.
|
||||
|
||||
// Generate creates a 12-byte random nonce.
|
||||
func Generate() ([]byte, error) {
|
||||
res := make([]byte, 12)
|
||||
_, err := rand.Read(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
)
|
||||
|
||||
func OptionalDID(s *string) (did.DID, error) {
|
||||
if s == nil {
|
||||
return did.Undef, nil
|
||||
}
|
||||
return did.Parse(*s)
|
||||
}
|
||||
|
||||
func OptionalTimestamp(sec *int64) *time.Time {
|
||||
if sec == nil {
|
||||
return nil
|
||||
}
|
||||
t := time.Unix(*sec, 0)
|
||||
return &t
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package invocation
|
||||
|
||||
import "errors"
|
||||
|
||||
// Loading errors
|
||||
var (
|
||||
// ErrMissingDelegation
|
||||
ErrMissingDelegation = errors.New("loader missing delegation for proof chain")
|
||||
)
|
||||
|
||||
// Time bound errors
|
||||
var (
|
||||
// ErrTokenExpired is returned if a token is invalid at execution time
|
||||
ErrTokenInvalidNow = errors.New("token has expired")
|
||||
)
|
||||
|
||||
// Principal alignment errors
|
||||
var (
|
||||
// ErrNoProof is returned when no delegations were provided to prove
|
||||
// that the invocation should be executed.
|
||||
ErrNoProof = errors.New("at least one delegation must be provided to validate the invocation")
|
||||
|
||||
// ErrLastNotRoot is returned if the last delegation token in the proof
|
||||
// chain is not a root delegation token.
|
||||
ErrLastNotRoot = errors.New("the last delegation token in proof chain must be a root token")
|
||||
|
||||
// ErrBrokenChain is returned when the Audience of a delegation is
|
||||
// not the Issuer of the previous one.
|
||||
ErrBrokenChain = errors.New("delegation proof chain doesn't connect the invocation to the subject")
|
||||
|
||||
// ErrWrongSub is returned when the Subject of a delegation is not the invocation audience.
|
||||
ErrWrongSub = errors.New("delegation subject need to match the invocation audience")
|
||||
|
||||
// ErrCommandNotCovered is returned when a delegation command doesn't cover (identical or parent of) the
|
||||
// next delegation or invocation's command.
|
||||
ErrCommandNotCovered = errors.New("allowed command doesn't cover the next delegation or invocation")
|
||||
)
|
||||
@@ -1,201 +0,0 @@
|
||||
package invocation_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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/codec/dagjson"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
"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/token/invocation"
|
||||
)
|
||||
|
||||
func ExampleNew() {
|
||||
privKey, iss, sub, cmd, args, prf, meta, err := setupExampleNew()
|
||||
if err != nil {
|
||||
fmt.Println("failed to create setup:", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
inv, err := invocation.New(iss, sub, cmd, prf,
|
||||
invocation.WithArgument("uri", args["uri"]),
|
||||
invocation.WithArgument("headers", args["headers"]),
|
||||
invocation.WithArgument("payload", args["payload"]),
|
||||
invocation.WithMeta("env", "development"),
|
||||
invocation.WithMeta("tags", meta["tags"]),
|
||||
invocation.WithExpirationIn(time.Minute),
|
||||
invocation.WithoutInvokedAt())
|
||||
if err != nil {
|
||||
fmt.Println("failed to create invocation:", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
data, cid, err := inv.ToSealed(privKey)
|
||||
if err != nil {
|
||||
fmt.Println("failed to seal invocation:", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
json, err := prettyDAGJSON(data)
|
||||
if err != nil {
|
||||
fmt.Println("failed to pretty DAG-JSON:", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("CID:", cid)
|
||||
fmt.Println("Token (pretty DAG-JSON):")
|
||||
fmt.Println(json)
|
||||
|
||||
// Expected CID and DAG-JSON output:
|
||||
// CID: bafyreid2n5q45vk4osned7k5huocbe3mxbisonh5vujepqftc5ftr543ae
|
||||
// Token (pretty DAG-JSON):
|
||||
// [
|
||||
// {
|
||||
// "/": {
|
||||
// "bytes": "gvyL7kdSkgmaDpDU/Qj9ohRwxYLCHER52HFMSFEqQqEcQC9qr4JCPP1f/WybvGGuVzYiA0Hx4JO+ohNz8BxUAA"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "h": {
|
||||
// "/": {
|
||||
// "bytes": "NO0BcQ"
|
||||
// }
|
||||
// },
|
||||
// "ucan/inv@1.0.0-rc.1": {
|
||||
// "args": {
|
||||
// "headers": {
|
||||
// "Content-Type": "application/json"
|
||||
// },
|
||||
// "payload": {
|
||||
// "body": "UCAN is great",
|
||||
// "draft": true,
|
||||
// "title": "UCAN for Fun and Profit",
|
||||
// "topics": [
|
||||
// "authz",
|
||||
// "journal"
|
||||
// ]
|
||||
// },
|
||||
// "uri": "https://example.com/blog/posts"
|
||||
// },
|
||||
// "cmd": "/crud/create",
|
||||
// "exp": 1729788921,
|
||||
// "iss": "did:key:z6MkhniGGyP88eZrq2dpMvUPdS2RQMhTUAWzcu6kVGUvEtCJ",
|
||||
// "meta": {
|
||||
// "env": "development",
|
||||
// "tags": [
|
||||
// "blog",
|
||||
// "post",
|
||||
// "pr#123"
|
||||
// ]
|
||||
// },
|
||||
// "nonce": {
|
||||
// "/": {
|
||||
// "bytes": "2xXPoZwWln1TfXIp"
|
||||
// }
|
||||
// },
|
||||
// "prf": [
|
||||
// {
|
||||
// "/": "bafyreigx3qxd2cndpe66j2mdssj773ecv7tqd7wovcnz5raguw6lj7sjoe"
|
||||
// },
|
||||
// {
|
||||
// "/": "bafyreib34ira254zdqgehz6f2bhwme2ja2re3ltcalejv4x4tkcveujvpa"
|
||||
// },
|
||||
// {
|
||||
// "/": "bafyreibkb66tpo2ixqx3fe5hmekkbuasrod6olt5bwm5u5pi726mduuwlq"
|
||||
// }
|
||||
// ],
|
||||
// "sub": "did:key:z6MktWuvPvBe5UyHnDGuEdw8aJ5qrhhwLG6jy7cQYM6ckP6P"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
}
|
||||
|
||||
func prettyDAGJSON(data []byte) (string, error) {
|
||||
var node ipld.Node
|
||||
|
||||
node, err := ipld.Decode(data, dagcbor.Decode)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
jsonData, err := ipld.Encode(node, dagjson.Encode)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
if err := json.Indent(&out, jsonData, "", " "); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
func setupExampleNew() (privKey crypto.PrivKey, iss, sub did.DID, cmd command.Command, args map[string]any, prf []cid.Cid, meta map[string]any, errs error) {
|
||||
var err error
|
||||
|
||||
privKey, iss, err = did.GenerateEd25519()
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, fmt.Errorf("failed to generate Issuer identity: %w", err))
|
||||
}
|
||||
|
||||
_, sub, err = did.GenerateEd25519()
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, fmt.Errorf("failed to generate Subject identity: %w", err))
|
||||
}
|
||||
|
||||
cmd, err = command.Parse("/crud/create")
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, fmt.Errorf("failed to parse command: %w", err))
|
||||
}
|
||||
|
||||
headers := map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
payload := map[string]any{
|
||||
"body": "UCAN is great",
|
||||
"draft": true,
|
||||
"title": "UCAN for Fun and Profit",
|
||||
"topics": []string{"authz", "journal"},
|
||||
}
|
||||
|
||||
args = map[string]any{
|
||||
// you can also directly pass IPLD values
|
||||
"uri": basicnode.NewString("https://example.com/blog/posts"),
|
||||
"headers": headers,
|
||||
"payload": payload,
|
||||
}
|
||||
|
||||
prf = make([]cid.Cid, 3)
|
||||
for i, v := range []string{
|
||||
"zdpuAzx4sBrBCabrZZqXgvK3NDzh7Mf5mKbG11aBkkMCdLtCp",
|
||||
"zdpuApTCXfoKh2sB1KaUaVSGofCBNPUnXoBb6WiCeitXEibZy",
|
||||
"zdpuAoFdXRPw4n6TLcncoDhq1Mr6FGbpjAiEtqSBrTSaYMKkf",
|
||||
} {
|
||||
prf[i], err = cid.Parse(v)
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, fmt.Errorf("failed to parse proof cid: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
meta = map[string]any{
|
||||
"env": basicnode.NewString("development"),
|
||||
"tags": []string{"blog", "post", "pr#123"},
|
||||
}
|
||||
|
||||
return // WARNING: named return values
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
// Package invocation implements the UCAN [invocation] specification with
|
||||
// an immutable Token type as well as methods to convert the Token to and
|
||||
// from the [envelope]-enclosed, signed and DAG-CBOR-encoded form that
|
||||
// should most commonly be used for transport and storage.
|
||||
//
|
||||
// [envelope]: https://github.com/ucan-wg/spec#envelope
|
||||
// [invocation]: https://github.com/ucan-wg/invocation
|
||||
package invocation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
"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 invocation.
|
||||
type Token struct {
|
||||
// The DID of the Invoker
|
||||
issuer did.DID
|
||||
// The DID of Subject being invoked
|
||||
subject did.DID
|
||||
// The DID of the intended Executor if different from the Subject
|
||||
audience did.DID
|
||||
|
||||
// The Command
|
||||
command command.Command
|
||||
// The Command's arguments
|
||||
arguments *args.Args
|
||||
// CIDs of the delegation.Token that prove the chain of authority
|
||||
// They need to form a strictly linear chain, and being ordered starting from the
|
||||
// leaf Delegation (with aud matching the invocation's iss), in a strict sequence
|
||||
// where the iss of the previous Delegation matches the aud of the next Delegation.
|
||||
proof []cid.Cid
|
||||
// Arbitrary Metadata
|
||||
meta *meta.Meta
|
||||
|
||||
// A unique, random nonce
|
||||
nonce []byte
|
||||
// The timestamp at which the Invocation becomes invalid
|
||||
expiration *time.Time
|
||||
// The timestamp at which the Invocation was created
|
||||
invokedAt *time.Time
|
||||
|
||||
// An optional CID of the Receipt that enqueued the Task
|
||||
cause *cid.Cid
|
||||
}
|
||||
|
||||
// New creates an invocation Token with the provided options.
|
||||
//
|
||||
// If no nonce is provided, a random 12-byte nonce is generated. Use the
|
||||
// WithNonce or WithEmptyNonce options to specify provide your own nonce
|
||||
// or to leave the nonce empty respectively.
|
||||
//
|
||||
// If no invokedAt is provided, the current time is used. Use the
|
||||
// WithInvokedAt or WithInvokedAtIn Options to specify a different time
|
||||
// or the WithoutInvokedAt Option to clear the Token's invokedAt field.
|
||||
//
|
||||
// With the exception of the WithMeta option, all others will overwrite
|
||||
// the previous contents of their target field.
|
||||
func New(iss, sub did.DID, cmd command.Command, prf []cid.Cid, opts ...Option) (*Token, error) {
|
||||
iat := time.Now()
|
||||
|
||||
tkn := Token{
|
||||
issuer: iss,
|
||||
subject: sub,
|
||||
command: cmd,
|
||||
arguments: args.New(),
|
||||
proof: prf,
|
||||
meta: meta.NewMeta(),
|
||||
nonce: nil,
|
||||
invokedAt: &iat,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(&tkn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(tkn.nonce) == 0 {
|
||||
tkn.nonce, err = nonce.Generate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tkn.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tkn, nil
|
||||
}
|
||||
|
||||
func (t *Token) ExecutionAllowed(loader delegation.Loader) error {
|
||||
return t.executionAllowed(loader, t.arguments)
|
||||
}
|
||||
|
||||
func (t *Token) ExecutionAllowedWithArgsHook(loader delegation.Loader, hook func(args args.ReadOnly) (*args.Args, error)) error {
|
||||
newArgs, err := hook(t.arguments.ReadOnly())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.executionAllowed(loader, newArgs)
|
||||
}
|
||||
|
||||
func (t *Token) executionAllowed(loader delegation.Loader, arguments *args.Args) error {
|
||||
delegations, err := t.loadProofs(loader)
|
||||
if err != nil {
|
||||
// All referenced delegations must be available - 4b
|
||||
return err
|
||||
}
|
||||
|
||||
if err := t.verifyProofs(delegations); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := t.verifyTimeBound(delegations); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := t.verifyArgs(delegations, arguments); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Issuer returns the did.DID representing the Token's issuer.
|
||||
func (t *Token) Issuer() did.DID {
|
||||
return t.issuer
|
||||
}
|
||||
|
||||
// Subject returns the did.DID representing the Token's subject.
|
||||
func (t *Token) Subject() did.DID {
|
||||
return t.subject
|
||||
}
|
||||
|
||||
// Audience returns the did.DID representing the Token's audience.
|
||||
func (t *Token) Audience() did.DID {
|
||||
return t.audience
|
||||
}
|
||||
|
||||
// Command returns the capability's command.Command.
|
||||
func (t *Token) Command() command.Command {
|
||||
return t.command
|
||||
}
|
||||
|
||||
// Arguments returns the arguments to be used when the command is
|
||||
// invoked.
|
||||
func (t *Token) Arguments() args.ReadOnly {
|
||||
return t.arguments.ReadOnly()
|
||||
}
|
||||
|
||||
// Proof() returns the ordered list of cid.Cid which reference the
|
||||
// delegation Tokens that authorize this invocation.
|
||||
func (t *Token) Proof() []cid.Cid {
|
||||
return t.proof
|
||||
}
|
||||
|
||||
// Meta returns the Token's metadata.
|
||||
func (t *Token) Meta() meta.ReadOnly {
|
||||
return t.meta.ReadOnly()
|
||||
}
|
||||
|
||||
// Nonce returns the random Nonce encapsulated in this Token.
|
||||
func (t *Token) Nonce() []byte {
|
||||
return t.nonce
|
||||
}
|
||||
|
||||
// Expiration returns the time at which the Token expires.
|
||||
func (t *Token) Expiration() *time.Time {
|
||||
return t.expiration
|
||||
}
|
||||
|
||||
// InvokedAt returns the time.Time at which the invocation token was
|
||||
// created.
|
||||
func (t *Token) InvokedAt() *time.Time {
|
||||
return t.invokedAt
|
||||
}
|
||||
|
||||
// Cause returns the Token's (optional) cause field which may specify
|
||||
// which describes the Receipt that requested the invocation.
|
||||
func (t *Token) Cause() *cid.Cid {
|
||||
return t.cause
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *Token) validate() error {
|
||||
var errs error
|
||||
|
||||
requiredDID := func(id did.DID, fieldname string) {
|
||||
if !id.Defined() {
|
||||
errs = errors.Join(errs, fmt.Errorf(`a valid did is required for %s: %s`, fieldname, id.String()))
|
||||
}
|
||||
}
|
||||
|
||||
requiredDID(t.issuer, "Issuer")
|
||||
requiredDID(t.subject, "Subject")
|
||||
|
||||
if len(t.nonce) < 12 {
|
||||
errs = errors.Join(errs, fmt.Errorf("token nonce too small"))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (t *Token) loadProofs(loader delegation.Loader) (res []*delegation.Token, err error) {
|
||||
res = make([]*delegation.Token, len(t.proof))
|
||||
for i, c := range t.proof {
|
||||
res[i], err = loader.GetDelegation(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: need %s", ErrMissingDelegation, c)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// tokenFromModel build a decoded view of the raw IPLD data.
|
||||
// This function also serves as validation.
|
||||
func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
||||
var (
|
||||
tkn Token
|
||||
err error
|
||||
)
|
||||
|
||||
if tkn.issuer, err = did.Parse(m.Iss); err != nil {
|
||||
return nil, fmt.Errorf("parse iss: %w", err)
|
||||
}
|
||||
|
||||
if tkn.subject, err = did.Parse(m.Sub); err != nil {
|
||||
return nil, fmt.Errorf("parse subject: %w", err)
|
||||
}
|
||||
|
||||
if tkn.audience, err = parse.OptionalDID(m.Aud); err != nil {
|
||||
return nil, fmt.Errorf("parse audience: %w", err)
|
||||
}
|
||||
|
||||
if tkn.command, err = command.Parse(m.Cmd); err != nil {
|
||||
return nil, fmt.Errorf("parse command: %w", err)
|
||||
}
|
||||
|
||||
if len(m.Nonce) == 0 {
|
||||
return nil, fmt.Errorf("nonce is required")
|
||||
}
|
||||
tkn.nonce = m.Nonce
|
||||
|
||||
tkn.arguments = m.Args
|
||||
tkn.proof = m.Prf
|
||||
tkn.meta = m.Meta
|
||||
|
||||
tkn.expiration = parse.OptionalTimestamp(m.Exp)
|
||||
tkn.invokedAt = parse.OptionalTimestamp(m.Iat)
|
||||
|
||||
tkn.cause = m.Cause
|
||||
|
||||
if err := tkn.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tkn, nil
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
type DID string
|
||||
|
||||
# The Invocation Payload attaches sender, receiver, and provenance to the Task.
|
||||
type Payload struct {
|
||||
# The DID of the invoker
|
||||
iss DID
|
||||
# The Subject being invoked
|
||||
sub DID
|
||||
# The DID of the intended Executor if different from the Subject
|
||||
aud optional DID
|
||||
|
||||
# The Command
|
||||
cmd String
|
||||
# The Command's Arguments
|
||||
args { String : Any}
|
||||
# Delegations that prove the chain of authority
|
||||
prf [ Link ]
|
||||
|
||||
# Arbitrary Metadata
|
||||
meta optional { String : Any }
|
||||
|
||||
# A unique, random nonce
|
||||
nonce optional Bytes
|
||||
# The timestamp at which the Invocation becomes invalid
|
||||
exp nullable Int
|
||||
# The Timestamp at which the Invocation was created
|
||||
iat optional Int
|
||||
|
||||
# An optional CID of the Receipt that enqueued the Task
|
||||
cause optional Link
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
package invocation_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did/didtest"
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation/delegationtest"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
)
|
||||
|
||||
const (
|
||||
missingPrivKeyCfg = "CAESQMjRvrEIjpPYRQKmkAGw/pV0XgE958rYa4vlnKJjl1zz/sdnGnyV1xKLJk8D39edyjhHWyqcpgFnozQK62SG16k="
|
||||
missingTknCIDStr = "bafyreigwypmw6eul6vadi6g6lnfbsfo2zck7gfzsbjoroqs3djhnzzc7mm"
|
||||
missingDIDStr = "did:key:z6MkwboxFsH3kEuehBZ5fLkRmxi68yv1u38swA4r9Jm2VRma"
|
||||
)
|
||||
|
||||
var emptyArguments = args.New()
|
||||
|
||||
func TestToken_ExecutionAllowed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("passes - only root", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testPasses(t, didtest.PersonaBob, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBob)
|
||||
})
|
||||
|
||||
t.Run("passes - valid chain", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
||||
})
|
||||
|
||||
t.Run("passes - proof chain attenuates command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testPasses(t, didtest.PersonaFrank, delegationtest.AttenuatedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidAttenuatedCommand)
|
||||
})
|
||||
|
||||
t.Run("passes - invocation attenuates command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testPasses(t, didtest.PersonaFrank, delegationtest.AttenuatedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
||||
})
|
||||
|
||||
t.Run("fails - no proof", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrNoProof, didtest.PersonaCarol, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofEmpty)
|
||||
})
|
||||
|
||||
t.Run("fails - missing referenced delegation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
missingTknCID, err := cid.Parse(missingTknCIDStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
prf := []cid.Cid{missingTknCID, delegationtest.TokenAliceBobCID}
|
||||
testFails(t, invocation.ErrMissingDelegation, didtest.PersonaCarol, delegationtest.NominalCommand, emptyArguments, prf)
|
||||
})
|
||||
|
||||
t.Run("fails - referenced delegation expired", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrTokenInvalidNow, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidExpired)
|
||||
|
||||
})
|
||||
|
||||
t.Run("fails - referenced delegation inactive", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrTokenInvalidNow, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidInactive)
|
||||
})
|
||||
|
||||
t.Run("fails - last (or only) delegation not root", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prf := []cid.Cid{delegationtest.TokenErinFrankCID, delegationtest.TokenDanErinCID, delegationtest.TokenCarolDanCID}
|
||||
testFails(t, invocation.ErrLastNotRoot, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, prf)
|
||||
})
|
||||
|
||||
t.Run("fails - broken chain", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prf := []cid.Cid{delegationtest.TokenCarolDanCID, delegationtest.TokenAliceBobCID}
|
||||
testFails(t, invocation.ErrBrokenChain, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, prf)
|
||||
})
|
||||
|
||||
t.Run("fails - first not issued to invoker", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prf := []cid.Cid{delegationtest.TokenBobCarolCID, delegationtest.TokenAliceBobCID}
|
||||
testFails(t, invocation.ErrBrokenChain, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, prf)
|
||||
})
|
||||
|
||||
t.Run("fails - proof chain expands command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrCommandNotCovered, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidExpandedCommand)
|
||||
})
|
||||
|
||||
t.Run("fails - invocation expands command", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrCommandNotCovered, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
|
||||
})
|
||||
|
||||
t.Run("fails - inconsistent subject", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testFails(t, invocation.ErrWrongSub, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidSubject)
|
||||
})
|
||||
}
|
||||
|
||||
func test(t *testing.T, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) error {
|
||||
t.Helper()
|
||||
|
||||
// TODO: use the args and add minimal test to check that they are verified against the policy
|
||||
|
||||
tkn, err := invocation.New(persona.DID(), didtest.PersonaAlice.DID(), cmd, prf, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
return tkn.ExecutionAllowed(delegationtest.GetDelegationLoader())
|
||||
}
|
||||
|
||||
func testFails(t *testing.T, expErr error, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) {
|
||||
err := test(t, persona, cmd, args, prf, opts...)
|
||||
require.ErrorIs(t, err, expErr)
|
||||
}
|
||||
|
||||
func testPasses(t *testing.T, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) {
|
||||
err := test(t, persona, cmd, args, prf, opts...)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
package invocation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||
)
|
||||
|
||||
// ToSealed wraps the invocation token in an envelope, generates the
|
||||
// signature, encodes the result to DAG-CBOR and calculates the CID of
|
||||
// the resulting binary data.
|
||||
func (t *Token) ToSealed(privKey crypto.PrivKey) ([]byte, cid.Cid, error) {
|
||||
data, err := t.ToDagCbor(privKey)
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
id, err := envelope.CIDFromBytes(data)
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
return data, id, nil
|
||||
}
|
||||
|
||||
// ToSealedWriter is the same as ToSealed but accepts an io.Writer.
|
||||
func (t *Token) ToSealedWriter(w io.Writer, privKey crypto.PrivKey) (cid.Cid, error) {
|
||||
cidWriter := envelope.NewCIDWriter(w)
|
||||
|
||||
if err := t.ToDagCborWriter(cidWriter, privKey); err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
return cidWriter.CID()
|
||||
}
|
||||
|
||||
// FromSealed decodes the provided binary data from the DAG-CBOR format,
|
||||
// verifies that the envelope's signature is correct based on the public
|
||||
// key taken from the issuer (iss) field and calculates the CID of the
|
||||
// incoming data.
|
||||
func FromSealed(data []byte) (*Token, cid.Cid, error) {
|
||||
tkn, err := FromDagCbor(data)
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
id, err := envelope.CIDFromBytes(data)
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
return tkn, id, nil
|
||||
}
|
||||
|
||||
// FromSealedReader is the same as Unseal but accepts an io.Reader.
|
||||
func FromSealedReader(r io.Reader) (*Token, cid.Cid, error) {
|
||||
cidReader := envelope.NewCIDReader(r)
|
||||
|
||||
tkn, err := FromDagCborReader(cidReader)
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
id, err := cidReader.CID()
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
return tkn, id, nil
|
||||
}
|
||||
|
||||
// Encode marshals a Token to the format specified by the provided
|
||||
// codec.Encoder.
|
||||
func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error) {
|
||||
node, err := t.toIPLD(privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ipld.Encode(node, encFn)
|
||||
}
|
||||
|
||||
// EncodeWriter is the same as Encode, but accepts an io.Writer.
|
||||
func (t *Token) EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.Encoder) error {
|
||||
node, err := t.toIPLD(privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ipld.EncodeStreaming(w, node, encFn)
|
||||
}
|
||||
|
||||
// ToDagCbor marshals the Token to the DAG-CBOR format.
|
||||
func (t *Token) ToDagCbor(privKey crypto.PrivKey) ([]byte, error) {
|
||||
return t.Encode(privKey, dagcbor.Encode)
|
||||
}
|
||||
|
||||
// ToDagCborWriter is the same as ToDagCbor, but it accepts an io.Writer.
|
||||
func (t *Token) ToDagCborWriter(w io.Writer, privKey crypto.PrivKey) error {
|
||||
return t.EncodeWriter(w, privKey, dagcbor.Encode)
|
||||
}
|
||||
|
||||
// ToDagJson marshals the Token to the DAG-JSON format.
|
||||
func (t *Token) ToDagJson(privKey crypto.PrivKey) ([]byte, error) {
|
||||
return t.Encode(privKey, dagjson.Encode)
|
||||
}
|
||||
|
||||
// ToDagJsonWriter is the same as ToDagJson, but it accepts an io.Writer.
|
||||
func (t *Token) ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error {
|
||||
return t.EncodeWriter(w, privKey, dagjson.Encode)
|
||||
}
|
||||
|
||||
// Decode unmarshals the input data using the format specified by the
|
||||
// provided codec.Decoder into a Token.
|
||||
//
|
||||
// An error is returned if the conversion fails, or if the resulting
|
||||
// Token is invalid.
|
||||
func Decode(b []byte, decFn codec.Decoder) (*Token, error) {
|
||||
node, err := ipld.Decode(b, decFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FromIPLD(node)
|
||||
}
|
||||
|
||||
// DecodeReader is the same as Decode, but accept an io.Reader.
|
||||
func DecodeReader(r io.Reader, decFn codec.Decoder) (*Token, error) {
|
||||
node, err := ipld.DecodeStreaming(r, decFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FromIPLD(node)
|
||||
}
|
||||
|
||||
// FromDagCbor unmarshals the input data into a Token.
|
||||
//
|
||||
// An error is returned if the conversion fails, or if the resulting
|
||||
// Token is invalid.
|
||||
func FromDagCbor(data []byte) (*Token, error) {
|
||||
pay, err := envelope.FromDagCbor[*tokenPayloadModel](data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkn, err := tokenFromModel(*pay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tkn, err
|
||||
}
|
||||
|
||||
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
||||
func FromDagCborReader(r io.Reader) (*Token, error) {
|
||||
return DecodeReader(r, dagcbor.Decode)
|
||||
}
|
||||
|
||||
// FromDagJson unmarshals the input data into a Token.
|
||||
//
|
||||
// An error is returned if the conversion fails, or if the resulting
|
||||
// Token is invalid.
|
||||
func FromDagJson(data []byte) (*Token, error) {
|
||||
return Decode(data, dagjson.Decode)
|
||||
}
|
||||
|
||||
// FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
|
||||
func FromDagJsonReader(r io.Reader) (*Token, error) {
|
||||
return DecodeReader(r, dagjson.Decode)
|
||||
}
|
||||
|
||||
// FromIPLD decode the given IPLD representation into a Token.
|
||||
func FromIPLD(node datamodel.Node) (*Token, error) {
|
||||
pay, err := envelope.FromIPLD[*tokenPayloadModel](node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkn, err := tokenFromModel(*pay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tkn, err
|
||||
}
|
||||
|
||||
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 aud *string
|
||||
|
||||
if t.audience != did.Undef {
|
||||
a := t.audience.String()
|
||||
aud = &a
|
||||
}
|
||||
|
||||
var exp *int64
|
||||
if t.expiration != nil {
|
||||
u := t.expiration.Unix()
|
||||
exp = &u
|
||||
}
|
||||
|
||||
var iat *int64
|
||||
if t.invokedAt != nil {
|
||||
i := t.invokedAt.Unix()
|
||||
iat = &i
|
||||
}
|
||||
|
||||
model := &tokenPayloadModel{
|
||||
Iss: t.issuer.String(),
|
||||
Aud: aud,
|
||||
Sub: t.subject.String(),
|
||||
Cmd: t.command.String(),
|
||||
Args: t.arguments,
|
||||
Prf: t.proof,
|
||||
Meta: t.meta,
|
||||
Nonce: t.nonce,
|
||||
Exp: exp,
|
||||
Iat: iat,
|
||||
Cause: t.cause,
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package invocation_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
)
|
||||
|
||||
func TestSealUnsealRoundtrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
privKey, iss, sub, cmd, args, prf, meta, err := setupExampleNew()
|
||||
require.NoError(t, err)
|
||||
|
||||
tkn1, err := invocation.New(iss, sub, cmd, prf,
|
||||
invocation.WithArgument("uri", args["uri"]),
|
||||
invocation.WithArgument("headers", args["headers"]),
|
||||
invocation.WithArgument("payload", args["payload"]),
|
||||
invocation.WithMeta("env", "development"),
|
||||
invocation.WithMeta("tags", meta["tags"]),
|
||||
invocation.WithExpirationIn(time.Minute),
|
||||
invocation.WithoutInvokedAt(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, cid1, err := tkn1.ToSealed(privKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
tkn2, cid2, err := invocation.FromSealed(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, cid1, cid2)
|
||||
assert.Equal(t, tkn1, tkn2)
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package invocation
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
)
|
||||
|
||||
// Option is a type that allows optional fields to be set during the
|
||||
// creation of an invocation Token.
|
||||
type Option func(*Token) error
|
||||
|
||||
// WithArgument adds a key/value pair to the Token's Arguments field.
|
||||
func WithArgument(key string, val any) Option {
|
||||
return func(t *Token) error {
|
||||
return t.arguments.Add(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// WithAudience sets the Token's audience to the provided did.DID.
|
||||
//
|
||||
// If the provided did.DID is the same as the Token's subject, the
|
||||
// audience is not set.
|
||||
func WithAudience(aud did.DID) Option {
|
||||
return func(t *Token) error {
|
||||
if t.subject.String() != aud.String() {
|
||||
t.audience = aud
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMeta adds a key/value pair in the "meta" field.
|
||||
//
|
||||
// WithMeta can be used multiple times in the same call.
|
||||
// Accepted types for the value are: bool, string, int, int32, int64, []byte,
|
||||
// and ipld.Node.
|
||||
func WithMeta(key string, val any) Option {
|
||||
return func(t *Token) error {
|
||||
return t.meta.Add(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// WithEncryptedMetaString adds a key/value pair in the "meta" field.
|
||||
// The string value is encrypted with the given aesKey.
|
||||
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 aesKey.
|
||||
func WithEncryptedMetaBytes(key string, val, encryptionKey []byte) Option {
|
||||
return func(t *Token) error {
|
||||
return t.meta.AddEncrypted(key, val, encryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
// 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. If you truly want to create an invocation Token
|
||||
// without a nonce, use the WithEmptyNonce Option which will set the
|
||||
// nonce to an empty byte array.
|
||||
func WithNonce(nonce []byte) Option {
|
||||
return func(t *Token) error {
|
||||
t.nonce = nonce
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithEmptyNonce sets the Token's nonce to an empty byte slice as
|
||||
// suggested by the UCAN spec for invocation tokens that represent
|
||||
// idempotent operations.
|
||||
func WithEmptyNonce() Option {
|
||||
return func(t *Token) error {
|
||||
t.nonce = []byte{}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithExpiration set's the Token's optional "expiration" field to the
|
||||
// value of the provided time.Time.
|
||||
func WithExpiration(exp time.Time) Option {
|
||||
return func(t *Token) error {
|
||||
exp = exp.Round(time.Second)
|
||||
t.expiration = &exp
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithExpirationIn set's the Token's optional "expiration" field to
|
||||
// Now() plus the given duration.
|
||||
func WithExpirationIn(after time.Duration) Option {
|
||||
return WithExpiration(time.Now().Add(after))
|
||||
}
|
||||
|
||||
// WithInvokedAt sets the Token's invokedAt field to the provided
|
||||
// time.Time.
|
||||
//
|
||||
// If this Option is not provided, the invocation Token's iat field will
|
||||
// be set to the value of time.Now(). If you want to create an invocation
|
||||
// Token without this field being set, use the WithoutInvokedAt Option.
|
||||
func WithInvokedAt(iat time.Time) Option {
|
||||
return func(t *Token) error {
|
||||
t.invokedAt = &iat
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithInvokedAtIn sets the Token's invokedAt field to Now() plus the
|
||||
// given duration.
|
||||
func WithInvokedAtIn(after time.Duration) Option {
|
||||
return WithInvokedAt(time.Now().Add(after))
|
||||
}
|
||||
|
||||
// WithoutInvokedAt clears the Token's invokedAt field.
|
||||
func WithoutInvokedAt() Option {
|
||||
return func(t *Token) error {
|
||||
t.invokedAt = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCause sets the Token's cause field to the provided cid.Cid.
|
||||
func WithCause(cause *cid.Cid) Option {
|
||||
return func(t *Token) error {
|
||||
t.cause = cause
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package invocation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
)
|
||||
|
||||
// # Invocation token validation
|
||||
//
|
||||
// Per the specification, invocation Tokens must be validated before the command is executed.
|
||||
// This validation effectively happens in multiple places in the codebase.
|
||||
// Steps 1 and 2 are the same for all token types.
|
||||
//
|
||||
// 1. When a token is read/unsealed from its containing envelope (`envelope` package):
|
||||
// a. The envelope can be decoded.
|
||||
// b. The envelope contains a Signature, VarsigHeader and Payload.
|
||||
// c. The Payload contains an iss field that contains a valid `did:key`.
|
||||
// d. The public key can be extracted from the `did:key`.
|
||||
// e. The public key type is supported by go-ucan.
|
||||
// f. The Signature can be decoded per the VarsigHeader.
|
||||
// g. The SigPayload can be verified using the Signature and public key.
|
||||
// h. The field key of the TokenPayload matches the expected tag.
|
||||
//
|
||||
// 2. When the token is created or passes step one (token constructor or decoder):
|
||||
// a. All required fields are present
|
||||
// b. All populated fields respect their own rules (example: a policy is legal)
|
||||
//
|
||||
// 3. When an unsealed invocation passes steps one and two for execution (verifyTimeBound below):
|
||||
// a. The invocation cannot be expired (expiration in the future or absent).
|
||||
// b. All the delegation must not be expired (expiration in the future or absent).
|
||||
// c. All the delegation must be active (nbf in the past or absent).
|
||||
//
|
||||
// 4. When the proof chain is being validated (verifyProofs below):
|
||||
// a. There must be at least one delegation in the proof chain.
|
||||
// b. All referenced delegations must be available.
|
||||
// c. The first proof must be issued to the Invoker (audience DID).
|
||||
// d. The Issuer of each delegation must be the Audience in the next one.
|
||||
// e. The last token must be a root delegation.
|
||||
// f. The Subject of each delegation must equal the invocation's Audience field.
|
||||
// g. The command of each delegation must "allow" the one before it.
|
||||
//
|
||||
// 5. If steps 1-4 pass:
|
||||
// a. The policy must "match" the arguments. (verifyArgs below)
|
||||
// b. The nonce (if present) is not reused. (out of scope for go-ucan)
|
||||
|
||||
// verifyProofs controls that the proof chain allows the invocation:
|
||||
// - principal alignment
|
||||
// - command alignment
|
||||
func (t *Token) verifyProofs(delegations []*delegation.Token) error {
|
||||
// There must be at least one delegation referenced - 4a
|
||||
if len(delegations) < 1 {
|
||||
return ErrNoProof
|
||||
}
|
||||
|
||||
cmd := t.command
|
||||
iss := t.issuer
|
||||
aud := t.audience
|
||||
if !aud.Defined() {
|
||||
aud = t.subject
|
||||
}
|
||||
|
||||
// control from the invocation to the root
|
||||
for i, dlgCid := range t.proof {
|
||||
dlg := delegations[i]
|
||||
|
||||
// The Subject of each delegation must equal the invocation's Audience field. - 4f
|
||||
if dlg.Subject() != aud {
|
||||
return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrWrongSub, dlgCid, aud, dlg.Subject())
|
||||
}
|
||||
|
||||
// The first proof must be issued to the Invoker (audience DID). - 4c
|
||||
// The Issuer of each delegation must be the Audience in the next one. - 4d
|
||||
if dlg.Audience() != iss {
|
||||
return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrBrokenChain, dlgCid, iss, dlg.Audience())
|
||||
}
|
||||
iss = dlg.Issuer()
|
||||
|
||||
// The command of each delegation must "allow" the one before it. - 4g
|
||||
if !dlg.Command().Covers(cmd) {
|
||||
return fmt.Errorf("%w: delegation %s, %s doesn't cover %s", ErrCommandNotCovered, dlgCid, dlg.Command(), cmd)
|
||||
}
|
||||
cmd = dlg.Command()
|
||||
}
|
||||
|
||||
// The last prf value must be a root delegation (have the issuer field match the Subject field) - 4e
|
||||
if last := delegations[len(delegations)-1]; last.Issuer() != last.Subject() {
|
||||
return fmt.Errorf("%w: expected %s, got %s", ErrLastNotRoot, last.Subject(), last.Issuer())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Token) verifyTimeBound(dlgs []*delegation.Token) error {
|
||||
return t.verifyTimeBoundAt(time.Now(), dlgs)
|
||||
}
|
||||
|
||||
func (t *Token) verifyTimeBoundAt(at time.Time, delegations []*delegation.Token) error {
|
||||
// The invocation cannot be expired (expiration in the future or absent). - 3a
|
||||
if !t.IsValidAt(at) {
|
||||
return fmt.Errorf("%w: invocation", ErrTokenInvalidNow)
|
||||
}
|
||||
|
||||
for i, dlgCid := range t.proof {
|
||||
dlg := delegations[i]
|
||||
|
||||
// All the delegation must not be expired (expiration in the future or absent). - 3b
|
||||
// All the delegation must be active (nbf in the past or absent). - 3c
|
||||
if !dlg.IsValidAt(at) {
|
||||
return fmt.Errorf("%w: delegation %s", ErrTokenInvalidNow, dlgCid)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Token) verifyArgs(delegations []*delegation.Token, arguments *args.Args) error {
|
||||
var count int
|
||||
for i := range t.proof {
|
||||
count += len(delegations[i].Policy())
|
||||
}
|
||||
|
||||
policies := make(policy.Policy, 0, count)
|
||||
for i := range t.proof {
|
||||
policies = append(policies, delegations[i].Policy()...)
|
||||
}
|
||||
|
||||
argsIpld, err := arguments.ToIPLD()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, statement := policies.Match(argsIpld)
|
||||
if !ok {
|
||||
return fmt.Errorf("the following UCAN policy is not satisfied: %v", statement.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user