@inco/lightning 0.8.0-devnet-2 → 0.8.0-devnet-3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -212,7 +212,7 @@ contract TestSignatureVerifier is TestUtils, SignatureVerifier {
212
212
  assertFalse(isValidSignature(digest, signatures));
213
213
  }
214
214
 
215
- function testInvalidSignatureDuplicateSigner() public {
215
+ function testDuplicateSignerReturnsFalse() public {
216
216
  exposedAddSigner(alice);
217
217
  exposedAddSigner(bob);
218
218
  exposedSetThreshold(2);
@@ -223,19 +223,11 @@ contract TestSignatureVerifier is TestUtils, SignatureVerifier {
223
223
  privKeys[1] = alicePrivKey; // Duplicate
224
224
  bytes[] memory signatures = getSortedSignatures(digest, privKeys);
225
225
 
226
- // Should revert because duplicate signers violate the ascending order requirement
227
- // Using try-catch to handle the revert from a view function
228
- try this.isValidSignature(digest, signatures) returns (bool result) {
229
- assertFalse(result, "Expected signature validation to fail with duplicates");
230
- } catch (bytes memory reason) {
231
- // Verify it's the correct error
232
- bytes4 expectedSelector = SignatureVerifier.SignersNotInAscendingOrder.selector;
233
- bytes4 receivedSelector = bytes4(reason);
234
- assertEq(receivedSelector, expectedSelector, "Wrong error selector");
235
- }
226
+ // Duplicate signers should return false (not revert)
227
+ assertFalse(isValidSignature(digest, signatures), "Duplicate signer should return false");
236
228
  }
237
229
 
238
- function testRevertWhenSignaturesNotInAscendingOrder() public {
230
+ function testUnsortedSignaturesStillWork() public {
239
231
  exposedAddSigner(alice);
240
232
  exposedAddSigner(bob);
241
233
  exposedAddSigner(carol);
@@ -259,17 +251,11 @@ contract TestSignatureVerifier is TestUtils, SignatureVerifier {
259
251
  signatures[1] = getSignatureForDigest(digest, bobPrivKey);
260
252
  }
261
253
 
262
- // Should revert with SignersNotInAscendingOrder
263
- try this.isValidSignature(digest, signatures) returns (bool result) {
264
- fail("Expected revert for signatures not in ascending order");
265
- } catch (bytes memory reason) {
266
- bytes4 expectedSelector = SignatureVerifier.SignersNotInAscendingOrder.selector;
267
- bytes4 receivedSelector = bytes4(reason);
268
- assertEq(receivedSelector, expectedSelector, "Wrong error selector");
269
- }
254
+ // Unsorted signatures should now work (optimistic sorting pattern)
255
+ assertTrue(isValidSignature(digest, signatures), "Unsorted signatures should work");
270
256
  }
271
257
 
272
- function testRevertWhenThreeSignaturesOutOfOrder() public {
258
+ function testThreeUnsortedSignaturesStillWork() public {
273
259
  exposedAddSigner(alice);
274
260
  exposedAddSigner(bob);
275
261
  exposedAddSigner(carol);
@@ -337,14 +323,8 @@ contract TestSignatureVerifier is TestUtils, SignatureVerifier {
337
323
  }
338
324
  }
339
325
 
340
- // Should revert with SignersNotInAscendingOrder
341
- try this.isValidSignature(digest, signatures) returns (bool result) {
342
- fail("Expected revert for signatures not in ascending order");
343
- } catch (bytes memory reason) {
344
- bytes4 expectedSelector = SignatureVerifier.SignersNotInAscendingOrder.selector;
345
- bytes4 receivedSelector = bytes4(reason);
346
- assertEq(receivedSelector, expectedSelector, "Wrong error selector");
347
- }
326
+ // Unsorted signatures should now work (optimistic sorting pattern)
327
+ assertTrue(isValidSignature(digest, signatures), "Unsorted signatures should work");
348
328
  }
349
329
 
350
330
  function testSignaturesInCorrectAscendingOrderPasses() public {
@@ -835,4 +815,449 @@ contract TestSignatureVerifier is TestUtils, SignatureVerifier {
835
815
  assertFalse(isValidSignature(digest, sigs));
836
816
  }
837
817
 
818
+ // ============ Tests for Malformed Signatures ============
819
+
820
+ function testMalformedSignatureIsSkipped() public {
821
+ exposedAddSigner(alice);
822
+ exposedAddSigner(bob);
823
+ exposedSetThreshold(2);
824
+
825
+ bytes32 digest = keccak256("test message");
826
+
827
+ // Create valid signatures
828
+ uint256[] memory privKeys = new uint256[](2);
829
+ privKeys[0] = alicePrivKey;
830
+ privKeys[1] = bobPrivKey;
831
+ bytes[] memory validSigs = getSortedSignatures(digest, privKeys);
832
+
833
+ // Create array with malformed signature in the middle
834
+ bytes[] memory signatures = new bytes[](3);
835
+ signatures[0] = validSigs[0];
836
+ signatures[1] = hex"deadbeef"; // Malformed signature
837
+ signatures[2] = validSigs[1];
838
+
839
+ // Should still pass - malformed signature is skipped
840
+ assertTrue(isValidSignature(digest, signatures), "Malformed signature should be skipped");
841
+ }
842
+
843
+ function testAllMalformedSignaturesFails() public {
844
+ exposedAddSigner(alice);
845
+ exposedSetThreshold(1);
846
+
847
+ bytes32 digest = keccak256("test message");
848
+
849
+ bytes[] memory signatures = new bytes[](2);
850
+ signatures[0] = hex"deadbeef";
851
+ signatures[1] = hex"cafebabe";
852
+
853
+ // Should fail - no valid signatures
854
+ assertFalse(isValidSignature(digest, signatures), "All malformed should fail");
855
+ }
856
+
857
+ function testEmptySignatureIsSkipped() public {
858
+ exposedAddSigner(alice);
859
+ exposedAddSigner(bob);
860
+ exposedSetThreshold(2);
861
+
862
+ bytes32 digest = keccak256("test message");
863
+
864
+ uint256[] memory privKeys = new uint256[](2);
865
+ privKeys[0] = alicePrivKey;
866
+ privKeys[1] = bobPrivKey;
867
+ bytes[] memory validSigs = getSortedSignatures(digest, privKeys);
868
+
869
+ // Create array with empty signature
870
+ bytes[] memory signatures = new bytes[](3);
871
+ signatures[0] = validSigs[0];
872
+ signatures[1] = ""; // Empty signature
873
+ signatures[2] = validSigs[1];
874
+
875
+ // Should pass - empty signature is skipped
876
+ assertTrue(isValidSignature(digest, signatures), "Empty signature should be skipped");
877
+ }
878
+
879
+ function testUnsortedSignaturesWithMalformedInMiddle() public {
880
+ exposedAddSigner(alice);
881
+ exposedAddSigner(bob);
882
+ exposedSetThreshold(2);
883
+
884
+ bytes32 digest = keccak256("test message");
885
+
886
+ // Get individual signatures
887
+ bytes memory aliceSig = getSignatureForDigest(digest, alicePrivKey);
888
+ bytes memory bobSig = getSignatureForDigest(digest, bobPrivKey);
889
+
890
+ // Determine addresses to create descending order
891
+ address aliceAddr = vm.addr(alicePrivKey);
892
+ address bobAddr = vm.addr(bobPrivKey);
893
+
894
+ // Create array: [higherAddr, malformed, lowerAddr] - descending order with invalid in middle
895
+ bytes[] memory signatures = new bytes[](3);
896
+ if (aliceAddr > bobAddr) {
897
+ // Alice > Bob, so put Alice first (descending)
898
+ signatures[0] = aliceSig;
899
+ signatures[1] = hex"deadbeef"; // Malformed signature in middle
900
+ signatures[2] = bobSig;
901
+ } else {
902
+ // Bob > Alice, so put Bob first (descending)
903
+ signatures[0] = bobSig;
904
+ signatures[1] = hex"deadbeef"; // Malformed signature in middle
905
+ signatures[2] = aliceSig;
906
+ }
907
+
908
+ // Should pass: unsorted valid signatures with malformed skipped
909
+ // This tests that fallback duplicate detection works correctly when
910
+ // malformed signatures create gaps in processing
911
+ assertTrue(isValidSignature(digest, signatures), "Unsorted with malformed in middle should work");
912
+ }
913
+
914
+ // ============ Tests for validCount tracking ============
915
+
916
+ /// @notice Tests that validCount correctly tracks only valid signatures, not array indices
917
+ /// @dev This is critical for the duplicate detection fallback loop which iterates over validCount
918
+ function testValidCountWithMultipleMalformedSignatures() public {
919
+ exposedAddSigner(alice);
920
+ exposedAddSigner(bob);
921
+ exposedAddSigner(carol);
922
+ exposedSetThreshold(3);
923
+
924
+ bytes32 digest = keccak256("test message");
925
+
926
+ // Get sorted valid signatures
927
+ uint256[] memory privKeys = new uint256[](3);
928
+ privKeys[0] = alicePrivKey;
929
+ privKeys[1] = bobPrivKey;
930
+ privKeys[2] = carolPrivKey;
931
+ bytes[] memory validSigs = getSortedSignatures(digest, privKeys);
932
+
933
+ // Create array with multiple malformed signatures interspersed
934
+ // [malformed, valid0, malformed, malformed, valid1, malformed, valid2]
935
+ bytes[] memory signatures = new bytes[](7);
936
+ signatures[0] = hex"dead";
937
+ signatures[1] = validSigs[0];
938
+ signatures[2] = hex"beef";
939
+ signatures[3] = hex"cafe";
940
+ signatures[4] = validSigs[1];
941
+ signatures[5] = hex"babe";
942
+ signatures[6] = validSigs[2];
943
+
944
+ // Should pass: validCount should correctly track 3 valid signatures
945
+ // despite 4 malformed ones creating gaps in the array
946
+ assertTrue(isValidSignature(digest, signatures), "Should work with interspersed malformed signatures");
947
+ }
948
+
949
+ /// @notice Tests duplicate detection works when duplicates are separated by malformed signatures
950
+ /// @dev Validates that validCount-based iteration catches duplicates even with gaps
951
+ function testDuplicateDetectionWithMalformedGaps() public {
952
+ exposedAddSigner(alice);
953
+ exposedAddSigner(bob);
954
+ exposedSetThreshold(2);
955
+
956
+ bytes32 digest = keccak256("test message");
957
+
958
+ bytes memory aliceSig = getSignatureForDigest(digest, alicePrivKey);
959
+
960
+ // [alice, malformed, malformed, alice] - duplicate with gaps
961
+ bytes[] memory signatures = new bytes[](4);
962
+ signatures[0] = aliceSig;
963
+ signatures[1] = hex"dead";
964
+ signatures[2] = hex"beef";
965
+ signatures[3] = aliceSig; // Duplicate
966
+
967
+ // Should fail: duplicate alice signature should be detected
968
+ // even though malformed signatures create gaps in recoveredSigners
969
+ assertFalse(isValidSignature(digest, signatures), "Should detect duplicate with malformed gaps");
970
+ }
971
+
972
+ /// @notice Tests that unsorted duplicates are detected via fallback loop with malformed gaps
973
+ /// @dev Combines unsorted order + duplicates + malformed signatures
974
+ function testUnsortedDuplicateWithMalformedGaps() public {
975
+ exposedAddSigner(alice);
976
+ exposedAddSigner(bob);
977
+ exposedAddSigner(carol);
978
+ exposedSetThreshold(3);
979
+
980
+ bytes32 digest = keccak256("test message");
981
+
982
+ bytes memory aliceSig = getSignatureForDigest(digest, alicePrivKey);
983
+ bytes memory bobSig = getSignatureForDigest(digest, bobPrivKey);
984
+
985
+ address aliceAddr = vm.addr(alicePrivKey);
986
+ address bobAddr = vm.addr(bobPrivKey);
987
+
988
+ // Create: [higher, malformed, lower, malformed, higher(duplicate)]
989
+ // This forces fallback path AND tests duplicate detection across gaps
990
+ bytes[] memory signatures = new bytes[](5);
991
+ if (aliceAddr > bobAddr) {
992
+ signatures[0] = aliceSig; // Higher first (will set lastSigner)
993
+ signatures[1] = hex"dead";
994
+ signatures[2] = bobSig; // Lower (triggers fallback)
995
+ signatures[3] = hex"beef";
996
+ signatures[4] = aliceSig; // Duplicate of signatures[0]
997
+ } else {
998
+ signatures[0] = bobSig;
999
+ signatures[1] = hex"dead";
1000
+ signatures[2] = aliceSig;
1001
+ signatures[3] = hex"beef";
1002
+ signatures[4] = bobSig; // Duplicate
1003
+ }
1004
+
1005
+ // Should fail: duplicate should be caught by fallback loop
1006
+ assertFalse(isValidSignature(digest, signatures), "Should detect unsorted duplicate with gaps");
1007
+ }
1008
+
1009
+ /// @notice Tests edge case where all signatures before valid ones are malformed
1010
+ function testAllMalformedBeforeValid() public {
1011
+ exposedAddSigner(alice);
1012
+ exposedAddSigner(bob);
1013
+ exposedSetThreshold(2);
1014
+
1015
+ bytes32 digest = keccak256("test message");
1016
+
1017
+ uint256[] memory privKeys = new uint256[](2);
1018
+ privKeys[0] = alicePrivKey;
1019
+ privKeys[1] = bobPrivKey;
1020
+ bytes[] memory validSigs = getSortedSignatures(digest, privKeys);
1021
+
1022
+ // [malformed, malformed, malformed, valid0, valid1]
1023
+ bytes[] memory signatures = new bytes[](5);
1024
+ signatures[0] = hex"11";
1025
+ signatures[1] = hex"22";
1026
+ signatures[2] = hex"33";
1027
+ signatures[3] = validSigs[0];
1028
+ signatures[4] = validSigs[1];
1029
+
1030
+ // Should pass: validCount starts at 0, first valid signature at index 3
1031
+ // becomes recoveredSigners[0], second at index 4 becomes recoveredSigners[1]
1032
+ assertTrue(isValidSignature(digest, signatures), "Should work with leading malformed signatures");
1033
+ }
1034
+
1035
+ // ============ Tests for getSignerAtIndex and getSignersCount ============
1036
+
1037
+ function testGetSignersCount() public {
1038
+ assertEq(this.getSignersCount(), 0);
1039
+
1040
+ exposedAddSigner(alice);
1041
+ assertEq(this.getSignersCount(), 1);
1042
+
1043
+ exposedAddSigner(bob);
1044
+ assertEq(this.getSignersCount(), 2);
1045
+
1046
+ exposedAddSigner(carol);
1047
+ assertEq(this.getSignersCount(), 3);
1048
+ }
1049
+
1050
+ function testGetSignerAtIndex() public {
1051
+ exposedAddSigner(alice);
1052
+ exposedAddSigner(bob);
1053
+ exposedAddSigner(carol);
1054
+
1055
+ assertEq(this.getSignerAtIndex(0), alice);
1056
+ assertEq(this.getSignerAtIndex(1), bob);
1057
+ assertEq(this.getSignerAtIndex(2), carol);
1058
+ }
1059
+
1060
+ function testGetSignerAtIndexAfterRemoval() public {
1061
+ exposedAddSigner(alice);
1062
+ exposedAddSigner(bob);
1063
+ exposedAddSigner(carol);
1064
+ exposedSetThreshold(1);
1065
+
1066
+ // Remove bob (middle element) - should move carol to bob's position
1067
+ exposedRemoveSigner(bob);
1068
+
1069
+ assertEq(this.getSignersCount(), 2);
1070
+ assertEq(this.getSignerAtIndex(0), alice);
1071
+ assertEq(this.getSignerAtIndex(1), carol); // carol moved to index 1
1072
+ }
1073
+
1074
+ /// @dev Test that getSignerAtIndex reverts when accessing out of bounds index
1075
+ function testGetSignerAtIndexOutOfBounds() public {
1076
+ // Empty signers array - any index should revert
1077
+ vm.expectRevert();
1078
+ this.getSignerAtIndex(0);
1079
+
1080
+ // Add one signer, then try to access index 1
1081
+ exposedAddSigner(alice);
1082
+ vm.expectRevert();
1083
+ this.getSignerAtIndex(1);
1084
+
1085
+ // Add more signers, then try to access beyond the count
1086
+ exposedAddSigner(bob);
1087
+ exposedAddSigner(carol);
1088
+ vm.expectRevert();
1089
+ this.getSignerAtIndex(3);
1090
+ }
1091
+
1092
+ function testGetThreshold() public {
1093
+ exposedAddSigner(alice);
1094
+ exposedAddSigner(bob);
1095
+ exposedSetThreshold(2);
1096
+
1097
+ assertEq(this.getThreshold(), 2);
1098
+ }
1099
+
1100
+ function testIsSigner() public {
1101
+ assertFalse(this.isSigner(alice));
1102
+
1103
+ exposedAddSigner(alice);
1104
+ assertTrue(this.isSigner(alice));
1105
+ assertFalse(this.isSigner(bob));
1106
+
1107
+ exposedAddSigner(bob);
1108
+ assertTrue(this.isSigner(bob));
1109
+ }
1110
+
1111
+ // ============ Fuzz Tests for Signature Verification ============
1112
+
1113
+ /// @dev Fuzz test for threshold validation with varying number of signers
1114
+ /// Tests that signatures meeting the threshold return true
1115
+ function testFuzzValidSignaturesWithVaryingThreshold(uint8 numSigners, uint8 threshold) public {
1116
+ // Constrain inputs to reasonable values
1117
+ numSigners = uint8(bound(numSigners, 1, 10));
1118
+ threshold = uint8(bound(threshold, 1, numSigners));
1119
+
1120
+ // Generate signers with unique private keys
1121
+ uint256[] memory privKeys = new uint256[](numSigners);
1122
+ for (uint256 i = 0; i < numSigners; i++) {
1123
+ privKeys[i] = uint256(keccak256(abi.encodePacked("signer", i))) % (type(uint256).max - 1) + 1;
1124
+ address signerAddr = vm.addr(privKeys[i]);
1125
+ exposedAddSigner(signerAddr);
1126
+ }
1127
+
1128
+ exposedSetThreshold(threshold);
1129
+
1130
+ // Create signatures from exactly threshold number of signers
1131
+ bytes32 digest = keccak256("fuzz test message");
1132
+ uint256[] memory signingKeys = new uint256[](threshold);
1133
+ for (uint256 i = 0; i < threshold; i++) {
1134
+ signingKeys[i] = privKeys[i];
1135
+ }
1136
+ bytes[] memory signatures = getSortedSignatures(digest, signingKeys);
1137
+
1138
+ assertTrue(isValidSignature(digest, signatures), "Valid signatures meeting threshold should pass");
1139
+ }
1140
+
1141
+ /// @dev Fuzz test that signatures below threshold return false
1142
+ function testFuzzInsufficientSignatures(uint8 numSigners, uint8 threshold, uint8 sigCount) public {
1143
+ // Constrain inputs
1144
+ numSigners = uint8(bound(numSigners, 2, 10));
1145
+ threshold = uint8(bound(threshold, 2, numSigners));
1146
+ sigCount = uint8(bound(sigCount, 1, threshold - 1)); // Always below threshold
1147
+
1148
+ // Generate signers
1149
+ uint256[] memory privKeys = new uint256[](numSigners);
1150
+ for (uint256 i = 0; i < numSigners; i++) {
1151
+ privKeys[i] = uint256(keccak256(abi.encodePacked("signer_insuf", i))) % (type(uint256).max - 1) + 1;
1152
+ address signerAddr = vm.addr(privKeys[i]);
1153
+ exposedAddSigner(signerAddr);
1154
+ }
1155
+
1156
+ exposedSetThreshold(threshold);
1157
+
1158
+ // Create fewer signatures than threshold
1159
+ bytes32 digest = keccak256("fuzz insufficient test");
1160
+ uint256[] memory signingKeys = new uint256[](sigCount);
1161
+ for (uint256 i = 0; i < sigCount; i++) {
1162
+ signingKeys[i] = privKeys[i];
1163
+ }
1164
+ bytes[] memory signatures = getSortedSignatures(digest, signingKeys);
1165
+
1166
+ assertFalse(isValidSignature(digest, signatures), "Insufficient signatures should fail");
1167
+ }
1168
+
1169
+ /// @dev Fuzz test that more signatures than threshold still passes
1170
+ function testFuzzExcessSignatures(uint8 numSigners, uint8 threshold) public {
1171
+ // Constrain inputs - need at least one extra signature beyond threshold
1172
+ numSigners = uint8(bound(numSigners, 2, 10));
1173
+ threshold = uint8(bound(threshold, 1, numSigners - 1)); // At least one extra signer
1174
+
1175
+ // Generate signers
1176
+ uint256[] memory privKeys = new uint256[](numSigners);
1177
+ for (uint256 i = 0; i < numSigners; i++) {
1178
+ privKeys[i] = uint256(keccak256(abi.encodePacked("signer_excess", i))) % (type(uint256).max - 1) + 1;
1179
+ address signerAddr = vm.addr(privKeys[i]);
1180
+ exposedAddSigner(signerAddr);
1181
+ }
1182
+
1183
+ exposedSetThreshold(threshold);
1184
+
1185
+ // Create signatures from ALL signers (more than threshold)
1186
+ bytes32 digest = keccak256("fuzz excess test");
1187
+ bytes[] memory signatures = getSortedSignatures(digest, privKeys);
1188
+
1189
+ assertTrue(isValidSignature(digest, signatures), "Excess valid signatures should pass");
1190
+ }
1191
+
1192
+ /// @dev Fuzz test that threshold equal to number of signers works correctly
1193
+ function testFuzzThresholdEqualsSignerCount(uint8 numSigners) public {
1194
+ numSigners = uint8(bound(numSigners, 1, 10));
1195
+
1196
+ // Generate signers
1197
+ uint256[] memory privKeys = new uint256[](numSigners);
1198
+ for (uint256 i = 0; i < numSigners; i++) {
1199
+ privKeys[i] = uint256(keccak256(abi.encodePacked("signer_equal", i))) % (type(uint256).max - 1) + 1;
1200
+ address signerAddr = vm.addr(privKeys[i]);
1201
+ exposedAddSigner(signerAddr);
1202
+ }
1203
+
1204
+ // Set threshold equal to signer count
1205
+ exposedSetThreshold(numSigners);
1206
+
1207
+ bytes32 digest = keccak256("fuzz equal threshold test");
1208
+ bytes[] memory signatures = getSortedSignatures(digest, privKeys);
1209
+
1210
+ assertTrue(isValidSignature(digest, signatures), "All signers signing should pass when threshold equals count");
1211
+ }
1212
+
1213
+ /// @dev Fuzz test that mixing valid and invalid signers correctly counts only valid ones
1214
+ function testFuzzMixedValidInvalidSigners(uint8 numValidSigners, uint8 numInvalidSigners, uint8 threshold) public {
1215
+ // Constrain inputs
1216
+ numValidSigners = uint8(bound(numValidSigners, 1, 5));
1217
+ numInvalidSigners = uint8(bound(numInvalidSigners, 1, 5));
1218
+ threshold = uint8(bound(threshold, 1, numValidSigners)); // Must be achievable with valid signers
1219
+
1220
+ // Generate and register valid signers
1221
+ uint256[] memory validPrivKeys = new uint256[](numValidSigners);
1222
+ for (uint256 i = 0; i < numValidSigners; i++) {
1223
+ validPrivKeys[i] = uint256(keccak256(abi.encodePacked("valid_signer", i))) % (type(uint256).max - 1) + 1;
1224
+ address signerAddr = vm.addr(validPrivKeys[i]);
1225
+ exposedAddSigner(signerAddr);
1226
+ }
1227
+
1228
+ exposedSetThreshold(threshold);
1229
+
1230
+ // Generate invalid signer keys (not registered)
1231
+ uint256[] memory invalidPrivKeys = new uint256[](numInvalidSigners);
1232
+ for (uint256 i = 0; i < numInvalidSigners; i++) {
1233
+ invalidPrivKeys[i] = uint256(keccak256(abi.encodePacked("invalid_signer", i))) % (type(uint256).max - 1) + 1;
1234
+ }
1235
+
1236
+ // Create signatures from all valid signers (should pass regardless of invalid ones)
1237
+ bytes32 digest = keccak256("fuzz mixed signers test");
1238
+ bytes[] memory signatures = getSortedSignatures(digest, validPrivKeys);
1239
+
1240
+ assertTrue(isValidSignature(digest, signatures), "Valid signatures meeting threshold should pass");
1241
+ }
1242
+
1243
+ /// @dev Fuzz test for digest variation - same signers, different messages
1244
+ function testFuzzDifferentDigests(bytes32 digest, uint8 numSigners) public {
1245
+ numSigners = uint8(bound(numSigners, 1, 5));
1246
+
1247
+ // Generate signers
1248
+ uint256[] memory privKeys = new uint256[](numSigners);
1249
+ for (uint256 i = 0; i < numSigners; i++) {
1250
+ privKeys[i] = uint256(keccak256(abi.encodePacked("digest_signer", i))) % (type(uint256).max - 1) + 1;
1251
+ address signerAddr = vm.addr(privKeys[i]);
1252
+ exposedAddSigner(signerAddr);
1253
+ }
1254
+
1255
+ exposedSetThreshold(numSigners);
1256
+
1257
+ // Sign the fuzzed digest
1258
+ bytes[] memory signatures = getSortedSignatures(digest, privKeys);
1259
+
1260
+ assertTrue(isValidSignature(digest, signatures), "Valid signatures should pass for any digest");
1261
+ }
1262
+
838
1263
  }