toolkit/client: fix FindProof to handle self-delegation properly

This commit is contained in:
Michael Muré
2025-12-08 19:01:51 +01:00
parent 4b3a0c590a
commit a7e698e4ec
3 changed files with 84 additions and 17 deletions

View File

@@ -197,7 +197,7 @@ func TestToken_ExecutionAllowed(t *testing.T) {
cmd: delegationtest.NominalCommand,
args: emptyArguments,
proofs: []cid.Cid{delegationtest.TokenBobBobCID},
err: invocation.ErrBrokenChain,
err: invocation.ErrWrongSub,
},
} {
t.Run(tc.name, func(t *testing.T) {
@@ -206,6 +206,8 @@ func TestToken_ExecutionAllowed(t *testing.T) {
tkn, err := invocation.New(tc.issuer.DID(), tc.cmd, didtest.PersonaAlice.DID(), tc.proofs, tc.opts...)
require.NoError(t, err)
t.Log(tkn.String())
err = tkn.ExecutionAllowed(delegationtest.GetDelegationLoader())
if tc.err != nil {

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)
})
}
}