@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.
- package/Cargo.toml +28 -0
- package/LICENSE +23 -0
- package/clippy.toml +7 -0
- package/package.json +43 -0
- package/rust-toolchain.toml +4 -0
- package/rustfmt.toml +15 -0
- package/src/auth.rs +48 -0
- package/src/buffer_reader.rs +194 -0
- package/src/buffer_writer.rs +138 -0
- package/src/bytes_ext.rs +18 -0
- package/src/errors.rs +82 -0
- package/src/lib.rs +20 -0
- package/src/multisig.rs +247 -0
- package/src/option_ext.rs +38 -0
- package/src/ownable.rs +227 -0
- package/src/rbac.rs +438 -0
- package/src/testing_utils.rs +180 -0
- package/src/tests/auth.rs +179 -0
- package/src/tests/buffer_reader.rs +969 -0
- package/src/tests/buffer_writer.rs +689 -0
- package/src/tests/bytes_ext.rs +160 -0
- package/src/tests/mod.rs +13 -0
- package/src/tests/multisig.rs +760 -0
- package/src/tests/option_ext.rs +18 -0
- package/src/tests/ownable.rs +822 -0
- package/src/tests/rbac.rs +559 -0
- package/src/tests/test_helper.rs +67 -0
- package/src/tests/testing_utils.rs +522 -0
- package/src/tests/ttl_configurable.rs +586 -0
- package/src/tests/ttl_extendable.rs +64 -0
- package/src/tests/upgradeable.rs +529 -0
- package/src/ttl_configurable.rs +169 -0
- package/src/ttl_extendable.rs +25 -0
- package/src/upgradeable.rs +103 -0
|
@@ -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
|
+
}
|