Compare commits
175 Commits
feat/deleg
...
v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dddb67a2b7 | ||
|
|
60bdc8873b | ||
|
|
d90715d1fe | ||
|
|
5f8536e480 | ||
|
|
c19e38356d | ||
|
|
aea1880386 | ||
|
|
2fb5a3dc01 | ||
|
|
e980d6c0b9 | ||
|
|
1098e76cba | ||
|
|
bb36d61d93 | ||
|
|
417ef78570 | ||
|
|
00d2380f14 | ||
|
|
8ca088bf27 | ||
|
|
25ca34923f | ||
|
|
fc4c8f2de1 | ||
|
|
64b989452f | ||
|
|
92065ca0d3 | ||
|
|
814cec1495 | ||
|
|
0f70557309 | ||
|
|
89e4d5d419 | ||
|
|
9057cbcba6 | ||
|
|
98d9cadcbd | ||
|
|
e938d64220 | ||
|
|
c577d73f3e | ||
|
|
be185a8496 | ||
|
|
17a57c622a | ||
|
|
6298fa28bd | ||
|
|
d3e97aaa08 | ||
|
|
fdff79d23a | ||
|
|
a26d836025 | ||
|
|
9f47418bdf | ||
|
|
81c7a0f80d | ||
|
|
3987e8649c | ||
|
|
17a1d54b6f | ||
|
|
7cb0f97b30 | ||
|
|
c4a53f42b6 | ||
|
|
522181b16a | ||
|
|
633b3d210a | ||
|
|
3c705ca150 | ||
|
|
1fa2b5e6fc | ||
|
|
11bc085c60 | ||
|
|
a4a8634eb8 | ||
|
|
d353dfe652 | ||
|
|
1e5ecdc205 | ||
|
|
f9065d39d8 | ||
|
|
cddade4670 | ||
|
|
948087744d | ||
|
|
bfb93d6988 | ||
|
|
cfcb199818 | ||
|
|
85557ab6b5 | ||
|
|
adc2b8d0da | ||
|
|
bcdaf0cca3 | ||
|
|
d754c5837b | ||
|
|
d89fb395e3 | ||
|
|
4932e32052 | ||
|
|
a52b48cf47 | ||
|
|
e6e4d85381 | ||
|
|
962e897ff5 | ||
|
|
58bb5cdb8f | ||
|
|
ce7f653ab0 | ||
|
|
7d4f973171 | ||
|
|
3dc0011628 | ||
|
|
08f821f23d | ||
|
|
1b61f2e4db | ||
|
|
187e7a869c | ||
|
|
a98653b769 | ||
|
|
31d16ac468 | ||
|
|
d2b004c405 | ||
|
|
884d63a689 | ||
|
|
c9f3a6033a | ||
|
|
8447499c5a | ||
|
|
b4e222f8a0 | ||
|
|
41b8600fbc | ||
|
|
824c8fe523 | ||
|
|
6aeb6a8b70 | ||
|
|
a1aaf47d7c | ||
|
|
728696f169 | ||
|
|
cfb4446a05 | ||
|
|
06a72868a5 | ||
|
|
6f9a6fa5c1 | ||
|
|
f2b4c3ac20 | ||
|
|
7a7db684c3 | ||
|
|
d7454156d2 | ||
|
|
d3ad6715d9 | ||
|
|
602bdf9c7a | ||
|
|
d21c17c4ca | ||
|
|
72f4ef7b5e | ||
|
|
02be4010d6 | ||
|
|
61e031529f | ||
|
|
19721027e4 | ||
|
|
bc847ee027 | ||
|
|
5bfe430934 | ||
|
|
10b5e1e603 | ||
|
|
3cf1de6b67 | ||
|
|
400f689a85 | ||
|
|
6717a3a89c | ||
|
|
9e9c632ded | ||
|
|
b210c69173 | ||
|
|
6d85b2ba3c | ||
|
|
76c015e78b | ||
|
|
fcb527cc52 | ||
|
|
89f648a94e | ||
|
|
e1d771333c | ||
|
|
f44cf8af78 | ||
|
|
2b2fc4a13f | ||
|
|
6b72799818 | ||
|
|
ccc85d4697 | ||
|
|
0d63e90b67 | ||
|
|
d784c92c29 | ||
|
|
7662fe34db | ||
|
|
6d0fbd4d5a | ||
|
|
e76354fb0a | ||
|
|
deaf9c4fe9 | ||
|
|
a1c2c5c067 | ||
|
|
2c58fedfd5 | ||
|
|
2ea9f8c93b | ||
|
|
00ff88ef23 | ||
|
|
2ffdf004ac | ||
|
|
a8780f750c | ||
|
|
c70f68b886 | ||
|
|
a27eb258e5 | ||
|
|
866683347f | ||
|
|
2fafbe7bf3 | ||
|
|
1728bf29b8 | ||
|
|
8fac97b7e7 | ||
|
|
7ad940844c | ||
|
|
52ae2eaf60 | ||
|
|
570bcdcb6c | ||
|
|
5abb870462 | ||
|
|
4ec675861d | ||
|
|
b941b507e0 | ||
|
|
e66beb662e | ||
|
|
87e25090bb | ||
|
|
6011f0740a | ||
|
|
2bd177ce4d | ||
|
|
abda49061d | ||
|
|
fb978ee574 | ||
|
|
da1310b78a | ||
|
|
ac1b03f144 | ||
|
|
7fa3ba1492 | ||
|
|
081d382028 | ||
|
|
2ad3aeb6da | ||
|
|
030db7ec0d | ||
|
|
aa4ad2fc10 | ||
|
|
51e8d5ce04 | ||
|
|
f8b5fa3a32 | ||
|
|
59da2d1a2c | ||
|
|
88ed55b252 | ||
|
|
9051e5250b | ||
|
|
5f2877f0ff | ||
|
|
100a510097 | ||
|
|
2a51d61b46 | ||
|
|
3e3c5a83cc | ||
|
|
d60fb71156 | ||
|
|
40639b6715 | ||
|
|
60922ced96 | ||
|
|
f7b4b48791 | ||
|
|
346efbd31d | ||
|
|
df9beadf9c | ||
|
|
8615f6c72b | ||
|
|
d9739a3bab | ||
|
|
6b8fbcee0a | ||
|
|
a7037dbc47 | ||
|
|
50ea43e3fa | ||
|
|
0ec16a085c | ||
|
|
2b45f7630e | ||
|
|
637973b10b | ||
|
|
bb4725d87c | ||
|
|
8782554a7b | ||
|
|
a8302ad441 | ||
|
|
b4dd8c0757 | ||
|
|
4201ab2dca | ||
|
|
1b7059c029 | ||
|
|
f3a5209cec | ||
|
|
a2822f02c7 |
34
.github/workflows/bench.yml
vendored
Normal file
34
.github/workflows/bench.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ "ubuntu" ]
|
os: [ "ubuntu" ]
|
||||||
go: [ "1.21.x", "1.22.x", "1.23.x", ]
|
go: [ "1.22.x", "1.23.x", ]
|
||||||
env:
|
env:
|
||||||
COVERAGES: ""
|
COVERAGES: ""
|
||||||
runs-on: ${{ matrix.os }}-latest
|
runs-on: ${{ matrix.os }}-latest
|
||||||
@@ -22,6 +22,6 @@ jobs:
|
|||||||
go version
|
go version
|
||||||
go env
|
go env
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: go test -v ./...
|
run: go test -v ./... -tags jwx_es256k
|
||||||
- name: Check formatted
|
- name: Check formatted
|
||||||
run: gofmt -l .
|
run: gofmt -l .
|
||||||
@@ -29,7 +29,7 @@ Verbatim copies of both licenses are included below:
|
|||||||
```
|
```
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
|||||||
77
Readme.md
Normal file
77
Readme.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<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
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
31
did/README.md
Normal file
31
did/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## 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,43 +1,171 @@
|
|||||||
package did
|
package did
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||||
"github.com/libp2p/go-libp2p/core/crypto/pb"
|
"github.com/libp2p/go-libp2p/core/crypto/pb"
|
||||||
"github.com/multiformats/go-multicodec"
|
"github.com/multiformats/go-multicodec"
|
||||||
"github.com/multiformats/go-varint"
|
"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) {
|
func FromPrivKey(privKey crypto.PrivKey) (DID, error) {
|
||||||
return FromPubKey(privKey.GetPublic())
|
return FromPubKey(privKey.GetPublic())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromPubKey returns a did:key constructed from the provided public key.
|
||||||
func FromPubKey(pubKey crypto.PubKey) (DID, error) {
|
func FromPubKey(pubKey crypto.PubKey) (DID, error) {
|
||||||
code, ok := map[pb.KeyType]multicodec.Code{
|
var code multicodec.Code
|
||||||
pb.KeyType_Ed25519: multicodec.Ed25519Pub,
|
|
||||||
pb.KeyType_RSA: multicodec.RsaPub,
|
switch pubKey.Type() {
|
||||||
pb.KeyType_Secp256k1: multicodec.Secp256k1Pub,
|
case pb.KeyType_Ed25519:
|
||||||
pb.KeyType_ECDSA: multicodec.Es256,
|
code = multicodec.Ed25519Pub
|
||||||
}[pubKey.Type()]
|
case pb.KeyType_RSA:
|
||||||
if !ok {
|
code = RSA
|
||||||
return Undef, errors.New("Blah")
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := varint.ToUvarint(uint64(code))
|
if pubKey.Type() == pb.KeyType_ECDSA && code == Secp256k1 {
|
||||||
|
var err error
|
||||||
|
|
||||||
pubBytes, err := pubKey.Raw()
|
pubKey, err = coerceECDSAToSecp256k1(pubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Undef, err
|
return Undef, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes []byte
|
||||||
|
|
||||||
|
switch pubKey.Type() {
|
||||||
|
case pb.KeyType_ECDSA:
|
||||||
|
pkix, err := pubKey.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := x509.ParsePKIXPublicKey(pkix)
|
||||||
|
if err != nil {
|
||||||
|
return Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsaPublicKey := publicKey.(*ecdsa.PublicKey)
|
||||||
|
|
||||||
|
bytes = elliptic.MarshalCompressed(ecdsaPublicKey.Curve, ecdsaPublicKey.X, ecdsaPublicKey.Y)
|
||||||
|
case pb.KeyType_Ed25519, pb.KeyType_Secp256k1:
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if bytes, err = pubKey.Raw(); err != nil {
|
||||||
|
return Undef, err
|
||||||
|
}
|
||||||
|
case pb.KeyType_RSA:
|
||||||
|
var err error
|
||||||
|
|
||||||
|
pkix, err := pubKey.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := x509.ParsePKIXPublicKey(pkix)
|
||||||
|
if err != nil {
|
||||||
|
return Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes = x509.MarshalPKCS1PublicKey(publicKey.(*rsa.PublicKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
return DID{
|
return DID{
|
||||||
str: string(append(buf, pubBytes...)),
|
code: code,
|
||||||
code: uint64(code),
|
bytes: string(append(varint.ToUvarint(uint64(code)), bytes...)),
|
||||||
key: true,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToPubKey returns the crypto.PubKey encapsulated in the DID formed by
|
||||||
|
// parsing the provided string.
|
||||||
func ToPubKey(s string) (crypto.PubKey, error) {
|
func ToPubKey(s string) (crypto.PubKey, error) {
|
||||||
id, err := Parse(s)
|
id, err := Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,3 +174,58 @@ func ToPubKey(s string) (crypto.PubKey, error) {
|
|||||||
|
|
||||||
return id.PubKey()
|
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,10 +1,17 @@
|
|||||||
package did_test
|
package did_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"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/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,9 +23,59 @@ const (
|
|||||||
func TestFromPubKey(t *testing.T) {
|
func TestFromPubKey(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
id, err := did.FromPubKey(examplePubKey(t))
|
_, ecdsaP256, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P256(), rand.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, exampleDID(t), id)
|
_, ecdsaP384, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P384(), rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, ecdsaP521, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P521(), rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, ecdsaSecp256k1, err := crypto.GenerateECDSAKeyPairWithCurve(secp256k1.S256(), rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, ed25519, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, rsa, err := crypto.GenerateRSAKeyPair(2048, rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, secp256k1PubKey1, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
test := func(pub crypto.PubKey, code multicodec.Code) func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
id, err := did.FromPubKey(pub)
|
||||||
|
require.NoError(t, err)
|
||||||
|
p, err := id.PubKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, pub, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("ECDSA with P256 curve", test(ecdsaP256, did.P256))
|
||||||
|
t.Run("ECDSA with P384 curve", test(ecdsaP384, did.P384))
|
||||||
|
t.Run("ECDSA with P521 curve", test(ecdsaP521, did.P521))
|
||||||
|
t.Run("Ed25519", test(ed25519, did.Ed25519))
|
||||||
|
t.Run("RSA", test(rsa, did.RSA))
|
||||||
|
t.Run("secp256k1", test(secp256k1PubKey1, did.Secp256k1))
|
||||||
|
|
||||||
|
t.Run("ECDSA with secp256k1 curve (coerced)", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
id, err := did.FromPubKey(ecdsaSecp256k1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
p, err := id.PubKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, pb.KeyType_Secp256k1, p.Type())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unmarshaled example key (secp256k1)", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
id, err := did.FromPubKey(examplePubKey(t))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, exampleDID(t), id)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToPubKey(t *testing.T) {
|
func TestToPubKey(t *testing.T) {
|
||||||
|
|||||||
188
did/did.go
188
did/did.go
@@ -1,6 +1,9 @@
|
|||||||
package did
|
package did
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -10,113 +13,61 @@ import (
|
|||||||
varint "github.com/multiformats/go-varint"
|
varint "github.com/multiformats/go-varint"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Prefix = "did:"
|
// Signature algorithms from the [did:key specification]
|
||||||
const KeyPrefix = "did:key:"
|
|
||||||
|
|
||||||
const DIDCore = 0x0d1d
|
|
||||||
const Ed25519 = 0xed
|
|
||||||
const RSA = uint64(multicodec.RsaPub)
|
|
||||||
|
|
||||||
var MethodOffset = varint.UvarintSize(uint64(DIDCore))
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// [did:key format]: https://w3c-ccg.github.io/did-method-key/
|
// [did:key specification]: https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm
|
||||||
type DID struct {
|
const (
|
||||||
key bool
|
X25519 = multicodec.X25519Pub
|
||||||
code uint64
|
Ed25519 = multicodec.Ed25519Pub // UCAN required/recommended
|
||||||
str string
|
P256 = multicodec.P256Pub // UCAN required
|
||||||
}
|
P384 = multicodec.P384Pub
|
||||||
|
P521 = multicodec.P521Pub
|
||||||
|
Secp256k1 = multicodec.Secp256k1Pub // UCAN required
|
||||||
|
RSA = multicodec.RsaPub
|
||||||
|
)
|
||||||
|
|
||||||
// Undef can be used to represent a nil or undefined DID, using DID{}
|
// Undef can be used to represent a nil or undefined DID, using DID{}
|
||||||
// directly is also acceptable.
|
// directly is also acceptable.
|
||||||
var Undef = DID{}
|
var Undef = DID{}
|
||||||
|
|
||||||
func (d DID) Defined() bool {
|
// DID is a Decentralized Identifier of the did:key type, directly holding a cryptographic public key.
|
||||||
return d.str != ""
|
// [did:key format]: https://w3c-ccg.github.io/did-method-key/
|
||||||
|
type DID struct {
|
||||||
|
code multicodec.Code
|
||||||
|
bytes string // as string instead of []byte to allow the == operator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d DID) Bytes() []byte {
|
// Parse returns the DID from the string representation or an error if
|
||||||
if !d.Defined() {
|
// the prefix and method are incorrect, if an unknown encryption algorithm
|
||||||
return nil
|
// is specified or if the method-specific-identifier's bytes don't
|
||||||
}
|
// represent a public key for the specified encryption algorithm.
|
||||||
return []byte(d.str)
|
func Parse(str string) (DID, error) {
|
||||||
}
|
const keyPrefix = "did:key:"
|
||||||
|
|
||||||
func (d DID) Code() uint64 {
|
if !strings.HasPrefix(str, keyPrefix) {
|
||||||
return d.code
|
return Undef, fmt.Errorf("must start with 'did:key'")
|
||||||
}
|
|
||||||
|
|
||||||
func (d DID) DID() DID {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DID) Key() bool {
|
|
||||||
return d.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DID) PubKey() (crypto.PubKey, error) {
|
|
||||||
if !d.key {
|
|
||||||
return nil, fmt.Errorf("unsupported did type: %s", d.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unmarshaler, ok := map[multicodec.Code]crypto.PubKeyUnmarshaller{
|
baseCodec, bytes, err := mbase.Decode(str[len(keyPrefix):])
|
||||||
multicodec.Ed25519Pub: crypto.UnmarshalEd25519PublicKey,
|
if err != nil {
|
||||||
multicodec.RsaPub: crypto.UnmarshalRsaPublicKey,
|
return Undef, err
|
||||||
multicodec.Secp256k1Pub: crypto.UnmarshalSecp256k1PublicKey,
|
|
||||||
multicodec.Es256: crypto.UnmarshalECDSAPublicKey,
|
|
||||||
}[multicodec.Code(d.code)]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unsupported multicodec: %d", d.code)
|
|
||||||
}
|
}
|
||||||
|
if baseCodec != mbase.Base58BTC {
|
||||||
return unmarshaler(d.Bytes()[varint.UvarintSize(d.code):])
|
return Undef, fmt.Errorf("not Base58BTC encoded")
|
||||||
}
|
|
||||||
|
|
||||||
// String formats the decentralized identity document (DID) as a string.
|
|
||||||
func (d DID) String() string {
|
|
||||||
if d.key {
|
|
||||||
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str))
|
|
||||||
return "did:key:" + key
|
|
||||||
}
|
}
|
||||||
return "did:" + d.str[MethodOffset:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func Decode(bytes []byte) (DID, error) {
|
|
||||||
code, _, err := varint.FromUvarint(bytes)
|
code, _, err := varint.FromUvarint(bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Undef, err
|
return Undef, err
|
||||||
}
|
}
|
||||||
if code == Ed25519 || code == RSA {
|
switch multicodec.Code(code) {
|
||||||
return DID{str: string(bytes), code: code, key: true}, nil
|
case Ed25519, P256, Secp256k1, RSA:
|
||||||
} else if code == DIDCore {
|
return DID{bytes: string(bytes), code: multicodec.Code(code)}, nil
|
||||||
return DID{str: string(bytes)}, nil
|
default:
|
||||||
|
return Undef, fmt.Errorf("unsupported did:key multicodec: 0x%x", code)
|
||||||
}
|
}
|
||||||
return Undef, fmt.Errorf("unsupported DID encoding: 0x%x", code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(str string) (DID, error) {
|
|
||||||
if !strings.HasPrefix(str, Prefix) {
|
|
||||||
return Undef, fmt.Errorf("must start with 'did:'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(str, KeyPrefix) {
|
|
||||||
code, bytes, err := mbase.Decode(str[len(KeyPrefix):])
|
|
||||||
if err != nil {
|
|
||||||
return Undef, err
|
|
||||||
}
|
|
||||||
if code != mbase.Base58BTC {
|
|
||||||
return Undef, fmt.Errorf("not Base58BTC encoded")
|
|
||||||
}
|
|
||||||
return Decode(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, MethodOffset)
|
|
||||||
varint.PutUvarint(buf, DIDCore)
|
|
||||||
suffix, _ := strings.CutPrefix(str, Prefix)
|
|
||||||
buf = append(buf, suffix...)
|
|
||||||
return DID{str: string(buf), code: DIDCore}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustParse is like Parse but panics instead of returning an error.
|
||||||
func MustParse(str string) DID {
|
func MustParse(str string) DID {
|
||||||
did, err := Parse(str)
|
did, err := Parse(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,3 +75,66 @@ func MustParse(str string) DID {
|
|||||||
}
|
}
|
||||||
return did
|
return did
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Defined tells if the DID is defined, not equal to Undef.
|
||||||
|
func (d DID) Defined() bool {
|
||||||
|
return d.code != 0 || len(d.bytes) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// PubKey returns the public key encapsulated by the did:key.
|
||||||
|
func (d DID) PubKey() (crypto.PubKey, error) {
|
||||||
|
unmarshaler, ok := map[multicodec.Code]crypto.PubKeyUnmarshaller{
|
||||||
|
X25519: crypto.UnmarshalEd25519PublicKey,
|
||||||
|
Ed25519: crypto.UnmarshalEd25519PublicKey,
|
||||||
|
P256: ecdsaPubKeyUnmarshaler(elliptic.P256()),
|
||||||
|
P384: ecdsaPubKeyUnmarshaler(elliptic.P384()),
|
||||||
|
P521: ecdsaPubKeyUnmarshaler(elliptic.P521()),
|
||||||
|
Secp256k1: crypto.UnmarshalSecp256k1PublicKey,
|
||||||
|
RSA: rsaPubKeyUnmarshaller,
|
||||||
|
}[d.code]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unsupported multicodec: %d", d.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
codeSize := varint.UvarintSize(uint64(d.code))
|
||||||
|
return unmarshaler([]byte(d.bytes)[codeSize:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// String formats the decentralized identity document (DID) as a string.
|
||||||
|
func (d DID) String() string {
|
||||||
|
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes))
|
||||||
|
return "did:key:" + key
|
||||||
|
}
|
||||||
|
|
||||||
|
func ecdsaPubKeyUnmarshaler(curve elliptic.Curve) crypto.PubKeyUnmarshaller {
|
||||||
|
return func(data []byte) (crypto.PubKey, error) {
|
||||||
|
x, y := elliptic.UnmarshalCompressed(curve, data)
|
||||||
|
|
||||||
|
ecdsaPublicKey := &ecdsa.PublicKey{
|
||||||
|
Curve: curve,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
}
|
||||||
|
|
||||||
|
pkix, err := x509.MarshalPKIXPublicKey(ecdsaPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.UnmarshalECDSAPublicKey(pkix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rsaPubKeyUnmarshaller(data []byte) (crypto.PubKey, error) {
|
||||||
|
rsaPublicKey, err := x509.ParsePKCS1PublicKey(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkix, err := x509.MarshalPKIXPublicKey(rsaPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.UnmarshalRsaPublicKey(pkix)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,12 +9,8 @@ import (
|
|||||||
func TestParseDIDKey(t *testing.T) {
|
func TestParseDIDKey(t *testing.T) {
|
||||||
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
||||||
d, err := Parse(str)
|
d, err := Parse(str)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("%v", err)
|
require.Equal(t, str, d.String())
|
||||||
}
|
|
||||||
if d.String() != str {
|
|
||||||
t.Fatalf("expected %v to equal %v", d.String(), str)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMustParseDIDKey(t *testing.T) {
|
func TestMustParseDIDKey(t *testing.T) {
|
||||||
@@ -29,65 +25,17 @@ func TestMustParseDIDKey(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecodeDIDKey(t *testing.T) {
|
|
||||||
str := "did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z"
|
|
||||||
d0, err := Parse(str)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
d1, err := Decode(d0.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
if d1.String() != str {
|
|
||||||
t.Fatalf("expected %v to equal %v", d1.String(), str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDIDWeb(t *testing.T) {
|
|
||||||
str := "did:web:up.web3.storage"
|
|
||||||
d, err := Parse(str)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
if d.String() != str {
|
|
||||||
t.Fatalf("expected %v to equal %v", d.String(), str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecodeDIDWeb(t *testing.T) {
|
|
||||||
str := "did:web:up.web3.storage"
|
|
||||||
d0, err := Parse(str)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
d1, err := Decode(d0.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
if d1.String() != str {
|
|
||||||
t.Fatalf("expected %v to equal %v", d1.String(), str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEquivalence(t *testing.T) {
|
func TestEquivalence(t *testing.T) {
|
||||||
u0 := DID{}
|
undef0 := DID{}
|
||||||
u1 := Undef
|
undef1 := Undef
|
||||||
if u0 != u1 {
|
|
||||||
t.Fatalf("undef DID not equivalent")
|
|
||||||
}
|
|
||||||
|
|
||||||
d0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
did0, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("%v", err)
|
did1, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
|
||||||
d1, err := Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
|
require.True(t, undef0 == undef1)
|
||||||
if err != nil {
|
require.False(t, undef0 == did0)
|
||||||
t.Fatalf("%v", err)
|
require.True(t, did0 == did1)
|
||||||
}
|
require.False(t, undef1 == did1)
|
||||||
|
|
||||||
if d0 != d1 {
|
|
||||||
t.Fatalf("two equivalent DID not equivalent")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
127
did/didtest/crypto.go
Normal file
127
did/didtest/crypto.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
82
did/key_spec_test.go
Normal file
82
did/key_spec_test.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
231
did/testvectors/bls12381.json
Normal file
231
did/testvectors/bls12381.json
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
293
did/testvectors/ed25519-x25519.json
Normal file
293
did/testvectors/ed25519-x25519.json
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
371
did/testvectors/nist-curves.json
Normal file
371
did/testvectors/nist-curves.json
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
did/testvectors/rsa.json
Normal file
106
did/testvectors/rsa.json
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
257
did/testvectors/secp256k1.json
Normal file
257
did/testvectors/secp256k1.json
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
163
did/testvectors/vectors.go
Normal file
163
did/testvectors/vectors.go
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
//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)
|
||||||
|
}
|
||||||
80
did/testvectors/x25519.json
Normal file
80
did/testvectors/x25519.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"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,13 +1,15 @@
|
|||||||
module github.com/ucan-wg/go-ucan
|
module github.com/ucan-wg/go-ucan
|
||||||
|
|
||||||
go 1.22
|
go 1.23
|
||||||
|
|
||||||
toolchain go1.22.4
|
|
||||||
|
|
||||||
require (
|
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/ipfs/go-cid v0.4.1
|
||||||
github.com/ipld/go-ipld-prime v0.21.0
|
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/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-multibase v0.2.0
|
||||||
github.com/multiformats/go-multicodec v0.9.0
|
github.com/multiformats/go-multicodec v0.9.0
|
||||||
github.com/multiformats/go-multihash v0.2.3
|
github.com/multiformats/go-multihash v0.2.3
|
||||||
@@ -18,19 +20,25 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // 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/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-base32 v0.1.0 // indirect
|
||||||
github.com/multiformats/go-base36 v0.2.0 // indirect
|
github.com/multiformats/go-base36 v0.2.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/polydawn/refmt v0.89.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
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
golang.org/x/crypto v0.25.0 // indirect
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // 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
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
26
go.sum
26
go.sum
@@ -1,5 +1,8 @@
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||||
@@ -9,6 +12,8 @@ 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 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
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/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
@@ -25,6 +30,18 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
||||||
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
|
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
|
||||||
github.com/libp2p/go-libp2p v0.36.3 h1:NHz30+G7D8Y8YmznrVZZla0ofVANrvBl2c+oARfMeDQ=
|
github.com/libp2p/go-libp2p v0.36.3 h1:NHz30+G7D8Y8YmznrVZZla0ofVANrvBl2c+oARfMeDQ=
|
||||||
@@ -54,6 +71,8 @@ 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 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
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/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/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 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||||
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
@@ -61,6 +80,9 @@ github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hg
|
|||||||
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
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 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
@@ -80,11 +102,13 @@ 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=
|
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 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
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 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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
|
|||||||
137
pkg/args/args.go
Normal file
137
pkg/args/args.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
179
pkg/args/args_test.go
Normal file
179
pkg/args/args_test.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
23
pkg/args/readonly.go
Normal file
23
pkg/args/readonly.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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,14 +16,12 @@ var _ fmt.Stringer = (*Command)(nil)
|
|||||||
// by one or more slash-separated Segments of lowercase characters.
|
// by one or more slash-separated Segments of lowercase characters.
|
||||||
//
|
//
|
||||||
// [Command]: https://github.com/ucan-wg/spec#command
|
// [Command]: https://github.com/ucan-wg/spec#command
|
||||||
type Command struct {
|
type Command string
|
||||||
segments []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a validated command from the provided list of segment strings.
|
// New creates a validated command from the provided list of segment strings.
|
||||||
// An error is returned if an invalid Command would be formed
|
// An error is returned if an invalid Command would be formed
|
||||||
func New(segments ...string) Command {
|
func New(segments ...string) Command {
|
||||||
return Command{segments: segments}
|
return Top().Join(segments...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse verifies that the provided string contains the required
|
// Parse verifies that the provided string contains the required
|
||||||
@@ -33,20 +31,20 @@ func New(segments ...string) Command {
|
|||||||
// [segment structure]: https://github.com/ucan-wg/spec#segment-structure
|
// [segment structure]: https://github.com/ucan-wg/spec#segment-structure
|
||||||
func Parse(s string) (Command, error) {
|
func Parse(s string) (Command, error) {
|
||||||
if !strings.HasPrefix(s, "/") {
|
if !strings.HasPrefix(s, "/") {
|
||||||
return Command{}, ErrRequiresLeadingSlash
|
return "", ErrRequiresLeadingSlash
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s) > 1 && strings.HasSuffix(s, "/") {
|
if len(s) > 1 && strings.HasSuffix(s, "/") {
|
||||||
return Command{}, ErrDisallowsTrailingSlash
|
return "", ErrDisallowsTrailingSlash
|
||||||
}
|
}
|
||||||
|
|
||||||
if s != strings.ToLower(s) {
|
if s != strings.ToLower(s) {
|
||||||
return Command{}, ErrRequiresLowercase
|
return "", ErrRequiresLowercase
|
||||||
}
|
}
|
||||||
|
|
||||||
// The leading slash will result in the first element from strings.Split
|
// 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.
|
// being an empty string which is removed as strings.Join will ignore it.
|
||||||
return Command{strings.Split(s, "/")[1:]}, nil
|
return Command(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustParse is the same as Parse, but panic() if the parsing fail.
|
// MustParse is the same as Parse, but panic() if the parsing fail.
|
||||||
@@ -58,14 +56,14 @@ func MustParse(s string) Command {
|
|||||||
return c
|
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
|
// This function returns a Command that is a wildcard and therefore represents the
|
||||||
// most powerful ability. As such, it should be handled with care and used sparingly.
|
// most powerful ability. As such, it should be handled with care and used sparingly.
|
||||||
//
|
//
|
||||||
// [Top]: https://github.com/ucan-wg/spec#-aka-top
|
// [Top]: https://github.com/ucan-wg/spec#-aka-top
|
||||||
func Top() Command {
|
func Top() Command {
|
||||||
return New()
|
return Command(separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValid returns true if the provided string is a valid UCAN command.
|
// IsValid returns true if the provided string is a valid UCAN command.
|
||||||
@@ -77,17 +75,61 @@ func IsValid(s string) bool {
|
|||||||
// Join appends segments to the end of this command using the required
|
// Join appends segments to the end of this command using the required
|
||||||
// segment separator.
|
// segment separator.
|
||||||
func (c Command) Join(segments ...string) Command {
|
func (c Command) Join(segments ...string) Command {
|
||||||
return Command{append(c.segments, segments...)}
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Segments returns the ordered segments that comprise the Command as a
|
// Segments returns the ordered segments that comprise the Command as a
|
||||||
// slice of strings.
|
// slice of strings.
|
||||||
func (c Command) Segments() []string {
|
func (c Command) Segments() []string {
|
||||||
return c.segments
|
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
|
||||||
|
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the composed representation the command. This is also
|
// String returns the composed representation the command. This is also
|
||||||
// the required wire representation (before IPLD encoding occurs.)
|
// the required wire representation (before IPLD encoding occurs.)
|
||||||
func (c Command) String() string {
|
func (c Command) String() string {
|
||||||
return "/" + strings.Join(c.segments, "/")
|
return string(c)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,73 +13,81 @@ func TestTop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIsValidCommand(t *testing.T) {
|
func TestIsValidCommand(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("succeeds when", func(t *testing.T) {
|
t.Run("succeeds when", func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, testcase := range validTestcases(t) {
|
for _, testcase := range validTestcases(t) {
|
||||||
testcase := testcase
|
|
||||||
|
|
||||||
t.Run(testcase.name, func(t *testing.T) {
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
require.True(t, command.IsValid(testcase.inp))
|
require.True(t, command.IsValid(testcase.inp))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fails when", func(t *testing.T) {
|
t.Run("fails when", func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, testcase := range invalidTestcases(t) {
|
for _, testcase := range invalidTestcases(t) {
|
||||||
testcase := testcase
|
|
||||||
|
|
||||||
t.Run(testcase.name, func(t *testing.T) {
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
require.False(t, command.IsValid(testcase.inp))
|
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) {
|
func TestParseCommand(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("succeeds when", func(t *testing.T) {
|
t.Run("succeeds when", func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, testcase := range validTestcases(t) {
|
for _, testcase := range validTestcases(t) {
|
||||||
testcase := testcase
|
|
||||||
|
|
||||||
t.Run(testcase.name, func(t *testing.T) {
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cmd, err := command.Parse("/elem0/elem1/elem2")
|
cmd, err := command.Parse("/elem0/elem1/elem2")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, cmd)
|
require.NotEmpty(t, cmd)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fails when", func(t *testing.T) {
|
t.Run("fails when", func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, testcase := range invalidTestcases(t) {
|
for _, testcase := range invalidTestcases(t) {
|
||||||
testcase := testcase
|
|
||||||
|
|
||||||
t.Run(testcase.name, func(t *testing.T) {
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cmd, err := command.Parse(testcase.inp)
|
cmd, err := command.Parse(testcase.inp)
|
||||||
require.ErrorIs(t, err, testcase.err)
|
require.ErrorIs(t, err, testcase.err)
|
||||||
require.Equal(t, command.Command{}, cmd)
|
require.Zero(t, cmd)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEquality(t *testing.T) {
|
||||||
|
require.True(t, command.MustParse("/foo/bar/baz") == command.MustParse("/foo/bar/baz"))
|
||||||
|
require.False(t, command.MustParse("/foo/bar/baz") == command.MustParse("/foo/bar/bazz"))
|
||||||
|
require.False(t, command.MustParse("/foo/bar") == command.MustParse("/foo/bar/baz"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJoin(t *testing.T) {
|
||||||
|
require.Equal(t, "/foo", command.Top().Join("foo").String())
|
||||||
|
require.Equal(t, "/foo/bar", command.Top().Join("foo/bar").String())
|
||||||
|
require.Equal(t, "/foo/bar", command.Top().Join("foo", "bar").String())
|
||||||
|
require.Equal(t, "/faz/boz/foo/bar", command.MustParse("/faz/boz").Join("foo/bar").String())
|
||||||
|
require.Equal(t, "/faz/boz/foo/bar", command.MustParse("/faz/boz").Join("foo", "bar").String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSegments(t *testing.T) {
|
||||||
|
require.Empty(t, command.Top().Segments())
|
||||||
|
require.Equal(t, []string{"foo", "bar", "baz"}, command.MustParse("/foo/bar/baz").Segments())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCovers(t *testing.T) {
|
||||||
|
require.True(t, command.MustParse("/foo/bar/baz").Covers(command.MustParse("/foo/bar/baz")))
|
||||||
|
require.True(t, command.MustParse("/foo/bar").Covers(command.MustParse("/foo/bar/baz")))
|
||||||
|
require.False(t, command.MustParse("/foo/bar/baz").Covers(command.MustParse("/foo/bar")))
|
||||||
|
require.True(t, command.MustParse("/").Covers(command.MustParse("/foo")))
|
||||||
|
require.True(t, command.MustParse("/").Covers(command.MustParse("/foo/bar/baz")))
|
||||||
|
require.False(t, command.MustParse("/foo").Covers(command.MustParse("/foo00")))
|
||||||
|
require.False(t, command.MustParse("/foo/bar").Covers(command.MustParse("/foo/bar00")))
|
||||||
|
}
|
||||||
|
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
name string
|
name string
|
||||||
inp string
|
inp string
|
||||||
|
|||||||
86
pkg/container/Readme.md
Normal file
86
pkg/container/Readme.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# 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.**
|
||||||
263
pkg/container/car.go
Normal file
263
pkg/container/car.go
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
85
pkg/container/car_test.go
Normal file
85
pkg/container/car_test.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
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())
|
||||||
|
})
|
||||||
|
}
|
||||||
BIN
pkg/container/img/alloc_byte.png
Normal file
BIN
pkg/container/img/alloc_byte.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
pkg/container/img/alloc_count.png
Normal file
BIN
pkg/container/img/alloc_count.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
pkg/container/img/cpu.png
Normal file
BIN
pkg/container/img/cpu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
pkg/container/img/overhead_bytes.png
Normal file
BIN
pkg/container/img/overhead_bytes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
pkg/container/img/overhead_percent.png
Normal file
BIN
pkg/container/img/overhead_percent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
161
pkg/container/reader.go
Normal file
161
pkg/container/reader.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
205
pkg/container/serial_test.go
Normal file
205
pkg/container/serial_test.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
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
Normal file
BIN
pkg/container/testdata/sample-v1.car
vendored
Normal file
Binary file not shown.
65
pkg/container/writer.go
Normal file
65
pkg/container/writer.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
132
pkg/meta/internal/crypto/aes.go
Normal file
132
pkg/meta/internal/crypto/aes.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
124
pkg/meta/internal/crypto/aes_test.go
Normal file
124
pkg/meta/internal/crypto/aes_test.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
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,21 +3,26 @@ package meta
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"strings"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/printer"
|
||||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/meta/internal/crypto"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnsupported = errors.New("failure adding unsupported type to meta")
|
var ErrNotFound = errors.New("key not found in meta")
|
||||||
|
|
||||||
var ErrNotFound = errors.New("key-value not found in meta")
|
var ErrNotEncryptable = errors.New("value of this type cannot be encrypted")
|
||||||
|
|
||||||
// Meta is a container for meta key-value pairs in a UCAN token.
|
// 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,
|
// This also serves as a way to construct the underlying IPLD data with minimum allocations
|
||||||
// while hiding the IPLD complexity from the caller.
|
// and transformations, while hiding the IPLD complexity from the caller.
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
|
// This type must be compatible with the IPLD type represented by the IPLD
|
||||||
|
// schema { String : Any }.
|
||||||
|
|
||||||
Keys []string
|
Keys []string
|
||||||
Values map[string]ipld.Node
|
Values map[string]ipld.Node
|
||||||
}
|
}
|
||||||
@@ -49,6 +54,21 @@ func (m *Meta) GetString(key string) (string, error) {
|
|||||||
return v.AsString()
|
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.
|
// GetInt64 retrieves a value as an int64.
|
||||||
// Returns ErrNotFound if the given key is missing.
|
// Returns ErrNotFound if the given key is missing.
|
||||||
// Returns datamodel.ErrWrongKind if the value has the wrong type.
|
// Returns datamodel.ErrWrongKind if the value has the wrong type.
|
||||||
@@ -82,6 +102,21 @@ func (m *Meta) GetBytes(key string) ([]byte, error) {
|
|||||||
return v.AsBytes()
|
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.
|
// GetNode retrieves a value as a raw IPLD node.
|
||||||
// Returns ErrNotFound if the given key is missing.
|
// Returns ErrNotFound if the given key is missing.
|
||||||
// Returns datamodel.ErrWrongKind if the value has the wrong type.
|
// Returns datamodel.ErrWrongKind if the value has the wrong type.
|
||||||
@@ -94,43 +129,85 @@ func (m *Meta) GetNode(key string) (ipld.Node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a key/value pair in the meta set.
|
// Add adds a key/value pair in the meta set.
|
||||||
// Accepted types for the value are: bool, string, int, int32, int64, []byte,
|
// Accepted types for val are any CBOR compatible type, or directly IPLD values.
|
||||||
// and ipld.Node.
|
|
||||||
func (m *Meta) Add(key string, val any) error {
|
func (m *Meta) Add(key string, val any) error {
|
||||||
switch val := val.(type) {
|
if _, ok := m.Values[key]; ok {
|
||||||
case bool:
|
return fmt.Errorf("duplicate key %q", key)
|
||||||
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.Keys = append(m.Keys, key)
|
||||||
|
m.Values[key] = node
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fqtn(val any) string {
|
// AddEncrypted adds a key/value pair in the meta set.
|
||||||
var name string
|
// 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
|
||||||
|
|
||||||
t := reflect.TypeOf(val)
|
switch val := val.(type) {
|
||||||
for t.Kind() == reflect.Pointer {
|
case string:
|
||||||
name += "*"
|
encrypted, err = crypto.EncryptWithAESKey([]byte(val), encryptionKey)
|
||||||
t = t.Elem()
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case []byte:
|
||||||
|
encrypted, err = crypto.EncryptWithAESKey(val, encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ErrNotEncryptable
|
||||||
}
|
}
|
||||||
|
|
||||||
return name + t.PkgPath() + "." + t.Name()
|
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}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package meta_test
|
package meta_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||||
"gotest.tools/v3/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMeta_Add(t *testing.T) {
|
func TestMeta_Add(t *testing.T) {
|
||||||
@@ -13,11 +14,64 @@ func TestMeta_Add(t *testing.T) {
|
|||||||
|
|
||||||
type Unsupported struct{}
|
type Unsupported struct{}
|
||||||
|
|
||||||
t.Run("error if not primative or Node", func(t *testing.T) {
|
t.Run("error if not primitive or Node", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
err := (&meta.Meta{}).Add("invalid", &Unsupported{})
|
err := (&meta.Meta{}).Add("invalid", &Unsupported{})
|
||||||
require.ErrorIs(t, err, meta.ErrUnsupported)
|
require.Error(t, err)
|
||||||
assert.ErrorContains(t, err, "*github.com/ucan-wg/go-ucan/pkg/meta_test.Unsupported")
|
})
|
||||||
|
|
||||||
|
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")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
50
pkg/meta/readonly.go
Normal file
50
pkg/meta/readonly.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return Not(statement), nil
|
return negation{statement: statement}, nil
|
||||||
|
|
||||||
case KindAnd, KindOr:
|
case KindAnd, KindOr:
|
||||||
arg2, _ := node.LookupByIndex(1)
|
arg2, _ := node.LookupByIndex(1)
|
||||||
@@ -93,11 +93,11 @@ func statementFromIPLD(path string, node datamodel.Node) (Statement, error) {
|
|||||||
if pattern.Kind() != datamodel.Kind_String {
|
if pattern.Kind() != datamodel.Kind_String {
|
||||||
return nil, ErrNotAString(combinePath(path, op, 2))
|
return nil, ErrNotAString(combinePath(path, op, 2))
|
||||||
}
|
}
|
||||||
res, err := Like(sel, must.String(pattern))
|
g, err := parseGlob(must.String(pattern))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrInvalidPattern(combinePath(path, op, 2), err)
|
return nil, ErrInvalidPattern(combinePath(path, op, 2), err)
|
||||||
}
|
}
|
||||||
return res, nil
|
return wildcard{selector: sel, pattern: g}, nil
|
||||||
|
|
||||||
case KindAll, KindAny:
|
case KindAll, KindAny:
|
||||||
sel, err := arg2AsSelector(op)
|
sel, err := arg2AsSelector(op)
|
||||||
|
|||||||
@@ -1,48 +1,28 @@
|
|||||||
|
// Package literal holds a collection of functions to create IPLD types to use in policies, selector and args.
|
||||||
package literal
|
package literal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||||
|
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
||||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Node(n ipld.Node) ipld.Node {
|
var Bool = basicnode.NewBool
|
||||||
return n
|
var Int = basicnode.NewInt
|
||||||
}
|
var Float = basicnode.NewFloat
|
||||||
|
var String = basicnode.NewString
|
||||||
|
var Bytes = basicnode.NewBytes
|
||||||
|
var Link = basicnode.NewLink
|
||||||
|
|
||||||
func Link(cid ipld.Link) ipld.Node {
|
func LinkCid(cid cid.Cid) ipld.Node {
|
||||||
nb := basicnode.Prototype.Link.NewBuilder()
|
return Link(cidlink.Link{Cid: cid})
|
||||||
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 {
|
func Null() ipld.Node {
|
||||||
@@ -50,3 +30,158 @@ func Null() ipld.Node {
|
|||||||
nb.AssignNull()
|
nb.AssignNull()
|
||||||
return nb.Build()
|
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))
|
||||||
|
}
|
||||||
|
|||||||
262
pkg/policy/literal/literal_test.go
Normal file
262
pkg/policy/literal/literal_test.go
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
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,152 +7,246 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
"github.com/ipld/go-ipld-prime/must"
|
"github.com/ipld/go-ipld-prime/must"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Match determines if the IPLD node matches the policy document.
|
// Match determines if the IPLD node satisfies the policy.
|
||||||
func Match(policy Policy, node ipld.Node) bool {
|
// The first Statement failing to match is returned as well.
|
||||||
for _, stmt := range policy {
|
func (p Policy) Match(node datamodel.Node) (bool, Statement) {
|
||||||
ok := matchStatement(stmt, node)
|
for _, stmt := range p {
|
||||||
if !ok {
|
res, leaf := matchStatement(stmt, node)
|
||||||
return false
|
switch res {
|
||||||
|
case matchResultNoData, matchResultFalse:
|
||||||
|
return false, leaf
|
||||||
|
case matchResultOptionalNoData, matchResultTrue:
|
||||||
|
// continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchStatement(statement Statement, node ipld.Node) bool {
|
// PartialMatch returns false IIF one non-optional Statement has the corresponding data and doesn't match.
|
||||||
switch statement.Kind() {
|
// If the data is missing or the non-optional Statement is matching, true is returned.
|
||||||
case KindEqual:
|
//
|
||||||
if s, ok := statement.(equality); ok {
|
// This allows performing the policy checking in multiple steps, and find immediately if a Statement already failed.
|
||||||
one, many, err := selector.Select(s.selector, node)
|
// A final call to Match is necessary to make sure that the policy is fully matched, with no missing data
|
||||||
if err != nil {
|
// (apart from optional values).
|
||||||
return false
|
//
|
||||||
}
|
// The first Statement failing to match is returned as well.
|
||||||
if one != nil {
|
func (p Policy) PartialMatch(node datamodel.Node) (bool, Statement) {
|
||||||
return datamodel.DeepEqual(s.value, one)
|
for _, stmt := range p {
|
||||||
}
|
res, leaf := matchStatement(stmt, node)
|
||||||
if many != nil {
|
switch res {
|
||||||
for _, n := range many {
|
case matchResultFalse:
|
||||||
if eq := datamodel.DeepEqual(s.value, n); eq {
|
return false, leaf
|
||||||
return true
|
case matchResultNoData, matchResultOptionalNoData, matchResultTrue:
|
||||||
}
|
// continue
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case KindGreaterThan:
|
|
||||||
if s, ok := statement.(equality); ok {
|
|
||||||
one, _, err := selector.Select(s.selector, node)
|
|
||||||
if err != nil || one == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isOrdered(s.value, one, gt)
|
|
||||||
}
|
|
||||||
case KindGreaterThanOrEqual:
|
|
||||||
if s, ok := statement.(equality); ok {
|
|
||||||
one, _, err := selector.Select(s.selector, node)
|
|
||||||
if err != nil || one == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isOrdered(s.value, one, gte)
|
|
||||||
}
|
|
||||||
case KindLessThan:
|
|
||||||
if s, ok := statement.(equality); ok {
|
|
||||||
one, _, err := selector.Select(s.selector, node)
|
|
||||||
if err != nil || one == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isOrdered(s.value, one, lt)
|
|
||||||
}
|
|
||||||
case KindLessThanOrEqual:
|
|
||||||
if s, ok := statement.(equality); ok {
|
|
||||||
one, _, err := selector.Select(s.selector, node)
|
|
||||||
if err != nil || one == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isOrdered(s.value, one, lte)
|
|
||||||
}
|
|
||||||
case KindNot:
|
|
||||||
if s, ok := statement.(negation); ok {
|
|
||||||
return !matchStatement(s.statement, node)
|
|
||||||
}
|
|
||||||
case KindAnd:
|
|
||||||
if s, ok := statement.(connective); ok {
|
|
||||||
for _, cs := range s.statements {
|
|
||||||
r := matchStatement(cs, node)
|
|
||||||
if !r {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case KindOr:
|
|
||||||
if s, ok := statement.(connective); ok {
|
|
||||||
if len(s.statements) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, cs := range s.statements {
|
|
||||||
r := matchStatement(cs, node)
|
|
||||||
if r {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case KindLike:
|
|
||||||
if s, ok := statement.(wildcard); ok {
|
|
||||||
one, _, err := selector.Select(s.selector, node)
|
|
||||||
if err != nil || one == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
v, err := one.AsString()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s.pattern.Match(v)
|
|
||||||
}
|
|
||||||
case KindAll:
|
|
||||||
if s, ok := statement.(quantifier); ok {
|
|
||||||
_, many, err := selector.Select(s.selector, node)
|
|
||||||
if err != nil || many == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, n := range many {
|
|
||||||
ok := matchStatement(s.statement, n)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case KindAny:
|
|
||||||
if s, ok := statement.(quantifier); ok {
|
|
||||||
one, many, err := selector.Select(s.selector, node)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if one != nil {
|
|
||||||
ok := matchStatement(s.statement, one)
|
|
||||||
if ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if many != nil {
|
|
||||||
for _, n := range many {
|
|
||||||
ok := matchStatement(s.statement, n)
|
|
||||||
if ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("unimplemented statement kind: %s", statement.Kind()))
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type matchResult int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
matchResultTrue matchResult = iota // statement has data and resolve to true
|
||||||
|
matchResultFalse // statement has data and resolve to false
|
||||||
|
matchResultNoData // statement has no data
|
||||||
|
matchResultOptionalNoData // statement has no data and is optional
|
||||||
|
)
|
||||||
|
|
||||||
|
// matchStatement evaluate the policy against the given ipld.Node and returns:
|
||||||
|
// - matchResultTrue: if the selector matched and the statement evaluated to true.
|
||||||
|
// - matchResultFalse: if the selector matched and the statement evaluated to false.
|
||||||
|
// - matchResultNoData: if the selector didn't match the expected data.
|
||||||
|
// For matchResultTrue and matchResultNoData, the leaf-most (innermost) statement failing to be true is returned,
|
||||||
|
// as well as the corresponding root-most encompassing statement.
|
||||||
|
func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Statement) {
|
||||||
|
var boolToRes = func(v bool) (matchResult, Statement) {
|
||||||
|
if v {
|
||||||
|
return matchResultTrue, nil
|
||||||
|
} else {
|
||||||
|
return matchResultFalse, cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cur.Kind() {
|
||||||
|
case KindEqual:
|
||||||
|
if s, ok := cur.(equality); ok {
|
||||||
|
res, err := s.selector.Select(node)
|
||||||
|
if err != nil {
|
||||||
|
return matchResultNoData, cur
|
||||||
|
}
|
||||||
|
if res == nil { // optional selector didn't match
|
||||||
|
return matchResultOptionalNoData, nil
|
||||||
|
}
|
||||||
|
return boolToRes(datamodel.DeepEqual(s.value, res))
|
||||||
|
}
|
||||||
|
case KindGreaterThan:
|
||||||
|
if s, ok := cur.(equality); ok {
|
||||||
|
res, err := s.selector.Select(node)
|
||||||
|
if err != nil {
|
||||||
|
return matchResultNoData, cur
|
||||||
|
}
|
||||||
|
if res == nil { // optional selector didn't match
|
||||||
|
return matchResultOptionalNoData, nil
|
||||||
|
}
|
||||||
|
return boolToRes(isOrdered(s.value, res, gt))
|
||||||
|
}
|
||||||
|
case KindGreaterThanOrEqual:
|
||||||
|
if s, ok := cur.(equality); ok {
|
||||||
|
res, err := s.selector.Select(node)
|
||||||
|
if err != nil {
|
||||||
|
return matchResultNoData, cur
|
||||||
|
}
|
||||||
|
if res == nil { // optional selector didn't match
|
||||||
|
return matchResultOptionalNoData, nil
|
||||||
|
}
|
||||||
|
return boolToRes(isOrdered(s.value, res, gte))
|
||||||
|
}
|
||||||
|
case KindLessThan:
|
||||||
|
if s, ok := cur.(equality); ok {
|
||||||
|
res, err := s.selector.Select(node)
|
||||||
|
if err != nil {
|
||||||
|
return matchResultNoData, cur
|
||||||
|
}
|
||||||
|
if res == nil { // optional selector didn't match
|
||||||
|
return matchResultOptionalNoData, nil
|
||||||
|
}
|
||||||
|
return boolToRes(isOrdered(s.value, res, lt))
|
||||||
|
}
|
||||||
|
case KindLessThanOrEqual:
|
||||||
|
if s, ok := cur.(equality); ok {
|
||||||
|
res, err := s.selector.Select(node)
|
||||||
|
if err != nil {
|
||||||
|
return matchResultNoData, cur
|
||||||
|
}
|
||||||
|
if res == nil { // optional selector didn't match
|
||||||
|
return matchResultOptionalNoData, nil
|
||||||
|
}
|
||||||
|
return boolToRes(isOrdered(s.value, res, lte))
|
||||||
|
}
|
||||||
|
case KindNot:
|
||||||
|
if s, ok := cur.(negation); ok {
|
||||||
|
res, leaf := matchStatement(s.statement, node)
|
||||||
|
switch res {
|
||||||
|
case matchResultNoData, matchResultOptionalNoData:
|
||||||
|
return res, leaf
|
||||||
|
case matchResultTrue:
|
||||||
|
return matchResultFalse, cur
|
||||||
|
case matchResultFalse:
|
||||||
|
return matchResultTrue, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case KindAnd:
|
||||||
|
if s, ok := cur.(connective); ok {
|
||||||
|
for _, cs := range s.statements {
|
||||||
|
res, leaf := matchStatement(cs, node)
|
||||||
|
switch res {
|
||||||
|
case matchResultNoData, matchResultOptionalNoData:
|
||||||
|
return res, leaf
|
||||||
|
case matchResultTrue:
|
||||||
|
// continue
|
||||||
|
case matchResultFalse:
|
||||||
|
return matchResultFalse, leaf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchResultTrue, nil
|
||||||
|
}
|
||||||
|
case KindOr:
|
||||||
|
if s, ok := cur.(connective); ok {
|
||||||
|
if len(s.statements) == 0 {
|
||||||
|
return matchResultTrue, nil
|
||||||
|
}
|
||||||
|
for _, cs := range s.statements {
|
||||||
|
res, leaf := matchStatement(cs, node)
|
||||||
|
switch res {
|
||||||
|
case matchResultNoData, matchResultOptionalNoData:
|
||||||
|
return res, leaf
|
||||||
|
case matchResultTrue:
|
||||||
|
return matchResultTrue, leaf
|
||||||
|
case matchResultFalse:
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchResultFalse, cur
|
||||||
|
}
|
||||||
|
case KindLike:
|
||||||
|
if s, ok := cur.(wildcard); ok {
|
||||||
|
res, err := s.selector.Select(node)
|
||||||
|
if err != nil {
|
||||||
|
return matchResultNoData, cur
|
||||||
|
}
|
||||||
|
if res == nil { // optional selector didn't match
|
||||||
|
return matchResultOptionalNoData, nil
|
||||||
|
}
|
||||||
|
v, err := res.AsString()
|
||||||
|
if err != nil {
|
||||||
|
return matchResultFalse, cur // not a string
|
||||||
|
}
|
||||||
|
return boolToRes(s.pattern.Match(v))
|
||||||
|
}
|
||||||
|
case KindAll:
|
||||||
|
if s, ok := cur.(quantifier); ok {
|
||||||
|
res, err := s.selector.Select(node)
|
||||||
|
if err != nil {
|
||||||
|
return matchResultNoData, cur
|
||||||
|
}
|
||||||
|
if res == nil {
|
||||||
|
return matchResultOptionalNoData, nil
|
||||||
|
}
|
||||||
|
it := res.ListIterator()
|
||||||
|
if it == nil {
|
||||||
|
return matchResultFalse, cur // not a list
|
||||||
|
}
|
||||||
|
for !it.Done() {
|
||||||
|
_, v, err := it.Next()
|
||||||
|
if err != nil {
|
||||||
|
panic("should never happen")
|
||||||
|
}
|
||||||
|
matchRes, leaf := matchStatement(s.statement, v)
|
||||||
|
switch matchRes {
|
||||||
|
case matchResultNoData, matchResultOptionalNoData:
|
||||||
|
return matchRes, leaf
|
||||||
|
case matchResultTrue:
|
||||||
|
// continue
|
||||||
|
case matchResultFalse:
|
||||||
|
return matchResultFalse, leaf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchResultTrue, nil
|
||||||
|
}
|
||||||
|
case KindAny:
|
||||||
|
if s, ok := cur.(quantifier); ok {
|
||||||
|
res, err := s.selector.Select(node)
|
||||||
|
if err != nil {
|
||||||
|
return matchResultNoData, cur
|
||||||
|
}
|
||||||
|
if res == nil {
|
||||||
|
return matchResultOptionalNoData, nil
|
||||||
|
}
|
||||||
|
it := res.ListIterator()
|
||||||
|
if it == nil {
|
||||||
|
return matchResultFalse, cur // not a list
|
||||||
|
}
|
||||||
|
for !it.Done() {
|
||||||
|
_, v, err := it.Next()
|
||||||
|
if err != nil {
|
||||||
|
panic("should never happen")
|
||||||
|
}
|
||||||
|
matchRes, leaf := matchStatement(s.statement, v)
|
||||||
|
switch matchRes {
|
||||||
|
case matchResultNoData, matchResultOptionalNoData:
|
||||||
|
return matchRes, leaf
|
||||||
|
case matchResultTrue:
|
||||||
|
return matchResultTrue, nil
|
||||||
|
case matchResultFalse:
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchResultFalse, cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf("unimplemented statement kind: %s", cur.Kind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) bool) bool {
|
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,9 +3,13 @@ package policy
|
|||||||
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#policy
|
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ipld/go-ipld-prime"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||||
|
|
||||||
|
selpkg "github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -24,13 +28,43 @@ const (
|
|||||||
|
|
||||||
type Policy []Statement
|
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 {
|
type Statement interface {
|
||||||
Kind() string
|
Kind() string
|
||||||
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type equality struct {
|
type equality struct {
|
||||||
kind string
|
kind string
|
||||||
selector selector.Selector
|
selector selpkg.Selector
|
||||||
value ipld.Node
|
value ipld.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,24 +72,47 @@ func (e equality) Kind() string {
|
|||||||
return e.kind
|
return e.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
func Equal(selector selector.Selector, value ipld.Node) Statement {
|
func (e equality) String() string {
|
||||||
return equality{kind: KindEqual, selector: selector, value: value}
|
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 GreaterThan(selector selector.Selector, value ipld.Node) Statement {
|
func Equal(selector string, value ipld.Node) Constructor {
|
||||||
return equality{kind: KindGreaterThan, selector: selector, value: value}
|
return func() (Statement, error) {
|
||||||
|
sel, err := selpkg.Parse(selector)
|
||||||
|
return equality{kind: KindEqual, selector: sel, value: value}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GreaterThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
|
func GreaterThan(selector string, value ipld.Node) Constructor {
|
||||||
return equality{kind: KindGreaterThanOrEqual, selector: selector, value: value}
|
return func() (Statement, error) {
|
||||||
|
sel, err := selpkg.Parse(selector)
|
||||||
|
return equality{kind: KindGreaterThan, selector: sel, value: value}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LessThan(selector selector.Selector, value ipld.Node) Statement {
|
func GreaterThanOrEqual(selector string, value ipld.Node) Constructor {
|
||||||
return equality{kind: KindLessThan, selector: selector, value: value}
|
return func() (Statement, error) {
|
||||||
|
sel, err := selpkg.Parse(selector)
|
||||||
|
return equality{kind: KindGreaterThanOrEqual, selector: sel, value: value}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LessThanOrEqual(selector selector.Selector, value ipld.Node) Statement {
|
func LessThan(selector string, value ipld.Node) Constructor {
|
||||||
return equality{kind: KindLessThanOrEqual, selector: selector, value: value}
|
return func() (Statement, error) {
|
||||||
|
sel, err := selpkg.Parse(selector)
|
||||||
|
return equality{kind: KindLessThan, selector: sel, value: value}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LessThanOrEqual(selector string, value ipld.Node) Constructor {
|
||||||
|
return func() (Statement, error) {
|
||||||
|
sel, err := selpkg.Parse(selector)
|
||||||
|
return equality{kind: KindLessThanOrEqual, selector: sel, value: value}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type negation struct {
|
type negation struct {
|
||||||
@@ -66,8 +123,16 @@ func (n negation) Kind() string {
|
|||||||
return KindNot
|
return KindNot
|
||||||
}
|
}
|
||||||
|
|
||||||
func Not(stmt Statement) Statement {
|
func (n negation) String() string {
|
||||||
return negation{statement: stmt}
|
child := n.statement.String()
|
||||||
|
return fmt.Sprintf(`["%s", "%s"]`, n.Kind(), strings.ReplaceAll(child, "\n", "\n "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Not(cstor Constructor) Constructor {
|
||||||
|
return func() (Statement, error) {
|
||||||
|
stmt, err := cstor()
|
||||||
|
return negation{statement: stmt}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type connective struct {
|
type connective struct {
|
||||||
@@ -79,16 +144,36 @@ func (c connective) Kind() string {
|
|||||||
return c.kind
|
return c.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
func And(stmts ...Statement) Statement {
|
func (c connective) String() string {
|
||||||
return connective{kind: KindAnd, statements: stmts}
|
childs := make([]string, len(c.statements))
|
||||||
|
for i, statement := range c.statements {
|
||||||
|
childs[i] = strings.ReplaceAll(statement.String(), "\n", "\n ")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[\"%s\", [\n %s]]\n", c.kind, strings.Join(childs, ",\n "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Or(stmts ...Statement) Statement {
|
func And(cstors ...Constructor) Constructor {
|
||||||
return connective{kind: KindOr, statements: stmts}
|
return func() (Statement, error) {
|
||||||
|
stmts, err := assemble(cstors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connective{kind: KindAnd, statements: stmts}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Or(cstors ...Constructor) Constructor {
|
||||||
|
return func() (Statement, error) {
|
||||||
|
stmts, err := assemble(cstors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connective{kind: KindOr, statements: stmts}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type wildcard struct {
|
type wildcard struct {
|
||||||
selector selector.Selector
|
selector selpkg.Selector
|
||||||
pattern glob
|
pattern glob
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,18 +181,24 @@ func (n wildcard) Kind() string {
|
|||||||
return KindLike
|
return KindLike
|
||||||
}
|
}
|
||||||
|
|
||||||
func Like(selector selector.Selector, pattern string) (Statement, error) {
|
func (n wildcard) String() string {
|
||||||
g, err := parseGlob(pattern)
|
return fmt.Sprintf(`["%s", "%s", "%s"]`, n.Kind(), n.selector, n.pattern)
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return wildcard{selector: selector, pattern: g}, nil
|
func Like(selector string, pattern string) Constructor {
|
||||||
|
return func() (Statement, error) {
|
||||||
|
g, err := parseGlob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sel, err := selpkg.Parse(selector)
|
||||||
|
return wildcard{selector: sel, pattern: g}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type quantifier struct {
|
type quantifier struct {
|
||||||
kind string
|
kind string
|
||||||
selector selector.Selector
|
selector selpkg.Selector
|
||||||
statement Statement
|
statement Statement
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,10 +206,41 @@ func (n quantifier) Kind() string {
|
|||||||
return n.kind
|
return n.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
func All(selector selector.Selector, statement Statement) Statement {
|
func (n quantifier) String() string {
|
||||||
return quantifier{kind: KindAll, selector: selector, statement: statement}
|
child := n.statement.String()
|
||||||
|
return fmt.Sprintf("[\"%s\", \"%s\",\n %s]", n.Kind(), n.selector, strings.ReplaceAll(child, "\n", "\n "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Any(selector selector.Selector, statement Statement) Statement {
|
func All(selector string, cstor Constructor) Constructor {
|
||||||
return quantifier{kind: KindAny, selector: selector, statement: statement}
|
return func() (Statement, error) {
|
||||||
|
stmt, err := cstor()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sel, err := selpkg.Parse(selector)
|
||||||
|
return quantifier{kind: KindAll, selector: sel, statement: stmt}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Any(selector string, cstor Constructor) Constructor {
|
||||||
|
return func() (Statement, error) {
|
||||||
|
stmt, err := cstor()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sel, err := selpkg.Parse(selector)
|
||||||
|
return quantifier{kind: KindAny, selector: sel, statement: stmt}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assemble(cstors []Constructor) ([]Statement, error) {
|
||||||
|
stmts := make([]Statement, 0, len(cstors))
|
||||||
|
for _, cstor := range cstors {
|
||||||
|
stmt, err := cstor()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stmts = append(stmts, stmt)
|
||||||
|
}
|
||||||
|
return stmts, nil
|
||||||
}
|
}
|
||||||
|
|||||||
91
pkg/policy/policy_test.go
Normal file
91
pkg/policy/policy_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
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,10 +2,18 @@ package selector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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) {
|
func Parse(str string) (Selector, error) {
|
||||||
if len(str) == 0 {
|
if len(str) == 0 {
|
||||||
return nil, newParseError("empty selector", str, 0, "")
|
return nil, newParseError("empty selector", str, 0, "")
|
||||||
@@ -13,6 +21,12 @@ func Parse(str string) (Selector, error) {
|
|||||||
if string(str[0]) != "." {
|
if string(str[0]) != "." {
|
||||||
return nil, newParseError("selector must start with identity segment '.'", str, 0, 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
|
col := 0
|
||||||
var sel Selector
|
var sel Selector
|
||||||
@@ -20,56 +34,70 @@ func Parse(str string) (Selector, error) {
|
|||||||
seg := tok
|
seg := tok
|
||||||
opt := strings.HasSuffix(tok, "?")
|
opt := strings.HasSuffix(tok, "?")
|
||||||
if opt {
|
if opt {
|
||||||
seg = tok[0 : len(tok)-1]
|
seg = strings.TrimRight(tok, "?")
|
||||||
}
|
}
|
||||||
switch seg {
|
switch {
|
||||||
case ".":
|
case seg == ".":
|
||||||
if len(sel) > 0 && sel[len(sel)-1].Identity() {
|
if len(sel) > 0 && sel[len(sel)-1].Identity() {
|
||||||
return nil, newParseError("selector contains unsupported recursive descent segment: '..'", str, col, tok)
|
return nil, newParseError("selector contains unsupported recursive descent segment: '..'", str, col, tok)
|
||||||
}
|
}
|
||||||
sel = append(sel, Identity)
|
sel = append(sel, segment{str: ".", identity: true})
|
||||||
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]
|
|
||||||
|
|
||||||
if indexRegex.MatchString(lookup) { // index
|
case seg == "[]":
|
||||||
idx, err := strconv.Atoi(lookup)
|
sel = append(sel, segment{str: tok, optional: opt, iterator: true})
|
||||||
if err != nil {
|
|
||||||
return nil, newParseError("invalid index", str, col, tok)
|
case strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]"):
|
||||||
}
|
lookup := seg[1 : len(seg)-1]
|
||||||
sel = append(sel, segment{str: tok, optional: opt, index: idx})
|
|
||||||
} else if strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\"") { // explicit field
|
switch {
|
||||||
sel = append(sel, segment{str: tok, optional: opt, field: lookup[1 : len(lookup)-1]})
|
// index, [123]
|
||||||
} else if sliceRegex.MatchString(lookup) { // slice [3:5] or [:5] or [3:]
|
case indexRegex.MatchString(lookup):
|
||||||
var rng []int
|
idx, err := strconv.Atoi(lookup)
|
||||||
splt := strings.Split(lookup, ":")
|
if err != nil {
|
||||||
if splt[0] == "" {
|
return nil, newParseError("invalid index", str, col, tok)
|
||||||
rng = append(rng, 0)
|
}
|
||||||
} else {
|
sel = append(sel, segment{str: tok, optional: opt, index: idx})
|
||||||
i, err := strconv.Atoi(splt[0])
|
|
||||||
if err != nil {
|
// explicit field, ["abcd"]
|
||||||
return nil, newParseError("invalid slice index", str, col, tok)
|
case strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\""):
|
||||||
}
|
fieldName := lookup[1 : len(lookup)-1]
|
||||||
rng = append(rng, i)
|
if strings.Contains(fieldName, ":") {
|
||||||
}
|
|
||||||
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)
|
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
|
||||||
}
|
}
|
||||||
} else if fieldRegex.MatchString(seg) {
|
sel = append(sel, segment{str: tok, optional: opt, field: fieldName})
|
||||||
sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]})
|
|
||||||
} else {
|
// 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:
|
||||||
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
|
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)
|
col += len(tok)
|
||||||
}
|
}
|
||||||
|
|||||||
552
pkg/policy/selector/parsing_test.go
Normal file
552
pkg/policy/selector/parsing_test.go
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
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,19 +2,29 @@ package selector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"math"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"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/node/basicnode"
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Selector describes a UCAN policy selector, as specified here:
|
// Selector describes a UCAN policy selector, as specified here:
|
||||||
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#selectors
|
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#selectors
|
||||||
type Selector []segment
|
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 {
|
func (s Selector) String() string {
|
||||||
var res strings.Builder
|
var res strings.Builder
|
||||||
for _, seg := range s {
|
for _, seg := range s {
|
||||||
@@ -23,20 +33,12 @@ func (s Selector) String() string {
|
|||||||
return res.String()
|
return res.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
var Identity = segment{".", true, false, false, nil, "", 0}
|
|
||||||
|
|
||||||
var (
|
|
||||||
indexRegex = regexp.MustCompile(`^-?\d+$`)
|
|
||||||
sliceRegex = regexp.MustCompile(`^((\-?\d+:\-?\d*)|(\-?\d*:\-?\d+))$`)
|
|
||||||
fieldRegex = regexp.MustCompile(`^\.[a-zA-Z_]*?$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
type segment struct {
|
type segment struct {
|
||||||
str string
|
str string
|
||||||
identity bool
|
identity bool
|
||||||
optional bool
|
optional bool
|
||||||
iterator bool
|
iterator bool
|
||||||
slice []int
|
slice []int64 // either 0-length or 2-length
|
||||||
field string
|
field string
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
@@ -62,7 +64,7 @@ func (s segment) Iterator() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Slice flags that this segment targets a range of a slice.
|
// Slice flags that this segment targets a range of a slice.
|
||||||
func (s segment) Slice() []int {
|
func (s segment) Slice() []int64 {
|
||||||
return s.slice
|
return s.slice
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,308 +78,168 @@ func (s segment) Index() int {
|
|||||||
return s.index
|
return s.index
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select uses a selector to extract an IPLD node or set of nodes from the
|
func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, error) {
|
||||||
// passed subject node.
|
errIfNotOptional := func(s segment, err error) error {
|
||||||
func Select(sel Selector, subject ipld.Node) (ipld.Node, []ipld.Node, error) {
|
if !s.Optional() {
|
||||||
return resolve(sel, subject, nil)
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.Node, error) {
|
|
||||||
cur := subject
|
|
||||||
for i, seg := range sel {
|
|
||||||
if seg.Identity() {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cur := subject
|
||||||
|
for _, seg := range sel {
|
||||||
// 1st level: handle the different segment types (iterator, field, slice, index)
|
// 1st level: handle the different segment types (iterator, field, slice, index)
|
||||||
// 2nd level: handle different node kinds (list, map, string, bytes)
|
// 2nd level: handle different node kinds (list, map, string, bytes)
|
||||||
switch {
|
switch {
|
||||||
|
case seg.Identity():
|
||||||
|
continue
|
||||||
|
|
||||||
case seg.Iterator():
|
case seg.Iterator():
|
||||||
if cur == nil || cur.Kind() == datamodel.Kind_Null {
|
switch {
|
||||||
|
case cur == nil || cur.Kind() == datamodel.Kind_Null:
|
||||||
if seg.Optional() {
|
if seg.Optional() {
|
||||||
// build empty list
|
// build an empty list
|
||||||
nb := basicnode.Prototype.List.NewBuilder()
|
n, _ := qp.BuildList(basicnode.Prototype.Any, 0, func(_ datamodel.ListAssembler) {})
|
||||||
assembler, err := nb.BeginList(0)
|
return n, nil
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = assembler.Finish(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nb.Build(), nil, nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
|
||||||
}
|
}
|
||||||
} else {
|
return nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
||||||
var many []ipld.Node
|
|
||||||
switch cur.Kind() {
|
|
||||||
case datamodel.Kind_List:
|
|
||||||
it := cur.ListIterator()
|
|
||||||
for !it.Done() {
|
|
||||||
_, v, err := it.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if there are more iterator segments
|
case cur.Kind() == datamodel.Kind_List:
|
||||||
if len(sel) > i+1 && sel[i+1].Iterator() {
|
// iterators are no-op on list
|
||||||
if v.Kind() == datamodel.Kind_List {
|
continue
|
||||||
// recursively resolve the remaining selector segments
|
|
||||||
var o ipld.Node
|
case cur.Kind() == datamodel.Kind_Map:
|
||||||
var m []ipld.Node
|
// iterators on maps collect the values
|
||||||
o, m, err = resolve(sel[i+1:], v, at)
|
nd, err := qp.BuildList(basicnode.Prototype.Any, cur.Length(), func(l datamodel.ListAssembler) {
|
||||||
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()
|
it := cur.MapIterator()
|
||||||
for !it.Done() {
|
for !it.Done() {
|
||||||
_, v, err := it.Next()
|
_, v, err := it.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
// recovered by BuildList
|
||||||
}
|
// Error is bubbled up, but should never occur as we already checked the type,
|
||||||
|
// and are using the iterator correctly.
|
||||||
if len(sel) > i+1 && sel[i+1].Iterator() {
|
// This is verified with fuzzing.
|
||||||
if v.Kind() == datamodel.Kind_List {
|
panic(err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
qp.ListEntry(l, qp.Node(v))
|
||||||
}
|
}
|
||||||
default:
|
})
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
if err != nil {
|
||||||
|
panic("should never happen")
|
||||||
}
|
}
|
||||||
|
return nd, nil
|
||||||
|
|
||||||
return nil, many, nil
|
default:
|
||||||
|
return nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
||||||
}
|
}
|
||||||
|
|
||||||
case seg.Field() != "":
|
case seg.Field() != "":
|
||||||
at = append(at, seg.Field())
|
at = append(at, seg.Field())
|
||||||
if cur == nil {
|
switch {
|
||||||
if seg.Optional() {
|
case cur == nil:
|
||||||
cur = nil
|
err := newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
||||||
} else {
|
return nil, errIfNotOptional(seg, err)
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
|
||||||
}
|
case cur.Kind() == datamodel.Kind_Map:
|
||||||
} else {
|
n, err := cur.LookupByString(seg.Field())
|
||||||
switch cur.Kind() {
|
if err != nil {
|
||||||
case datamodel.Kind_Map:
|
// the only possible error is missing field as we already check the type
|
||||||
n, err := cur.LookupByString(seg.Field())
|
if seg.Optional() {
|
||||||
if err != nil {
|
cur = 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 {
|
} else {
|
||||||
cur = n
|
return nil, newResolutionError(fmt.Sprintf("object has no field named: %s", seg.Field()), at)
|
||||||
}
|
}
|
||||||
case datamodel.Kind_List:
|
} else {
|
||||||
var many []ipld.Node
|
cur = n
|
||||||
it := cur.ListIterator()
|
}
|
||||||
for !it.Done() {
|
|
||||||
_, v, err := it.Next()
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
// recovered by BuildList
|
||||||
}
|
// Error is bubbled up, but should never occur as we already checked the type and boundaries
|
||||||
if v.Kind() == datamodel.Kind_Map {
|
// This is verified with fuzzing.
|
||||||
n, err := v.LookupByString(seg.Field())
|
panic(err)
|
||||||
if err == nil {
|
|
||||||
many = append(many, n)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
qp.ListEntry(l, qp.Node(item))
|
||||||
}
|
}
|
||||||
if len(many) > 0 {
|
})
|
||||||
cur = nil
|
if err != nil {
|
||||||
return nil, many, nil
|
panic("should never happen")
|
||||||
} else if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("no elements in list have field named: %s", seg.Field()), at)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
cur = sliced
|
||||||
|
|
||||||
|
case datamodel.Kind_Bytes:
|
||||||
|
b, _ := cur.AsBytes()
|
||||||
|
start, end := resolveSliceIndices(slice, int64(len(b)))
|
||||||
|
cur = basicnode.NewBytes(b[start:end])
|
||||||
|
|
||||||
|
case datamodel.Kind_String:
|
||||||
|
str, _ := cur.AsString()
|
||||||
|
runes := []rune(str)
|
||||||
|
start, end := resolveSliceIndices(slice, int64(len(runes)))
|
||||||
|
cur = basicnode.NewString(string(runes[start:end]))
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
|
||||||
}
|
}
|
||||||
|
|
||||||
case seg.Slice() != nil:
|
default: // Index()
|
||||||
if cur == nil {
|
at = append(at, strconv.Itoa(seg.Index()))
|
||||||
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 cur == nil {
|
||||||
if seg.Optional() {
|
err := newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
||||||
cur = nil
|
return nil, errIfNotOptional(seg, err)
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("slice out of bounds: [%d:%d]", start, end), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch cur.Kind() {
|
|
||||||
case datamodel.Kind_List:
|
|
||||||
if end > cur.Length() {
|
|
||||||
end = cur.Length()
|
|
||||||
}
|
|
||||||
nb := basicnode.Prototype.List.NewBuilder()
|
|
||||||
assembler, _ := nb.BeginList(int64(end - start))
|
|
||||||
for i := start; i < end; i++ {
|
|
||||||
item, _ := cur.LookupByIndex(int64(i))
|
|
||||||
assembler.AssembleValue().AssignNode(item)
|
|
||||||
}
|
|
||||||
assembler.Finish()
|
|
||||||
cur = nb.Build()
|
|
||||||
case datamodel.Kind_Bytes:
|
|
||||||
b, _ := cur.AsBytes()
|
|
||||||
l := int64(len(b))
|
|
||||||
if end > l {
|
|
||||||
end = l
|
|
||||||
}
|
|
||||||
cur = basicnode.NewBytes(b[start:end])
|
|
||||||
case datamodel.Kind_String:
|
|
||||||
str, _ := cur.AsString()
|
|
||||||
l := int64(len(str))
|
|
||||||
if end > l {
|
|
||||||
end = l
|
|
||||||
}
|
|
||||||
cur = basicnode.NewString(str[start:end])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
idx := seg.Index()
|
||||||
at = append(at, fmt.Sprintf("%d", seg.Index()))
|
switch cur.Kind() {
|
||||||
if cur == nil {
|
case datamodel.Kind_List:
|
||||||
if seg.Optional() {
|
if idx < 0 {
|
||||||
cur = nil
|
idx = int(cur.Length()) + idx
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
|
||||||
}
|
}
|
||||||
} else {
|
if idx < 0 || idx >= int(cur.Length()) {
|
||||||
idx := seg.Index()
|
err := newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
|
||||||
switch cur.Kind() {
|
return nil, errIfNotOptional(seg, err)
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cur, nil, nil
|
// segment exhausted, we return where we are
|
||||||
|
return cur, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveSliceIndices resolves the start and end indices for slicing a list or byte array.
|
// resolveSliceIndices resolves the start and end indices for slicing a list or byte array.
|
||||||
@@ -391,26 +253,41 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No
|
|||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - start: The resolved start index for slicing.
|
// - start: The resolved start index for slicing.
|
||||||
// - end: The resolved end index for slicing.
|
// - end: The resolved **excluded** end index for slicing.
|
||||||
func resolveSliceIndices(slice []int, length int64) (int64, int64) {
|
func resolveSliceIndices(slice []int64, length int64) (start int64, end int64) {
|
||||||
start, end := int64(0), length
|
if len(slice) != 2 {
|
||||||
if len(slice) > 0 {
|
panic("should always be 2-length")
|
||||||
start = int64(slice[0])
|
|
||||||
if start < 0 {
|
|
||||||
start = length + start
|
|
||||||
if start < 0 {
|
|
||||||
start = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(slice) > 1 {
|
|
||||||
end = int64(slice[1])
|
start, end = slice[0], slice[1]
|
||||||
if end <= 0 {
|
|
||||||
end = length + end
|
// adjust boundaries
|
||||||
if end < start {
|
switch {
|
||||||
end = start
|
case slice[0] == math.MinInt:
|
||||||
}
|
start = 0
|
||||||
}
|
case slice[0] < 0:
|
||||||
|
start = length + slice[0]
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case slice[1] == math.MaxInt:
|
||||||
|
end = length
|
||||||
|
case slice[1] < 0:
|
||||||
|
end = length + slice[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// backward iteration is not allowed, shortcut to an empty result
|
||||||
|
if start >= end {
|
||||||
|
start, end = 0, 0
|
||||||
|
}
|
||||||
|
// clamp out of bound
|
||||||
|
if start < 0 {
|
||||||
|
start = 0
|
||||||
|
}
|
||||||
|
if start > length {
|
||||||
|
start = length
|
||||||
|
}
|
||||||
|
if end > length {
|
||||||
|
end = length
|
||||||
}
|
}
|
||||||
|
|
||||||
return start, end
|
return start, end
|
||||||
@@ -423,19 +300,6 @@ func kindString(n datamodel.Node) string {
|
|||||||
return n.Kind().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 {
|
type resolutionerr struct {
|
||||||
msg string
|
msg string
|
||||||
at []string
|
at []string
|
||||||
|
|||||||
@@ -1,252 +1,20 @@
|
|||||||
package selector
|
package selector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
"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"
|
"github.com/ipld/go-ipld-prime/must"
|
||||||
basicnode "github.com/ipld/go-ipld-prime/node/basic"
|
basicnode "github.com/ipld/go-ipld-prime/node/basic"
|
||||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||||
"github.com/ipld/go-ipld-prime/printer"
|
|
||||||
"github.com/stretchr/testify/require"
|
"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) {
|
func TestSelect(t *testing.T) {
|
||||||
type name struct {
|
type name struct {
|
||||||
First string
|
First string
|
||||||
@@ -313,14 +81,11 @@ func TestSelect(t *testing.T) {
|
|||||||
sel, err := Parse(".")
|
sel, err := Parse(".")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := Select(sel, anode)
|
res, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(printer.Sprint(one))
|
age := must.Int(must.Node(res.LookupByString("age")))
|
||||||
|
|
||||||
age := must.Int(must.Node(one.LookupByString("age")))
|
|
||||||
require.Equal(t, int64(alice.Age), age)
|
require.Equal(t, int64(alice.Age), age)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -328,24 +93,18 @@ func TestSelect(t *testing.T) {
|
|||||||
sel, err := Parse(".name.first")
|
sel, err := Parse(".name.first")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := Select(sel, anode)
|
res, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(printer.Sprint(one))
|
name := must.String(res)
|
||||||
|
|
||||||
name := must.String(one)
|
|
||||||
require.Equal(t, alice.Name.First, name)
|
require.Equal(t, alice.Name.First, name)
|
||||||
|
|
||||||
one, many, err = Select(sel, bnode)
|
res, err = sel.Select(bnode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(printer.Sprint(one))
|
name = must.String(res)
|
||||||
|
|
||||||
name = must.String(one)
|
|
||||||
require.Equal(t, bob.Name.First, name)
|
require.Equal(t, bob.Name.First, name)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -353,81 +112,177 @@ func TestSelect(t *testing.T) {
|
|||||||
sel, err := Parse(".name.middle?")
|
sel, err := Parse(".name.middle?")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := Select(sel, anode)
|
res, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(printer.Sprint(one))
|
name := must.String(res)
|
||||||
|
|
||||||
name := must.String(one)
|
|
||||||
require.Equal(t, *alice.Name.Middle, name)
|
require.Equal(t, *alice.Name.Middle, name)
|
||||||
|
|
||||||
one, many, err = Select(sel, bnode)
|
res, err = sel.Select(bnode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, one)
|
require.Empty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("not exists", func(t *testing.T) {
|
t.Run("not exists", func(t *testing.T) {
|
||||||
sel, err := Parse(".name.foo")
|
sel, err := Parse(".name.foo")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := Select(sel, anode)
|
res, err := sel.Select(anode)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Empty(t, one)
|
require.Empty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(err)
|
require.ErrorAs(t, err, &resolutionerr{}, "error should be a resolution error")
|
||||||
|
|
||||||
require.ErrorAs(t, err, &resolutionerr{}, "error was not a resolution error")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("optional not exists", func(t *testing.T) {
|
t.Run("optional not exists", func(t *testing.T) {
|
||||||
sel, err := Parse(".name.foo?")
|
sel, err := Parse(".name.foo?")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := Select(sel, anode)
|
one, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, one)
|
require.Empty(t, one)
|
||||||
require.Empty(t, many)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("iterator", func(t *testing.T) {
|
t.Run("iterator", func(t *testing.T) {
|
||||||
sel, err := Parse(".interests[]")
|
sel, err := Parse(".interests[]")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := Select(sel, anode)
|
res, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.NotEmpty(t, many)
|
|
||||||
|
|
||||||
for _, n := range many {
|
iname := must.String(must.Node(must.Node(res.LookupByIndex(0)).LookupByString("name")))
|
||||||
fmt.Println(printer.Sprint(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
iname := must.String(must.Node(many[0].LookupByString("name")))
|
|
||||||
require.Equal(t, alice.Interests[0].Name, iname)
|
require.Equal(t, alice.Interests[0].Name, iname)
|
||||||
|
|
||||||
iname = must.String(must.Node(many[1].LookupByString("name")))
|
iname = must.String(must.Node(must.Node(res.LookupByIndex(1)).LookupByString("name")))
|
||||||
require.Equal(t, alice.Interests[1].Name, iname)
|
require.Equal(t, alice.Interests[1].Name, iname)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("map iterator", func(t *testing.T) {
|
t.Run("slice on string", func(t *testing.T) {
|
||||||
sel, err := Parse(".interests[0][]")
|
sel, err := Parse(`.[1:3]`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := Select(sel, anode)
|
node := basicnode.NewString("hello")
|
||||||
|
res, err := sel.Select(node)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.NotEmpty(t, many)
|
|
||||||
|
|
||||||
for _, n := range many {
|
str, err := res.AsString()
|
||||||
fmt.Println(printer.Sprint(n))
|
require.NoError(t, err)
|
||||||
}
|
require.Equal(t, "el", str) // assert sliced substring
|
||||||
|
})
|
||||||
|
|
||||||
require.Equal(t, alice.Interests[0].Name, must.String(many[0]))
|
t.Run("out of bounds slicing", func(t *testing.T) {
|
||||||
require.Equal(t, alice.Interests[0].Experience, int(must.Int(many[2])))
|
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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,6 +349,10 @@ func FuzzParseAndSelect(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// look for panic()
|
// look for panic()
|
||||||
_, _, _ = Select(sel, node)
|
_, err = sel.Select(node)
|
||||||
|
if err != nil && !errors.As(err, &resolutionerr{}) {
|
||||||
|
// not normal, we should only have resolution errors
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package selector_test
|
package selector_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
|
||||||
basicnode "github.com/ipld/go-ipld-prime/node/basic"
|
basicnode "github.com/ipld/go-ipld-prime/node/basic"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
"github.com/ucan-wg/go-ucan/pkg/policy/selector"
|
||||||
@@ -26,17 +23,15 @@ func TestSupportedForms(t *testing.T) {
|
|||||||
Output string
|
Output string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass
|
// Pass and return a node
|
||||||
for _, testcase := range []Testcase{
|
for _, testcase := range []Testcase{
|
||||||
{Name: "Identity", Selector: `.`, Input: `{"x":1}`, Output: `{"x":1}`},
|
{Name: "Identity", Selector: `.`, Input: `{"x":1}`, Output: `{"x":1}`},
|
||||||
{Name: "Iterator", Selector: `.[]`, Input: `[1, 2]`, Output: `[1, 2]`},
|
{Name: "Iterator", Selector: `.[]`, Input: `[1, 2]`, Output: `[1, 2]`},
|
||||||
{Name: "Optional Null Iterator", Selector: `.[]?`, Input: `null`, Output: `[]`},
|
{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: "Object Key", Selector: `.x`, Input: `{"x": 1 }`, Output: `1`},
|
||||||
{Name: "Quoted 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: "Index", Selector: `.[0]`, Input: `[1, 2]`, Output: `1`},
|
||||||
{Name: "Negative Index", Selector: `.[-1]`, Input: `[1, 2]`, Output: `2`},
|
{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: "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: `.[0:2]`, Input: `[0, 1, 2]`, Output: `[0, 1]`},
|
||||||
{Name: "Array Slice", Selector: `.[1:]`, Input: `[0, 1, 2]`, Output: `[1, 2]`},
|
{Name: "Array Slice", Selector: `.[1:]`, Input: `[0, 1, 2]`, Output: `[1, 2]`},
|
||||||
@@ -52,35 +47,16 @@ func TestSupportedForms(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// attempt to select
|
// attempt to select
|
||||||
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
|
res, err := sel.Select(makeNode(t, tc.Input))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, node != nil, len(nodes) > 0) // XOR (only one of node or nodes should be set)
|
require.NotNil(t, res)
|
||||||
|
|
||||||
// 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)
|
exp := makeNode(t, tc.Output)
|
||||||
equalIPLD(t, exp, node)
|
require.True(t, ipld.DeepEqual(exp, res))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// null
|
// No error and return null, as optional
|
||||||
for _, testcase := range []Testcase{
|
for _, testcase := range []Testcase{
|
||||||
{Name: "Optional Missing Key", Selector: `.x?`, Input: `{}`},
|
{Name: "Optional Missing Key", Selector: `.x?`, Input: `{}`},
|
||||||
{Name: "Optional Null Key", Selector: `.x?`, Input: `null`},
|
{Name: "Optional Null Key", Selector: `.x?`, Input: `null`},
|
||||||
@@ -97,19 +73,15 @@ func TestSupportedForms(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// attempt to select
|
// attempt to select
|
||||||
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
|
res, err := sel.Select(makeNode(t, tc.Input))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// TODO: should Select return a single node which is sometimes a list or null?
|
require.Nil(t, res)
|
||||||
// require.Equal(t, datamodel.Null, node)
|
|
||||||
assert.Nil(t, node)
|
|
||||||
assert.Empty(t, nodes)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// fail to select and return an error
|
||||||
for _, testcase := range []Testcase{
|
for _, testcase := range []Testcase{
|
||||||
{Name: "Null Iterator", Selector: `.[]`, Input: `null`},
|
{Name: "Null Iterator", Selector: `.[]`, Input: `null`},
|
||||||
{Name: "Nested Iterator", Selector: `.[][]`, Input: `[[1], 2, [3]]`},
|
|
||||||
{Name: "Missing Key", Selector: `.x`, Input: `{}`},
|
{Name: "Missing Key", Selector: `.x`, Input: `{}`},
|
||||||
{Name: "Null Key", Selector: `.x`, Input: `null`},
|
{Name: "Null Key", Selector: `.x`, Input: `null`},
|
||||||
{Name: "Array Key", Selector: `.x`, Input: `[]`},
|
{Name: "Array Key", Selector: `.x`, Input: `[]`},
|
||||||
@@ -124,31 +96,13 @@ func TestSupportedForms(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// attempt to select
|
// attempt to select
|
||||||
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
|
res, err := sel.Select(makeNode(t, tc.Input))
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Nil(t, node)
|
require.Nil(t, res)
|
||||||
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 {
|
func makeNode(t *testing.T, dagJsonInput string) ipld.Node {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
@@ -10,18 +10,16 @@ package delegation
|
|||||||
// TODO: change the "delegation" link above when the specification is merged
|
// TODO: change the "delegation" link above when the specification is merged
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
|
||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||||
|
"github.com/ucan-wg/go-ucan/token/internal/nonce"
|
||||||
|
"github.com/ucan-wg/go-ucan/token/internal/parse"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Token is an immutable type that holds the fields of a UCAN delegation.
|
// Token is an immutable type that holds the fields of a UCAN delegation.
|
||||||
@@ -44,21 +42,14 @@ type Token struct {
|
|||||||
notBefore *time.Time
|
notBefore *time.Time
|
||||||
// The timestamp at which the Invocation becomes invalid
|
// The timestamp at which the Invocation becomes invalid
|
||||||
expiration *time.Time
|
expiration *time.Time
|
||||||
// The CID of the Token when enclosed in an Envelope and encoded to DAG-CBOR
|
|
||||||
cid cid.Cid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a validated Token from the provided parameters and options.
|
// New creates a validated Token from the provided parameters and options.
|
||||||
//
|
//
|
||||||
// When creating a delegated token, the Issuer's (iss) DID is assembed
|
// When creating a delegated token, the Issuer's (iss) DID is assembled
|
||||||
// using the public key associated with the private key sent as the first
|
// using the public key associated with the private key sent as the first
|
||||||
// parameter.
|
// parameter.
|
||||||
func New(privKey crypto.PrivKey, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
|
func New(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
|
||||||
iss, err := did.FromPrivKey(privKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tkn := &Token{
|
tkn := &Token{
|
||||||
issuer: iss,
|
issuer: iss,
|
||||||
audience: aud,
|
audience: aud,
|
||||||
@@ -67,7 +58,6 @@ func New(privKey crypto.PrivKey, aud did.DID, cmd command.Command, pol policy.Po
|
|||||||
policy: pol,
|
policy: pol,
|
||||||
meta: meta.NewMeta(),
|
meta: meta.NewMeta(),
|
||||||
nonce: nil,
|
nonce: nil,
|
||||||
cid: cid.Undef,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
@@ -76,8 +66,9 @@ func New(privKey crypto.PrivKey, aud did.DID, cmd command.Command, pol policy.Po
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
if len(tkn.nonce) == 0 {
|
if len(tkn.nonce) == 0 {
|
||||||
tkn.nonce, err = generateNonce()
|
tkn.nonce, err = nonce.Generate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -96,15 +87,10 @@ func New(privKey crypto.PrivKey, aud did.DID, cmd command.Command, pol policy.Po
|
|||||||
// When creating a root token, both the Issuer's (iss) and Subject's
|
// When creating a root token, both the Issuer's (iss) and Subject's
|
||||||
// (sub) DIDs are assembled from the public key associated with the
|
// (sub) DIDs are assembled from the public key associated with the
|
||||||
// private key passed as the first argument.
|
// private key passed as the first argument.
|
||||||
func Root(privKey crypto.PrivKey, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
|
func Root(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
|
||||||
sub, err := did.FromPrivKey(privKey)
|
opts = append(opts, WithSubject(iss))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = append(opts, WithSubject(sub))
|
return New(iss, aud, cmd, pol, opts...)
|
||||||
|
|
||||||
return New(privKey, aud, cmd, pol, opts...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issuer returns the did.DID representing the Token's issuer.
|
// Issuer returns the did.DID representing the Token's issuer.
|
||||||
@@ -142,8 +128,8 @@ func (t *Token) Nonce() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Meta returns the Token's metadata.
|
// Meta returns the Token's metadata.
|
||||||
func (t *Token) Meta() *meta.Meta {
|
func (t *Token) Meta() meta.ReadOnly {
|
||||||
return t.meta
|
return t.meta.ReadOnly()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotBefore returns the time at which the Token becomes "active".
|
// NotBefore returns the time at which the Token becomes "active".
|
||||||
@@ -156,11 +142,22 @@ func (t *Token) Expiration() *time.Time {
|
|||||||
return t.expiration
|
return t.expiration
|
||||||
}
|
}
|
||||||
|
|
||||||
// CID returns the content identifier of the Token model when enclosed
|
// IsValidNow verifies that the token can be used at the current time, based on expiration or "not before" fields.
|
||||||
// in an Envelope and encoded to DAG-CBOR.
|
// This does NOT do any other kind of verifications.
|
||||||
// Returns cid.Undef if the token has not been serialized or deserialized yet.
|
func (t *Token) IsValidNow() bool {
|
||||||
func (t *Token) CID() cid.Cid {
|
return t.IsValidAt(time.Now())
|
||||||
return t.cid
|
}
|
||||||
|
|
||||||
|
// IsValidNow verifies that the token can be used at the given time, based on expiration or "not before" fields.
|
||||||
|
// This does NOT do any other kind of verifications.
|
||||||
|
func (t *Token) IsValidAt(ti time.Time) bool {
|
||||||
|
if t.expiration != nil && ti.After(*t.expiration) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if t.notBefore != nil && ti.Before(*t.notBefore) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Token) validate() error {
|
func (t *Token) validate() error {
|
||||||
@@ -195,27 +192,19 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
|||||||
return nil, fmt.Errorf("parse iss: %w", err)
|
return nil, fmt.Errorf("parse iss: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn.audience, err = did.Parse(m.Aud)
|
if tkn.audience, err = did.Parse(m.Aud); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse audience: %w", err)
|
return nil, fmt.Errorf("parse audience: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.Sub != nil {
|
if tkn.subject, err = parse.OptionalDID(m.Sub); err != nil {
|
||||||
tkn.subject, err = did.Parse(*m.Sub)
|
return nil, fmt.Errorf("parse subject: %w", err)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse subject: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tkn.subject = did.Undef
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn.command, err = command.Parse(m.Cmd)
|
if tkn.command, err = command.Parse(m.Cmd); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse command: %w", err)
|
return nil, fmt.Errorf("parse command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn.policy, err = policy.FromIPLD(m.Pol)
|
if tkn.policy, err = policy.FromIPLD(m.Pol); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse policy: %w", err)
|
return nil, fmt.Errorf("parse policy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,17 +213,10 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
|||||||
}
|
}
|
||||||
tkn.nonce = m.Nonce
|
tkn.nonce = m.Nonce
|
||||||
|
|
||||||
tkn.meta = &m.Meta
|
tkn.meta = m.Meta
|
||||||
|
|
||||||
if m.Nbf != nil {
|
tkn.notBefore = parse.OptionalTimestamp(m.Nbf)
|
||||||
t := time.Unix(*m.Nbf, 0)
|
tkn.expiration = parse.OptionalTimestamp(m.Exp)
|
||||||
tkn.notBefore = &t
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Exp != nil {
|
|
||||||
t := time.Unix(*m.Exp, 0)
|
|
||||||
tkn.expiration = &t
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tkn.validate(); err != nil {
|
if err := tkn.validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -242,14 +224,3 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
|
|||||||
|
|
||||||
return &tkn, nil
|
return &tkn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateNonce creates a 12-byte random nonce.
|
|
||||||
// TODO: some crypto scheme require more, is that our case?
|
|
||||||
func generateNonce() ([]byte, error) {
|
|
||||||
res := make([]byte, 12)
|
|
||||||
_, err := rand.Read(res)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@ type Payload struct {
|
|||||||
nonce Bytes
|
nonce Bytes
|
||||||
|
|
||||||
# Arbitrary Metadata
|
# Arbitrary Metadata
|
||||||
meta {String : Any}
|
meta optional {String : Any}
|
||||||
|
|
||||||
# "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
# "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
||||||
nbf optional Int
|
nbf optional Int
|
||||||
209
token/delegation/delegation_test.go
Normal file
209
token/delegation/delegation_test.go
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
5
token/delegation/delegationtest/README.md
Normal file
5
token/delegation/delegationtest/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 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.
|
||||||
BIN
token/delegation/delegationtest/data/TokenAliceBob.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenAliceBob.dagcbor
Normal file
Binary file not shown.
BIN
token/delegation/delegationtest/data/TokenBobCarol.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenBobCarol.dagcbor
Normal file
Binary file not shown.
BIN
token/delegation/delegationtest/data/TokenCarolDan.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenCarolDan.dagcbor
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
token/delegation/delegationtest/data/TokenDanErin.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenDanErin.dagcbor
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
token/delegation/delegationtest/data/TokenErinFrank.dagcbor
Normal file
BIN
token/delegation/delegationtest/data/TokenErinFrank.dagcbor
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
33
token/delegation/delegationtest/doc.go
Normal file
33
token/delegation/delegationtest/doc.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// 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
|
||||||
234
token/delegation/delegationtest/generator/generator.go
Normal file
234
token/delegation/delegationtest/generator/generator.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
17
token/delegation/delegationtest/generator/main.go
Normal file
17
token/delegation/delegationtest/generator/main.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
112
token/delegation/delegationtest/token.go
Normal file
112
token/delegation/delegationtest/token.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
240
token/delegation/delegationtest/token_gen.go
Normal file
240
token/delegation/delegationtest/token_gen.go
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
||||||
30
token/delegation/delegationtest/token_test.go
Normal file
30
token/delegation/delegationtest/token_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
311
token/delegation/examples_test.go
Normal file
311
token/delegation/examples_test.go
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
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,6 +1,7 @@
|
|||||||
package delegation
|
package delegation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
@@ -12,7 +13,7 @@ import (
|
|||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToSealed wraps the delegation token in an envelope, generates the
|
// ToSealed wraps the delegation token in an envelope, generates the
|
||||||
@@ -32,7 +33,7 @@ func (t *Token) ToSealed(privKey crypto.PrivKey) ([]byte, cid.Cid, error) {
|
|||||||
return data, id, nil
|
return data, id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToSealedWriter is the same as Seal but accepts an io.Writer.
|
// ToSealedWriter is the same as ToSealed but accepts an io.Writer.
|
||||||
func (t *Token) ToSealedWriter(w io.Writer, privKey crypto.PrivKey) (cid.Cid, error) {
|
func (t *Token) ToSealedWriter(w io.Writer, privKey crypto.PrivKey) (cid.Cid, error) {
|
||||||
cidWriter := envelope.NewCIDWriter(w)
|
cidWriter := envelope.NewCIDWriter(w)
|
||||||
|
|
||||||
@@ -47,42 +48,38 @@ func (t *Token) ToSealedWriter(w io.Writer, privKey crypto.PrivKey) (cid.Cid, er
|
|||||||
// verifies that the envelope's signature is correct based on the public
|
// 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
|
// key taken from the issuer (iss) field and calculates the CID of the
|
||||||
// incoming data.
|
// incoming data.
|
||||||
func FromSealed(data []byte) (*Token, error) {
|
func FromSealed(data []byte) (*Token, cid.Cid, error) {
|
||||||
tkn, err := FromDagCbor(data)
|
tkn, err := FromDagCbor(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, cid.Undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := envelope.CIDFromBytes(data)
|
id, err := envelope.CIDFromBytes(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, cid.Undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn.cid = id
|
return tkn, id, nil
|
||||||
|
|
||||||
return tkn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromSealedReader is the same as Unseal but accepts an io.Reader.
|
// FromSealedReader is the same as Unseal but accepts an io.Reader.
|
||||||
func FromSealedReader(r io.Reader) (*Token, error) {
|
func FromSealedReader(r io.Reader) (*Token, cid.Cid, error) {
|
||||||
cidReader := envelope.NewCIDReader(r)
|
cidReader := envelope.NewCIDReader(r)
|
||||||
|
|
||||||
tkn, err := FromDagCborReader(cidReader)
|
tkn, err := FromDagCborReader(cidReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, cid.Undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := cidReader.CID()
|
id, err := cidReader.CID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, cid.Undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn.cid = id
|
return tkn, id, nil
|
||||||
|
|
||||||
return tkn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode marshals a View to the format specified by the provided
|
// Encode marshals a Token to the format specified by the provided
|
||||||
// codec.Encoder.
|
// codec.Encoder.
|
||||||
func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error) {
|
func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error) {
|
||||||
node, err := t.toIPLD(privKey)
|
node, err := t.toIPLD(privKey)
|
||||||
@@ -93,7 +90,7 @@ func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, err
|
|||||||
return ipld.Encode(node, encFn)
|
return ipld.Encode(node, encFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeWriter is the same as Encode but accepts an io.Writer.
|
// 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 {
|
func (t *Token) EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.Encoder) error {
|
||||||
node, err := t.toIPLD(privKey)
|
node, err := t.toIPLD(privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,37 +100,37 @@ func (t *Token) EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.En
|
|||||||
return ipld.EncodeStreaming(w, node, encFn)
|
return ipld.EncodeStreaming(w, node, encFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDagCbor marshals the View to the DAG-CBOR format.
|
// ToDagCbor marshals the Token to the DAG-CBOR format.
|
||||||
func (t *Token) ToDagCbor(privKey crypto.PrivKey) ([]byte, error) {
|
func (t *Token) ToDagCbor(privKey crypto.PrivKey) ([]byte, error) {
|
||||||
return t.Encode(privKey, dagcbor.Encode)
|
return t.Encode(privKey, dagcbor.Encode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDagCborWriter is the same as ToDagCbor but it accepts an io.Writer.
|
// ToDagCborWriter is the same as ToDagCbor, but it accepts an io.Writer.
|
||||||
func (t *Token) ToDagCborWriter(w io.Writer, privKey crypto.PrivKey) error {
|
func (t *Token) ToDagCborWriter(w io.Writer, privKey crypto.PrivKey) error {
|
||||||
return t.EncodeWriter(w, privKey, dagcbor.Encode)
|
return t.EncodeWriter(w, privKey, dagcbor.Encode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDagJson marshals the View to the DAG-JSON format.
|
// ToDagJson marshals the Token to the DAG-JSON format.
|
||||||
func (t *Token) ToDagJson(privKey crypto.PrivKey) ([]byte, error) {
|
func (t *Token) ToDagJson(privKey crypto.PrivKey) ([]byte, error) {
|
||||||
return t.Encode(privKey, dagjson.Encode)
|
return t.Encode(privKey, dagjson.Encode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDagJsonWriter is the same as ToDagJson but it accepts an io.Writer.
|
// ToDagJsonWriter is the same as ToDagJson, but it accepts an io.Writer.
|
||||||
func (t *Token) ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error {
|
func (t *Token) ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error {
|
||||||
return t.EncodeWriter(w, privKey, dagjson.Encode)
|
return t.EncodeWriter(w, privKey, dagjson.Encode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode unmarshals the input data using the format specified by the
|
// Decode unmarshals the input data using the format specified by the
|
||||||
// provided codec.Decoder into a View.
|
// provided codec.Decoder into a Token.
|
||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// View is invalid.
|
// Token is invalid.
|
||||||
func Decode(b []byte, decFn codec.Decoder) (*Token, error) {
|
func Decode(b []byte, decFn codec.Decoder) (*Token, error) {
|
||||||
node, err := ipld.Decode(b, decFn)
|
node, err := ipld.Decode(b, decFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return fromIPLD(node)
|
return FromIPLD(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeReader is the same as Decode, but accept an io.Reader.
|
// DecodeReader is the same as Decode, but accept an io.Reader.
|
||||||
@@ -142,13 +139,13 @@ func DecodeReader(r io.Reader, decFn codec.Decoder) (*Token, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return fromIPLD(node)
|
return FromIPLD(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDagCbor unmarshals the input data into a View.
|
// FromDagCbor unmarshals the input data into a Token.
|
||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// View is invalid.
|
// Token is invalid.
|
||||||
func FromDagCbor(data []byte) (*Token, error) {
|
func FromDagCbor(data []byte) (*Token, error) {
|
||||||
pay, err := envelope.FromDagCbor[*tokenPayloadModel](data)
|
pay, err := envelope.FromDagCbor[*tokenPayloadModel](data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -168,10 +165,10 @@ func FromDagCborReader(r io.Reader) (*Token, error) {
|
|||||||
return DecodeReader(r, dagcbor.Decode)
|
return DecodeReader(r, dagcbor.Decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDagJson unmarshals the input data into a View.
|
// FromDagJson unmarshals the input data into a Token.
|
||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// View is invalid.
|
// Token is invalid.
|
||||||
func FromDagJson(data []byte) (*Token, error) {
|
func FromDagJson(data []byte) (*Token, error) {
|
||||||
return Decode(data, dagjson.Decode)
|
return Decode(data, dagjson.Decode)
|
||||||
}
|
}
|
||||||
@@ -181,7 +178,8 @@ func FromDagJsonReader(r io.Reader) (*Token, error) {
|
|||||||
return DecodeReader(r, dagjson.Decode)
|
return DecodeReader(r, dagjson.Decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromIPLD(node datamodel.Node) (*Token, error) {
|
// FromIPLD decode the given IPLD representation into a Token.
|
||||||
|
func FromIPLD(node datamodel.Node) (*Token, error) {
|
||||||
pay, err := envelope.FromIPLD[*tokenPayloadModel](node)
|
pay, err := envelope.FromIPLD[*tokenPayloadModel](node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -196,8 +194,16 @@ func fromIPLD(node datamodel.Node) (*Token, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
||||||
var sub *string
|
// sanity check that privKey and issuer are matching
|
||||||
|
issPub, err := t.issuer.PubKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !issPub.Equals(privKey.GetPublic()) {
|
||||||
|
return nil, fmt.Errorf("private key doesn't match the issuer")
|
||||||
|
}
|
||||||
|
|
||||||
|
var sub *string
|
||||||
if t.subject != did.Undef {
|
if t.subject != did.Undef {
|
||||||
s := t.subject.String()
|
s := t.subject.String()
|
||||||
sub = &s
|
sub = &s
|
||||||
@@ -227,10 +233,15 @@ func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
|||||||
Cmd: t.command.String(),
|
Cmd: t.command.String(),
|
||||||
Pol: pol,
|
Pol: pol,
|
||||||
Nonce: t.nonce,
|
Nonce: t.nonce,
|
||||||
Meta: *t.meta,
|
Meta: t.meta,
|
||||||
Nbf: nbf,
|
Nbf: nbf,
|
||||||
Exp: exp,
|
Exp: exp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// seems like it's a requirement to have a null meta if there are no values?
|
||||||
|
if len(model.Meta.Keys) == 0 {
|
||||||
|
model.Meta = nil
|
||||||
|
}
|
||||||
|
|
||||||
return envelope.ToIPLD(privKey, model)
|
return envelope.ToIPLD(privKey, model)
|
||||||
}
|
}
|
||||||
17
token/delegation/loader.go
Normal file
17
token/delegation/loader.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -24,6 +24,15 @@ func WithExpiration(exp time.Time) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithExpirationIn set's the Token's optional "expiration" field to Now() plus the given duration.
|
||||||
|
func WithExpirationIn(exp time.Duration) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
expTime := time.Now().Add(exp)
|
||||||
|
t.expiration = &expTime
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithMeta adds a key/value pair in the "meta" field.
|
// WithMeta adds a key/value pair in the "meta" field.
|
||||||
//
|
//
|
||||||
// WithMeta can be used multiple times in the same call.
|
// WithMeta can be used multiple times in the same call.
|
||||||
@@ -35,6 +44,22 @@ func WithMeta(key string, val any) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithEncryptedMetaString adds a key/value pair in the "meta" field.
|
||||||
|
// The string value is encrypted with the given 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithNotBefore set's the Token's optional "notBefore" field to the value
|
// WithNotBefore set's the Token's optional "notBefore" field to the value
|
||||||
// of the provided time.Time.
|
// of the provided time.Time.
|
||||||
func WithNotBefore(nbf time.Time) Option {
|
func WithNotBefore(nbf time.Time) Option {
|
||||||
@@ -48,11 +73,21 @@ func WithNotBefore(nbf time.Time) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithNotBeforeIn set's the Token's optional "notBefore" field to the value
|
||||||
|
// of the provided time.Time.
|
||||||
|
func WithNotBeforeIn(nbf time.Duration) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
nbfTime := time.Now().Add(nbf)
|
||||||
|
t.notBefore = &nbfTime
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithSubject sets the Tokens's optional "subject" field to the value of
|
// WithSubject sets the Tokens's optional "subject" field to the value of
|
||||||
// provided did.DID.
|
// provided did.DID.
|
||||||
//
|
//
|
||||||
// This Option should only be used with the New constructor - since
|
// This Option should only be used with the New constructor - since
|
||||||
// Subject is a required parameter when creating a Token via the Root
|
// Subject is a required parameter when creating a Token via the Root
|
||||||
// constructor, any value provided via this Option will be silently
|
// constructor, any value provided via this Option will be silently
|
||||||
// overwritten.
|
// overwritten.
|
||||||
func WithSubject(sub did.DID) Option {
|
func WithSubject(sub did.DID) Option {
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
// [Tag] is the string used as a key within the SigPayload that identifies
|
// [Tag] is the string used as a key within the SigPayload that identifies
|
||||||
@@ -26,17 +26,17 @@ const Tag = "ucan/dlg@1.0.0-rc.1"
|
|||||||
var schemaBytes []byte
|
var schemaBytes []byte
|
||||||
|
|
||||||
var (
|
var (
|
||||||
once sync.Once
|
once sync.Once
|
||||||
ts *schema.TypeSystem
|
ts *schema.TypeSystem
|
||||||
err error
|
errSchema error
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustLoadSchema() *schema.TypeSystem {
|
func mustLoadSchema() *schema.TypeSystem {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
ts, err = ipld.LoadSchemaBytes(schemaBytes)
|
ts, errSchema = ipld.LoadSchemaBytes(schemaBytes)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if errSchema != nil {
|
||||||
panic(fmt.Errorf("failed to load IPLD schema: %s", err))
|
panic(fmt.Errorf("failed to load IPLD schema: %s", errSchema))
|
||||||
}
|
}
|
||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ type tokenPayloadModel struct {
|
|||||||
Nonce []byte
|
Nonce []byte
|
||||||
|
|
||||||
// Arbitrary Metadata
|
// Arbitrary Metadata
|
||||||
Meta meta.Meta
|
Meta *meta.Meta
|
||||||
|
|
||||||
// "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
// "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
||||||
// optional: can be nil
|
// optional: can be nil
|
||||||
@@ -3,7 +3,6 @@ package delegation_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
@@ -11,8 +10,9 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/tokens/delegation"
|
"github.com/ucan-wg/go-ucan/did/didtest"
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||||
|
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed delegation.ipldsch
|
//go:embed delegation.ipldsch
|
||||||
@@ -22,7 +22,7 @@ func TestSchemaRoundTrip(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
delegationJson := golden.Get(t, "new.dagjson")
|
delegationJson := golden.Get(t, "new.dagjson")
|
||||||
privKey := privKey(t, issuerPrivKeyCfg)
|
privKey := didtest.PersonaAlice.PrivKey()
|
||||||
|
|
||||||
t.Run("via buffers", func(t *testing.T) {
|
t.Run("via buffers", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -36,18 +36,13 @@ func TestSchemaRoundTrip(t *testing.T) {
|
|||||||
cborBytes, id, err := p1.ToSealed(privKey)
|
cborBytes, id, err := p1.ToSealed(privKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
|
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
|
||||||
fmt.Println("cborBytes length", len(cborBytes))
|
|
||||||
fmt.Println("cbor", string(cborBytes))
|
|
||||||
|
|
||||||
p2, err := delegation.FromSealed(cborBytes)
|
p2, c2, err := delegation.FromSealed(cborBytes)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, id, p2.CID())
|
assert.Equal(t, id, c2)
|
||||||
fmt.Println("read Cbor", p2)
|
|
||||||
|
|
||||||
readJson, err := p2.ToDagJson(privKey)
|
readJson, err := p2.ToDagJson(privKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
fmt.Println("readJson length", len(readJson))
|
|
||||||
fmt.Println("json: ", string(readJson))
|
|
||||||
|
|
||||||
assert.JSONEq(t, string(delegationJson), string(readJson))
|
assert.JSONEq(t, string(delegationJson), string(readJson))
|
||||||
})
|
})
|
||||||
@@ -65,21 +60,29 @@ func TestSchemaRoundTrip(t *testing.T) {
|
|||||||
|
|
||||||
cborBytes := &bytes.Buffer{}
|
cborBytes := &bytes.Buffer{}
|
||||||
id, err := p1.ToSealedWriter(cborBytes, privKey)
|
id, err := p1.ToSealedWriter(cborBytes, privKey)
|
||||||
t.Log(len(id.Bytes()), id.Bytes())
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
|
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
|
||||||
|
|
||||||
// buf = bytes.NewBuffer(cborBytes.Bytes())
|
// buf = bytes.NewBuffer(cborBytes.Bytes())
|
||||||
p2, err := delegation.FromSealedReader(cborBytes)
|
p2, c2, err := delegation.FromSealedReader(cborBytes)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Log(len(p2.CID().Bytes()), p2.CID().Bytes())
|
assert.Equal(t, envelope.CIDToBase58BTC(id), envelope.CIDToBase58BTC(c2))
|
||||||
assert.Equal(t, envelope.CIDToBase58BTC(id), envelope.CIDToBase58BTC(p2.CID()))
|
|
||||||
|
|
||||||
readJson := &bytes.Buffer{}
|
readJson := &bytes.Buffer{}
|
||||||
require.NoError(t, p2.ToDagJsonWriter(readJson, privKey))
|
require.NoError(t, p2.ToDagJsonWriter(readJson, privKey))
|
||||||
|
|
||||||
assert.JSONEq(t, string(delegationJson), readJson.String())
|
assert.JSONEq(t, string(delegationJson), readJson.String())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("fails with wrong PrivKey", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
p1, err := delegation.FromDagJson(delegationJson)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, _, err = p1.ToSealed(didtest.PersonaBob.PrivKey())
|
||||||
|
require.EqualError(t, err, "private key doesn't match the issuer")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkSchemaLoad(b *testing.B) {
|
func BenchmarkSchemaLoad(b *testing.B) {
|
||||||
@@ -91,12 +94,12 @@ func BenchmarkSchemaLoad(b *testing.B) {
|
|||||||
|
|
||||||
func BenchmarkRoundTrip(b *testing.B) {
|
func BenchmarkRoundTrip(b *testing.B) {
|
||||||
delegationJson := golden.Get(b, "new.dagjson")
|
delegationJson := golden.Get(b, "new.dagjson")
|
||||||
privKey := privKey(b, issuerPrivKeyCfg)
|
privKey := didtest.PersonaAlice.PrivKey()
|
||||||
|
|
||||||
b.Run("via buffers", func(b *testing.B) {
|
b.Run("via buffers", func(b *testing.B) {
|
||||||
p1, _ := delegation.FromDagJson(delegationJson)
|
p1, _ := delegation.FromDagJson(delegationJson)
|
||||||
cborBytes, _, _ := p1.ToSealed(privKey)
|
cborBytes, _, _ := p1.ToSealed(privKey)
|
||||||
p2, _ := delegation.FromSealed(cborBytes)
|
p2, _, _ := delegation.FromSealed(cborBytes)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
@@ -117,7 +120,7 @@ func BenchmarkRoundTrip(b *testing.B) {
|
|||||||
b.Run("Unseal", func(b *testing.B) {
|
b.Run("Unseal", func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = delegation.FromSealed(cborBytes)
|
_, _, _ = delegation.FromSealed(cborBytes)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -134,7 +137,7 @@ func BenchmarkRoundTrip(b *testing.B) {
|
|||||||
cborBuf := &bytes.Buffer{}
|
cborBuf := &bytes.Buffer{}
|
||||||
_, _ = p1.ToSealedWriter(cborBuf, privKey)
|
_, _ = p1.ToSealedWriter(cborBuf, privKey)
|
||||||
cborBytes := cborBuf.Bytes()
|
cborBytes := cborBuf.Bytes()
|
||||||
p2, _ := delegation.FromSealedReader(bytes.NewReader(cborBytes))
|
p2, _, _ := delegation.FromSealedReader(bytes.NewReader(cborBytes))
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
@@ -161,7 +164,7 @@ func BenchmarkRoundTrip(b *testing.B) {
|
|||||||
reader := bytes.NewReader(cborBytes)
|
reader := bytes.NewReader(cborBytes)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = reader.Seek(0, 0)
|
_, _ = reader.Seek(0, 0)
|
||||||
_, _ = delegation.FromSealedReader(reader)
|
_, _, _ = delegation.FromSealedReader(reader)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
1
token/delegation/testdata/new.dagjson
vendored
Normal file
1
token/delegation/testdata/new.dagjson
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[{"/":{"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
Normal file
1
token/delegation/testdata/root.dagjson
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[{"/":{"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"}}]
|
||||||
19
token/inspect.go
Normal file
19
token/inspect.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
41
token/interface.go
Normal file
41
token/interface.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCidFromBytes(t *testing.T) {
|
func TestCidFromBytes(t *testing.T) {
|
||||||
@@ -16,8 +16,9 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -27,7 +27,9 @@ package envelope
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/codec"
|
"github.com/ipld/go-ipld-prime/codec"
|
||||||
@@ -41,10 +43,13 @@ import (
|
|||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/varsig"
|
"github.com/ucan-wg/go-ucan/token/internal/varsig"
|
||||||
)
|
)
|
||||||
|
|
||||||
const varsigHeaderKey = "h"
|
const (
|
||||||
|
VarsigHeaderKey = "h"
|
||||||
|
UCANTagPrefix = "ucan/"
|
||||||
|
)
|
||||||
|
|
||||||
// Tokener must be implemented by types that wish to be enclosed in a
|
// Tokener must be implemented by types that wish to be enclosed in a
|
||||||
// UCAN Envelope (presumbably one of the UCAN token types).
|
// UCAN Envelope (presumbably one of the UCAN token types).
|
||||||
@@ -89,19 +94,7 @@ func DecodeReader[T Tokener](r io.Reader, decFn codec.Decoder) (T, error) {
|
|||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// Tokener is invalid.
|
// Tokener is invalid.
|
||||||
func FromDagCbor[T Tokener](b []byte) (T, error) {
|
func FromDagCbor[T Tokener](b []byte) (T, error) {
|
||||||
undef := *new(T)
|
return Decode[T](b, dagcbor.Decode)
|
||||||
|
|
||||||
node, err := ipld.Decode(b, dagcbor.Decode)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tkn, err := fromIPLD[T](node)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tkn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
||||||
@@ -127,113 +120,82 @@ func FromDagJsonReader[T Tokener](r io.Reader) (T, error) {
|
|||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// Tokener is invalid.
|
// Tokener is invalid.
|
||||||
func FromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
func FromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
||||||
undef := *new(T)
|
zero := *new(T)
|
||||||
|
|
||||||
tkn, err := fromIPLD[T](node)
|
info, err := Inspect(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tkn, nil
|
if info.Tag != zero.Tag() {
|
||||||
}
|
return zero, errors.New("data doesn't match the expected type")
|
||||||
|
|
||||||
func fromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
|
||||||
undef := *new(T)
|
|
||||||
|
|
||||||
signatureNode, err := node.LookupByIndex(0)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
signature, err := signatureNode.AsBytes()
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sigPayloadNode, err := node.LookupByIndex(1)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
varsigHeaderNode, err := sigPayloadNode.LookupByString(varsigHeaderKey)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenPayloadNode, err := sigPayloadNode.LookupByString(undef.Tag())
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This needs to be done before converting this node to its schema
|
// This needs to be done before converting this node to its schema
|
||||||
// representation (afterwards, the field might be renamed os it's safer
|
// representation (afterwards, the field might be renamed os it's safer
|
||||||
// to use the wire name).
|
// to use the wire name).
|
||||||
issuerNode, err := tokenPayloadNode.LookupByString("iss")
|
issuerNode, err := info.tokenPayloadNode.LookupByString("iss")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replaces the datamodel.Node in tokenPayloadNode with a
|
// Replaces the datamodel.Node in tokenPayloadNode with a
|
||||||
// schema.TypedNode so that we can cast it to a *token.Token after
|
// schema.TypedNode so that we can cast it to a *token.Token after
|
||||||
// unwrapping it.
|
// unwrapping it.
|
||||||
nb := undef.Prototype().Representation().NewBuilder()
|
nb := zero.Prototype().Representation().NewBuilder()
|
||||||
|
|
||||||
err = nb.AssignNode(tokenPayloadNode)
|
err = nb.AssignNode(info.tokenPayloadNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenPayloadNode = nb.Build()
|
tokenPayloadNode := nb.Build()
|
||||||
|
|
||||||
tokenPayload := bindnode.Unwrap(tokenPayloadNode)
|
tokenPayload := bindnode.Unwrap(tokenPayloadNode)
|
||||||
if tokenPayload == nil {
|
if tokenPayload == nil {
|
||||||
return undef, errors.New("failed to Unwrap the TokenPayload")
|
return zero, errors.New("failed to Unwrap the TokenPayload")
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn, ok := tokenPayload.(T)
|
tkn, ok := tokenPayload.(T)
|
||||||
if !ok {
|
if !ok {
|
||||||
return undef, errors.New("failed to assert the TokenPayload type as *token.Token")
|
return zero, errors.New("failed to assert the TokenPayload type as *token.Token")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the issuer's DID contains a public key with a type that
|
// Check that the issuer's DID contains a public key with a type that
|
||||||
// matches the VarsigHeader and then verify the SigPayload.
|
// matches the VarsigHeader and then verify the SigPayload.
|
||||||
issuer, err := issuerNode.AsString()
|
issuer, err := issuerNode.AsString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerDID, err := did.Parse(issuer)
|
issuerDID, err := did.Parse(issuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerPubKey, err := issuerDID.PubKey()
|
issuerPubKey, err := issuerDID.PubKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerVarsigHeader, err := varsig.Encode(issuerPubKey.Type())
|
issuerVarsigHeader, err := varsig.Encode(issuerPubKey.Type())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
varsigHeader, err := varsigHeaderNode.AsBytes()
|
if string(info.VarsigHeader) != string(issuerVarsigHeader) {
|
||||||
|
return zero, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: can we use the already serialized CBOR data here, instead of encoding again the payload?
|
||||||
|
data, err := ipld.Encode(info.sigPayloadNode, dagcbor.Encode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(varsigHeader) != string(issuerVarsigHeader) {
|
ok, err = issuerPubKey.Verify(data, info.Signature)
|
||||||
return undef, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ipld.Encode(sigPayloadNode, dagcbor.Encode)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, err = issuerPubKey.Verify(data, signature)
|
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
return undef, errors.New("failed to verify the token's signature")
|
return zero, errors.New("failed to verify the token's signature")
|
||||||
}
|
}
|
||||||
|
|
||||||
return tkn, nil
|
return tkn, nil
|
||||||
@@ -293,7 +255,7 @@ func ToIPLD(privKey crypto.PrivKey, token Tokener) (datamodel.Node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sigPayloadNode, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
|
sigPayloadNode, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
|
||||||
qp.MapEntry(ma, varsigHeaderKey, qp.Bytes(varsigHeader))
|
qp.MapEntry(ma, VarsigHeaderKey, qp.Bytes(varsigHeader))
|
||||||
qp.MapEntry(ma, token.Tag(), qp.Node(tokenPayloadNode))
|
qp.MapEntry(ma, token.Tag(), qp.Node(tokenPayloadNode))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -312,3 +274,121 @@ func ToIPLD(privKey crypto.PrivKey, token Tokener) (datamodel.Node, error) {
|
|||||||
qp.ListEntry(la, qp.Node(sigPayloadNode))
|
qp.ListEntry(la, qp.Node(sigPayloadNode))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindTag inspects the given token IPLD representation and extract the token tag.
|
||||||
|
func FindTag(node datamodel.Node) (string, error) {
|
||||||
|
sigPayloadNode, err := node.LookupByIndex(1)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if sigPayloadNode.Kind() != datamodel.Kind_Map {
|
||||||
|
return "", fmt.Errorf("unexpected type instead of map")
|
||||||
|
}
|
||||||
|
|
||||||
|
it := sigPayloadNode.MapIterator()
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for !it.Done() {
|
||||||
|
if i >= 2 {
|
||||||
|
return "", fmt.Errorf("expected two and only two fields in SigPayload")
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
k, _, err := it.Next()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := k.AsString()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(key, UCANTagPrefix) {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no token tag found")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Tag string
|
||||||
|
Signature []byte
|
||||||
|
VarsigHeader []byte
|
||||||
|
sigPayloadNode datamodel.Node // private, we don't want to expose that
|
||||||
|
tokenPayloadNode datamodel.Node // private, we don't want to expose that
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect inspects the given token IPLD representation and extract some envelope facts.
|
||||||
|
func Inspect(node datamodel.Node) (Info, error) {
|
||||||
|
var res Info
|
||||||
|
|
||||||
|
signatureNode, err := node.LookupByIndex(0)
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Signature, err = signatureNode.AsBytes()
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sigPayloadNode, err = node.LookupByIndex(1)
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.sigPayloadNode.Kind() != datamodel.Kind_Map {
|
||||||
|
return Info{}, fmt.Errorf("unexpected type instead of map")
|
||||||
|
}
|
||||||
|
|
||||||
|
it := res.sigPayloadNode.MapIterator()
|
||||||
|
foundVarsigHeader := false
|
||||||
|
foundTokenPayload := false
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for !it.Done() {
|
||||||
|
if i >= 2 {
|
||||||
|
return Info{}, fmt.Errorf("expected two and only two fields in SigPayload")
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
k, v, err := it.Next()
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := k.AsString()
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case key == VarsigHeaderKey:
|
||||||
|
foundVarsigHeader = true
|
||||||
|
res.VarsigHeader, err = v.AsBytes()
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(key, UCANTagPrefix):
|
||||||
|
foundTokenPayload = true
|
||||||
|
res.Tag = key
|
||||||
|
res.tokenPayloadNode = v
|
||||||
|
default:
|
||||||
|
return Info{}, fmt.Errorf("unexpected key type %q", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != 2 {
|
||||||
|
return Info{}, fmt.Errorf("expected two and only two fields in SigPayload: %d", i)
|
||||||
|
}
|
||||||
|
if !foundVarsigHeader {
|
||||||
|
return Info{}, errors.New("failed to find VarsigHeader field")
|
||||||
|
}
|
||||||
|
if !foundTokenPayload {
|
||||||
|
return Info{}, errors.New("failed to find TokenPayload field")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
@@ -3,12 +3,17 @@ package envelope_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/token/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecode(t *testing.T) {
|
func TestDecode(t *testing.T) {
|
||||||
@@ -149,3 +154,56 @@ func TestHash(t *testing.T) {
|
|||||||
require.Equal(t, hash1[:], hash2)
|
require.Equal(t, hash1[:], hash2)
|
||||||
require.Equal(t, hash1[:], hash3)
|
require.Equal(t, hash1[:], hash3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInspect(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
data := golden.Get(t, "example.dagcbor")
|
||||||
|
node, err := ipld.Decode(data, dagcbor.Decode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expSig, err := base64.RawStdEncoding.DecodeString("fPqfwL3iFpbw9SvBiq0DIbUurv9o6c36R08tC/yslGrJcwV51ghzWahxdetpEf6T5LCszXX9I/K8khvnmAxjAg")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
info, err := envelope.Inspect(node)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expSig, info.Signature)
|
||||||
|
assert.Equal(t, "ucan/example@v1.0.0-rc.1", info.Tag)
|
||||||
|
assert.Equal(t, []byte{0x34, 0xed, 0x1, 0x71}, info.VarsigHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzInspect(f *testing.F) {
|
||||||
|
data, err := os.ReadFile("testdata/example.dagcbor")
|
||||||
|
require.NoError(f, err)
|
||||||
|
|
||||||
|
f.Add(data)
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
node, err := ipld.Decode(data, dagcbor.Decode)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
_, err = envelope.Inspect(node)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzFindTag(f *testing.F) {
|
||||||
|
data, err := os.ReadFile("testdata/example.dagcbor")
|
||||||
|
require.NoError(f, err)
|
||||||
|
|
||||||
|
f.Add(data)
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
node, err := ipld.Decode(data, dagcbor.Decode)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
_, err = envelope.FindTag(node)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user