@layerzerolabs/protocol-stellar-v2 0.2.11 → 0.2.12
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/.turbo/turbo-build.log +202 -194
- package/.turbo/turbo-lint.log +38 -38
- package/.turbo/turbo-test.log +891 -891
- package/Cargo.lock +1 -1
- package/contracts/common-macros/src/lib.rs +3 -36
- package/contracts/endpoint-v2/src/endpoint_v2.rs +4 -4
- package/contracts/endpoint-v2/src/events.rs +40 -22
- package/contracts/endpoint-v2/src/interfaces/message_lib.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/message_lib_manager.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/messaging_composer.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/send_lib.rs +2 -2
- package/contracts/endpoint-v2/src/message_lib_manager.rs +3 -3
- package/contracts/endpoint-v2/src/messaging_channel.rs +1 -1
- package/contracts/endpoint-v2/src/messaging_composer.rs +1 -1
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_lib_timeout.rs +4 -8
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_library.rs +3 -7
- package/contracts/message-libs/{block-message-lib → blocked-message-lib}/Cargo.toml +1 -1
- package/contracts/message-libs/treasury/src/events.rs +9 -6
- package/contracts/message-libs/uln-302/src/events.rs +19 -11
- package/contracts/message-libs/uln-302/src/interfaces/receive_uln.rs +2 -2
- package/contracts/message-libs/uln-302/src/interfaces/send_uln.rs +2 -2
- package/contracts/message-libs/uln-302/src/receive_uln.rs +2 -2
- package/contracts/message-libs/uln-302/src/send_uln.rs +3 -3
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/set_default_receive_uln_configs.rs +5 -5
- package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_send_uln_configs.rs +5 -5
- package/contracts/message-libs/uln-302/src/tests/setup.rs +3 -3
- package/contracts/message-libs/uln-302/src/types.rs +24 -24
- package/contracts/message-libs/uln-302/src/uln302.rs +1 -1
- package/contracts/oapps/counter/integration_tests/utils.rs +1 -1
- package/contracts/oapps/oapp/src/oapp_core.rs +4 -3
- package/contracts/oapps/oapp/src/oapp_options_type3.rs +4 -3
- package/contracts/oapps/oft/integration-tests/utils.rs +1 -1
- package/contracts/oapps/oft/src/events.rs +5 -4
- package/contracts/oapps/oft/src/extensions/oft_fee.rs +10 -6
- package/contracts/oapps/oft/src/extensions/pausable.rs +4 -4
- package/contracts/oapps/oft/src/extensions/rate_limiter.rs +8 -6
- package/contracts/utils/src/ownable.rs +6 -4
- package/contracts/utils/src/tests/testing_utils.rs +7 -5
- package/contracts/utils/src/ttl.rs +5 -4
- package/contracts/workers/dvn/src/auth.rs +59 -45
- package/contracts/workers/dvn/src/dvn.rs +84 -16
- package/contracts/workers/dvn/src/errors.rs +10 -13
- package/contracts/workers/dvn/src/events.rs +7 -5
- package/contracts/workers/dvn/src/interfaces/dvn.rs +29 -1
- package/contracts/workers/dvn/src/multisig.rs +94 -71
- package/contracts/workers/dvn/src/storage.rs +9 -12
- package/contracts/workers/dvn/src/tests/auth.rs +56 -26
- package/contracts/workers/dvn/src/tests/dvn.rs +37 -37
- package/contracts/workers/dvn/src/tests/multisig/set_signer.rs +8 -8
- package/contracts/workers/dvn/src/tests/multisig/set_threshold.rs +9 -9
- package/contracts/workers/dvn/src/tests/multisig/verify_signatures.rs +6 -6
- package/contracts/workers/dvn/src/tests/setup.rs +5 -5
- package/contracts/workers/executor/src/auth.rs +93 -0
- package/contracts/workers/executor/src/events.rs +5 -4
- package/contracts/workers/executor/src/{lz_executor.rs → executor.rs} +25 -98
- package/contracts/workers/executor/src/interfaces/mod.rs +1 -1
- package/contracts/workers/executor/src/lib.rs +6 -5
- package/contracts/workers/worker/src/events.rs +23 -13
- package/contracts/workers/worker/src/worker.rs +32 -21
- package/package.json +3 -3
- package/sdk/dist/generated/bml.js +23 -23
- package/sdk/dist/generated/counter.js +25 -25
- package/sdk/dist/generated/endpoint.js +23 -23
- package/sdk/dist/generated/sml.js +23 -23
- package/sdk/dist/generated/uln302.d.ts +1 -1
- package/sdk/dist/generated/uln302.js +33 -33
- package/sdk/package.json +1 -1
- package/sdk/test/index.test.ts +1 -1
- package/sdk/test/oft.test.ts +847 -0
- package/sdk/test/suites/scan.ts +20 -4
- package/tools/ts-bindings-gen/src/main.rs +2 -1
- package/contracts/common-macros/src/event.rs +0 -16
- /package/contracts/message-libs/{block-message-lib → blocked-message-lib}/src/lib.rs +0 -0
|
@@ -1,23 +1,50 @@
|
|
|
1
|
+
//! LayerZero Decentralized Verifier Network (DVN) contract.
|
|
2
|
+
//!
|
|
3
|
+
//! The DVN is responsible for verifying cross-chain messages by providing
|
|
4
|
+
//! cryptographic attestations. It uses a multisig scheme with secp256k1
|
|
5
|
+
//! signatures for authorization and implements Soroban's custom account
|
|
6
|
+
//! interface for transaction signing.
|
|
7
|
+
|
|
1
8
|
use crate::{
|
|
2
|
-
|
|
3
|
-
DstConfigParam, IMultisig, IDVN,
|
|
9
|
+
errors::DvnError, events::SetDstConfig, storage::DvnStorage, Call, DstConfig, DstConfigParam, IMultisig, IDVN,
|
|
4
10
|
};
|
|
5
11
|
use common_macros::{contract_impl, ttl_configurable};
|
|
6
12
|
use endpoint_v2::FeeRecipient;
|
|
7
13
|
use message_lib_common::interfaces::ILayerZeroDVN;
|
|
8
|
-
use soroban_sdk::{contract, Address, Bytes, BytesN, Env, Vec};
|
|
14
|
+
use soroban_sdk::{contract, xdr::ToXdr, Address, Bytes, BytesN, Env, Vec};
|
|
15
|
+
use utils::buffer_writer::BufferWriter;
|
|
9
16
|
use utils::option_ext::OptionExt;
|
|
10
17
|
use worker::{
|
|
11
18
|
assert_acl, assert_not_paused, assert_supported_message_lib, init_worker, require_admin_auth, set_admin_by_admin,
|
|
12
|
-
set_admin_by_owner,
|
|
19
|
+
set_admin_by_owner, DvnFeeLibClient, DvnFeeParams, Worker,
|
|
13
20
|
};
|
|
14
21
|
|
|
22
|
+
/// LayerZero DVN contract.
|
|
23
|
+
///
|
|
24
|
+
/// Implements multisig-based verification with custom account authorization.
|
|
25
|
+
/// The contract owns itself, allowing the multisig quorum to authorize operations.
|
|
15
26
|
#[contract]
|
|
16
27
|
#[ttl_configurable]
|
|
17
|
-
pub struct
|
|
28
|
+
pub struct LzDVN;
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Core Implementation
|
|
32
|
+
// ============================================================================
|
|
18
33
|
|
|
19
34
|
#[contract_impl]
|
|
20
|
-
impl
|
|
35
|
+
impl LzDVN {
|
|
36
|
+
/// Initializes the DVN contract.
|
|
37
|
+
///
|
|
38
|
+
/// # Arguments
|
|
39
|
+
/// * `vid` - Verifier ID, unique identifier for this DVN
|
|
40
|
+
/// * `signers` - Initial multisig signers (20-byte Ethereum addresses)
|
|
41
|
+
/// * `threshold` - Minimum signatures required for multisig operations
|
|
42
|
+
/// * `admins` - Initial admin addresses for operational functions
|
|
43
|
+
/// * `supported_msglibs` - Message libraries this DVN supports (e.g., ULN302)
|
|
44
|
+
/// * `price_feed` - Price feed contract for fee calculations
|
|
45
|
+
/// * `default_multiplier_bps` - Default fee multiplier (10000 = 1x)
|
|
46
|
+
/// * `worker_fee_lib` - Fee library contract for computing DVN fees
|
|
47
|
+
/// * `deposit_address` - Address to receive fee payments
|
|
21
48
|
pub fn __constructor(
|
|
22
49
|
env: &Env,
|
|
23
50
|
vid: u32,
|
|
@@ -26,15 +53,22 @@ impl Dvn {
|
|
|
26
53
|
admins: &Vec<Address>,
|
|
27
54
|
supported_msglibs: &Vec<Address>,
|
|
28
55
|
price_feed: &Address,
|
|
29
|
-
worker_fee_lib: &Address,
|
|
30
56
|
default_multiplier_bps: u32,
|
|
57
|
+
worker_fee_lib: &Address,
|
|
31
58
|
deposit_address: &Address,
|
|
32
59
|
) {
|
|
33
|
-
init_multisig(env, signers, threshold);
|
|
60
|
+
Self::init_multisig(env, signers, threshold);
|
|
34
61
|
Self::init_owner(env, &env.current_contract_address());
|
|
35
|
-
init_worker::<Self>(
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
init_worker::<Self>(
|
|
63
|
+
env,
|
|
64
|
+
admins,
|
|
65
|
+
supported_msglibs,
|
|
66
|
+
price_feed,
|
|
67
|
+
default_multiplier_bps,
|
|
68
|
+
worker_fee_lib,
|
|
69
|
+
deposit_address,
|
|
70
|
+
);
|
|
71
|
+
|
|
38
72
|
DvnStorage::set_vid(env, &vid);
|
|
39
73
|
}
|
|
40
74
|
|
|
@@ -51,10 +85,13 @@ impl Dvn {
|
|
|
51
85
|
set_admin_by_admin::<Self>(env, caller, admin, active);
|
|
52
86
|
}
|
|
53
87
|
|
|
54
|
-
///
|
|
88
|
+
/// Allows the quorum to add/remove admins without requiring an admin signature.
|
|
55
89
|
///
|
|
56
|
-
///
|
|
90
|
+
/// This function bypasses the normal admin verification in `__check_auth`,
|
|
91
|
+
/// requiring only multisig quorum signatures. Must be called as a single
|
|
92
|
+
/// operation (cannot be bundled with other calls).
|
|
57
93
|
///
|
|
94
|
+
/// # Arguments
|
|
58
95
|
/// * `admin` - The address to set admin status for
|
|
59
96
|
/// * `active` - `true` to add admin, `false` to remove
|
|
60
97
|
pub fn quorum_change_admin(env: &Env, admin: &Address, active: bool) {
|
|
@@ -62,8 +99,13 @@ impl Dvn {
|
|
|
62
99
|
}
|
|
63
100
|
}
|
|
64
101
|
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// IDVN Implementation
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
65
106
|
#[contract_impl]
|
|
66
|
-
impl IDVN for
|
|
107
|
+
impl IDVN for LzDVN {
|
|
108
|
+
/// Sets destination chain configurations. Requires admin authorization.
|
|
67
109
|
fn set_dst_config(env: &Env, admin: &Address, params: &Vec<DstConfigParam>) {
|
|
68
110
|
require_admin_auth::<Self>(env, admin);
|
|
69
111
|
for param in params {
|
|
@@ -73,17 +115,35 @@ impl IDVN for Dvn {
|
|
|
73
115
|
SetDstConfig { params: params.clone() }.publish(env);
|
|
74
116
|
}
|
|
75
117
|
|
|
118
|
+
/// Returns the destination configuration for a specific endpoint ID.
|
|
76
119
|
fn dst_config(env: &Env, dst_eid: u32) -> Option<DstConfig> {
|
|
77
120
|
DvnStorage::dst_config(env, dst_eid)
|
|
78
121
|
}
|
|
79
122
|
|
|
123
|
+
/// Returns the Verifier ID for this DVN.
|
|
80
124
|
fn vid(env: &Env) -> u32 {
|
|
81
125
|
DvnStorage::vid(env).unwrap()
|
|
82
126
|
}
|
|
127
|
+
|
|
128
|
+
/// Computes the hash of call data for multisig signing.
|
|
129
|
+
///
|
|
130
|
+
/// Off-chain signers use this to compute the hash they need to sign.
|
|
131
|
+
fn hash_call_data(env: &Env, vid: u32, expiration: u64, calls: &Vec<Call>) -> BytesN<32> {
|
|
132
|
+
let mut writer = BufferWriter::new(env);
|
|
133
|
+
let data = writer.write_u32(vid).write_u64(expiration).write_bytes(&calls.to_xdr(env)).to_bytes();
|
|
134
|
+
env.crypto().keccak256(&data).into()
|
|
135
|
+
}
|
|
83
136
|
}
|
|
84
137
|
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// ILayerZeroDVN Implementation (Send Flow)
|
|
140
|
+
// ============================================================================
|
|
141
|
+
|
|
85
142
|
#[contract_impl]
|
|
86
|
-
impl ILayerZeroDVN for
|
|
143
|
+
impl ILayerZeroDVN for LzDVN {
|
|
144
|
+
/// Calculates the verification fee for a cross-chain message.
|
|
145
|
+
///
|
|
146
|
+
/// Called by the send library to quote DVN fees before sending.
|
|
87
147
|
fn get_fee(
|
|
88
148
|
env: &Env,
|
|
89
149
|
_send_lib: &Address,
|
|
@@ -114,6 +174,10 @@ impl ILayerZeroDVN for Dvn {
|
|
|
114
174
|
DvnFeeLibClient::new(env, &Self::worker_fee_lib(env)).get_fee(&env.current_contract_address(), ¶ms)
|
|
115
175
|
}
|
|
116
176
|
|
|
177
|
+
/// Assigns a verification job to this DVN and returns fee payment info.
|
|
178
|
+
///
|
|
179
|
+
/// Called by the send library when a message is sent. The DVN will later
|
|
180
|
+
/// verify the message on the destination chain.
|
|
117
181
|
fn assign_job(
|
|
118
182
|
env: &Env,
|
|
119
183
|
send_lib: &Address,
|
|
@@ -132,8 +196,12 @@ impl ILayerZeroDVN for Dvn {
|
|
|
132
196
|
}
|
|
133
197
|
}
|
|
134
198
|
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// Worker Implementation
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
135
203
|
#[contract_impl(contracttrait)]
|
|
136
|
-
impl Worker for
|
|
204
|
+
impl Worker for LzDVN {}
|
|
137
205
|
|
|
138
206
|
// ============================================================================
|
|
139
207
|
// Include SubModules
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
use common_macros::contract_error;
|
|
2
2
|
|
|
3
|
-
#[contract_error]
|
|
4
|
-
pub enum MultisigError {
|
|
5
|
-
ZeroThreshold,
|
|
6
|
-
TotalSignersLessThanThreshold,
|
|
7
|
-
InvalidSigner,
|
|
8
|
-
SignerAlreadyExists,
|
|
9
|
-
SignerNotFound,
|
|
10
|
-
UnsortedSigners,
|
|
11
|
-
SignatureError,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
3
|
#[contract_error]
|
|
15
4
|
pub enum DvnError {
|
|
16
|
-
InvalidVid,
|
|
17
5
|
AuthDataExpired,
|
|
6
|
+
EidNotSupported,
|
|
18
7
|
HashAlreadyUsed,
|
|
8
|
+
InvalidSigner,
|
|
9
|
+
InvalidVid,
|
|
10
|
+
NonContractInvoke,
|
|
19
11
|
OnlyAdmin,
|
|
20
|
-
|
|
12
|
+
SignatureError,
|
|
13
|
+
SignerAlreadyExists,
|
|
14
|
+
SignerNotFound,
|
|
15
|
+
TotalSignersLessThanThreshold,
|
|
16
|
+
UnsortedSigners,
|
|
17
|
+
ZeroThreshold,
|
|
21
18
|
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
use crate::DstConfigParam;
|
|
2
|
-
use
|
|
3
|
-
use soroban_sdk::{BytesN, Vec};
|
|
2
|
+
use soroban_sdk::{contractevent, BytesN, Vec};
|
|
4
3
|
|
|
5
|
-
#[
|
|
4
|
+
#[contractevent]
|
|
5
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
6
6
|
pub struct SignerSet {
|
|
7
7
|
pub signer: BytesN<20>,
|
|
8
8
|
pub active: bool,
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
#[
|
|
11
|
+
#[contractevent]
|
|
12
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
12
13
|
pub struct ThresholdSet {
|
|
13
14
|
pub threshold: u32,
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
#[
|
|
17
|
+
#[contractevent]
|
|
18
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
17
19
|
pub struct SetDstConfig {
|
|
18
20
|
pub params: Vec<DstConfigParam>,
|
|
19
21
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
use crate::IMultisig;
|
|
2
2
|
use message_lib_common::interfaces::ILayerZeroDVN;
|
|
3
|
-
use soroban_sdk::{contractclient, contracttype, Address, Env, Vec};
|
|
3
|
+
use soroban_sdk::{contractclient, contracttype, Address, BytesN, Env, Symbol, Val, Vec};
|
|
4
4
|
use worker::Worker;
|
|
5
5
|
|
|
6
6
|
/// Configuration for a destination chain.
|
|
@@ -28,6 +28,20 @@ pub struct DstConfigParam {
|
|
|
28
28
|
pub config: DstConfig,
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/// Represents a single contract invocation for multisig authorization.
|
|
32
|
+
///
|
|
33
|
+
/// Used in `hash_call_data` to compute the hash that signers sign over.
|
|
34
|
+
#[contracttype]
|
|
35
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
36
|
+
pub struct Call {
|
|
37
|
+
/// Target contract address.
|
|
38
|
+
pub to: Address,
|
|
39
|
+
/// Function name to invoke.
|
|
40
|
+
pub func: Symbol,
|
|
41
|
+
/// Function arguments.
|
|
42
|
+
pub args: Vec<Val>,
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
/// DVN (Decentralized Verifier Network) contract interface.
|
|
32
46
|
///
|
|
33
47
|
/// Extends the LayerZero DVN interface with destination configuration management
|
|
@@ -54,4 +68,18 @@ pub trait IDVN: ILayerZeroDVN + Worker + IMultisig {
|
|
|
54
68
|
///
|
|
55
69
|
/// The VID is a unique identifier used in multisig authentication.
|
|
56
70
|
fn vid(env: &Env) -> u32;
|
|
71
|
+
|
|
72
|
+
/// Computes the hash of call data for multisig signing.
|
|
73
|
+
///
|
|
74
|
+
/// Off-chain signers use this to compute the hash they need to sign.
|
|
75
|
+
/// The hash includes the VID, expiration, and the calls being authorized.
|
|
76
|
+
///
|
|
77
|
+
/// # Arguments
|
|
78
|
+
/// * `vid` - Verifier ID (must match contract's VID)
|
|
79
|
+
/// * `expiration` - Expiration timestamp for the authorization
|
|
80
|
+
/// * `calls` - The contract calls being authorized
|
|
81
|
+
///
|
|
82
|
+
/// # Returns
|
|
83
|
+
/// A 32-byte keccak256 hash to be signed by the multisig quorum
|
|
84
|
+
fn hash_call_data(env: &Env, vid: u32, expiration: u64, calls: &Vec<Call>) -> BytesN<32>;
|
|
57
85
|
}
|
|
@@ -1,65 +1,86 @@
|
|
|
1
|
+
//! Multisig functionality for the DVN contract.
|
|
2
|
+
//!
|
|
3
|
+
//! Provides secp256k1-based multisig signature verification using Ethereum-style
|
|
4
|
+
//! 20-byte addresses derived from recovered public keys.
|
|
5
|
+
|
|
1
6
|
use super::*;
|
|
2
7
|
|
|
3
8
|
use crate::{
|
|
4
|
-
errors::
|
|
9
|
+
errors::DvnError,
|
|
5
10
|
events::{SignerSet, ThresholdSet},
|
|
6
|
-
storage::
|
|
11
|
+
storage::DvnStorage,
|
|
7
12
|
};
|
|
8
13
|
use soroban_sdk::assert_with_error;
|
|
9
14
|
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// IMultisig Implementation
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
10
19
|
#[contract_impl]
|
|
11
|
-
impl IMultisig for
|
|
20
|
+
impl IMultisig for LzDVN {
|
|
21
|
+
/// Adds or removes a signer. Requires contract self-authorization (quorum).
|
|
12
22
|
fn set_signer(env: &Env, signer: &BytesN<20>, active: bool) {
|
|
13
23
|
env.current_contract_address().require_auth();
|
|
14
|
-
|
|
15
24
|
if active {
|
|
16
|
-
add_signer(env, signer);
|
|
25
|
+
Self::add_signer(env, signer);
|
|
17
26
|
} else {
|
|
18
|
-
remove_signer(env, signer);
|
|
27
|
+
Self::remove_signer(env, signer);
|
|
19
28
|
}
|
|
20
29
|
}
|
|
21
30
|
|
|
31
|
+
/// Updates the signature threshold. Requires contract self-authorization (quorum).
|
|
22
32
|
fn set_threshold(env: &Env, threshold: u32) {
|
|
23
33
|
env.current_contract_address().require_auth();
|
|
24
34
|
|
|
25
|
-
|
|
35
|
+
assert_with_error!(env, threshold > 0, DvnError::ZeroThreshold);
|
|
36
|
+
assert_with_error!(env, Self::total_signers(env) >= threshold, DvnError::TotalSignersLessThanThreshold);
|
|
37
|
+
|
|
38
|
+
DvnStorage::set_threshold(env, &threshold);
|
|
39
|
+
|
|
40
|
+
ThresholdSet { threshold }.publish(env);
|
|
26
41
|
}
|
|
27
42
|
|
|
43
|
+
/// Returns the list of all registered signers.
|
|
28
44
|
fn get_signers(env: &Env) -> Vec<BytesN<20>> {
|
|
29
|
-
|
|
45
|
+
DvnStorage::signers(env)
|
|
30
46
|
}
|
|
31
47
|
|
|
48
|
+
/// Returns the total number of registered signers.
|
|
32
49
|
fn total_signers(env: &Env) -> u32 {
|
|
33
|
-
|
|
50
|
+
DvnStorage::signers(env).len()
|
|
34
51
|
}
|
|
35
52
|
|
|
53
|
+
/// Checks if an address is a registered signer.
|
|
36
54
|
fn is_signer(env: &Env, signer: &BytesN<20>) -> bool {
|
|
37
|
-
|
|
55
|
+
DvnStorage::signers(env).iter().any(|s| &s == signer)
|
|
38
56
|
}
|
|
39
57
|
|
|
58
|
+
/// Returns the current signature threshold.
|
|
40
59
|
fn threshold(env: &Env) -> u32 {
|
|
41
|
-
|
|
60
|
+
DvnStorage::threshold(env)
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
/// Verifies signatures against the current threshold.
|
|
44
64
|
fn verify_signatures(env: &Env, hash: &BytesN<32>, signatures: &Vec<BytesN<65>>) {
|
|
45
65
|
Self::verify_n_signatures(env, hash, signatures, Self::threshold(env));
|
|
46
66
|
}
|
|
47
67
|
|
|
68
|
+
/// Verifies that at least `threshold` valid signatures exist for the given hash.
|
|
69
|
+
///
|
|
70
|
+
/// Signatures must be:
|
|
71
|
+
/// - From registered signers
|
|
72
|
+
/// - Sorted by signer address (ascending, no duplicates)
|
|
48
73
|
fn verify_n_signatures(env: &Env, hash: &BytesN<32>, signatures: &Vec<BytesN<65>>, threshold: u32) {
|
|
49
|
-
assert_with_error!(env, threshold > 0,
|
|
50
|
-
assert_with_error!(env, signatures.len() >= threshold,
|
|
74
|
+
assert_with_error!(env, threshold > 0, DvnError::ZeroThreshold);
|
|
75
|
+
assert_with_error!(env, signatures.len() >= threshold, DvnError::SignatureError);
|
|
51
76
|
|
|
52
77
|
let mut last_signer: Option<BytesN<20>> = None;
|
|
53
78
|
signatures.iter().for_each(|signature| {
|
|
54
|
-
let signer = recover_signer(env, hash, &signature);
|
|
79
|
+
let signer = Self::recover_signer(env, hash, &signature);
|
|
55
80
|
|
|
56
|
-
// Signers must be strictly increasing
|
|
57
|
-
assert_with_error!(
|
|
58
|
-
|
|
59
|
-
last_signer.as_ref().is_none_or(|last| &signer > last),
|
|
60
|
-
MultisigError::UnsortedSigners
|
|
61
|
-
);
|
|
62
|
-
assert_with_error!(env, Self::is_signer(env, &signer), MultisigError::SignerNotFound);
|
|
81
|
+
// Signers must be strictly increasing (ensures no duplicates)
|
|
82
|
+
assert_with_error!(env, last_signer.as_ref().is_none_or(|last| &signer > last), DvnError::UnsortedSigners);
|
|
83
|
+
assert_with_error!(env, Self::is_signer(env, &signer), DvnError::SignerNotFound);
|
|
63
84
|
|
|
64
85
|
last_signer = Some(signer);
|
|
65
86
|
});
|
|
@@ -67,64 +88,66 @@ impl IMultisig for Dvn {
|
|
|
67
88
|
}
|
|
68
89
|
|
|
69
90
|
// ============================================================================
|
|
70
|
-
// Internal
|
|
91
|
+
// Internal Functions
|
|
71
92
|
// ============================================================================
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
MultisigStorage::set_signers(env, &signers);
|
|
81
|
-
|
|
82
|
-
SignerSet { signer: signer.clone(), active: true }.publish(env);
|
|
83
|
-
}
|
|
94
|
+
impl LzDVN {
|
|
95
|
+
/// Initializes the multisig with a set of signers and threshold.
|
|
96
|
+
/// Called during contract construction.
|
|
97
|
+
pub fn init_multisig(env: &Env, signers: &Vec<BytesN<20>>, threshold: u32) {
|
|
98
|
+
signers.iter().for_each(|signer| Self::add_signer(env, &signer));
|
|
99
|
+
Self::set_threshold(env, threshold);
|
|
100
|
+
}
|
|
84
101
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
signers
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
102
|
+
/// Adds a new signer to the multisig.
|
|
103
|
+
///
|
|
104
|
+
/// # Errors
|
|
105
|
+
/// - `InvalidSigner` if the signer is the zero address
|
|
106
|
+
/// - `SignerAlreadyExists` if the signer is already registered
|
|
107
|
+
fn add_signer(env: &Env, signer: &BytesN<20>) {
|
|
108
|
+
let zero_signer = BytesN::from_array(env, &[0u8; 20]);
|
|
109
|
+
assert_with_error!(env, signer != &zero_signer, DvnError::InvalidSigner);
|
|
110
|
+
|
|
111
|
+
let mut signers = Self::get_signers(env);
|
|
112
|
+
assert_with_error!(env, !signers.iter().any(|s| &s == signer), DvnError::SignerAlreadyExists);
|
|
113
|
+
signers.push_back(signer.clone());
|
|
114
|
+
DvnStorage::set_signers(env, &signers);
|
|
115
|
+
|
|
116
|
+
SignerSet { signer: signer.clone(), active: true }.publish(env);
|
|
117
|
+
}
|
|
101
118
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
/// Removes a signer from the multisig.
|
|
120
|
+
///
|
|
121
|
+
/// # Errors
|
|
122
|
+
/// - `SignerNotFound` if the signer is not registered
|
|
123
|
+
/// - `TotalSignersLessThanThreshold` if removal would violate threshold constraint
|
|
124
|
+
fn remove_signer(env: &Env, signer: &BytesN<20>) {
|
|
125
|
+
let mut signers = Self::get_signers(env);
|
|
126
|
+
let index = signers.first_index_of(signer);
|
|
127
|
+
assert_with_error!(env, index.is_some(), DvnError::SignerNotFound);
|
|
109
128
|
|
|
110
|
-
|
|
129
|
+
signers.remove(index.unwrap());
|
|
111
130
|
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
// Ensure removal doesn't violate threshold constraint
|
|
132
|
+
assert_with_error!(env, signers.len() >= Self::threshold(env), DvnError::TotalSignersLessThanThreshold);
|
|
133
|
+
DvnStorage::set_signers(env, &signers);
|
|
114
134
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
set_threshold(env, threshold);
|
|
119
|
-
}
|
|
135
|
+
SignerSet { signer: signer.clone(), active: false }.publish(env);
|
|
136
|
+
}
|
|
120
137
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
138
|
+
/// Recovers the Ethereum-style signer address from a secp256k1 signature.
|
|
139
|
+
///
|
|
140
|
+
/// The signature format is 65 bytes: r (32) + s (32) + v (1).
|
|
141
|
+
/// Returns the last 20 bytes of keccak256(uncompressed_pubkey[1..65]).
|
|
142
|
+
fn recover_signer(env: &Env, digest: &BytesN<32>, signature: &BytesN<65>) -> BytesN<20> {
|
|
143
|
+
let sig_bytes: Bytes = signature.into();
|
|
144
|
+
let v = sig_bytes.get(64).unwrap();
|
|
145
|
+
let recovery_id = if (27..=30).contains(&v) { v - 27 } else { v };
|
|
146
|
+
let sig_rs: BytesN<64> = sig_bytes.slice(0..64).try_into().unwrap();
|
|
126
147
|
|
|
127
|
-
|
|
148
|
+
let public_key = env.crypto_hazmat().secp256k1_recover(digest, &sig_rs, recovery_id as u32);
|
|
128
149
|
|
|
129
|
-
|
|
150
|
+
// Derive Ethereum address: keccak256(pubkey[1..65])[12..32]
|
|
151
|
+
Bytes::from(env.crypto().keccak256(&Bytes::from(public_key).slice(1..65))).slice(12..32).try_into().unwrap()
|
|
152
|
+
}
|
|
130
153
|
}
|
|
@@ -2,33 +2,30 @@ use crate::DstConfig;
|
|
|
2
2
|
use common_macros::storage;
|
|
3
3
|
use soroban_sdk::{BytesN, Vec};
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
// Multisig Storage
|
|
7
|
-
// ============================================================================
|
|
8
|
-
|
|
5
|
+
/// DVN contract storage keys.
|
|
9
6
|
#[storage]
|
|
10
|
-
pub enum
|
|
7
|
+
pub enum DvnStorage {
|
|
8
|
+
// ======================== Multisig ========================
|
|
9
|
+
/// List of authorized signer addresses (20-byte Ethereum addresses).
|
|
11
10
|
#[persistent(Vec<BytesN<20>>)]
|
|
12
11
|
#[default(Vec::new(env))]
|
|
13
12
|
Signers,
|
|
14
13
|
|
|
14
|
+
/// Minimum number of signatures required for multisig operations.
|
|
15
15
|
#[instance(u32)]
|
|
16
16
|
#[default(0)]
|
|
17
17
|
Threshold,
|
|
18
|
-
}
|
|
19
18
|
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
#[storage]
|
|
25
|
-
pub enum DvnStorage {
|
|
19
|
+
// ======================== DVN ============================
|
|
20
|
+
/// Verifier ID - unique identifier for this DVN instance.
|
|
26
21
|
#[instance(u32)]
|
|
27
22
|
Vid,
|
|
28
23
|
|
|
24
|
+
/// Destination chain configuration, keyed by endpoint ID.
|
|
29
25
|
#[persistent(DstConfig)]
|
|
30
26
|
DstConfig { dst_eid: u32 },
|
|
31
27
|
|
|
28
|
+
/// Tracks used hashes for replay protection.
|
|
32
29
|
#[persistent(bool)]
|
|
33
30
|
#[default(false)]
|
|
34
31
|
UsedHash { hash: BytesN<32> },
|