Skip to content

Commit a001482

Browse files
committed
prune config update signatures
1 parent f6be644 commit a001482

1 file changed

Lines changed: 189 additions & 1 deletion

File tree

  • packages/wallet/core/src/state/arweave

packages/wallet/core/src/state/arweave/index.ts

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ type Candidate = {
5656
signatureEntries: Map<string, ItemEntry>
5757
}
5858

59+
type TopologyChoice = {
60+
topology: Config.Topology
61+
weight: bigint
62+
signatures: number
63+
size: number
64+
signatureMask: string
65+
}
66+
67+
type TopologyChoiceSet = {
68+
slotCount: number
69+
choices: Map<string, TopologyChoice>
70+
}
71+
5972
const PLAIN_SIGNATURE_TYPES = ['eip-712', 'eth_sign', 'erc-1271'] satisfies SignatureType[]
6073
const SAPIENT_SIGNATURE_TYPES = ['sapient', 'sapient-compact'] satisfies SignatureType[]
6174
const PAYLOAD_VERSION_FILTER = {
@@ -417,6 +430,175 @@ function fillTopologyWithSignatures(
417430
})
418431
}
419432

433+
function clampWeight(weight: bigint, cap: bigint): bigint {
434+
return weight > cap ? cap : weight
435+
}
436+
437+
function zeroMask(length: number): string {
438+
return '0'.repeat(length)
439+
}
440+
441+
function compareChoices(left: TopologyChoice, right: TopologyChoice): number {
442+
if (left.signatures !== right.signatures) {
443+
return left.signatures - right.signatures
444+
}
445+
446+
if (left.size !== right.size) {
447+
return left.size - right.size
448+
}
449+
450+
if (left.signatureMask !== right.signatureMask) {
451+
return left.signatureMask > right.signatureMask ? -1 : 1
452+
}
453+
454+
return 0
455+
}
456+
457+
function dominatesChoice(left: TopologyChoice, right: TopologyChoice): boolean {
458+
return (
459+
left.weight >= right.weight &&
460+
left.signatures <= right.signatures &&
461+
left.size <= right.size &&
462+
left.signatureMask >= right.signatureMask
463+
)
464+
}
465+
466+
function makeChoice(
467+
topology: Config.Topology,
468+
weight: bigint,
469+
signatures: number,
470+
signatureMask: string,
471+
): TopologyChoice {
472+
return {
473+
topology,
474+
weight,
475+
signatures,
476+
size: Signature.encodeTopology(topology).length,
477+
signatureMask,
478+
}
479+
}
480+
481+
function addChoice(choiceSet: TopologyChoiceSet, choice: TopologyChoice): void {
482+
const key = choice.weight.toString()
483+
const existing = choiceSet.choices.get(key)
484+
485+
if (!existing || compareChoices(choice, existing) < 0) {
486+
choiceSet.choices.set(key, choice)
487+
}
488+
}
489+
490+
function pruneChoiceSet(choiceSet: TopologyChoiceSet): TopologyChoiceSet {
491+
const choices = [...choiceSet.choices.values()]
492+
const pruned = new Map<string, TopologyChoice>()
493+
494+
for (const candidate of choices) {
495+
const dominated = choices.some((other) => other !== candidate && dominatesChoice(other, candidate))
496+
if (!dominated) {
497+
pruned.set(candidate.weight.toString(), candidate)
498+
}
499+
}
500+
501+
return { ...choiceSet, choices: pruned }
502+
}
503+
504+
function buildTopologyChoiceSet(topology: Config.Topology, cap: bigint): TopologyChoiceSet {
505+
if (Signature.isSignedSignerLeaf(topology)) {
506+
const choices: TopologyChoiceSet = { slotCount: 1, choices: new Map() }
507+
addChoice(
508+
choices,
509+
makeChoice({ type: 'signer', address: topology.address, weight: topology.weight }, 0n, 0, '0'),
510+
)
511+
512+
if (topology.weight > 0n) {
513+
addChoice(choices, makeChoice(topology, clampWeight(topology.weight, cap), 1, '1'))
514+
}
515+
516+
return choices
517+
}
518+
519+
if (Signature.isSignedSapientSignerLeaf(topology)) {
520+
const choices: TopologyChoiceSet = { slotCount: 1, choices: new Map() }
521+
addChoice(choices, makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, '0'))
522+
523+
if (topology.weight > 0n) {
524+
addChoice(choices, makeChoice(topology, clampWeight(topology.weight, cap), 1, '1'))
525+
}
526+
527+
return choices
528+
}
529+
530+
if (Config.isSignerLeaf(topology)) {
531+
return {
532+
slotCount: 0,
533+
choices: new Map([[0n.toString(), makeChoice(topology, 0n, 0, '')]]),
534+
}
535+
}
536+
537+
if (Config.isSapientSignerLeaf(topology)) {
538+
return {
539+
slotCount: 0,
540+
choices: new Map([[0n.toString(), makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, '')]]),
541+
}
542+
}
543+
544+
if (Config.isSubdigestLeaf(topology) || Config.isAnyAddressSubdigestLeaf(topology) || Config.isNodeLeaf(topology)) {
545+
return {
546+
slotCount: 0,
547+
choices: new Map([[0n.toString(), makeChoice(topology, 0n, 0, '')]]),
548+
}
549+
}
550+
551+
if (Config.isNestedLeaf(topology)) {
552+
const treeChoices = buildTopologyChoiceSet(topology.tree, topology.threshold)
553+
const choices: TopologyChoiceSet = { slotCount: treeChoices.slotCount, choices: new Map() }
554+
addChoice(choices, makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, zeroMask(treeChoices.slotCount)))
555+
556+
const satisfied = treeChoices.choices.get(topology.threshold.toString())
557+
if (satisfied && topology.weight > 0n) {
558+
addChoice(
559+
choices,
560+
makeChoice(
561+
{ ...topology, tree: satisfied.topology },
562+
clampWeight(topology.weight, cap),
563+
satisfied.signatures,
564+
satisfied.signatureMask,
565+
),
566+
)
567+
}
568+
569+
return pruneChoiceSet(choices)
570+
}
571+
572+
const leftChoices = buildTopologyChoiceSet(topology[0], cap)
573+
const rightChoices = buildTopologyChoiceSet(topology[1], cap)
574+
const choices: TopologyChoiceSet = {
575+
slotCount: leftChoices.slotCount + rightChoices.slotCount,
576+
choices: new Map(),
577+
}
578+
579+
addChoice(choices, makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, zeroMask(choices.slotCount)))
580+
581+
for (const leftChoice of leftChoices.choices.values()) {
582+
for (const rightChoice of rightChoices.choices.values()) {
583+
addChoice(
584+
choices,
585+
makeChoice(
586+
[leftChoice.topology, rightChoice.topology],
587+
clampWeight(leftChoice.weight + rightChoice.weight, cap),
588+
leftChoice.signatures + rightChoice.signatures,
589+
`${leftChoice.signatureMask}${rightChoice.signatureMask}`,
590+
),
591+
)
592+
}
593+
}
594+
595+
return pruneChoiceSet(choices)
596+
}
597+
598+
function minimizeTopologyForThreshold(topology: Config.Topology, threshold: bigint): Config.Topology | undefined {
599+
return buildTopologyChoiceSet(topology, threshold).choices.get(threshold.toString())?.topology
600+
}
601+
420602
export class Reader implements ReaderInterface {
421603
constructor(private readonly options: Options = defaults) {}
422604

@@ -764,7 +946,13 @@ export class Reader implements ReaderInterface {
764946
}
765947
}
766948

767-
const topology = toRecoveredLikeTopology(fillTopologyWithSignatures(currentConfig, signatures))
949+
const filledTopology = fillTopologyWithSignatures(currentConfig, signatures)
950+
const minimalTopology = minimizeTopologyForThreshold(filledTopology, currentConfig.threshold)
951+
if (!minimalTopology) {
952+
continue
953+
}
954+
955+
const topology = toRecoveredLikeTopology(minimalTopology)
768956
const { weight } = Config.getWeight(topology, () => false)
769957
if (weight < currentConfig.threshold) {
770958
continue

0 commit comments

Comments
 (0)