mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-05 03:55:58 +01:00
Algorithm port from Rust
This commit is contained in:
parent
e42e570911
commit
5b18a1b465
1 changed files with 639 additions and 0 deletions
|
@ -0,0 +1,639 @@
|
|||
// SPDX-FileCopyrightText: 2023 Heiko Schaefer <heiko@schaefer@name>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.wot.dijkstra
|
||||
|
||||
import org.pgpainless.wot.dijkstra.filter.*
|
||||
import org.pgpainless.wot.network.*
|
||||
import org.pgpainless.wot.query.Path
|
||||
import org.pgpainless.wot.query.Paths
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.Date
|
||||
import kotlin.math.min
|
||||
|
||||
// The amount of trust needed for a binding to be fully trusted.
|
||||
private const val FULLY_TRUSTED = 120
|
||||
|
||||
// The usual amount of trust assigned to a partially trusted
|
||||
// introducer.
|
||||
//
|
||||
// Normally, three partially trusted introducers are needed to
|
||||
// authenticate a binding. Thus, this is a third of `FULLY_TRUSTED`.
|
||||
private const val PARTIALLY_TRUSTED = 40
|
||||
|
||||
|
||||
/**
|
||||
* A path's cost.
|
||||
*
|
||||
* This is needed to do a Dijkstra.
|
||||
*/
|
||||
internal class Cost(
|
||||
// The path's length (i.e., the number of hops to the target).
|
||||
// *Less* is better (we prefer short paths).
|
||||
val length: Int,
|
||||
|
||||
// The trust amount along this path.
|
||||
// More is better (we prefer paths with a high trust amount).
|
||||
val amount: Int,
|
||||
) : Comparable<Cost> {
|
||||
|
||||
// "Greater than" means: the path is preferable, that is:
|
||||
// - It requires a small number of hops (length)
|
||||
// - It has a high "trust amount"
|
||||
override fun compareTo(other: Cost) =
|
||||
compareValuesBy(this, other, { -it.length }, { it.amount })
|
||||
|
||||
}
|
||||
|
||||
// We perform a Dijkstra in reserve from the target towards the roots.
|
||||
internal data class ForwardPointer(
|
||||
// If null, then the target.
|
||||
val next: EdgeComponent?
|
||||
)
|
||||
|
||||
class Query(
|
||||
private val network: Network,
|
||||
private val roots: Roots,
|
||||
private val certificationNetwork: Boolean) {
|
||||
|
||||
private val logger: Logger = LoggerFactory.getLogger(Query::class.java)
|
||||
|
||||
/**
|
||||
* Authenticates the specified binding.
|
||||
*
|
||||
* Enough independent paths are gotten to satisfy `target_trust_amount`.
|
||||
*
|
||||
* A fully trusted authentication is 120. If you require that a binding
|
||||
* be double authenticated, you can specify 240.
|
||||
*/
|
||||
fun authenticate(targetFpr: Fingerprint,
|
||||
targetUserid: String,
|
||||
targetTrustAmount: Int): Paths {
|
||||
|
||||
logger.debug("Query.authenticate")
|
||||
logger.debug("Authenticating <{}, '{}'>", targetFpr, targetUserid)
|
||||
logger.debug("Roots ({}):", roots.size())
|
||||
logger.debug(roots.roots().withIndex()
|
||||
.joinToString("\n") { (i, r) -> " $i: $r" })
|
||||
|
||||
val paths = Paths()
|
||||
|
||||
// This ChainFilter collects modifiers to the network over the course
|
||||
// of the calculation of this authentication.
|
||||
val filters = ChainFilter()
|
||||
|
||||
if (certificationNetwork) {
|
||||
// We're building a certification network.
|
||||
// (Treat all certifications like delegations with infinite depth
|
||||
// and no regular expressions.)
|
||||
filters.add(TrustedIntroducerFilter())
|
||||
} else {
|
||||
// We're building a regular authentication network.
|
||||
|
||||
// Model trust amounts of roots as a CapCertificateFilter
|
||||
// for roots that are not "FULLY_TRUSTED"
|
||||
if (roots.roots().any { it.amount != FULLY_TRUSTED }) {
|
||||
val caps = CapCertificateFilter()
|
||||
|
||||
roots.roots().forEach {
|
||||
if (it.amount != FULLY_TRUSTED) {
|
||||
caps.cap(it.fingerprint, it.amount)
|
||||
}
|
||||
}
|
||||
|
||||
filters.add(caps)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a (partial) run of the Ford Fulkerson algorithm.
|
||||
//
|
||||
// (The Ford Fulkerson algorithm finds a path, computes a residual
|
||||
// network by subtracting that path, and then loops until no paths
|
||||
// remain)
|
||||
|
||||
|
||||
var progress = true
|
||||
|
||||
// On this iteration approach
|
||||
// [https://gitlab.com/sequoia-pgp/sequoia-wot/-/commit/ff006688155aaa3ee0c14b88bef1a143b0ecae23]
|
||||
//
|
||||
// "Better mimic GnuPG's trust root semantics
|
||||
//
|
||||
// If Alice considers Bob and Carol to be fully trusted, Alice has
|
||||
// certified Bob, and Bob has certified Carol, then Carol should be
|
||||
// considered a trust root, because she is certified by Bob, who is
|
||||
// considered a trust root, because he is certified by Alice.
|
||||
//
|
||||
// In other words, we need to iterate."
|
||||
nextPath@ while (progress && paths.amount < targetTrustAmount) {
|
||||
progress = false
|
||||
|
||||
for (selfSigned in listOf(true, false)) {
|
||||
val authPaths = backwardPropagate(targetFpr, targetUserid, selfSigned, filters)
|
||||
|
||||
// The paths returned by backward_propagate may overlap.
|
||||
// So we only use one (picking one of the best, by trust and length).
|
||||
//
|
||||
// Then we subtract the path from the network and run backward_propagate
|
||||
// again, if we haven't yet reached 'targetTrustAmount'.
|
||||
val bestPath = roots.fingerprints()
|
||||
.mapNotNull { authPaths[it] } // Only consider paths that start at a root.
|
||||
.maxWithOrNull(compareBy(
|
||||
// We want the *most* amount of trust,
|
||||
{ it.second }, // path amount
|
||||
// but the *shortest* path.
|
||||
{ -it.first.length }, // -path.len
|
||||
// Be predictable. Break ties based on the fingerprint of the root.
|
||||
{ it.first.root.fingerprint })
|
||||
)
|
||||
|
||||
if (bestPath != null) {
|
||||
val (path, amount) = bestPath
|
||||
|
||||
if (path.length == 1) {
|
||||
// This path is a root.
|
||||
//
|
||||
// We've used 'amount' of trust from this root, so we'll detract that amount
|
||||
// from that root, with a filter.
|
||||
val suppress = SuppressIssuerFilter()
|
||||
suppress.suppressIssuer(path.root.fingerprint, amount)
|
||||
filters.add(suppress)
|
||||
} else {
|
||||
// Add the path to the filter to create a residual
|
||||
// network without this path.
|
||||
val suppress = SuppressCertificationFilter()
|
||||
|
||||
suppress.suppressPath(path, amount)
|
||||
filters.add(suppress)
|
||||
}
|
||||
|
||||
paths.add(path, amount)
|
||||
progress = true
|
||||
|
||||
// Prefer paths where the target User ID is self-signed as long as possible.
|
||||
continue@nextPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a path in the network from one or multiple `roots` that
|
||||
* authenticates the target binding.
|
||||
*
|
||||
* If `roots` is empty, authenticated paths starting from any node
|
||||
* are returned.
|
||||
*
|
||||
* Implements the algorithm outlined in:
|
||||
* https://gitlab.com/sequoia-pgp/sequoia-wot/-/blob/main/spec/sequoia-wot.md#implementation-strategy
|
||||
*
|
||||
* Note: the algorithm prefers shorter paths to longer paths. So the
|
||||
* returned path(s) may not be optimal in terms of the amount of trust.
|
||||
* To compensate for this, the caller should run the algorithm again on
|
||||
* a residual network.
|
||||
*
|
||||
* `selfSigned` picks between two variants of this algorithm. Each of the
|
||||
* modes finds a distinct subset of authenticated paths:
|
||||
*
|
||||
* - If `true`, this function only finds paths that end in a
|
||||
* self-certification, and only if the target node is
|
||||
* a trusted introducer.
|
||||
*
|
||||
* - If `false`, this function only finds paths that don't use
|
||||
* a self-certification as the last edge.
|
||||
*/
|
||||
private fun backwardPropagate(targetFpr: Fingerprint,
|
||||
targetUserid: String,
|
||||
selfSigned: Boolean,
|
||||
filter: CertificationFilter)
|
||||
: HashMap<Fingerprint, Pair<Path, Int>> {
|
||||
|
||||
logger.debug("Query.backward_propagate")
|
||||
|
||||
logger.debug("Roots (${roots.size()}):\n{}",
|
||||
roots.roots().withIndex().joinToString("\n") { (i, r) ->
|
||||
val fpr = r.fingerprint
|
||||
network.nodes[fpr]?.let { " {$i}. {$it}" } ?: " {$i}. {$fpr} (not found)"
|
||||
})
|
||||
|
||||
logger.debug("target: {}, {}", targetFpr, targetUserid)
|
||||
logger.debug("self signed: {}", selfSigned)
|
||||
|
||||
// If the node is not in the network, we're done.
|
||||
val target = network.nodes[targetFpr] ?: return hashMapOf()
|
||||
|
||||
// Make sure the target is valid (not expired and not revoked
|
||||
// at the reference time).
|
||||
if ((target.expirationTime != null) &&
|
||||
(target.expirationTime <= network.referenceTime.timestamp)) {
|
||||
logger.debug("{}: Target certificate is expired at reference time.", targetFpr)
|
||||
return hashMapOf()
|
||||
}
|
||||
|
||||
if (target.revocationState.isEffective(network.referenceTime)) {
|
||||
logger.debug("{}: Target certificate is revoked at reference time.", targetFpr)
|
||||
return hashMapOf()
|
||||
}
|
||||
|
||||
// Recall: the target doesn't need to have self-signed the
|
||||
// User ID to authenticate the User ID. But if the target has
|
||||
// revoked it, then it can't be authenticated.
|
||||
val targetUa: RevocationState? = target.userIds[targetUserid]
|
||||
targetUa?.let {
|
||||
if (it.isEffective(network.referenceTime)) {
|
||||
logger.debug("{}: Target user id is revoked at reference time.", targetFpr)
|
||||
return hashMapOf()
|
||||
}
|
||||
}
|
||||
|
||||
// Dijkstra.
|
||||
val bestNextNode: HashMap<Fingerprint, ForwardPointer> = HashMap()
|
||||
val queue: PairPriorityQueue<Fingerprint, Cost> = PairPriorityQueue()
|
||||
|
||||
fun fpCost(fp0: ForwardPointer): Cost {
|
||||
var fp = fp0
|
||||
|
||||
var amount = 120
|
||||
var length: Int = if (selfSigned) 1 else 0
|
||||
|
||||
while (fp.next != null) {
|
||||
val ec: EdgeComponent = fp.next!! // FIXME
|
||||
|
||||
val a = ec.trustAmount
|
||||
val d = ec.trustDepth
|
||||
|
||||
val value = FilterValues(d, a, null)
|
||||
|
||||
val r = filter.cost(ec, value, true)
|
||||
assert(r) { "cost function returned different result, but must be constant!" }
|
||||
|
||||
amount = min(value.amount, amount)
|
||||
length += 1
|
||||
fp = bestNextNode[ec.target.fingerprint]!!
|
||||
}
|
||||
|
||||
return Cost(length, amount)
|
||||
}
|
||||
|
||||
if (selfSigned) {
|
||||
// If the target is a trusted introducer and has self-signed
|
||||
// the User ID, then also consider that path.
|
||||
if (targetUa != null) {
|
||||
logger.debug("Target User ID is self signed.")
|
||||
|
||||
val cost = Cost(1, 120)
|
||||
queue.insert(targetFpr, cost)
|
||||
bestNextNode[targetFpr] = ForwardPointer(null)
|
||||
} else {
|
||||
logger.debug("Target User ID is not self-signed, but that is required.")
|
||||
return hashMapOf()
|
||||
}
|
||||
} else {
|
||||
val cost = Cost(0, 120)
|
||||
queue.insert(targetFpr, cost)
|
||||
bestNextNode[targetFpr] = ForwardPointer(null)
|
||||
}
|
||||
|
||||
|
||||
// Iterate over each node in the priority queue.
|
||||
while (true) {
|
||||
val signeeFpr = queue.pop()?.first ?: break
|
||||
|
||||
val it = roots.get(signeeFpr)
|
||||
if ((it != null) && (it.amount >= FULLY_TRUSTED)) {
|
||||
// XXX: Technically, we could stop if the root's trust
|
||||
// amount is at least the required trust amount.
|
||||
// Since we don't know it, and the maximum is
|
||||
// `FULLY_TRUSTED`, we use that.
|
||||
logger.debug("Skipping fully trust root: {}.", it.fingerprint)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
val signee = network.nodes[signeeFpr]!! // already looked up
|
||||
|
||||
// Get the signee's current forward pointer.
|
||||
//
|
||||
// We need to clone this, because we want to manipulate
|
||||
// 'distance' and we can't do that if there is a reference
|
||||
// to something in it.
|
||||
val signeeFp: ForwardPointer = bestNextNode[signeeFpr]!!
|
||||
val signeeFpCost = fpCost(signeeFp)
|
||||
|
||||
logger.debug("{}'s forward pointer: {}", signeeFpr, signeeFp.next?.target)
|
||||
|
||||
// Get signeeFp
|
||||
|
||||
// Not limiting by required_depth, because 'network' doesn't expose an interface for this
|
||||
val certificationSets: List<Edge> =
|
||||
network.reverseEdges[signeeFpr].orEmpty() // "certifications_of"
|
||||
|
||||
if (certificationSets.isEmpty()) {
|
||||
// Nothing certified it. The path is a dead end.
|
||||
logger.debug("{} was not certified, dead end", signeeFpr)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.debug("Visiting {} ({}), certified {} times",
|
||||
signee.fingerprint,
|
||||
signee.toString(),
|
||||
certificationSets.size)
|
||||
|
||||
for (certification in certificationSets
|
||||
.map { cs ->
|
||||
cs.components
|
||||
.map { it.value }.flatten()
|
||||
}.flatten()) {
|
||||
|
||||
val issuerFpr = certification.issuer.fingerprint
|
||||
|
||||
val fv = FilterValues(certification.trustDepth,
|
||||
certification.trustAmount,
|
||||
certification.regexes)
|
||||
|
||||
if (!filter.cost(certification, fv,
|
||||
false)) {
|
||||
logger.debug(" Cost function says to skip certification by {}", certification.issuer)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.debug(" Considering certification by: {}, depth: {} (of {}), amount: {} (of {}), regexes: {}",
|
||||
certification.issuer,
|
||||
fv.depth,
|
||||
certification.trustDepth,
|
||||
fv.amount,
|
||||
certification.trustAmount,
|
||||
fv.regexps)
|
||||
|
||||
|
||||
if (fv.amount == 0) {
|
||||
logger.debug(" Certification amount is 0, skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
if (!selfSigned
|
||||
&& signeeFpr == targetFpr
|
||||
&& certification.userId != targetUserid) {
|
||||
assert(signeeFp.next == null)
|
||||
|
||||
logger.debug(" Certification certifies target, but for the wrong user id (want: {}, got: {})",
|
||||
targetUserid, certification.userId)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (fv.depth < Depth.auto(signeeFpCost.length)) {
|
||||
logger.debug(" Certification does not have enough depth ({}, needed: {}), skipping", fv.depth, signeeFpCost.length)
|
||||
continue
|
||||
}
|
||||
|
||||
val re = fv.regexps
|
||||
if ((re != null) && !re.matches(targetUserid)) {
|
||||
logger.debug(" Certification's re does not match target User ID, skipping.")
|
||||
continue
|
||||
}
|
||||
|
||||
val proposedFp: ForwardPointer = ForwardPointer(certification)
|
||||
|
||||
val proposedFpCost = Cost(signeeFpCost.length + 1,
|
||||
min(fv.amount, signeeFpCost.amount))
|
||||
|
||||
logger.debug(" Forward pointer for {}:", certification.issuer)
|
||||
|
||||
val pn = proposedFp.next // cache value for debug output
|
||||
logger.debug(" Proposed: {}, amount: {}, depth: {}",
|
||||
pn?.target ?: "target", proposedFpCost.amount, proposedFpCost.length)
|
||||
|
||||
// distance.entry takes a mutable ref, so we can't
|
||||
// compute the current fp's cost in the next block.
|
||||
val currentFpCost: Cost? = bestNextNode[issuerFpr]?.let { fpCost(it) }
|
||||
|
||||
when (val currentFp = bestNextNode[issuerFpr]) {
|
||||
null -> {
|
||||
// We haven't seen this node before.
|
||||
|
||||
logger.debug(" Current: None")
|
||||
logger.debug(" Setting {}'s forward pointer to {}", certification.issuer, signee)
|
||||
logger.debug(" Queuing {}", certification.issuer)
|
||||
|
||||
queue.insert(issuerFpr, proposedFpCost)
|
||||
bestNextNode[issuerFpr] = proposedFp
|
||||
}
|
||||
|
||||
else -> {
|
||||
// We've visited this node in the past. Now
|
||||
// we need to determine whether using
|
||||
// certification and following the proposed
|
||||
// path is better than the current path.
|
||||
|
||||
val currentFpCost = currentFpCost!! // shadow the variable
|
||||
|
||||
val cn = currentFp.next // cache value for debug output
|
||||
logger.debug(" Current: {}, amount: {}, depth: {}",
|
||||
cn?.target ?: "target", currentFpCost.amount, currentFpCost.length)
|
||||
|
||||
// We prefer a shorter path (in terms of
|
||||
// edges) as this allows us to reach more of
|
||||
// the graph.
|
||||
//
|
||||
// If the path length is equal, we prefer the
|
||||
// larger amount of trust.
|
||||
|
||||
if (proposedFpCost.length < currentFpCost.length) {
|
||||
if (proposedFpCost.amount < currentFpCost.amount) {
|
||||
// We have two local optima: one has a shorter path, the other a
|
||||
// higher trust amount. We prefer the shorter path.
|
||||
|
||||
logger.debug(" Preferring proposed: current has a shorter path ({} < {}), but worse amount of trust ({} < {})",
|
||||
proposedFpCost.length, currentFpCost.length,
|
||||
proposedFpCost.amount, currentFpCost.amount)
|
||||
|
||||
bestNextNode[issuerFpr] = proposedFp
|
||||
} else {
|
||||
// Proposed fp is strictly better.
|
||||
|
||||
logger.debug(" Preferring proposed: current has a shorter path ({} < {}), and a better amount of trust ({} < {})",
|
||||
proposedFpCost.length, currentFpCost.length,
|
||||
proposedFpCost.amount, currentFpCost.amount)
|
||||
|
||||
bestNextNode[issuerFpr] = proposedFp
|
||||
}
|
||||
} else if (proposedFpCost.length == currentFpCost.length
|
||||
&& proposedFpCost.amount > currentFpCost.amount) {
|
||||
// Strictly better.
|
||||
|
||||
logger.debug(" Preferring proposed fp: same path length ({}), better amount ({} > {})",
|
||||
proposedFpCost.length,
|
||||
proposedFpCost.amount, currentFpCost.amount)
|
||||
|
||||
bestNextNode[issuerFpr] = proposedFp
|
||||
} else if (proposedFpCost.length > currentFpCost.length
|
||||
&& proposedFpCost.amount > currentFpCost.amount) {
|
||||
// There's another possible path through here.
|
||||
logger.debug(" Preferring current fp: proposed has more trust ({} > {}), but a longer path ({} > {})",
|
||||
proposedFpCost.amount, currentFpCost.amount,
|
||||
proposedFpCost.length, currentFpCost.length)
|
||||
} else {
|
||||
logger.debug(" Preferring current fp: it is strictly better (depth: {}, {}; amount: {}, {})",
|
||||
proposedFpCost.length, currentFpCost.length,
|
||||
proposedFpCost.amount, currentFpCost.amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Follow the forward pointers and reconstruct the paths.
|
||||
val authRpaths: HashMap<Fingerprint, Pair<Path, Int>> = hashMapOf()
|
||||
|
||||
for ((issuerFpr, fp) in bestNextNode.entries) {
|
||||
var fp = fp // Shadow for write access
|
||||
|
||||
// If roots were specified, then only return the optimal
|
||||
// paths from the roots.
|
||||
if (roots.size() > 0 && !roots.isRoot(issuerFpr)) {
|
||||
continue
|
||||
}
|
||||
|
||||
val c = fp.next
|
||||
val issuer =
|
||||
if (c != null) {
|
||||
c.issuer
|
||||
} else {
|
||||
|
||||
// The target.
|
||||
if (!selfSigned) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply any policy to the self certification.
|
||||
//
|
||||
// XXX: Self-signatures should be first class and not
|
||||
// synthesized like this on the fly.
|
||||
val selfsig = EdgeComponent(
|
||||
target, target, targetUserid,
|
||||
|
||||
// FIXME! Use userid binding signature by default, reference time only as fallback:
|
||||
|
||||
// target_ua.map(|ua| ua.binding_signature_creation_time())
|
||||
// .unwrap_or(self.network().reference_time()))
|
||||
|
||||
network.referenceTime.timestamp,
|
||||
|
||||
null, true, 120, Depth.limited(0), RegexSet.wildcard()
|
||||
)
|
||||
|
||||
val fv = FilterValues(Depth.auto(0), 120, null)
|
||||
if (filter.cost(selfsig, fv, true)) {
|
||||
logger.debug("Policy on selfsig => amount: {}", fv.amount)
|
||||
|
||||
if (fv.amount == 0) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
logger.debug("Policy says to ignore selfsig")
|
||||
continue
|
||||
}
|
||||
|
||||
val p = Path(target)
|
||||
logger.debug("Authenticated <{}, {}>:\n{}", targetFpr, targetUserid, p)
|
||||
|
||||
authRpaths[issuerFpr] = Pair(p, fv.amount)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
logger.debug("Recovering path starting at {}", network.nodes[issuerFpr])
|
||||
|
||||
var amount = 120
|
||||
|
||||
// nodes[0] is the root; nodes[nodes.len() - 1] is the target.
|
||||
val nodes: MutableList<EdgeComponent> = mutableListOf()
|
||||
while (true) {
|
||||
val c = fp.next ?: break
|
||||
|
||||
logger.debug(" {}", fp)
|
||||
|
||||
val fv = FilterValues(c.trustDepth, c.trustAmount, null)
|
||||
|
||||
val r = filter.cost(c, fv, true)
|
||||
|
||||
assert(r) {
|
||||
"cost function returned different result, but must be constant !"
|
||||
}
|
||||
amount = min(fv.amount, amount)
|
||||
|
||||
nodes.add(c)
|
||||
fp = bestNextNode[c.target.fingerprint]!! // FIXME !!
|
||||
}
|
||||
|
||||
if (selfSigned) {
|
||||
val tail = nodes.last()
|
||||
if (tail.userId != targetUserid) {
|
||||
/// XXX: don't synthesize selfsigs
|
||||
val selfsig = EdgeComponent(target, target, targetUserid, Date(),
|
||||
null, true, 120, Depth.limited(0), RegexSet.wildcard())
|
||||
nodes.add(selfsig)
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(" {}", fp)
|
||||
|
||||
logger.debug("\nShortest path from {} to <{} <-> {}>:\n {}",
|
||||
issuer.fingerprint,
|
||||
targetUserid, targetFpr,
|
||||
nodes.withIndex().joinToString("\n ") { (i, certification) ->
|
||||
"$i: $certification"
|
||||
})
|
||||
|
||||
assert(nodes.size > 0)
|
||||
|
||||
val p = Path(issuer)
|
||||
for (n in nodes.iterator()) {
|
||||
p.append(n)
|
||||
}
|
||||
logger.debug("Authenticated <{}, {}>:\n{}", targetFpr, targetUserid, p)
|
||||
|
||||
authRpaths[issuerFpr] = Pair(p, amount)
|
||||
}
|
||||
|
||||
// if TRACE {
|
||||
// t!("auth_rpaths:")
|
||||
// let mut v: Vec<_> = auth_rpaths.iter().collect()
|
||||
// v.sort_by(|(fpr_a, _), (fpr_b, _)| {
|
||||
// let userid_a = self.network()
|
||||
// .lookup_synopsis_by_fpr(*fpr_a).expect("already looked up")
|
||||
// .primary_userid().map(|userid| {
|
||||
// String::from_utf8_lossy(userid.value()).into_owned()
|
||||
// }).unwrap_or("".into())
|
||||
// let userid_b = self.network()
|
||||
// .lookup_synopsis_by_fpr(*fpr_b).expect("already looked up")
|
||||
// .primary_userid().map(|userid| {
|
||||
// String::from_utf8_lossy(userid.value()).into_owned()
|
||||
// }).unwrap_or("".into())
|
||||
//
|
||||
// userid_a.cmp(&userid_b).
|
||||
// then(fpr_a.cmp(&fpr_b))
|
||||
// })
|
||||
// for (fpr, (path, amount)) in v {
|
||||
// let userid = self.network()
|
||||
// .lookup_synopsis_by_fpr(fpr).expect("already looked up")
|
||||
// .primary_userid().map(|userid| {
|
||||
// String::from_utf8_lossy(userid.value()).into_owned()
|
||||
// })
|
||||
// .unwrap_or("<missing User ID>".into())
|
||||
// t!(" <{}, {}>: {}",
|
||||
// fpr, userid,
|
||||
// format!("{} trust amount (max: {}), {} edges",
|
||||
// amount, path.amount(),
|
||||
// path.len() - 1))
|
||||
// }
|
||||
// }
|
||||
|
||||
return authRpaths
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue