Skip to content

Commit 09ead03

Browse files
ScreamingHawkclaude
andcommitted
fix: require a collected signature before signing-power validation passes
The final signersWeight check counts payload-blind subdigest leaves as satisfied even for an empty signer set, so BuildRegularSignature and BuildNoChainIDSignature with validateSigningPower could succeed without collecting any signature. Restore the isValid flag, set only after a non-nil signature brings the optimistic signersWeight to the threshold, while cancellation keeps using collectedSignersWeight. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent be173e7 commit 09ead03

2 files changed

Lines changed: 38 additions & 6 deletions

File tree

‎core/v3/signature_build_test.go‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,25 @@ func TestBuildRegularSignatureCollectsAllSignersDespiteSubdigestLeaf(t *testing.
6666
require.Equal(t, first, data, "signature encoding must be deterministic")
6767
}
6868
}
69+
70+
// A subdigest leaf reports max signersWeight even for an empty signer set, so
71+
// signing-power validation must still fail when no signature is collected at all.
72+
func TestBuildSignatureValidationRejectsEmptySignerSetDespiteSubdigestLeaf(t *testing.T) {
73+
config := &v3.WalletConfig{
74+
Threshold_: 2,
75+
Tree: v3.WalletConfigTreeNodes(
76+
v3.WalletConfigTreeSubdigestLeaf{Subdigest: common.BigToHash(big.NewInt(1))},
77+
&v3.WalletConfigTreeAddressLeaf{Weight: 1, Address: common.HexToAddress("0x1111111111111111111111111111111111111111")},
78+
),
79+
}
80+
81+
signingFunc := func(ctx context.Context, signer core.Signer, _ []core.SignerSignature) (core.SignerSignatureType, []byte, error) {
82+
return 0, nil, nil
83+
}
84+
85+
_, err := config.BuildRegularSignature(context.Background(), signingFunc, true)
86+
require.ErrorContains(t, err, "not enough signers")
87+
88+
_, err = config.BuildNoChainIDSignature(context.Background(), signingFunc, true)
89+
require.ErrorContains(t, err, "not enough signers")
90+
}

‎core/v3/v3.go‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,7 +1999,9 @@ func (c *WalletConfig) BuildSubdigestSignature(noChainID bool) (core.Signature[*
19991999
}
20002000

20012001
func (c *WalletConfig) BuildRegularSignature(ctx context.Context, sign core.SigningFunction, validateSigningPower bool, checkpointerData ...[]byte) (core.Signature[*WalletConfig], error) {
2002+
var isValid bool
20022003
configSigners := c.Signers()
2004+
threshold := new(big.Int).SetUint64(uint64(c.Threshold_))
20032005

20042006
signCtx, signCancel := context.WithCancel(ctx)
20052007
defer signCancel()
@@ -2023,14 +2025,17 @@ func (c *WalletConfig) BuildRegularSignature(ctx context.Context, sign core.Sign
20232025
// threshold. signersWeight counts payload-blind subdigest leaves as satisfied,
20242026
// and cancelling on that estimate nondeterministically drops signatures that
20252027
// recovery of a non-matching payload still needs.
2026-
weight := c.Tree.collectedSignersWeight(signedSigners)
2027-
if weight.Cmp(new(big.Int).SetUint64(uint64(c.Threshold_))) >= 0 {
2028+
if c.Tree.collectedSignersWeight(signedSigners).Cmp(threshold) >= 0 {
20282029
signCancel()
20292030
}
2031+
2032+
if c.Tree.signersWeight(signedSigners).Cmp(threshold) >= 0 {
2033+
isValid = true
2034+
}
20302035
}
20312036
}
20322037

2033-
if validateSigningPower && c.Tree.signersWeight(signedSigners).Cmp(new(big.Int).SetUint64(uint64(c.Threshold_))) < 0 {
2038+
if !isValid && validateSigningPower {
20342039
return nil, fmt.Errorf("not enough signers to build regular signature")
20352040
}
20362041

@@ -2050,7 +2055,9 @@ func (c *WalletConfig) BuildRegularSignature(ctx context.Context, sign core.Sign
20502055
}
20512056

20522057
func (c *WalletConfig) BuildNoChainIDSignature(ctx context.Context, sign core.SigningFunction, validateSigningPower bool, checkpointerData ...[]byte) (core.Signature[*WalletConfig], error) {
2058+
var isValid bool
20532059
configSigners := c.Signers()
2060+
threshold := new(big.Int).SetUint64(uint64(c.Threshold_))
20542061

20552062
signCtx, signCancel := context.WithCancel(ctx)
20562063
defer signCancel()
@@ -2074,14 +2081,17 @@ func (c *WalletConfig) BuildNoChainIDSignature(ctx context.Context, sign core.Si
20742081
// threshold. signersWeight counts payload-blind subdigest leaves as satisfied,
20752082
// and cancelling on that estimate nondeterministically drops signatures that
20762083
// recovery of a non-matching payload still needs.
2077-
weight := c.Tree.collectedSignersWeight(signedSigners)
2078-
if weight.Cmp(new(big.Int).SetUint64(uint64(c.Threshold_))) >= 0 {
2084+
if c.Tree.collectedSignersWeight(signedSigners).Cmp(threshold) >= 0 {
20792085
signCancel()
20802086
}
2087+
2088+
if c.Tree.signersWeight(signedSigners).Cmp(threshold) >= 0 {
2089+
isValid = true
2090+
}
20812091
}
20822092
}
20832093

2084-
if validateSigningPower && c.Tree.signersWeight(signedSigners).Cmp(new(big.Int).SetUint64(uint64(c.Threshold_))) < 0 {
2094+
if !isValid && validateSigningPower {
20852095
return nil, fmt.Errorf("not enough signers to build no chain ID signature")
20862096
}
20872097

0 commit comments

Comments
 (0)