@@ -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+
5972const PLAIN_SIGNATURE_TYPES = [ 'eip-712' , 'eth_sign' , 'erc-1271' ] satisfies SignatureType [ ]
6073const SAPIENT_SIGNATURE_TYPES = [ 'sapient' , 'sapient-compact' ] satisfies SignatureType [ ]
6174const 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+
420602export 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