104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
package client
|
|
|
|
import (
|
|
"iter"
|
|
"math"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/ucan-wg/go-ucan/did"
|
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
|
"github.com/ucan-wg/go-ucan/token/delegation"
|
|
)
|
|
|
|
// FindProof find in the pool the best (shortest, smallest in bytes) chain of delegation(s) matching the given invocation parameters.
|
|
// - cmd: the command to execute
|
|
// - issuer: the DID of the client, also the issuer of the invocation token
|
|
// - audience: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
|
|
// 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], cmd command.Command, iss did.DID, aud did.DID) []cid.Cid {
|
|
// Find the possible leaf delegations, directly matching the invocation parameters
|
|
var candidateLeaf []*delegation.Bundle
|
|
|
|
for bundle := range dlgs() {
|
|
dlg := bundle.Decoded
|
|
|
|
// The Subject of each delegation must equal the invocation's Audience field. - 4f
|
|
if dlg.Subject() != aud {
|
|
continue
|
|
}
|
|
// The first proof must be issued to the Invoker (audience DID). - 4c
|
|
// The Issuer of each delegation must be the Audience in the next one. - 4d
|
|
if dlg.Audience() != iss {
|
|
continue
|
|
}
|
|
// The command of each delegation must "allow" the one before it. - 4g
|
|
if !dlg.Command().Covers(cmd) {
|
|
continue
|
|
}
|
|
// Time bound - 3b, 3c
|
|
if !dlg.IsValidNow() {
|
|
continue
|
|
}
|
|
|
|
candidateLeaf = append(candidateLeaf, bundle)
|
|
}
|
|
|
|
type state struct {
|
|
bundle *delegation.Bundle
|
|
path []cid.Cid
|
|
size int
|
|
}
|
|
|
|
var bestSize = math.MaxInt
|
|
var bestProof []cid.Cid
|
|
|
|
// Perform a depth-first search on the DAG of connected delegations, for each of our candidates
|
|
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
|
|
if at.Decoded.Issuer() == at.Decoded.Subject() {
|
|
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() {
|
|
// The Subject of each delegation must equal the invocation's Audience field. - 4f
|
|
if candidate.Decoded.Subject() != aud {
|
|
continue
|
|
}
|
|
// The first proof must be issued to the Invoker (audience DID). - 4c
|
|
// The Issuer of each delegation must be the Audience in the next one. - 4d
|
|
if candidate.Decoded.Audience() != at.Decoded.Issuer() {
|
|
continue
|
|
}
|
|
// The command of each delegation must "allow" the one before it. - 4g
|
|
if !candidate.Decoded.Command().Covers(at.Decoded.Command()) {
|
|
continue
|
|
}
|
|
// Time bound - 3b, 3c
|
|
if !candidate.Decoded.IsValidNow() {
|
|
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
|
|
}
|