Files
ucan/toolkit/client/proof.go

99 lines
3.3 KiB
Go
Raw Normal View History

2024-12-05 16:43:20 +01:00
package client
import (
"iter"
"math"
"code.sonr.org/go/did-it"
2024-12-05 16:43:20 +01:00
"github.com/ipfs/go-cid"
2025-08-05 12:11:20 +02:00
"code.sonr.org/go/ucan/pkg/command"
"code.sonr.org/go/ucan/token/delegation"
2024-12-05 16:43:20 +01:00
)
// FindProof find in the pool the best (shortest, smallest in bytes) chain of delegation(s) matching the given invocation parameters.
// - issuer: the DID of the client, also the issuer of the invocation token
// - cmd: the command to execute
// - subject: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
2025-01-16 15:16:01 +01:00
// The returned delegation chain is ordered starting from the leaf (the one matching the invocation) to the root
// (the one given by the service).
// Note: you can read it as "(issuer) wants to do (cmd) on (subject)".
2024-12-05 16:43:20 +01:00
// 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 {
2024-12-09 15:58:07 +01:00
// 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 {
// The Subject of each delegation must equal the invocation's Subject (or Audience if defined). - 4f
2025-08-05 12:11:20 +02:00
if !dlg.Subject().Equal(subject) {
2024-12-09 15:58:07 +01:00
return false
2024-12-05 16:43:20 +01:00
}
// The first proof must be issued to the Invoker (audience DID). - 4c
// The Issuer of each delegation must be the Audience in the next one. - 4d
2025-08-05 12:11:20 +02:00
if !dlg.Audience().Equal(issuer) {
2024-12-09 15:58:07 +01:00
return false
2024-12-05 16:43:20 +01:00
}
// The command of each delegation must "allow" the one before it. - 4g
if !dlg.Command().Covers(cmd) {
2024-12-09 15:58:07 +01:00
return false
2024-12-05 16:43:20 +01:00
}
// Time bound - 3b, 3c
if !dlg.IsValidNow() {
2024-12-09 15:58:07 +01:00
return false
2024-12-05 16:43:20 +01:00
}
2024-12-09 15:58:07 +01:00
return true
}
// STEP 1: Find the possible leaf delegations, directly matching the invocation parameters
var candidateLeaf []*delegation.Bundle
2024-12-05 16:43:20 +01:00
2024-12-09 15:58:07 +01:00
for bundle := range dlgs() {
2024-12-12 16:58:02 +01:00
if !dlgMatch(bundle.Decoded, issuer, cmd, subject) {
2024-12-09 15:58:07 +01:00
continue
}
2024-12-05 16:43:20 +01:00
candidateLeaf = append(candidateLeaf, bundle)
}
2024-12-09 15:58:07 +01:00
// STEP 2: Perform a depth-first search on the DAG of connected delegations, for each of our candidates
2024-12-05 16:43:20 +01:00
type state struct {
bundle *delegation.Bundle
path []cid.Cid
size int
}
var bestSize = math.MaxInt
var bestProof []cid.Cid
for _, leaf := range candidateLeaf {
var stack = []state{{bundle: leaf, path: []cid.Cid{leaf.Cid}, size: len(leaf.Sealed)}}
for len(stack) > 0 {
// dequeue a delegation
cur := stack[len(stack)-1]
stack = stack[:len(stack)-1]
at := cur.bundle
// if it's a root delegation, we found a valid proof
2025-08-05 12:11:20 +02:00
if at.Decoded.Issuer().Equal(at.Decoded.Subject()) {
2024-12-05 16:43:20 +01:00
if len(bestProof) == 0 || len(cur.path) < len(bestProof) || len(cur.path) == len(bestProof) && cur.size < bestSize {
bestProof = append([]cid.Cid{}, cur.path...) // make a copy
bestSize = cur.size
continue
}
}
// find parent delegation for our current delegation
for candidate := range dlgs() {
if !dlgMatch(candidate.Decoded, at.Decoded.Issuer(), at.Decoded.Command(), subject) {
2024-12-05 16:43:20 +01:00
continue
}
newPath := append([]cid.Cid{}, cur.path...) // make a copy
newPath = append(newPath, candidate.Cid)
stack = append(stack, state{bundle: candidate, path: newPath, size: cur.size + len(candidate.Sealed)})
}
}
}
return bestProof
}