@layerzerolabs/common-utils-stellar-contracts 0.2.122

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.
@@ -0,0 +1,760 @@
1
+ use crate::{
2
+ auth::Auth,
3
+ errors::MultiSigError,
4
+ multisig::{init_multisig, recover_signer, MultiSig, SignerSet, ThresholdSet},
5
+ testing_utils::{assert_eq_event, assert_eq_events},
6
+ };
7
+ use soroban_sdk::{
8
+ contract, contractimpl, testutils::AuthorizedFunction, vec, Address, BytesN, Env, IntoVal, Symbol, Val, Vec,
9
+ };
10
+
11
+ // Test contract implementing MultiSig + Auth (self-owning)
12
+ #[contract]
13
+ pub struct TestContract;
14
+
15
+ #[contractimpl]
16
+ impl TestContract {
17
+ pub fn __constructor(env: &Env, signers: &Vec<BytesN<20>>, threshold: u32) {
18
+ init_multisig(env, signers, threshold);
19
+ }
20
+ }
21
+
22
+ /// Self-owning contract: the contract owns itself.
23
+ /// This allows multisig quorum approval to serve as the authorizer.
24
+ #[contractimpl]
25
+ impl Auth for TestContract {
26
+ fn authorizer(env: &Env) -> Option<Address> {
27
+ Some(env.current_contract_address())
28
+ }
29
+ }
30
+
31
+ #[contractimpl(contracttrait)]
32
+ impl MultiSig for TestContract {}
33
+
34
+ fn signer(env: &Env, seed: u8) -> BytesN<20> {
35
+ let mut bytes = [0u8; 20];
36
+ bytes[0] = seed;
37
+ BytesN::from_array(env, &bytes)
38
+ }
39
+
40
+ fn zero_signer(env: &Env) -> BytesN<20> {
41
+ BytesN::from_array(env, &[0u8; 20])
42
+ }
43
+
44
+ fn assert_auth(env: &Env, contract: &Address, func: &str, expected_args: Vec<Val>) {
45
+ let auths = env.auths();
46
+ assert_eq!(auths.len(), 1);
47
+ let (addr, invocation) = &auths[0];
48
+ assert_eq!(addr, contract);
49
+ assert!(invocation.sub_invocations.is_empty());
50
+ match &invocation.function {
51
+ AuthorizedFunction::Contract((id, symbol, args)) => {
52
+ assert_eq!(id, contract);
53
+ assert_eq!(symbol, &Symbol::new(env, func));
54
+ assert_eq!(args, &expected_args);
55
+ }
56
+ _ => panic!("expected contract invocation auth"),
57
+ }
58
+ }
59
+
60
+ // ============================================
61
+ // init: Initialization tests
62
+ // ============================================
63
+
64
+ #[test]
65
+ fn init_sets_signers_and_threshold() {
66
+ let env = Env::default();
67
+
68
+ let s1 = signer(&env, 1);
69
+ let s2 = signer(&env, 2);
70
+ let signers = vec![&env, s1.clone(), s2.clone()];
71
+
72
+ let contract = env.register(TestContract, (&signers, 2u32));
73
+ let client = TestContractClient::new(&env, &contract);
74
+
75
+ assert_eq!(client.threshold(), 2);
76
+ assert_eq!(client.total_signers(), 2);
77
+ assert!(client.is_signer(&s1));
78
+ assert!(client.is_signer(&s2));
79
+ }
80
+
81
+ #[test]
82
+ #[should_panic(expected = "Error(Contract, #1068)")] // ZeroThreshold
83
+ fn init_zero_threshold_fails() {
84
+ let env = Env::default();
85
+ let s1 = signer(&env, 1);
86
+ let signers = vec![&env, s1];
87
+
88
+ env.register(TestContract, (&signers, 0u32));
89
+ }
90
+
91
+ #[test]
92
+ #[should_panic(expected = "Error(Contract, #1066)")] // TotalSignersLessThanThreshold
93
+ fn init_threshold_exceeds_signers_fails() {
94
+ let env = Env::default();
95
+ let s1 = signer(&env, 1);
96
+ let signers = vec![&env, s1];
97
+
98
+ env.register(TestContract, (&signers, 2u32));
99
+ }
100
+
101
+ #[test]
102
+ #[should_panic(expected = "Error(Contract, #1060)")] // AlreadyInitialized
103
+ fn init_already_initialized_fails() {
104
+ let env = Env::default();
105
+ let s1 = signer(&env, 1);
106
+ let signers = vec![&env, s1.clone()];
107
+
108
+ let contract = env.register(TestContract, (&signers, 1u32));
109
+
110
+ // Try to call init_multisig again - should fail with AlreadyInitialized
111
+ env.as_contract(&contract, || {
112
+ init_multisig(&env, &signers, 1);
113
+ });
114
+ }
115
+
116
+ #[test]
117
+ fn init_emits_signer_and_threshold_events() {
118
+ let env = Env::default();
119
+
120
+ let s1 = signer(&env, 1);
121
+ let s2 = signer(&env, 2);
122
+ let signers = vec![&env, s1.clone(), s2.clone()];
123
+
124
+ let contract = env.register(TestContract, (&signers, 2u32));
125
+
126
+ assert_eq_events(
127
+ &env,
128
+ &contract,
129
+ &[
130
+ &SignerSet { signer: s1, active: true },
131
+ &SignerSet { signer: s2, active: true },
132
+ &ThresholdSet { threshold: 2 },
133
+ ],
134
+ );
135
+ }
136
+
137
+ #[test]
138
+ #[should_panic(expected = "Error(Contract, #1062)")] // InvalidSigner
139
+ fn init_zero_signer_fails() {
140
+ let env = Env::default();
141
+ let signers = vec![&env, zero_signer(&env)];
142
+
143
+ env.register(TestContract, (&signers, 1u32));
144
+ }
145
+
146
+ #[test]
147
+ #[should_panic(expected = "Error(Contract, #1064)")] // SignerAlreadyExists
148
+ fn init_duplicate_signers_fails() {
149
+ let env = Env::default();
150
+ let s1 = signer(&env, 1);
151
+ let signers = vec![&env, s1.clone(), s1];
152
+
153
+ env.register(TestContract, (&signers, 1u32));
154
+ }
155
+
156
+ // ============================================
157
+ // set_signer: Add/remove signer tests
158
+ // ============================================
159
+
160
+ #[test]
161
+ fn set_signer_add() {
162
+ let env = Env::default();
163
+ env.mock_all_auths();
164
+
165
+ let s1 = signer(&env, 1);
166
+ let s2 = signer(&env, 2);
167
+ let signers = vec![&env, s1.clone()];
168
+
169
+ let contract = env.register(TestContract, (&signers, 1u32));
170
+ let client = TestContractClient::new(&env, &contract);
171
+
172
+ client.set_signer(&s2, &true);
173
+
174
+ // Verify auth was required from the contract (self-owning)
175
+ assert_auth(&env, &contract, "set_signer", (s2.clone(), true).into_val(&env));
176
+
177
+ // Verify SignerSet event was emitted
178
+ assert_eq_event(&env, &contract, SignerSet { signer: s2.clone(), active: true });
179
+
180
+ assert_eq!(client.total_signers(), 2);
181
+ assert!(client.is_signer(&s1));
182
+ assert!(client.is_signer(&s2));
183
+ }
184
+
185
+ #[test]
186
+ #[should_panic(expected = "Error(Auth, InvalidAction)")]
187
+ fn set_signer_requires_auth() {
188
+ let env = Env::default();
189
+
190
+ let s1 = signer(&env, 1);
191
+ let s2 = signer(&env, 2);
192
+ let signers = vec![&env, s1];
193
+
194
+ let contract = env.register(TestContract, (&signers, 1u32));
195
+ let client = TestContractClient::new(&env, &contract);
196
+
197
+ // No `mock_*auth*` provided -> contract.require_auth() must fail.
198
+ client.set_signer(&s2, &true);
199
+ }
200
+
201
+ #[test]
202
+ fn set_signer_remove() {
203
+ let env = Env::default();
204
+ env.mock_all_auths();
205
+
206
+ let s1 = signer(&env, 1);
207
+ let s2 = signer(&env, 2);
208
+ let signers = vec![&env, s1.clone(), s2.clone()];
209
+
210
+ let contract = env.register(TestContract, (&signers, 1u32));
211
+ let client = TestContractClient::new(&env, &contract);
212
+
213
+ client.set_signer(&s1, &false);
214
+
215
+ // Verify auth was required from the contract (self-owning)
216
+ assert_auth(&env, &contract, "set_signer", (s1.clone(), false).into_val(&env));
217
+
218
+ // Verify SignerSet event was emitted
219
+ assert_eq_event(&env, &contract, SignerSet { signer: s1.clone(), active: false });
220
+
221
+ assert_eq!(client.total_signers(), 1);
222
+ assert!(!client.is_signer(&s1));
223
+ assert!(client.is_signer(&s2));
224
+ }
225
+
226
+ #[test]
227
+ fn set_signer_zero_address_fails() {
228
+ let env = Env::default();
229
+ env.mock_all_auths();
230
+
231
+ let s1 = signer(&env, 1);
232
+ let signers = vec![&env, s1];
233
+
234
+ let contract = env.register(TestContract, (&signers, 1u32));
235
+ let client = TestContractClient::new(&env, &contract);
236
+
237
+ let res = client.try_set_signer(&zero_signer(&env), &true);
238
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::InvalidSigner.into());
239
+ }
240
+
241
+ #[test]
242
+ fn set_signer_duplicate_fails() {
243
+ let env = Env::default();
244
+ env.mock_all_auths();
245
+
246
+ let s1 = signer(&env, 1);
247
+ let signers = vec![&env, s1.clone()];
248
+
249
+ let contract = env.register(TestContract, (&signers, 1u32));
250
+ let client = TestContractClient::new(&env, &contract);
251
+
252
+ let res = client.try_set_signer(&s1, &true);
253
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::SignerAlreadyExists.into());
254
+ }
255
+
256
+ #[test]
257
+ fn set_signer_remove_not_found_fails() {
258
+ let env = Env::default();
259
+ env.mock_all_auths();
260
+
261
+ let s1 = signer(&env, 1);
262
+ let s2 = signer(&env, 2);
263
+ let signers = vec![&env, s1];
264
+
265
+ let contract = env.register(TestContract, (&signers, 1u32));
266
+ let client = TestContractClient::new(&env, &contract);
267
+
268
+ let res = client.try_set_signer(&s2, &false);
269
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::SignerNotFound.into());
270
+ }
271
+
272
+ #[test]
273
+ fn set_signer_remove_violates_threshold_fails() {
274
+ let env = Env::default();
275
+ env.mock_all_auths();
276
+
277
+ let s1 = signer(&env, 1);
278
+ let s2 = signer(&env, 2);
279
+ let signers = vec![&env, s1.clone(), s2];
280
+
281
+ let contract = env.register(TestContract, (&signers, 2u32));
282
+ let client = TestContractClient::new(&env, &contract);
283
+
284
+ let res = client.try_set_signer(&s1, &false);
285
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::TotalSignersLessThanThreshold.into());
286
+ }
287
+
288
+ // ============================================
289
+ // set_threshold: Threshold tests
290
+ // ============================================
291
+
292
+ #[test]
293
+ fn set_threshold_success() {
294
+ let env = Env::default();
295
+ env.mock_all_auths();
296
+
297
+ let s1 = signer(&env, 1);
298
+ let s2 = signer(&env, 2);
299
+ let signers = vec![&env, s1, s2];
300
+
301
+ let contract = env.register(TestContract, (&signers, 1u32));
302
+ let client = TestContractClient::new(&env, &contract);
303
+
304
+ client.set_threshold(&2);
305
+
306
+ // Verify auth was required from the contract (self-owning)
307
+ assert_auth(&env, &contract, "set_threshold", (2u32,).into_val(&env));
308
+
309
+ // Verify ThresholdSet event was emitted
310
+ assert_eq_event(&env, &contract, ThresholdSet { threshold: 2 });
311
+
312
+ assert_eq!(client.threshold(), 2);
313
+ }
314
+
315
+ #[test]
316
+ #[should_panic(expected = "Error(Auth, InvalidAction)")]
317
+ fn set_threshold_requires_auth() {
318
+ let env = Env::default();
319
+
320
+ let s1 = signer(&env, 1);
321
+ let s2 = signer(&env, 2);
322
+ let signers = vec![&env, s1, s2];
323
+
324
+ let contract = env.register(TestContract, (&signers, 1u32));
325
+ let client = TestContractClient::new(&env, &contract);
326
+
327
+ // No `mock_*auth*` provided -> contract.require_auth() must fail.
328
+ client.set_threshold(&2);
329
+ }
330
+
331
+ #[test]
332
+ fn set_threshold_zero_fails() {
333
+ let env = Env::default();
334
+ env.mock_all_auths();
335
+
336
+ let s1 = signer(&env, 1);
337
+ let signers = vec![&env, s1];
338
+
339
+ let contract = env.register(TestContract, (&signers, 1u32));
340
+ let client = TestContractClient::new(&env, &contract);
341
+
342
+ let res = client.try_set_threshold(&0);
343
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::ZeroThreshold.into());
344
+ }
345
+
346
+ #[test]
347
+ fn set_threshold_exceeds_signers_fails() {
348
+ let env = Env::default();
349
+ env.mock_all_auths();
350
+
351
+ let s1 = signer(&env, 1);
352
+ let signers = vec![&env, s1];
353
+
354
+ let contract = env.register(TestContract, (&signers, 1u32));
355
+ let client = TestContractClient::new(&env, &contract);
356
+
357
+ let res = client.try_set_threshold(&2);
358
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::TotalSignersLessThanThreshold.into());
359
+ }
360
+
361
+ // ============================================
362
+ // get_signers: Returns all signers
363
+ // ============================================
364
+
365
+ #[test]
366
+ fn get_signers_returns_all() {
367
+ let env = Env::default();
368
+
369
+ let s1 = signer(&env, 1);
370
+ let s2 = signer(&env, 2);
371
+ let s3 = signer(&env, 3);
372
+ let signers = vec![&env, s1.clone(), s2.clone(), s3.clone()];
373
+
374
+ let contract = env.register(TestContract, (&signers, 1u32));
375
+ let client = TestContractClient::new(&env, &contract);
376
+
377
+ let result = client.get_signers();
378
+ assert_eq!(result.len(), 3);
379
+ assert!(result.iter().any(|s| s == s1));
380
+ assert!(result.iter().any(|s| s == s2));
381
+ assert!(result.iter().any(|s| s == s3));
382
+ }
383
+
384
+ // ============================================
385
+ // verify: Signature verification tests
386
+ // ============================================
387
+
388
+ // Known test vectors for signature verification
389
+ fn test_digest(env: &Env) -> BytesN<32> {
390
+ BytesN::from_array(
391
+ env,
392
+ &[
393
+ 0x7d, 0x84, 0xfd, 0x50, 0x8a, 0x27, 0xac, 0x81, 0xde, 0xe4, 0xbf, 0xa9, 0x7f, 0x29, 0xbe, 0xdb, 0x88, 0x5c,
394
+ 0x1b, 0xd7, 0xfc, 0x46, 0x50, 0xf4, 0x91, 0xe6, 0x4f, 0xbd, 0xaa, 0x05, 0xcd, 0xac,
395
+ ],
396
+ )
397
+ }
398
+
399
+ // Signer 1: 0x9b6ababd080456f900ed64e74d122ff9ca40daa1
400
+ fn test_signer1(env: &Env) -> BytesN<20> {
401
+ BytesN::from_array(
402
+ env,
403
+ &[
404
+ 0x9b, 0x6a, 0xba, 0xbd, 0x08, 0x04, 0x56, 0xf9, 0x00, 0xed, 0x64, 0xe7, 0x4d, 0x12, 0x2f, 0xf9, 0xca, 0x40,
405
+ 0xda, 0xa1,
406
+ ],
407
+ )
408
+ }
409
+
410
+ // Signer 2: 0xd6869dacf9c6ce629cf042864737690641d0e2d7
411
+ fn test_signer2(env: &Env) -> BytesN<20> {
412
+ BytesN::from_array(
413
+ env,
414
+ &[
415
+ 0xd6, 0x86, 0x9d, 0xac, 0xf9, 0xc6, 0xce, 0x62, 0x9c, 0xf0, 0x42, 0x86, 0x47, 0x37, 0x69, 0x06, 0x41, 0xd0,
416
+ 0xe2, 0xd7,
417
+ ],
418
+ )
419
+ }
420
+
421
+ // Signature from signer1 for test_digest
422
+ fn test_sig1(env: &Env) -> BytesN<65> {
423
+ BytesN::from_array(
424
+ env,
425
+ &[
426
+ 0x31, 0x53, 0x7d, 0xcf, 0xe1, 0xec, 0x4d, 0x7e, 0x89, 0xf5, 0x97, 0x2c, 0xcd, 0x30, 0x65, 0xe3, 0x4e, 0x17,
427
+ 0x8e, 0x30, 0x2c, 0x1b, 0x9e, 0x8e, 0x17, 0x5d, 0x38, 0x43, 0xa2, 0x96, 0x4f, 0x28, // r
428
+ 0x63, 0xf2, 0xb6, 0x0c, 0x7e, 0xf7, 0x43, 0x63, 0xb6, 0x87, 0xf8, 0x22, 0xdb, 0x6c, 0x31, 0x3e, 0x87, 0x62,
429
+ 0xb3, 0x8d, 0x30, 0x97, 0xb6, 0xfe, 0xc9, 0xb7, 0xb7, 0x61, 0x65, 0x63, 0x0a, 0xaa, // s
430
+ 0x1b, // v = 27
431
+ ],
432
+ )
433
+ }
434
+
435
+ // Signature from signer2 for test_digest
436
+ fn test_sig2(env: &Env) -> BytesN<65> {
437
+ BytesN::from_array(
438
+ env,
439
+ &[
440
+ 0x42, 0x67, 0x4f, 0xc6, 0x30, 0xe3, 0x2f, 0x90, 0x00, 0xbc, 0x95, 0x27, 0x1e, 0xc5, 0x42, 0x83, 0xc8, 0xad,
441
+ 0x35, 0x00, 0x16, 0xbb, 0x66, 0xa1, 0x84, 0x64, 0x95, 0x89, 0x63, 0xa5, 0x1f, 0x03, // r
442
+ 0x14, 0x6e, 0x8b, 0x3f, 0xbb, 0x3a, 0x5b, 0xd0, 0xb8, 0x9d, 0x3b, 0x6c, 0x1e, 0x01, 0xdd, 0x61, 0x4e, 0x86,
443
+ 0xba, 0xe0, 0x13, 0x51, 0xdc, 0x3d, 0x1f, 0xfe, 0x35, 0x22, 0xc8, 0x3e, 0xd4, 0x50, // s
444
+ 0x1c, // v = 28
445
+ ],
446
+ )
447
+ }
448
+
449
+ #[test]
450
+ fn verify_signatures_success() {
451
+ let env = Env::default();
452
+
453
+ let s1 = test_signer1(&env);
454
+ let s2 = test_signer2(&env);
455
+ let signers = vec![&env, s1, s2];
456
+
457
+ let contract = env.register(TestContract, (&signers, 2u32));
458
+ let client = TestContractClient::new(&env, &contract);
459
+
460
+ let digest = test_digest(&env);
461
+ // Signatures must be sorted by signer address (s1 < s2)
462
+ let signatures = vec![&env, test_sig1(&env), test_sig2(&env)];
463
+
464
+ // Should not panic - uses configured threshold (2)
465
+ client.verify_signatures(&digest, &signatures);
466
+ }
467
+
468
+ #[test]
469
+ fn verify_n_signatures_success() {
470
+ let env = Env::default();
471
+
472
+ let s1 = test_signer1(&env);
473
+ let s2 = test_signer2(&env);
474
+ let signers = vec![&env, s1, s2];
475
+
476
+ let contract = env.register(TestContract, (&signers, 2u32));
477
+ let client = TestContractClient::new(&env, &contract);
478
+
479
+ let digest = test_digest(&env);
480
+ let signatures = vec![&env, test_sig1(&env), test_sig2(&env)];
481
+
482
+ // Should not panic - custom threshold of 1 (only need 1 sig)
483
+ client.verify_n_signatures(&digest, &signatures, &1);
484
+ }
485
+
486
+ #[test]
487
+ fn verify_n_signatures_zero_threshold_fails() {
488
+ let env = Env::default();
489
+
490
+ let s1 = test_signer1(&env);
491
+ let signers = vec![&env, s1];
492
+
493
+ let contract = env.register(TestContract, (&signers, 1u32));
494
+ let client = TestContractClient::new(&env, &contract);
495
+
496
+ let digest = BytesN::from_array(&env, &[0u8; 32]);
497
+ let signatures: Vec<BytesN<65>> = vec![&env];
498
+
499
+ let res = client.try_verify_n_signatures(&digest, &signatures, &0);
500
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::ZeroThreshold.into());
501
+ }
502
+
503
+ #[test]
504
+ fn verify_n_signatures_insufficient_signatures_fails() {
505
+ let env = Env::default();
506
+
507
+ let s1 = test_signer1(&env);
508
+ let signers = vec![&env, s1];
509
+
510
+ let contract = env.register(TestContract, (&signers, 1u32));
511
+ let client = TestContractClient::new(&env, &contract);
512
+
513
+ let digest = BytesN::from_array(&env, &[0u8; 32]);
514
+ let signatures: Vec<BytesN<65>> = vec![&env]; // Empty
515
+
516
+ let res = client.try_verify_n_signatures(&digest, &signatures, &1);
517
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::SignatureError.into());
518
+ }
519
+
520
+ #[test]
521
+ fn verify_n_signatures_signer_not_found_fails() {
522
+ let env = Env::default();
523
+
524
+ // Register with only signer2, but provide sig1 (from signer1)
525
+ let s2 = test_signer2(&env);
526
+ let signers = vec![&env, s2];
527
+
528
+ let contract = env.register(TestContract, (&signers, 1u32));
529
+ let client = TestContractClient::new(&env, &contract);
530
+
531
+ let digest = test_digest(&env);
532
+ let signatures = vec![&env, test_sig1(&env)]; // sig1 is from signer1, not registered
533
+
534
+ let res = client.try_verify_n_signatures(&digest, &signatures, &1);
535
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::SignerNotFound.into());
536
+ }
537
+
538
+ #[test]
539
+ fn verify_n_signatures_unsorted_signers_fails() {
540
+ let env = Env::default();
541
+
542
+ let s1 = test_signer1(&env);
543
+ let s2 = test_signer2(&env);
544
+ let signers = vec![&env, s1, s2];
545
+
546
+ let contract = env.register(TestContract, (&signers, 2u32));
547
+ let client = TestContractClient::new(&env, &contract);
548
+
549
+ let digest = test_digest(&env);
550
+ // Wrong order: sig2 before sig1 (signer2 > signer1)
551
+ let signatures = vec![&env, test_sig2(&env), test_sig1(&env)];
552
+
553
+ let res = client.try_verify_n_signatures(&digest, &signatures, &2);
554
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::UnsortedSigners.into());
555
+ }
556
+
557
+ #[test]
558
+ fn verify_n_signatures_duplicate_signers_fails() {
559
+ let env = Env::default();
560
+
561
+ let s1 = test_signer1(&env);
562
+ let s2 = test_signer2(&env);
563
+ let signers = vec![&env, s1, s2];
564
+
565
+ let contract = env.register(TestContract, (&signers, 2u32));
566
+ let client = TestContractClient::new(&env, &contract);
567
+
568
+ let digest = test_digest(&env);
569
+ // Duplicate signer (sig1 twice) must fail the strict ordering check (not strictly increasing).
570
+ let signatures = vec![&env, test_sig1(&env), test_sig1(&env)];
571
+
572
+ let res = client.try_verify_n_signatures(&digest, &signatures, &2);
573
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::UnsortedSigners.into());
574
+ }
575
+
576
+ #[test]
577
+ fn verify_n_signatures_threshold_one_still_requires_sorted_if_multiple_provided() {
578
+ let env = Env::default();
579
+
580
+ let s1 = test_signer1(&env);
581
+ let s2 = test_signer2(&env);
582
+ let signers = vec![&env, s1, s2];
583
+
584
+ let contract = env.register(TestContract, (&signers, 2u32));
585
+ let client = TestContractClient::new(&env, &contract);
586
+
587
+ let digest = test_digest(&env);
588
+ // Even though threshold=1, passing multiple signatures still requires them to be strictly sorted.
589
+ let signatures = vec![&env, test_sig2(&env), test_sig1(&env)];
590
+
591
+ let res = client.try_verify_n_signatures(&digest, &signatures, &1);
592
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::UnsortedSigners.into());
593
+ }
594
+
595
+ #[test]
596
+ fn verify_n_signatures_threshold_met_but_extra_invalid_signature_fails() {
597
+ let env = Env::default();
598
+
599
+ // Register only signer1 with threshold=1 (so sig1 alone would be sufficient).
600
+ let s1 = test_signer1(&env);
601
+ let signers = vec![&env, s1];
602
+
603
+ let contract = env.register(TestContract, (&signers, 1u32));
604
+ let client = TestContractClient::new(&env, &contract);
605
+
606
+ let digest = test_digest(&env);
607
+ // Provide an extra signature from signer2 (not registered). Even though threshold is met,
608
+ // verification checks *all provided signatures* and must fail.
609
+ let signatures = vec![&env, test_sig1(&env), test_sig2(&env)];
610
+
611
+ let res = client.try_verify_n_signatures(&digest, &signatures, &1);
612
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::SignerNotFound.into());
613
+ }
614
+
615
+ #[test]
616
+ fn verify_signatures_enforces_configured_threshold() {
617
+ let env = Env::default();
618
+
619
+ let s1 = test_signer1(&env);
620
+ let s2 = test_signer2(&env);
621
+ let signers = vec![&env, s1, s2];
622
+
623
+ // Configured threshold = 2
624
+ let contract = env.register(TestContract, (&signers, 2u32));
625
+ let client = TestContractClient::new(&env, &contract);
626
+
627
+ let digest = test_digest(&env);
628
+ // Only one signature -> must fail because `verify_signatures` uses configured threshold.
629
+ let signatures = vec![&env, test_sig1(&env)];
630
+ let res = client.try_verify_signatures(&digest, &signatures);
631
+ assert_eq!(res.err().unwrap().ok().unwrap(), MultiSigError::SignatureError.into());
632
+ }
633
+
634
+ // ============================================
635
+ // recover: Signature recovery tests
636
+ // ============================================
637
+
638
+ #[test]
639
+ fn recover_signer_valid_signature() {
640
+ let env = Env::default();
641
+
642
+ // Known test vector: digest + signature -> expected signer
643
+ // Using the same test data from onesig tests
644
+ let digest_bytes = [
645
+ 0x7d, 0x84, 0xfd, 0x50, 0x8a, 0x27, 0xac, 0x81, 0xde, 0xe4, 0xbf, 0xa9, 0x7f, 0x29, 0xbe, 0xdb, 0x88, 0x5c,
646
+ 0x1b, 0xd7, 0xfc, 0x46, 0x50, 0xf4, 0x91, 0xe6, 0x4f, 0xbd, 0xaa, 0x05, 0xcd, 0xac,
647
+ ];
648
+ let digest = BytesN::from_array(&env, &digest_bytes);
649
+
650
+ // Signature from signer 0x9b6ababd080456f900ed64e74d122ff9ca40daa1
651
+ let sig_bytes = [
652
+ 0x31, 0x53, 0x7d, 0xcf, 0xe1, 0xec, 0x4d, 0x7e, 0x89, 0xf5, 0x97, 0x2c, 0xcd, 0x30, 0x65, 0xe3, 0x4e, 0x17,
653
+ 0x8e, 0x30, 0x2c, 0x1b, 0x9e, 0x8e, 0x17, 0x5d, 0x38, 0x43, 0xa2, 0x96, 0x4f, 0x28, // r
654
+ 0x63, 0xf2, 0xb6, 0x0c, 0x7e, 0xf7, 0x43, 0x63, 0xb6, 0x87, 0xf8, 0x22, 0xdb, 0x6c, 0x31, 0x3e, 0x87, 0x62,
655
+ 0xb3, 0x8d, 0x30, 0x97, 0xb6, 0xfe, 0xc9, 0xb7, 0xb7, 0x61, 0x65, 0x63, 0x0a, 0xaa, // s
656
+ 0x1b, // v = 27
657
+ ];
658
+ let signature = BytesN::from_array(&env, &sig_bytes);
659
+
660
+ let expected_signer = BytesN::from_array(
661
+ &env,
662
+ &[
663
+ 0x9b, 0x6a, 0xba, 0xbd, 0x08, 0x04, 0x56, 0xf9, 0x00, 0xed, 0x64, 0xe7, 0x4d, 0x12, 0x2f, 0xf9, 0xca, 0x40,
664
+ 0xda, 0xa1,
665
+ ],
666
+ );
667
+
668
+ let recovered = recover_signer(&env, &digest, &signature);
669
+ assert_eq!(recovered, expected_signer);
670
+ }
671
+
672
+ #[test]
673
+ fn recover_signer_raw_v_path() {
674
+ let env = Env::default();
675
+
676
+ let digest_bytes = [
677
+ 0x7d, 0x84, 0xfd, 0x50, 0x8a, 0x27, 0xac, 0x81, 0xde, 0xe4, 0xbf, 0xa9, 0x7f, 0x29, 0xbe, 0xdb, 0x88, 0x5c,
678
+ 0x1b, 0xd7, 0xfc, 0x46, 0x50, 0xf4, 0x91, 0xe6, 0x4f, 0xbd, 0xaa, 0x05, 0xcd, 0xac,
679
+ ];
680
+ let digest = BytesN::from_array(&env, &digest_bytes);
681
+
682
+ // Same signature but with v=0 instead of v=27
683
+ let sig_bytes = [
684
+ 0x31, 0x53, 0x7d, 0xcf, 0xe1, 0xec, 0x4d, 0x7e, 0x89, 0xf5, 0x97, 0x2c, 0xcd, 0x30, 0x65, 0xe3, 0x4e, 0x17,
685
+ 0x8e, 0x30, 0x2c, 0x1b, 0x9e, 0x8e, 0x17, 0x5d, 0x38, 0x43, 0xa2, 0x96, 0x4f, 0x28, // r
686
+ 0x63, 0xf2, 0xb6, 0x0c, 0x7e, 0xf7, 0x43, 0x63, 0xb6, 0x87, 0xf8, 0x22, 0xdb, 0x6c, 0x31, 0x3e, 0x87, 0x62,
687
+ 0xb3, 0x8d, 0x30, 0x97, 0xb6, 0xfe, 0xc9, 0xb7, 0xb7, 0x61, 0x65, 0x63, 0x0a, 0xaa, // s
688
+ 0x00, // v = 0 (raw)
689
+ ];
690
+ let signature = BytesN::from_array(&env, &sig_bytes);
691
+
692
+ let expected_signer = BytesN::from_array(
693
+ &env,
694
+ &[
695
+ 0x9b, 0x6a, 0xba, 0xbd, 0x08, 0x04, 0x56, 0xf9, 0x00, 0xed, 0x64, 0xe7, 0x4d, 0x12, 0x2f, 0xf9, 0xca, 0x40,
696
+ 0xda, 0xa1,
697
+ ],
698
+ );
699
+
700
+ let recovered = recover_signer(&env, &digest, &signature);
701
+ assert_eq!(recovered, expected_signer);
702
+ }
703
+
704
+ #[test]
705
+ #[should_panic(expected = "Error(Crypto, InvalidInput)")]
706
+ fn recover_signer_invalid_r_zero_fails() {
707
+ let env = Env::default();
708
+
709
+ let digest = BytesN::from_array(&env, &[0u8; 32]);
710
+
711
+ // Signature with r=0 (invalid for secp256k1)
712
+ let mut sig_bytes = [0u8; 65];
713
+ // r = 0 (bytes 0-31 already 0)
714
+ // s = some non-zero value (bytes 32-63)
715
+ for i in 32..64 {
716
+ sig_bytes[i] = 1;
717
+ }
718
+ sig_bytes[64] = 27; // v
719
+ let signature = BytesN::from_array(&env, &sig_bytes);
720
+
721
+ // Should panic - r=0 is invalid for secp256k1
722
+ recover_signer(&env, &digest, &signature);
723
+ }
724
+
725
+ #[test]
726
+ #[should_panic(expected = "Error(Crypto, InvalidInput)")]
727
+ fn recover_signer_invalid_s_zero_fails() {
728
+ let env = Env::default();
729
+
730
+ let digest = BytesN::from_array(&env, &[0u8; 32]);
731
+
732
+ // Signature with s=0 (invalid for secp256k1)
733
+ let mut sig_bytes = [0u8; 65];
734
+ // r = some non-zero value (bytes 0-31)
735
+ for i in 0..32 {
736
+ sig_bytes[i] = 1;
737
+ }
738
+ // s = 0 (bytes 32-63 already 0)
739
+ sig_bytes[64] = 27; // v
740
+ let signature = BytesN::from_array(&env, &sig_bytes);
741
+
742
+ // Should panic - s=0 is invalid for secp256k1
743
+ recover_signer(&env, &digest, &signature);
744
+ }
745
+
746
+ #[test]
747
+ #[should_panic(expected = "Error(Crypto, InvalidInput)")]
748
+ fn recover_signer_invalid_v_fails() {
749
+ let env = Env::default();
750
+
751
+ let digest = BytesN::from_array(&env, &[0u8; 32]);
752
+
753
+ // Signature with invalid v value (v=29 normalizes to 2, which is invalid)
754
+ let mut sig_bytes = [1u8; 65];
755
+ sig_bytes[64] = 29; // v=29 -> normalized to 2 (invalid, must be 0 or 1)
756
+ let signature = BytesN::from_array(&env, &sig_bytes);
757
+
758
+ // Should panic - recovery_id must be 0 or 1
759
+ recover_signer(&env, &digest, &signature);
760
+ }