6 Commits

Author SHA1 Message Date
Michael Muré
a7e698e4ec toolkit/client: fix FindProof to handle self-delegation properly 2025-12-08 20:25:28 +01:00
Michael Muré
4b3a0c590a invocation: add support for self-signed invocations (issuer=subject) 2025-12-08 18:11:58 +01:00
Michael Muré
4b99c9f1df Merge pull request #118 from alanshaw/ash/fix/sort-tokens
fix: sort tokens
2025-12-03 17:04:04 +01:00
Michael Muré
39694340cd Merge pull request #117 from ucan-wg/ash/fix/licence-name
fix: license name
2025-12-03 13:32:54 +01:00
Alan Shaw
e93e464977 fix: sort tokens 2025-12-03 12:19:27 +00:00
ash
823af27272 fix: license name
License is Apache-2.0 **OR** MIT i.e. not "and"/"+"

Signed-off-by: ash <alan138@gmail.com>
2025-11-02 22:02:28 +00:00
24 changed files with 452 additions and 668 deletions

View File

@@ -17,7 +17,7 @@
<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">
<img alt="Apache 2.0 OR MIT License" src="https://img.shields.io/badge/License-Apache--2.0_OR_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">
@@ -73,4 +73,4 @@ Artwork by [Bruno Monts](https://www.instagram.com/bruno_monts). Thank you [Rene
## License
This project is licensed under the double license [Apache 2.0 + MIT](https://github.com/ucan-wg/go-ucan/blob/v1/LICENSE.md).
This project is licensed under the dual license [Apache 2.0 OR MIT](https://github.com/ucan-wg/go-ucan/blob/v1/LICENSE.md).

View File

@@ -3,6 +3,7 @@ package container
import (
"bytes"
"io"
"slices"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/cbor"
@@ -109,8 +110,13 @@ func (ctn Writer) toWriter(header header, w io.Writer) (err error) {
}()
node, err := qp.BuildMap(basicnode.Prototype.Any, 1, func(ma datamodel.MapAssembler) {
qp.MapEntry(ma, containerVersionTag, qp.List(int64(len(ctn)), func(la datamodel.ListAssembler) {
for data, _ := range ctn {
qp.ListEntry(la, qp.Bytes([]byte(data)))
tokens := make([][]byte, 0, len(ctn))
for data := range ctn {
tokens = append(tokens, []byte(data))
}
slices.SortFunc(tokens, bytes.Compare)
for _, data := range tokens {
qp.ListEntry(la, qp.Bytes(data))
}
}))
})

View File

@@ -238,7 +238,7 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
tkn.issuer, err = did.Parse(m.Iss)
if err != nil {
return nil, fmt.Errorf("parse iss: %w", err)
return nil, fmt.Errorf("parse issuer: %w", err)
}
if tkn.audience, err = did.Parse(m.Aud); err != nil {

View File

@@ -71,6 +71,25 @@ type generator struct {
chains []proof
}
func (g *generator) createSelfDelegations(personas []didtest.Persona) error {
for _, persona := range personas {
_, err := g.createDelegation(newDelegationParams{
privKey: persona.PrivKey(),
aud: persona.DID(),
cmd: delegationtest.NominalCommand,
pol: policytest.EmptyPolicy,
sub: persona.DID(),
opts: []delegation.Option{
delegation.WithNonce(constantNonce),
},
}, persona.Name()+persona.Name(), noopVariant())
if err != nil {
return err
}
}
return nil
}
func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari variant) error {
acc.name += personas[0].Name()

View File

@@ -6,7 +6,11 @@ import (
func main() {
gen := &generator{}
err := gen.chainPersonas(didtest.Personas(), acc{}, noopVariant())
err := gen.createSelfDelegations(didtest.Personas())
if err != nil {
panic(err)
}
err = gen.chainPersonas(didtest.Personas(), acc{}, noopVariant())
if err != nil {
panic(err)
}

View File

@@ -8,6 +8,48 @@ import (
"github.com/ucan-wg/go-ucan/token/delegation"
)
var (
TokenAliceAliceCID = cid.MustParse("bafyreiddqsv5rrpcormtcs3dg7hzwjr2grxyyozc2f2surxdbnctdqpfzi")
TokenAliceAliceSealed = mustGetBundle(TokenAliceAliceCID).Sealed
TokenAliceAliceBundle = mustGetBundle(TokenAliceAliceCID)
TokenAliceAlice = mustGetBundle(TokenAliceAliceCID).Decoded
)
var (
TokenBobBobCID = cid.MustParse("bafyreid4dwdov4yijvnb7xxhcndsxifzw5yry4sm4frex6relttlnledo4")
TokenBobBobSealed = mustGetBundle(TokenBobBobCID).Sealed
TokenBobBobBundle = mustGetBundle(TokenBobBobCID)
TokenBobBob = mustGetBundle(TokenBobBobCID).Decoded
)
var (
TokenCarolCarolCID = cid.MustParse("bafyreiekuehdsubdfllqecsat4gsfveyqq6442ejuiqfsgu3tplrus5l3e")
TokenCarolCarolSealed = mustGetBundle(TokenCarolCarolCID).Sealed
TokenCarolCarolBundle = mustGetBundle(TokenCarolCarolCID)
TokenCarolCarol = mustGetBundle(TokenCarolCarolCID).Decoded
)
var (
TokenDanDanCID = cid.MustParse("bafyreigzd442yhyizbx54kd76ewxssh5owuxv26ziittnblnj4h3a555dm")
TokenDanDanSealed = mustGetBundle(TokenDanDanCID).Sealed
TokenDanDanBundle = mustGetBundle(TokenDanDanCID)
TokenDanDan = mustGetBundle(TokenDanDanCID).Decoded
)
var (
TokenErinErinCID = cid.MustParse("bafyreigl5lbogpzq7iyz6qkzhicv4zscu26j62k4ydgcqogdiqmks5tz7q")
TokenErinErinSealed = mustGetBundle(TokenErinErinCID).Sealed
TokenErinErinBundle = mustGetBundle(TokenErinErinCID)
TokenErinErin = mustGetBundle(TokenErinErinCID).Decoded
)
var (
TokenFrankFrankCID = cid.MustParse("bafyreic6hgmqf2vwszboldlqeobpy2plpkcmj4dhhug76akcnafb2pt6em")
TokenFrankFrankSealed = mustGetBundle(TokenFrankFrankCID).Sealed
TokenFrankFrankBundle = mustGetBundle(TokenFrankFrankCID)
TokenFrankFrank = mustGetBundle(TokenFrankFrankCID).Decoded
)
var (
TokenAliceBobCID = cid.MustParse("bafyreifa35rjstdm37cjudzs72ab22rnh5blny725khtapox63fnsj6pbe")
TokenAliceBobSealed = mustGetBundle(TokenAliceBobCID).Sealed
@@ -170,6 +212,12 @@ var (
)
var AllTokens = []*delegation.Token{
TokenAliceAlice,
TokenBobBob,
TokenCarolCarol,
TokenDanDan,
TokenErinErin,
TokenFrankFrank,
TokenAliceBob,
TokenBobCarol,
TokenCarolDan,
@@ -196,6 +244,12 @@ var AllTokens = []*delegation.Token{
}
var AllBundles = []delegation.Bundle{
TokenAliceAliceBundle,
TokenBobBobBundle,
TokenCarolCarolBundle,
TokenDanDanBundle,
TokenErinErinBundle,
TokenFrankFrankBundle,
TokenAliceBobBundle,
TokenBobCarolBundle,
TokenCarolDanBundle,
@@ -222,6 +276,12 @@ var AllBundles = []delegation.Bundle{
}
var cidToName = map[cid.Cid]string{
TokenAliceAliceCID: "TokenAliceAlice",
TokenBobBobCID: "TokenBobBob",
TokenCarolCarolCID: "TokenCarolCarol",
TokenDanDanCID: "TokenDanDan",
TokenErinErinCID: "TokenErinErin",
TokenFrankFrankCID: "TokenFrankFrank",
TokenAliceBobCID: "TokenAliceBob",
TokenBobCarolCID: "TokenBobCarol",
TokenCarolDanCID: "TokenCarolDan",

View File

@@ -1,115 +0,0 @@
package invocation_test
import (
_ "embed"
"fmt"
"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/codec/dagjson"
"github.com/ipld/go-ipld-prime/schema"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/token/delegation"
"github.com/ucan-wg/go-ucan/token/internal/envelope"
"github.com/ucan-wg/go-ucan/token/invocation"
)
// This comes from https://github.com/ucan-wg/spec/blob/main/fixtures/1.0.0/invocation.json
//
//go:embed testdata/interop_invocation.json
var interopInvocation []byte
//go:embed testdata/interop.ipldsch
var schemaBytes []byte
func fixturesType(t *testing.T) schema.Type {
ts, err := ipld.LoadSchemaBytes(schemaBytes)
require.NoError(t, err)
return ts.TypeByName("Fixtures")
}
type ErrorModel struct {
Name string
}
type VectorModel struct {
Name string
Description string
Invocation []byte
Proofs [][]byte
Error *ErrorModel
}
type FixturesModel struct {
Version string
Comments string
Valid []VectorModel
Invalid []VectorModel
}
func TestInterop(t *testing.T) {
var fixtures FixturesModel
_, err := ipld.Unmarshal(interopInvocation, dagjson.Decode, &fixtures, fixturesType(t))
require.NoError(t, err)
for _, vector := range fixtures.Valid {
t.Run("valid "+vector.Name, func(t *testing.T) {
err := decodeAndValidate(vector)
require.NoError(t, err, "execution should have been allowed for invocation with %s", vector.Description)
})
}
for _, vector := range fixtures.Invalid {
t.Run("invalid "+vector.Name, func(t *testing.T) {
err := decodeAndValidate(vector)
require.Error(t, err, "execution should not have been allowed for invocation because %s", vector.Description)
})
}
}
type mapDelegationLoader struct {
data map[cid.Cid]*delegation.Token
}
func (ml *mapDelegationLoader) GetDelegation(cid cid.Cid) (*delegation.Token, error) {
d, ok := ml.data[cid]
if !ok {
return nil, fmt.Errorf("delegation not found: %s", cid)
}
return d, nil
}
func decodeAndValidate(vector VectorModel) error {
inv, err := invocation.Decode(vector.Invocation, dagcbor.Decode)
if err != nil {
return err
}
proofs, err := decodeProofs(vector.Proofs)
if err != nil {
return err
}
err = inv.ExecutionAllowed(&mapDelegationLoader{proofs})
if err != nil {
return err
}
return nil
}
func decodeProofs(vectorProofs [][]byte) (map[cid.Cid]*delegation.Token, error) {
proofs := map[cid.Cid]*delegation.Token{}
for _, p := range vectorProofs {
dlg, err := delegation.Decode(p, dagcbor.Decode)
if err != nil {
return nil, err
}
c, err := envelope.CIDFromBytes(p)
if err != nil {
return nil, err
}
proofs[c] = dlg
}
return proofs, nil
}

View File

@@ -109,10 +109,23 @@ func New(iss did.DID, cmd command.Command, sub did.DID, prf []cid.Cid, opts ...O
return &tkn, nil
}
// NewSelfSigned is similar to New, but self-signs the invocation, and therefore does not require a proof.
// It's similar to having an invocation with a delegation from the invoker to itself.
// This can be useful in some protocols where the invoker is the same as the subject, or to prove ownership of a resource.
//
// You can read it as "(Issuer - I) executes (command) on itself".
func NewSelfSigned(iss did.DID, cmd command.Command, opts ...Option) (*Token, error) {
return New(iss, cmd, iss, nil, opts...)
}
// ExecutionAllowed verifies that the invocation respects the rules and can be executed.
// IMPORTANT: this function does NOT verify that the subject (and audience if set) makes sense in your context.
func (t *Token) ExecutionAllowed(loader delegation.Loader) error {
return t.executionAllowed(loader, t.arguments)
}
// ExecutionAllowedWithArgsHook is the same as ExecutionAllowed, but allows to modify the arguments before verifying them.
// IMPORTANT: this function does NOT verify that the subject (and audience if set) makes sense in your context.
func (t *Token) ExecutionAllowedWithArgsHook(loader delegation.Loader, hook func(args args.ReadOnly) (*args.Args, error)) error {
newArgs, err := hook(t.arguments.ReadOnly())
if err != nil {
@@ -204,6 +217,11 @@ func (t *Token) Cause() *cid.Cid {
return t.cause
}
// IsSelfSigned returns true if the token is self-signed, ie it has the same issuer and subject.
func (t *Token) IsSelfSigned() bool {
return t.issuer.Equal(t.subject)
}
// IsValidNow verifies that the token can be used at the current time, based on expiration or "not before" fields.
// This does NOT do any other kind of verifications.
func (t *Token) IsValidNow() bool {
@@ -276,7 +294,7 @@ func tokenFromModel(m tokenPayloadModel) (*Token, error) {
)
if tkn.issuer, err = did.Parse(m.Iss); err != nil {
return nil, fmt.Errorf("parse iss: %w", err)
return nil, fmt.Errorf("parse issuer: %w", err)
}
if tkn.subject, err = did.Parse(m.Sub); err != nil {

View File

@@ -3,6 +3,7 @@ package invocation_test
import (
_ "embed"
"testing"
"time"
"github.com/MetaMask/go-did-it/didtest"
"github.com/ipfs/go-cid"
@@ -18,144 +19,257 @@ import (
//go:embed testdata/new.dagjson
var newDagJson []byte
const (
missingTknCIDStr = "bafyreigwypmw6eul6vadi6g6lnfbsfo2zck7gfzsbjoroqs3djhnzzc7mm"
)
//go:embed testdata/selfsigned.dagjson
var selfsignedDagJson []byte
const missingTknCIDStr = "bafyreigwypmw6eul6vadi6g6lnfbsfo2zck7gfzsbjoroqs3djhnzzc7mm"
var emptyArguments = args.New()
func TestToken_ExecutionAllowed(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
issuer didtest.Persona
cmd command.Command
args *args.Args
proofs []cid.Cid
opts []invocation.Option
err error
}{
// Passes
{
name: "passes - only root",
issuer: didtest.PersonaBob,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: delegationtest.ProofAliceBob,
err: nil,
},
{
name: "passes - valid chain",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank,
err: nil,
},
{
name: "passes - proof chain attenuates command",
issuer: didtest.PersonaFrank,
cmd: delegationtest.AttenuatedCommand,
args: emptyArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank_ValidAttenuatedCommand,
err: nil,
},
{
name: "passes - invocation attenuates command",
issuer: didtest.PersonaFrank,
cmd: delegationtest.AttenuatedCommand,
args: emptyArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank,
err: nil,
},
{
name: "passes - arguments satisfy empty policy",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: policytest.SpecValidArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank,
err: nil,
},
{
name: "passes - arguments satisfy example policy",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: policytest.SpecValidArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy,
err: nil,
},
{
name: "passes - self-signed invocation doesn't require proof",
issuer: didtest.PersonaAlice,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: nil,
err: nil,
},
{
name: "passes - self-signed invocation accepts a delegation to itself",
issuer: didtest.PersonaAlice,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: []cid.Cid{delegationtest.TokenAliceAliceCID},
err: nil,
},
t.Run("passes - only root", func(t *testing.T) {
t.Parallel()
// Fails
{
name: "fails - no proof",
issuer: didtest.PersonaCarol,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: delegationtest.ProofEmpty,
err: invocation.ErrNoProof,
},
{
name: "fails - missing referenced delegation",
issuer: didtest.PersonaCarol,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: []cid.Cid{cid.MustParse(missingTknCIDStr), delegationtest.TokenAliceBobCID},
err: invocation.ErrMissingDelegation,
},
{
name: "fails - referenced delegation expired",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank_InvalidExpired,
err: invocation.ErrTokenInvalidNow,
},
{
name: "fails - referenced delegation inactive",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank_InvalidInactive,
err: invocation.ErrTokenInvalidNow,
},
{
name: "fails - last (or only) delegation not root",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: []cid.Cid{delegationtest.TokenErinFrankCID, delegationtest.TokenDanErinCID, delegationtest.TokenCarolDanCID},
err: invocation.ErrLastNotRoot,
},
{
name: "fails - broken chain",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: []cid.Cid{delegationtest.TokenCarolDanCID, delegationtest.TokenAliceBobCID},
err: invocation.ErrBrokenChain,
},
{
name: "fails - first not issued to invoker",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: []cid.Cid{delegationtest.TokenBobCarolCID, delegationtest.TokenAliceBobCID},
err: invocation.ErrBrokenChain,
},
{
name: "fails - proof chain expands command",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank_InvalidExpandedCommand,
err: invocation.ErrCommandNotCovered,
},
{
name: "fails - invocation expands command",
issuer: didtest.PersonaFrank,
cmd: delegationtest.ExpandedCommand,
args: emptyArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank,
err: invocation.ErrCommandNotCovered,
},
{
name: "fails - inconsistent subject",
issuer: didtest.PersonaFrank,
cmd: delegationtest.ExpandedCommand,
args: emptyArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank_InvalidSubject,
err: invocation.ErrWrongSub,
},
{
name: "fails - arguments don't satisfy example policy",
issuer: didtest.PersonaFrank,
cmd: delegationtest.NominalCommand,
args: policytest.SpecInvalidArguments,
proofs: delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy,
err: invocation.ErrPolicyNotSatisfied,
},
{
name: "fails - self-signed invocation refuses a delegation to itself for a different DID",
issuer: didtest.PersonaAlice,
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: []cid.Cid{delegationtest.TokenBobBobCID},
err: invocation.ErrWrongSub,
},
} {
t.Run(tc.name, func(t *testing.T) {
tc.opts = append(tc.opts, invocation.WithArguments(tc.args))
testPasses(t, didtest.PersonaBob, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBob)
})
tkn, err := invocation.New(tc.issuer.DID(), tc.cmd, didtest.PersonaAlice.DID(), tc.proofs, tc.opts...)
require.NoError(t, err)
t.Run("passes - valid chain", func(t *testing.T) {
t.Parallel()
t.Log(tkn.String())
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
})
err = tkn.ExecutionAllowed(delegationtest.GetDelegationLoader())
t.Run("passes - proof chain attenuates command", func(t *testing.T) {
t.Parallel()
if tc.err != nil {
require.ErrorIs(t, err, tc.err)
} else {
require.NoError(t, err)
}
})
}
}
testPasses(t, didtest.PersonaFrank, delegationtest.AttenuatedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidAttenuatedCommand)
})
const (
nonce = "6roDhGi0kiNriQAz7J3d+bOeoI/tj8ENikmQNbtjnD0"
subjectCmd = "/foo/bar"
)
t.Run("passes - invocation attenuates command", func(t *testing.T) {
t.Parallel()
func TestConstructors(t *testing.T) {
cmd, err := command.Parse(subjectCmd)
require.NoError(t, err)
testPasses(t, didtest.PersonaFrank, delegationtest.AttenuatedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
})
iat, err := time.Parse(time.RFC3339, "2100-01-01T00:00:00Z")
require.NoError(t, err)
t.Run("passes - arguments satisfy empty policy", func(t *testing.T) {
t.Parallel()
exp, err := time.Parse(time.RFC3339, "2200-01-01T00:00:00Z")
require.NoError(t, err)
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecValidArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
})
t.Run("passes - arguments satify example policy", func(t *testing.T) {
t.Parallel()
testPasses(t, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecValidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
})
t.Run("fails - no proof", func(t *testing.T) {
t.Parallel()
testFails(t, invocation.ErrNoProof, didtest.PersonaCarol, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofEmpty)
})
t.Run("fails - missing referenced delegation", func(t *testing.T) {
t.Parallel()
missingTknCID, err := cid.Parse(missingTknCIDStr)
t.Run("New", func(t *testing.T) {
tkn, err := invocation.New(
didtest.PersonaAlice.DID(), cmd, didtest.PersonaBob.DID(),
delegationtest.ProofAliceBob,
invocation.WithNonce([]byte(nonce)),
invocation.WithIssuedAt(iat),
invocation.WithExpiration(exp),
invocation.WithArgument("foo", "bar"),
invocation.WithMeta("baz", 123),
)
require.NoError(t, err)
prf := []cid.Cid{missingTknCID, delegationtest.TokenAliceBobCID}
testFails(t, invocation.ErrMissingDelegation, didtest.PersonaCarol, delegationtest.NominalCommand, emptyArguments, prf)
require.False(t, tkn.IsSelfSigned())
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
require.NoError(t, err)
require.JSONEq(t, string(newDagJson), string(data))
})
t.Run("fails - referenced delegation expired", func(t *testing.T) {
t.Parallel()
t.Run("Self-Signed", func(t *testing.T) {
tkn, err := invocation.NewSelfSigned(
didtest.PersonaAlice.DID(), cmd,
invocation.WithNonce([]byte(nonce)),
invocation.WithIssuedAt(iat),
invocation.WithExpiration(exp),
invocation.WithArgument("foo", "bar"),
invocation.WithMeta("baz", 123),
)
require.NoError(t, err)
testFails(t, invocation.ErrTokenInvalidNow, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidExpired)
require.True(t, tkn.IsSelfSigned())
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
require.NoError(t, err)
require.JSONEq(t, string(selfsignedDagJson), string(data))
})
t.Run("fails - referenced delegation inactive", func(t *testing.T) {
t.Parallel()
testFails(t, invocation.ErrTokenInvalidNow, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidInactive)
})
t.Run("fails - last (or only) delegation not root", func(t *testing.T) {
t.Parallel()
prf := []cid.Cid{delegationtest.TokenErinFrankCID, delegationtest.TokenDanErinCID, delegationtest.TokenCarolDanCID}
testFails(t, invocation.ErrLastNotRoot, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, prf)
})
t.Run("fails - broken chain", func(t *testing.T) {
t.Parallel()
prf := []cid.Cid{delegationtest.TokenCarolDanCID, delegationtest.TokenAliceBobCID}
testFails(t, invocation.ErrBrokenChain, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, prf)
})
t.Run("fails - first not issued to invoker", func(t *testing.T) {
t.Parallel()
prf := []cid.Cid{delegationtest.TokenBobCarolCID, delegationtest.TokenAliceBobCID}
testFails(t, invocation.ErrBrokenChain, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, prf)
})
t.Run("fails - proof chain expands command", func(t *testing.T) {
t.Parallel()
testFails(t, invocation.ErrCommandNotCovered, didtest.PersonaFrank, delegationtest.NominalCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidExpandedCommand)
})
t.Run("fails - invocation expands command", func(t *testing.T) {
t.Parallel()
testFails(t, invocation.ErrCommandNotCovered, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank)
})
t.Run("fails - inconsistent subject", func(t *testing.T) {
t.Parallel()
testFails(t, invocation.ErrWrongSub, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidSubject)
})
t.Run("passes - arguments satisfy example policy", func(t *testing.T) {
t.Parallel()
testFails(t, invocation.ErrPolicyNotSatisfied, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecInvalidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
})
}
func test(t *testing.T, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) error {
t.Helper()
opts = append(opts, invocation.WithArguments(args))
tkn, err := invocation.New(persona.DID(), cmd, didtest.PersonaAlice.DID(), prf, opts...)
require.NoError(t, err)
return tkn.ExecutionAllowed(delegationtest.GetDelegationLoader())
}
func testFails(t *testing.T, expErr error, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) {
err := test(t, persona, cmd, args, prf, opts...)
require.ErrorIs(t, err, expErr)
}
func testPasses(t *testing.T, persona didtest.Persona, cmd command.Command, args *args.Args, prf []cid.Cid, opts ...invocation.Option) {
err := test(t, persona, cmd, args, prf, opts...)
require.NoError(t, err)
}

View File

@@ -18,11 +18,11 @@ import (
// 1. When a token is read/unsealed from its containing envelope (`envelope` package):
// a. The envelope can be decoded.
// b. The envelope contains a Signature, VarsigHeader and Payload.
// c. The Payload contains an iss field that contains a valid `did:key`.
// d. The public key can be extracted from the `did:key`.
// e. The public key type is supported by go-ucan.
// c. The Payload contains an iss field that contains a valid DID.
// d. One or more public keys can be derived from the DID.
// e. One or more public keys are supported by go-ucan.
// f. The Signature can be decoded per the VarsigHeader.
// g. The SigPayload can be verified using the Signature and public key.
// g. The SigPayload can be verified using the Signature and one public key.
// h. The field key of the TokenPayload matches the expected tag.
//
// 2. When the token is created or passes step one (token constructor or decoder):
@@ -35,7 +35,7 @@ import (
// c. All the delegation must be active (nbf in the past or absent).
//
// 4. When the proof chain is being validated (verifyProofs below):
// a. There must be at least one delegation in the proof chain.
// a. Self-signed invocations (issuer == subject) are allowed and don't require further proof. Otherwise, proof is required.
// b. All referenced delegations must be available.
// c. The first proof must be issued to the Invoker (audience DID).
// d. The Issuer of each delegation must be the Audience in the next one.
@@ -51,8 +51,11 @@ import (
// - principal alignment
// - command alignment
func (t *Token) verifyProofs(delegations []*delegation.Token) error {
// There must be at least one delegation referenced - 4a
if len(delegations) < 1 {
// Self-signed invocations (issuer == subject) are allowed and don't require further proof. Otherwise, proof is required. - 4a
if len(delegations) == 0 && t.issuer.Equal(t.subject) {
return nil
}
if len(delegations) == 0 {
return ErrNoProof
}

View File

@@ -2,6 +2,7 @@ package invocation_test
import (
"bytes"
_ "embed"
"encoding/base64"
"testing"
@@ -14,9 +15,12 @@ import (
"github.com/ucan-wg/go-ucan/token/invocation"
)
//go:embed testdata/full_example.dagjson
var fullExampleDagJson []byte
const (
issuerPrivKeyCfg = "BeAgktAj8irGgWjp4PGk/fV67e5CcML/KRmmHSldco3etP5lRiuYQ+VVO/39ol3XXruJC8deSuBxoEXzgdYpYw=="
newCID = "zdpuB1NjhETofEUp5iYzoHjSc2KKgZvSoT6FBaLMoVzzsxiR1"
fullExampleCID = "zdpuB1NjhETofEUp5iYzoHjSc2KKgZvSoT6FBaLMoVzzsxiR1"
)
func TestSchemaRoundTrip(t *testing.T) {
@@ -30,12 +34,12 @@ func TestSchemaRoundTrip(t *testing.T) {
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
// function: DecodeDagJson() Seal() Unseal() EncodeDagJson()
p1, err := invocation.FromDagJson(newDagJson)
p1, err := invocation.FromDagJson(fullExampleDagJson)
require.NoError(t, err)
cborBytes, id, err := p1.ToSealed(privKey)
require.NoError(t, err)
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
assert.Equal(t, fullExampleCID, envelope.CIDToBase58BTC(id))
p2, c2, err := invocation.FromSealed(cborBytes)
require.NoError(t, err)
@@ -44,13 +48,13 @@ func TestSchemaRoundTrip(t *testing.T) {
readJson, err := p2.ToDagJson(privKey)
require.NoError(t, err)
assert.JSONEq(t, string(newDagJson), string(readJson))
assert.JSONEq(t, string(fullExampleDagJson), string(readJson))
})
t.Run("via streaming", func(t *testing.T) {
t.Parallel()
buf := bytes.NewBuffer(newDagJson)
buf := bytes.NewBuffer(fullExampleDagJson)
// format: dagJson --> PayloadModel --> dagCbor --> PayloadModel --> dagJson
// function: DecodeDagJson() Seal() Unseal() EncodeDagJson()
@@ -61,7 +65,7 @@ func TestSchemaRoundTrip(t *testing.T) {
cborBytes := &bytes.Buffer{}
id, err := p1.ToSealedWriter(cborBytes, privKey)
require.NoError(t, err)
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
assert.Equal(t, fullExampleCID, envelope.CIDToBase58BTC(id))
p2, c2, err := invocation.FromSealedReader(cborBytes)
require.NoError(t, err)
@@ -70,7 +74,7 @@ func TestSchemaRoundTrip(t *testing.T) {
readJson := &bytes.Buffer{}
require.NoError(t, p2.ToDagJsonWriter(readJson, privKey))
assert.JSONEq(t, string(newDagJson), readJson.String())
assert.JSONEq(t, string(fullExampleDagJson), readJson.String())
})
}

View File

@@ -0,0 +1 @@
[{"/":{"bytes":"tRKNRahqwdyR6OpytuGIdcYI7HxXvKI5I594zznCLbN2C6WP5f8FIfIQlo0Nnqg4xFgKjJGAbIEVqeCZdib1Dw"}},{"h":{"/":{"bytes":"NAHtAe0BE3E"}},"ucan/inv@1.0.0-rc.1":{"args":{"headers":{"Content-Type":"application/json"},"payload":{"body":"UCAN is great","draft":true,"title":"UCAN for Fun and Profit","topics":["authz","journal"]},"uri":"https://example.com/blog/posts"},"cmd":"/crud/create","exp":1753965668,"iss":"did:key:z6MkuScdGeTmbWubyoWWpPmX9wkwdZAshkTcLKb1bf4Cyj8N","meta":{"env":"development","tags":["blog","post","pr#123"]},"nonce":{"/":{"bytes":"BBR5znl7VpRof4ac"}},"prf":[{"/":"bafyreigx3qxd2cndpe66j2mdssj773ecv7tqd7wovcnz5raguw6lj7sjoe"},{"/":"bafyreib34ira254zdqgehz6f2bhwme2ja2re3ltcalejv4x4tkcveujvpa"},{"/":"bafyreibkb66tpo2ixqx3fe5hmekkbuasrod6olt5bwm5u5pi726mduuwlq"}],"sub":"did:key:z6MkuQU8kqxCAUeurotHyrnMgkMUBtJN8ozYxkwctnop4zzB"}}]

View File

@@ -1,18 +0,0 @@
type Fixtures struct {
version String
comments String
valid [Vector]
invalid [Vector]
}
type Vector struct {
name String
description String
invocation Bytes
proofs [Bytes]
error optional Error
}
type Error struct {
name String
}

View File

@@ -1,378 +0,0 @@
{
"comments": "Encoded as dag-json.",
"invalid": [
{
"description": "it has no proofs",
"error": {
"name": "InvalidClaim"
},
"invocation": {
"/": {
"bytes": "glhAF7fivUGyq7qB6p9O9XzzA3Fpct17AiqtTGLjz1soHaTI/ik/Q5vRALuuFD4BEZbpvR3+a710lA+JHIS9RqWVA6JhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaAY3N1Yng4ZGlkOmtleTp6Nk1rbUpjZVZvUVNIczQ1Y1JlRVhvTHRXbTF3b3NDRzhSTHhmS3doeG9xem9Ua0NkYXJnc6Blbm9uY2VQAQIDBAECAwQBAgMEAQIDBA"
}
},
"name": "no proof",
"proofs": []
},
{
"description": "a proof is not provided or resolvable externally",
"error": {
"name": "UnavailableProof"
},
"invocation": {
"/": {
"bytes": "glhALmBqgaPi6kKawSh5pxVkDOlGgdNefcmjvnJ3YXE/zX+T1zwRdF4I8qxOjTzRbYijDaShIgLR20i1nPpRO15qC6JhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaB2CpYJQABcRIgMI8CZFZ8CH7BynpUg0BDCfuktrmFJjLSu+KK/DXIDmpjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2RhcmdzoGVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
"name": "missing proof",
"proofs": []
},
{
"description": "a proof is expired",
"error": {
"name": "Expired"
},
"invocation": {
"/": {
"bytes": "glhAupTs833VJzKG3oeSpebiP/5P8AfBdRxEbUc9DxVRpbKXsysDTSnqftMLktOkWnlVmCGLiLzqh9uWKMQR4ERpCaJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaB2CpYJQABcRIgahqZy6Hcb2Q/Q6g1uJviOY/c7oHsoNyBTh+eDpoV3dJjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2RhcmdzoGVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
"name": "expired proof",
"proofs": [
{
"/": {
"bytes": "glhAet8bzLxBK7Oeqxt6t/eeTYyHb/wh7odbGXA8weuljc9COjyNhs/DaMd8YyQH2TF3UyB36oEKm/TDY3DGyvkXDqJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhwGmj2GDNjaXNzeDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemNwb2yAY3N1Yng4ZGlkOmtleTp6Nk1rbVQ5ajZmVlpxelhWOHUyd1ZWU3U0OWdZU1JZR1NRbmR1V1hGNmZvQUpycXplbm9uY2VQAQIDBAECAwQBAgMEAQIDBA"
}
}
]
},
{
"description": "a proof has a not before time in the future",
"error": {
"name": "TooEarly"
},
"invocation": {
"/": {
"bytes": "glhAbt3YbyszHCOW27S8A6Xy8fR/S2iDH2L8lYgyBoOkzgoC4Syeg2zcP/Hi7DXDobFrLlYWqgdpemdYKRe4jEOUBaJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaB2CpYJQABcRIghcgCW54v0moLFh3AXOyu5eUzEy73I5sonBNPd+6avkBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2RhcmdzoGVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
"name": "inactive proof",
"proofs": [
{
"/": {
"bytes": "glhAcWHiqJIcx2+8bfTXblonOaoi4if0kXWSZnw8ijBAZlblRspyJvt+YGSIMGSEEp/BIPmr2Qem3F/NqvASpitnBaJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xqGNhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y25iZhsAAAA6//RBf2Nwb2yAY3N1Yng4ZGlkOmtleTp6Nk1rbVQ5ajZmVlpxelhWOHUyd1ZWU3U0OWdZU1JZR1NRbmR1V1hGNmZvQUpycXplbm9uY2VQAQIDBAECAwQBAgMEAQIDBA"
}
}
]
},
{
"description": "the issuer of a delegation in the proof chain is not the audience of the next delegation",
"error": {
"name": "InvalidAudience"
},
"invocation": {
"/": {
"bytes": "glhAV3jsUznrpXG+hac6mx+04u5vGp0vhJBRCQ4qbYD7AlfswNdxHWNBNKnU8VE84xdGR5N7Xc6fmxJfX8SbLd//AaJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaC2CpYJQABcRIguIISrxoWsh3AnaF/wx3Skabg7tI8LNRsZi8txfnlPu7YKlglAAFxEiDBaGyFMdE9Md5+YWVv9anDlPXtGqNAmeBIYZifKHpV2WNzdWJ4OGRpZDprZXk6ejZNa2g3d0p0UmVDZWVUOXlEUjJuUjUyb21LQ2F5UzZ6Ymc4dG5XOEpvazlDSmhrZGFyZ3OgZW5vbmNlUAEBAwgBAQMIAQEDCAEBAwg"
}
},
"name": "proof principal alignment",
"proofs": [
{
"/": {
"bytes": "glhAg4P89/nYQzrLHgCddyhPslAuiA/mgfDkIZBRKI2vQs8ybbtLHYSW+uo4Zgfn4blseU4YABu1tJs8ClXSpJ5cDaJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIBjc3VieDhkaWQ6a2V5Ono2TWtoN3dKdFJlQ2VlVDl5RFIyblI1Mm9tS0NheVM2emJnOHRuVzhKb2s5Q0poa2Vub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
{
"/": {
"bytes": "glhA47UkXV3nVndtGRqPLlB2F3/TNDd5lg5F8++X6oqe+SENmVD8Jvj3+MwPSEW50vNlpQ3HXv8/RwXax5TFjU/7BaJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa2g3d0p0UmVDZWVUOXlEUjJuUjUyb21LQ2F5UzZ6Ymc4dG5XOEpvazlDSmhrY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWtoN3dKdFJlQ2VlVDl5RFIyblI1Mm9tS0NheVM2emJnOHRuVzhKb2s5Q0poa2Vub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "the audience of the delegation is not the issuer of the invocation",
"error": {
"name": "InvalidAudience"
},
"invocation": {
"/": {
"bytes": "glhA11XpH25/k+6xFkH3aiIZxEeFI2NE/9Iy4Di2VraiHbffqKeMlcSVN+DpE84xcdVbG/iv5e3kxFnoNVQHbaSyB6JhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaC2CpYJQABcRIgMbdCFygvZdJDnLIXS+18gygbucsf10G6i6Qnv5hx7ALYKlglAAFxEiDBaGyFMdE9Md5+YWVv9anDlPXtGqNAmeBIYZifKHpV2WNzdWJ4OGRpZDprZXk6ejZNa2g3d0p0UmVDZWVUOXlEUjJuUjUyb21LQ2F5UzZ6Ymc4dG5XOEpvazlDSmhrZGFyZ3OgZW5vbmNlUAEBAwgBAQMIAQEDCAEBAwg"
}
},
"name": "invocation principal alignment",
"proofs": [
{
"/": {
"bytes": "glhAvrf2Fq+KzyVLbMrHV+darhgqtO583QC8U7ZcPV6SgkN6r5fC/SbyzBcoaGxKgrRsE5wHZPXBBlaGAf4H6e40DqJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWtoN3dKdFJlQ2VlVDl5RFIyblI1Mm9tS0NheVM2emJnOHRuVzhKb2s5Q0poa2Vub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
{
"/": {
"bytes": "glhA47UkXV3nVndtGRqPLlB2F3/TNDd5lg5F8++X6oqe+SENmVD8Jvj3+MwPSEW50vNlpQ3HXv8/RwXax5TFjU/7BaJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa2g3d0p0UmVDZWVUOXlEUjJuUjUyb21LQ2F5UzZ6Ymc4dG5XOEpvazlDSmhrY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWtoN3dKdFJlQ2VlVDl5RFIyblI1Mm9tS0NheVM2emJnOHRuVzhKb2s5Q0poa2Vub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "the subject is not the same for every delegation in the proof chain",
"error": {
"name": "InvalidSubject"
},
"invocation": {
"/": {
"bytes": "glhA0Tyn7Eh9SFJnYJ4Q3/n0QYFdEnVHgHze9o1woHh9qXpBJIFy2e09r3dvziYIDi4aR98JpvoHb1oWSl/JxXyYDqJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaC2CpYJQABcRIgMzCHsHkGw9b93/fHkm1OAu7W39a8kV2no54Oc03pnALYKlglAAFxEiCAZApnv/CyrmAr4/yrhGrA5kKuHKIghMOkcz9tkg+nJGNzdWJ4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDZGFyZ3OgZW5vbmNlUAEBAwgBAQMIAQEDCAEBAwg"
}
},
"name": "proof subject alignment",
"proofs": [
{
"/": {
"bytes": "glhA18glIcesPTSsyxIvvSsnrLctcBHs20EEAUI+WSnOJBwBElj2b6z0UhR2pz5MSR8cRxPWdp5NffgA08Hn7EsrB6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
{
"/": {
"bytes": "glhA1IoOl5rpsUsdwbfKD6Ly/Tf6h90ODLY5ru2FSDJ9s2Z5/kLmFvBlypu9Ff0fnoa7+pKtVlDsucD5aCYINbhRC6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2Vub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "the subject of the invocation is not the same as the subject of the delegation",
"error": {
"name": "InvalidSubject"
},
"invocation": {
"/": {
"bytes": "glhA07DNIr7D+7fvC9HQjAFj0aWAUV5HAGohKLU15hOXgFcqETC2AfmWwuP9+F3c5ElM1Rb+6nbm4C3POSU3c7PuAKJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaC2CpYJQABcRIgeYBtHcIlGGAgkV3vk0qB0sDih8Kco3ny/NHWBzcsM4HYKlglAAFxEiCAZApnv/CyrmAr4/yrhGrA5kKuHKIghMOkcz9tkg+nJGNzdWJ4OGRpZDprZXk6ejZNa2g3d0p0UmVDZWVUOXlEUjJuUjUyb21LQ2F5UzZ6Ymc4dG5XOEpvazlDSmhrZGFyZ3OgZW5vbmNlUAEBAwgBAQMIAQEDCAEBAwg"
}
},
"name": "invocation subject alignment",
"proofs": [
{
"/": {
"bytes": "glhAlC+rtXKanMs7eUsDKDbzBoPI1RIoBhxs3nHh/XbBc9Ou5quAnDM987yV+PAgR6X8nmJ8hQozLL3yEJyCKhuyA6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2Vub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
{
"/": {
"bytes": "glhA1IoOl5rpsUsdwbfKD6Ly/Tf6h90ODLY5ru2FSDJ9s2Z5/kLmFvBlypu9Ff0fnoa7+pKtVlDsucD5aCYINbhRC6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2Vub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "the invocation is expired",
"error": {
"name": "Expired"
},
"invocation": {
"/": {
"bytes": "glhAdRZEsVZjlWYsJywI48Ejjc1qPkr68ChTUU6Fgeq1JNY+uTB93mRA+uk5RShNJOVvdjR2fWtqpIiKp1ar4NohBaJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cBpo9hgzY2lhdBpo9XuAY2lzc3g4ZGlkOmtleTp6Nk1rZ0d5a045QVJORmpFem93VnE0bUxQMmtMNE5zeUFhREdYZUpGUTVxRTFiZmdjcHJmgdgqWCUAAXESIDCPAmRWfAh+wcp6VINAQwn7pLa5hSYy0rviivw1yA5qY3N1Yng4ZGlkOmtleTp6Nk1rbUpjZVZvUVNIczQ1Y1JlRVhvTHRXbTF3b3NDRzhSTHhmS3doeG9xem9Ua0NkYXJnc6Blbm9uY2VQBQYHCAUGBwgFBgcIBQYHCA"
}
},
"name": "expired invocation",
"proofs": [
{
"/": {
"bytes": "glhAR4mfb8ah/x4Ec2ncbW1jfm4T/SSqX2rbe7XSLiXZPTRaSsdR94U/dyC0dviEpYjou21f0iC4DUho1DFzms/bAKJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemVub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "the signature of a proof is not verifiable",
"error": {
"name": "InvalidSignature"
},
"invocation": {
"/": {
"bytes": "glhAHU3J47ECvjF9KCPCsIMmur6KzCbZbN1aIhzr7omh4Fpsi+XvLrnTX699HZwgGK4tBf3pi+cFrpxxR10KHxRLAqJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaB2CpYJQABcRIgZ2PkUy2e7sFj+vPz64bBtCo2vEklHkkDcH2bcqqGZu1jc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2RhcmdzoGVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
"name": "invalid proof signature",
"proofs": [
{
"/": {
"bytes": "gkMBAgOiYWhINAHtAe0BE3FzdWNhbi9kbGdAMS4wLjAtcmMuMadjYXVkeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NjbWRpL21zZy9zZW5kY2V4cPZjaXNzeDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemNwb2yAY3N1Yng4ZGlkOmtleTp6Nk1rbVQ5ajZmVlpxelhWOHUyd1ZWU3U0OWdZU1JZR1NRbmR1V1hGNmZvQUpycXplbm9uY2VQAQIDBAECAwQBAgMEAQIDBA"
}
}
]
},
{
"description": "the signature of the invocation is not verifiable",
"error": {
"name": "InvalidSignature"
},
"invocation": {
"/": {
"bytes": "gkMBAgOiYWhINAHtAe0BE3FzdWNhbi9pbnZAMS4wLjAtcmMuMahjY21kaS9tc2cvc2VuZGNleHD2Y2lhdBpo9XuAY2lzc3g4ZGlkOmtleTp6Nk1rZ0d5a045QVJORmpFem93VnE0bUxQMmtMNE5zeUFhREdYZUpGUTVxRTFiZmdjcHJmgGNzdWJ4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDZGFyZ3OgZW5vbmNlUAECAwQBAgMEAQIDBAECAwQ"
}
},
"name": "invalid invocation signature",
"proofs": []
},
{
"description": "the root delegation has a null subject",
"error": {
"name": "InvalidClaim"
},
"invocation": {
"/": {
"bytes": "glhAOf76yuhkNFory2OB/FwO+CNAk5gwLG4/Js0G0BcC722fg2ryeoNUG+c55CNufBk+fgH2i8ETe+fzMC0MKju1BqJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaB2CpYJQABcRIgt6n94T9vRWPC57vQapzS7XNBzK9V3I2xeygms8qzRiBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2RhcmdzoGVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
"name": "invalid powerline",
"proofs": [
{
"/": {
"bytes": "glhA33+8rqRZRkeqDa0Yw2ihnWeEDIJMRw0NhO9GwJo4Y106Yw6FpzxZZAAHrhHwrSc5KIsz1hjnD9j+6N1IQiz0B6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIBjc3Vi9mVub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "the invocation violates a policy set in an delegation",
"error": {
"name": "MatchError"
},
"invocation": {
"/": {
"bytes": "glhAEthKYbrG9TZn8VtgFRXlLPvkNzruKldy9W0Fsz2uPG7QMwCCSLKVCZ6m6tkleuvfNjFHS7oUPTzlAJoVDPbOA6JhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaB2CpYJQABcRIgxwdNm1TWllZdDo20zoLeaPUWN3JXnT1yd1QNRXaLPyFjc3VieDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemRhcmdzoWZhbnN3ZXIYKWVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
"name": "policy violation",
"proofs": [
{
"/": {
"bytes": "glhAJjUfUFt3TlI4pdVLEsOiqy60JIBJ7UPie3bvIdCtPa8pizVxOALlXPXgiPbY04X91l/pqfjLmjfvO0gukE/zBaJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIGDYj09Zy5hbnN3ZXIYKmNzdWJ4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6ZW5vbmNlUAECAwQBAgMEAQIDBAECAwQ"
}
}
]
}
],
"valid": [
{
"description": "no proofs, the subject is the issuer so no proof is necessary",
"invocation": {
"/": {
"bytes": "glhAUv3MZ6zkolZCY2CH7qdfIvjMlUTWsLwJ0YJSse71Y6CMxWZiRmCmSQnzF/rWjxqeWibDsAdnwbgeU5ZdehR/CqJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaAY3N1Yng4ZGlkOmtleTp6Nk1rZ0d5a045QVJORmpFem93VnE0bUxQMmtMNE5zeUFhREdYZUpGUTVxRTFiZmdkYXJnc6Blbm9uY2VQAQIDBAECAwQBAgMEAQIDBA"
}
},
"name": "self signed",
"proofs": []
},
{
"description": "a single proof that has no expiry",
"invocation": {
"/": {
"bytes": "glhAdcYl9DBuAPXRsY+qsB2kemOcTvJ6vIKAsf3gIYL7imHQNAevH5sW2bKgb9btUM2Xjx9KD9PMHyEeM3Ls0bKJA6JhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaB2CpYJQABcRIgMI8CZFZ8CH7BynpUg0BDCfuktrmFJjLSu+KK/DXIDmpjc3VieDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemRhcmdzoGVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
"name": "single non-time bounded proof",
"proofs": [
{
"/": {
"bytes": "glhAR4mfb8ah/x4Ec2ncbW1jfm4T/SSqX2rbe7XSLiXZPTRaSsdR94U/dyC0dviEpYjou21f0iC4DUho1DFzms/bAKJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemVub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "a single proof that has no expiry and is active (a not before timestamp in the past)",
"invocation": {
"/": {
"bytes": "glhAOmm+7QD5uqpwCTiJuxauUWr0gTclEBpgddgeVQ+U2t8zEpVESwB+LVts+7IDSCfdo7AWAqg7hRBfc4xHXJ/XCqJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaB2CpYJQABcRIgTH8Q0Iflk+FIroLv27e/IMaijMXphLoS5grrbEfdOBxjc3VieDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemRhcmdzoGVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
"name": "single active non-expired proof",
"proofs": [
{
"/": {
"bytes": "glhATG1Om9LOQykef7Xp3WfygLsX1/4Fuul3y9BtWwHwwtXRAlKQg/TF45kmpiCEWPvO3fpeKFle3mrWV7dCEZNIAaJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xqGNhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y25iZhpo9hgzY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemVub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "a proof chain more than one delegation long",
"invocation": {
"/": {
"bytes": "glhA74q4zj+6ilxI0pO3LjdqWwwXha7edZX71ZxAb8AzD2/M/s4NykMa3YVKbpV5CiyB8onoFYNHTKAeIDqV4xVaAaJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaC2CpYJQABcRIgeYBtHcIlGGAgkV3vk0qB0sDih8Kco3ny/NHWBzcsM4HYKlglAAFxEiCAZApnv/CyrmAr4/yrhGrA5kKuHKIghMOkcz9tkg+nJGNzdWJ4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDZGFyZ3OgZW5vbmNlUAEBAwgBAQMIAQEDCAEBAwg"
}
},
"name": "multiple proofs",
"proofs": [
{
"/": {
"bytes": "glhAlC+rtXKanMs7eUsDKDbzBoPI1RIoBhxs3nHh/XbBc9Ou5quAnDM987yV+PAgR6X8nmJ8hQozLL3yEJyCKhuyA6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2Vub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
{
"/": {
"bytes": "glhA1IoOl5rpsUsdwbfKD6Ly/Tf6h90ODLY5ru2FSDJ9s2Z5/kLmFvBlypu9Ff0fnoa7+pKtVlDsucD5aCYINbhRC6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2Vub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "a proof chain more than one delegation long where one or more proofs have a not before time in the past",
"invocation": {
"/": {
"bytes": "glhAf9uM0B1/N4AncATj1QGvs1s2OwR9p2Ha6s8zpbgFEVvQ2sUYZhsUQQ58TIOgzipukcVsipU7NbxJwWFGy/iEC6JhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaC2CpYJQABcRIgWsLKG1xNCqD8GkqaZ/HxOEtMcnBqSebL3O7sCjUa8lXYKlglAAFxEiCAZApnv/CyrmAr4/yrhGrA5kKuHKIghMOkcz9tkg+nJGNzdWJ4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDZGFyZ3OgZW5vbmNlUAEBAwgBAQMIAQEDCAEBAwg"
}
},
"name": "multiple active proofs",
"proofs": [
{
"/": {
"bytes": "glhAGEQPkrF++2YMScD/UnB2gaGhwENHr4uUvGW5vIWmL5845cVBU5uxBknS5dVD+FU5rtuhH7JsExMsw9Gbvn6uBqJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xqGNhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y25iZhpo9hgzY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2Vub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
{
"/": {
"bytes": "glhA1IoOl5rpsUsdwbfKD6Ly/Tf6h90ODLY5ru2FSDJ9s2Z5/kLmFvBlypu9Ff0fnoa7+pKtVlDsucD5aCYINbhRC6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2Vub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "a proof chain with a powerline delegation (null value for subject)",
"invocation": {
"/": {
"bytes": "glhAziUOhJX5mwR8NeB0oeN3N114ud9NPX4VSsZl5pvMwv1/J0y4YMO4dMnZMP4luZWVdMJDh384Ttf/p7U0tslaDKJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaC2CpYJQABcRIgYLbz3tTH2Ehxb9e7SjBbdU2a29964JR6TvWS9W0xlxbYKlglAAFxEiCAZApnv/CyrmAr4/yrhGrA5kKuHKIghMOkcz9tkg+nJGNzdWJ4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDZGFyZ3OgZW5vbmNlUAEBAwgBAQMIAQEDCAEBAwg"
}
},
"name": "powerline",
"proofs": [
{
"/": {
"bytes": "glhAGIeARv6xTuoTDPhUnUfrJ+nNTp1DIhvYzmt55gB2vqAzlei5W5QfvGcSOlnZtCGmQjf/LT2pHFGTDJOqvrScB6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIBjc3Vi9mVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
{
"/": {
"bytes": "glhA1IoOl5rpsUsdwbfKD6Ly/Tf6h90ODLY5ru2FSDJ9s2Z5/kLmFvBlypu9Ff0fnoa7+pKtVlDsucD5aCYINbhRC6JhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDY3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttSmNlVm9RU0hzNDVjUmVFWG9MdFdtMXdvc0NHOFJMeGZLd2h4b3F6b1RrQ2Vub25jZVABAgMEAQIDBAECAwQBAgME"
}
}
]
},
{
"description": "a policy that matches the invocation arguments",
"invocation": {
"/": {
"bytes": "glhA8GJF1ltl/tiqkXXMVjB9eJMVkWJlbPn9wEE6y2nr1tySr1yjBcEaGZxpIF5MBxgoSn6vNVXSbwR7EBSn6shxBqJhaEg0Ae0B7QETcXN1Y2FuL2ludkAxLjAuMC1yYy4xqGNjbWRpL21zZy9zZW5kY2V4cPZjaWF0Gmj1e4BjaXNzeDhkaWQ6a2V5Ono2TWtnR3lrTjlBUk5GakV6b3dWcTRtTFAya0w0TnN5QWFER1hlSkZRNXFFMWJmZ2NwcmaB2CpYJQABcRIgxwdNm1TWllZdDo20zoLeaPUWN3JXnT1yd1QNRXaLPyFjc3VieDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemRhcmdzoWZhbnN3ZXIYKmVub25jZVAFBgcIBQYHCAUGBwgFBgcI"
}
},
"name": "policy match",
"proofs": [
{
"/": {
"bytes": "glhAJjUfUFt3TlI4pdVLEsOiqy60JIBJ7UPie3bvIdCtPa8pizVxOALlXPXgiPbY04X91l/pqfjLmjfvO0gukE/zBaJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa2dHeWtOOUFSTkZqRXpvd1ZxNG1MUDJrTDROc3lBYURHWGVKRlE1cUUxYmZnY2NtZGkvbXNnL3NlbmRjZXhw9mNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIGDYj09Zy5hbnN3ZXIYKmNzdWJ4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6ZW5vbmNlUAECAwQBAgMEAQIDBAECAwQ"
}
}
]
}
],
"version": "1.0.0-rc.1"
}

View File

@@ -1 +1 @@
[{"/":{"bytes":"tRKNRahqwdyR6OpytuGIdcYI7HxXvKI5I594zznCLbN2C6WP5f8FIfIQlo0Nnqg4xFgKjJGAbIEVqeCZdib1Dw"}},{"h":{"/":{"bytes":"NAHtAe0BE3E"}},"ucan/inv@1.0.0-rc.1":{"args":{"headers":{"Content-Type":"application/json"},"payload":{"body":"UCAN is great","draft":true,"title":"UCAN for Fun and Profit","topics":["authz","journal"]},"uri":"https://example.com/blog/posts"},"cmd":"/crud/create","exp":1753965668,"iss":"did:key:z6MkuScdGeTmbWubyoWWpPmX9wkwdZAshkTcLKb1bf4Cyj8N","meta":{"env":"development","tags":["blog","post","pr#123"]},"nonce":{"/":{"bytes":"BBR5znl7VpRof4ac"}},"prf":[{"/":"bafyreigx3qxd2cndpe66j2mdssj773ecv7tqd7wovcnz5raguw6lj7sjoe"},{"/":"bafyreib34ira254zdqgehz6f2bhwme2ja2re3ltcalejv4x4tkcveujvpa"},{"/":"bafyreibkb66tpo2ixqx3fe5hmekkbuasrod6olt5bwm5u5pi726mduuwlq"}],"sub":"did:key:z6MkuQU8kqxCAUeurotHyrnMgkMUBtJN8ozYxkwctnop4zzB"}}]
[{"/":{"bytes":"8BxXBbXtPVoqn/z804w2w2gZH9m6kT55ivv7u2kxqptAfDcFzlRWBu3YKE9ijfIezpa79Btq5ja0PpqwjfSLAw"}},{"h":{"/":{"bytes":"NAHtAe0BE3E"}},"ucan/inv@1.0.0-rc.1":{"args":{"foo":"bar"},"cmd":"/foo/bar","exp":7258118400,"iat":4102444800,"iss":"did:key:z6MknUz1mSj4pvS6aUUHekCHdUPv7HBhDyDBZQ2W3Vujc5qC","meta":{"baz":123},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"prf":[{"/":"bafyreifa35rjstdm37cjudzs72ab22rnh5blny725khtapox63fnsj6pbe"}],"sub":"did:key:z6Mkf4WtCwPDtamsZvBJA4eSVcE7vZuRPy5Skm4HaoQv81i1"}}]

View File

@@ -0,0 +1 @@
[{"/":{"bytes":"ejXoQIdp3OGXewEkfQF4Z4Vd8c3H0XF319dsNh5DEP/2l9Nt9H1IhMpks1+HXoYFOKN3QmtxpPMoYmf/rhKaAQ"}},{"h":{"/":{"bytes":"NAHtAe0BE3E"}},"ucan/inv@1.0.0-rc.1":{"args":{"foo":"bar"},"cmd":"/foo/bar","exp":7258118400,"iat":4102444800,"iss":"did:key:z6MknUz1mSj4pvS6aUUHekCHdUPv7HBhDyDBZQ2W3Vujc5qC","meta":{"baz":123},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"prf":[],"sub":"did:key:z6MknUz1mSj4pvS6aUUHekCHdUPv7HBhDyDBZQ2W3Vujc5qC"}}]

View File

@@ -21,8 +21,7 @@ import (
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
// Note: the implemented algorithm won't perform well with a large number of delegations.
func FindProof(dlgs func() iter.Seq[*delegation.Bundle], issuer did.DID, cmd command.Command, subject did.DID) []cid.Cid {
// TODO: maybe that should be part of delegation.Token directly?
dlgMatch := func(dlg *delegation.Token, issuer did.DID, cmd command.Command, subject did.DID) bool {
continuePath := func(dlg *delegation.Token, issuer did.DID, cmd command.Command, subject did.DID) bool {
// The Subject of each delegation must equal the invocation's Subject (or Audience if defined). - 4f
if !dlg.Subject().Equal(subject) {
return false
@@ -47,7 +46,7 @@ func FindProof(dlgs func() iter.Seq[*delegation.Bundle], issuer did.DID, cmd com
var candidateLeaf []*delegation.Bundle
for bundle := range dlgs() {
if !dlgMatch(bundle.Decoded, issuer, cmd, subject) {
if !continuePath(bundle.Decoded, issuer, cmd, subject) {
continue
}
candidateLeaf = append(candidateLeaf, bundle)
@@ -83,7 +82,12 @@ func FindProof(dlgs func() iter.Seq[*delegation.Bundle], issuer did.DID, cmd com
// find parent delegation for our current delegation
for candidate := range dlgs() {
if !dlgMatch(candidate.Decoded, at.Decoded.Issuer(), at.Decoded.Command(), subject) {
// Prune the delegations that don't match the current proof.
if !continuePath(candidate.Decoded, at.Decoded.Issuer(), at.Decoded.Command(), subject) {
continue
}
// Prune the self-delegations as they can't get us closer to what we are looking for.
if candidate.Decoded.Issuer().Equal(candidate.Decoded.Audience()) {
continue
}

View File

@@ -4,7 +4,9 @@ import (
"iter"
"testing"
"github.com/MetaMask/go-did-it"
"github.com/MetaMask/go-did-it/didtest"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/command"
@@ -23,17 +25,76 @@ func TestFindProof(t *testing.T) {
}
}
require.Equal(t, delegationtest.ProofAliceBob,
FindProof(dlgs, didtest.PersonaBob.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
require.Equal(t, delegationtest.ProofAliceBobCarol,
FindProof(dlgs, didtest.PersonaCarol.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
require.Equal(t, delegationtest.ProofAliceBobCarolDan,
FindProof(dlgs, didtest.PersonaDan.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
require.Equal(t, delegationtest.ProofAliceBobCarolDanErin,
FindProof(dlgs, didtest.PersonaErin.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
require.Equal(t, delegationtest.ProofAliceBobCarolDanErinFrank,
FindProof(dlgs, didtest.PersonaFrank.DID(), delegationtest.NominalCommand, didtest.PersonaAlice.DID()))
for _, tc := range []struct {
name string
issuer did.DID
command command.Command
subject did.DID
expected []cid.Cid
}{
// Passes
{
name: "Alice --> Alice (self-delegation)",
issuer: didtest.PersonaAlice.DID(),
command: delegationtest.NominalCommand,
subject: didtest.PersonaAlice.DID(),
expected: []cid.Cid{delegationtest.TokenAliceAliceCID},
},
{
name: "Alice --> Bob",
issuer: didtest.PersonaBob.DID(),
command: delegationtest.NominalCommand,
subject: didtest.PersonaAlice.DID(),
expected: delegationtest.ProofAliceBob,
},
{
name: "Alice --> Bob --> Carol",
issuer: didtest.PersonaCarol.DID(),
command: delegationtest.NominalCommand,
subject: didtest.PersonaAlice.DID(),
expected: delegationtest.ProofAliceBobCarol,
},
{
name: "Alice --> Bob --> Carol --> Dan",
issuer: didtest.PersonaDan.DID(),
command: delegationtest.NominalCommand,
subject: didtest.PersonaAlice.DID(),
expected: delegationtest.ProofAliceBobCarolDan,
},
{
name: "Alice --> Bob --> Carol --> Dan --> Erin",
issuer: didtest.PersonaErin.DID(),
command: delegationtest.NominalCommand,
subject: didtest.PersonaAlice.DID(),
expected: delegationtest.ProofAliceBobCarolDanErin,
},
{
name: "Alice --> Bob --> Carol --> Dan --> Erin --> Frank",
issuer: didtest.PersonaFrank.DID(),
command: delegationtest.NominalCommand,
subject: didtest.PersonaAlice.DID(),
expected: delegationtest.ProofAliceBobCarolDanErinFrank,
},
// wrong command
require.Empty(t, FindProof(dlgs, didtest.PersonaBob.DID(), command.New("foo"), didtest.PersonaAlice.DID()))
// Fails
{
name: "wrong command",
issuer: didtest.PersonaBob.DID(),
command: command.New("foo"),
subject: didtest.PersonaAlice.DID(),
expected: nil,
},
{
name: "wrong subject",
issuer: didtest.PersonaBob.DID(),
command: delegationtest.NominalCommand,
subject: didtest.PersonaDan.DID(),
expected: nil,
},
} {
t.Run(tc.name, func(t *testing.T) {
res := FindProof(dlgs, tc.issuer, tc.command, tc.subject)
require.Equal(t, tc.expected, res)
})
}
}