1use crate::{
5 error::{BridgeError, BridgeResult},
6 types::{BridgeAction, BridgeCommittee, SignedBridgeAction, VerifiedSignedBridgeAction},
7};
8use ethers::core::k256::ecdsa::VerifyingKey;
9use ethers::core::k256::elliptic_curve::sec1::ToEncodedPoint;
10use ethers::types::Address as EthAddress;
11use fastcrypto::hash::HashFunction;
12use fastcrypto::{
13 encoding::{Encoding, Hex},
14 error::FastCryptoError,
15 secp256k1::{
16 Secp256k1KeyPair, Secp256k1PublicKey, Secp256k1PublicKeyAsBytes,
17 recoverable::Secp256k1RecoverableSignature,
18 },
19 traits::{RecoverableSigner, ToFromBytes, VerifyRecoverable},
20};
21use fastcrypto::{hash::Keccak256, traits::KeyPair};
22use serde::{Deserialize, Serialize};
23use std::fmt::Debug;
24use std::fmt::{Display, Formatter};
25use sui_types::{base_types::ConciseableName, message_envelope::VerifiedEnvelope};
26use tap::TapFallible;
27pub type BridgeAuthorityKeyPair = Secp256k1KeyPair;
28pub type BridgeAuthorityPublicKey = Secp256k1PublicKey;
29pub type BridgeAuthorityRecoverableSignature = Secp256k1RecoverableSignature;
30
31#[derive(Ord, PartialOrd, PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
32pub struct BridgeAuthorityPublicKeyBytes(Secp256k1PublicKeyAsBytes);
33
34impl BridgeAuthorityPublicKeyBytes {
35 pub fn to_eth_address(&self) -> EthAddress {
36 let pubkey = VerifyingKey::from_sec1_bytes(self.as_bytes()).unwrap();
38 let affine: ðers::core::k256::AffinePoint = pubkey.as_ref();
39 let encoded = affine.to_encoded_point(false);
40 let pubkey = &encoded.as_bytes()[1..];
41 assert_eq!(pubkey.len(), 64, "raw public key must be 64 bytes");
42 let hash = Keccak256::digest(pubkey).digest;
43 EthAddress::from_slice(&hash[12..])
44 }
45}
46
47impl From<&BridgeAuthorityPublicKey> for BridgeAuthorityPublicKeyBytes {
48 fn from(pk: &BridgeAuthorityPublicKey) -> Self {
49 Self(Secp256k1PublicKeyAsBytes::from(pk))
50 }
51}
52
53impl ToFromBytes for BridgeAuthorityPublicKeyBytes {
54 fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
56 let pk = BridgeAuthorityPublicKey::from_bytes(bytes)?;
57 Ok(Self::from(&pk))
58 }
59
60 fn as_bytes(&self) -> &[u8] {
62 self.as_ref()
63 }
64}
65
66impl std::str::FromStr for BridgeAuthorityPublicKeyBytes {
69 type Err = FastCryptoError;
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 let bytes = Hex::decode(s).map_err(|e| {
72 FastCryptoError::GeneralError(format!("Failed to decode hex string: {}", e))
73 })?;
74 Self::from_bytes(&bytes)
75 }
76}
77
78pub struct ConciseBridgeAuthorityPublicKeyBytesRef<'a>(&'a BridgeAuthorityPublicKeyBytes);
79
80impl Debug for ConciseBridgeAuthorityPublicKeyBytesRef<'_> {
81 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
82 let s = Hex::encode(self.0.0.0.get(0..4).ok_or(std::fmt::Error)?);
83 write!(f, "k#{}..", s)
84 }
85}
86
87impl Display for ConciseBridgeAuthorityPublicKeyBytesRef<'_> {
88 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
89 Debug::fmt(self, f)
90 }
91}
92
93impl AsRef<[u8]> for BridgeAuthorityPublicKeyBytes {
94 fn as_ref(&self) -> &[u8] {
95 self.0.0.as_ref()
96 }
97}
98
99impl<'a> ConciseableName<'a> for BridgeAuthorityPublicKeyBytes {
100 type ConciseTypeRef = ConciseBridgeAuthorityPublicKeyBytesRef<'a>;
101 type ConciseType = String;
102
103 fn concise(&'a self) -> ConciseBridgeAuthorityPublicKeyBytesRef<'a> {
104 ConciseBridgeAuthorityPublicKeyBytesRef(self)
105 }
106
107 fn concise_owned(&self) -> String {
108 format!("{:?}", ConciseBridgeAuthorityPublicKeyBytesRef(self))
109 }
110}
111
112#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
114pub struct BridgeAuthoritySignInfo {
115 pub authority_pub_key: BridgeAuthorityPublicKey,
116 pub signature: BridgeAuthorityRecoverableSignature,
117}
118
119impl BridgeAuthoritySignInfo {
120 pub fn new(msg: &BridgeAction, secret: &BridgeAuthorityKeyPair) -> Self {
121 let msg_bytes = msg
122 .to_bytes()
123 .expect("Message encoding should not fail for valid actions");
124 Self {
125 authority_pub_key: secret.public().clone(),
126 signature: secret.sign_recoverable_with_hash::<Keccak256>(&msg_bytes),
127 }
128 }
129
130 pub fn verify(&self, msg: &BridgeAction, committee: &BridgeCommittee) -> BridgeResult<()> {
131 if !committee.is_active_member(&self.authority_pub_key_bytes()) {
133 return Err(BridgeError::InvalidBridgeAuthority(
134 self.authority_pub_key_bytes(),
135 ));
136 }
137
138 let msg_bytes = msg.to_bytes().map_err(|e| {
140 BridgeError::Generic(format!("Failed to encode message for verification: {}", e))
141 })?;
142
143 self.authority_pub_key
144 .verify_recoverable_with_hash::<Keccak256>(&msg_bytes, &self.signature)
145 .map_err(|e| {
146 BridgeError::InvalidBridgeAuthoritySignature((
147 self.authority_pub_key_bytes(),
148 e.to_string(),
149 ))
150 })
151 }
152
153 pub fn authority_pub_key_bytes(&self) -> BridgeAuthorityPublicKeyBytes {
154 BridgeAuthorityPublicKeyBytes::from(&self.authority_pub_key)
155 }
156}
157
158pub fn verify_signed_bridge_action(
161 expected_action: &BridgeAction,
162 signed_action: SignedBridgeAction,
163 expected_signer: &BridgeAuthorityPublicKeyBytes,
164 committee: &BridgeCommittee,
165) -> BridgeResult<VerifiedSignedBridgeAction> {
166 if signed_action.data() != expected_action {
167 return Err(BridgeError::MismatchedAction);
168 }
169
170 let sig = signed_action.auth_sig();
171 if &sig.authority_pub_key_bytes() != expected_signer {
172 return Err(BridgeError::MismatchedAuthoritySigner);
173 }
174 sig.verify(signed_action.data(), committee).tap_err(|e| {
175 tracing::error!(
176 "Failed to verify SignedBridgeEvent {:?}. Error {:?}",
177 signed_action,
178 e
179 )
180 })?;
181 Ok(VerifiedEnvelope::new_from_verified(signed_action))
182}
183
184#[cfg(test)]
185mod tests {
186 use crate::events::EmittedSuiToEthTokenBridgeV1;
187 use crate::test_utils::{get_test_authority_and_key, get_test_sui_to_eth_bridge_action};
188 use crate::types::SignedBridgeAction;
189 use crate::types::{BridgeAction, BridgeAuthority, SuiToEthBridgeAction};
190 use ethers::types::Address as EthAddress;
191 use fastcrypto::traits::{KeyPair, ToFromBytes};
192 use prometheus::Registry;
193 use std::str::FromStr;
194 use std::sync::Arc;
195 use sui_types::base_types::SuiAddress;
196 use sui_types::bridge::{BridgeChainId, TOKEN_ID_ETH};
197 use sui_types::crypto::get_key_pair;
198 use sui_types::digests::TransactionDigest;
199
200 use super::*;
201
202 #[test]
203 fn test_sign_and_verify_bridge_event_basic() -> anyhow::Result<()> {
204 telemetry_subscribers::init_for_testing();
205 let registry = Registry::new();
206 mysten_metrics::init_metrics(®istry);
207
208 let (mut authority1, pubkey, secret) = get_test_authority_and_key(5000, 9999);
209 let pubkey_bytes = BridgeAuthorityPublicKeyBytes::from(&pubkey);
210
211 let (authority2, pubkey2, _secret) = get_test_authority_and_key(5000, 9999);
212 let pubkey_bytes2 = BridgeAuthorityPublicKeyBytes::from(&pubkey2);
213
214 let committee = BridgeCommittee::new(vec![authority1.clone(), authority2.clone()]).unwrap();
215
216 let action: BridgeAction =
217 get_test_sui_to_eth_bridge_action(None, Some(1), Some(1), Some(100), None, None, None);
218
219 let sig = BridgeAuthoritySignInfo::new(&action, &secret);
220
221 let signed_action = SignedBridgeAction::new_from_data_and_sig(action.clone(), sig.clone());
222
223 let _ =
225 verify_signed_bridge_action(&action, signed_action.clone(), &pubkey_bytes, &committee)
226 .unwrap();
227
228 assert!(matches!(
230 verify_signed_bridge_action(&action, signed_action.clone(), &pubkey_bytes2, &committee)
231 .unwrap_err(),
232 BridgeError::MismatchedAuthoritySigner
233 ));
234
235 let mismatched_action: BridgeAction =
236 get_test_sui_to_eth_bridge_action(None, Some(2), Some(3), Some(4), None, None, None);
237 assert!(matches!(
239 verify_signed_bridge_action(
240 &mismatched_action,
241 signed_action.clone(),
242 &pubkey_bytes2,
243 &committee
244 )
245 .unwrap_err(),
246 BridgeError::MismatchedAction,
247 ));
248
249 let action2: BridgeAction =
251 get_test_sui_to_eth_bridge_action(None, Some(3), Some(5), Some(77), None, None, None);
252
253 let invalid_sig = BridgeAuthoritySignInfo::new(&action2, &secret);
254 let signed_action = SignedBridgeAction::new_from_data_and_sig(action.clone(), invalid_sig);
255 let _ = verify_signed_bridge_action(&action, signed_action, &pubkey_bytes, &committee)
256 .unwrap_err();
257
258 let (_, kp2): (_, fastcrypto::secp256k1::Secp256k1KeyPair) = get_key_pair();
260 let pubkey_bytes_2 = BridgeAuthorityPublicKeyBytes::from(kp2.public());
261 let secret2 = Arc::pin(kp2);
262 let sig2 = BridgeAuthoritySignInfo::new(&action, &secret2);
263 let signed_action2 = SignedBridgeAction::new_from_data_and_sig(action.clone(), sig2);
264 let _ = verify_signed_bridge_action(&action, signed_action2, &pubkey_bytes_2, &committee)
265 .unwrap_err();
266
267 authority1.is_blocklisted = true;
269 let committee = BridgeCommittee::new(vec![authority1, authority2]).unwrap();
270 let signed_action = SignedBridgeAction::new_from_data_and_sig(action.clone(), sig);
271 let _ = verify_signed_bridge_action(&action, signed_action, &pubkey_bytes, &committee)
272 .unwrap_err();
273
274 Ok(())
275 }
276
277 #[test]
278 fn test_bridge_sig_verification_regression_test() {
279 telemetry_subscribers::init_for_testing();
280 let registry = Registry::new();
281 mysten_metrics::init_metrics(®istry);
282
283 let public_key_bytes =
284 Hex::decode("02321ede33d2c2d7a8a152f275a1484edef2098f034121a602cb7d767d38680aa4")
285 .unwrap();
286 let pubkey1 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap();
287 let authority1 = BridgeAuthority {
288 sui_address: SuiAddress::random_for_testing_only(),
289 pubkey: pubkey1.clone(),
290 voting_power: 2500,
291 is_blocklisted: false,
292 base_url: "".into(),
293 };
294
295 let public_key_bytes =
296 Hex::decode("027f1178ff417fc9f5b8290bd8876f0a157a505a6c52db100a8492203ddd1d4279")
297 .unwrap();
298 let pubkey2 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap();
299 let authority2 = BridgeAuthority {
300 sui_address: SuiAddress::random_for_testing_only(),
301 pubkey: pubkey2.clone(),
302 voting_power: 2500,
303 is_blocklisted: false,
304 base_url: "".into(),
305 };
306
307 let public_key_bytes =
308 Hex::decode("026f311bcd1c2664c14277c7a80e4857c690626597064f89edc33b8f67b99c6bc0")
309 .unwrap();
310 let pubkey3 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap();
311 let authority3 = BridgeAuthority {
312 sui_address: SuiAddress::random_for_testing_only(),
313 pubkey: pubkey3.clone(),
314 voting_power: 2500,
315 is_blocklisted: false,
316 base_url: "".into(),
317 };
318
319 let public_key_bytes =
320 Hex::decode("03a57b85771aedeb6d31c808be9a6e73194e4b70e679608f2bca68bcc684773736")
321 .unwrap();
322 let pubkey4 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap();
323 let authority4 = BridgeAuthority {
324 sui_address: SuiAddress::random_for_testing_only(),
325 pubkey: pubkey4.clone(),
326 voting_power: 2500,
327 is_blocklisted: false,
328 base_url: "".into(),
329 };
330
331 let committee = BridgeCommittee::new(vec![
332 authority1.clone(),
333 authority2.clone(),
334 authority3.clone(),
335 authority4.clone(),
336 ])
337 .unwrap();
338
339 let action = BridgeAction::SuiToEthBridgeAction(SuiToEthBridgeAction {
340 sui_tx_digest: TransactionDigest::random(),
341 sui_tx_event_index: 0,
342 sui_bridge_event: EmittedSuiToEthTokenBridgeV1 {
343 nonce: 1,
344 sui_chain_id: BridgeChainId::SuiTestnet,
345 sui_address: SuiAddress::from_str(
346 "0x80ab1ee086210a3a37355300ca24672e81062fcdb5ced6618dab203f6a3b291c",
347 )
348 .unwrap(),
349 eth_chain_id: BridgeChainId::EthSepolia,
350 eth_address: EthAddress::from_str("0xb18f79Fe671db47393315fFDB377Da4Ea1B7AF96")
351 .unwrap(),
352 token_id: TOKEN_ID_ETH,
353 amount_sui_adjusted: 100000u64,
354 },
355 });
356 let sig = BridgeAuthoritySignInfo {
357 authority_pub_key: pubkey1,
358 signature: BridgeAuthorityRecoverableSignature::from_bytes(
359 &Hex::decode("e1cf11b380855ff1d4a451ebc2fd68477cf701b7d4ec88da3082709fe95201a5061b4b60cf13815a80ba9dfead23e220506aa74c4a863ba045d95715b4cc6b6e00").unwrap(),
360 ).unwrap(),
361 };
362 sig.verify(&action, &committee).unwrap();
363
364 let sig = BridgeAuthoritySignInfo {
365 authority_pub_key: pubkey4.clone(),
366 signature: BridgeAuthorityRecoverableSignature::from_bytes(
367 &Hex::decode("8ba9ec92c2d5a44ecc123182f689b901a93921fd35f581354fea20b25a0ded6d055b96a64bdda77dd5a62b93d29abe93640aa3c1a136348093cd7a2418c6bfa301").unwrap(),
368 ).unwrap(),
369 };
370 sig.verify(&action, &committee).unwrap();
371
372 let sig = BridgeAuthoritySignInfo {
373 authority_pub_key: pubkey4,
374 signature: BridgeAuthorityRecoverableSignature::from_bytes(
375 &Hex::decode("8ba9ec92c2d5a44ecc123182f689b901a93921fd35f581354fea20b25a0ded6d055b96a64bdda77dd5a62b93d29abe93640aa3c1a136348093cd7a2418c6bfa302").unwrap(),
377 ).unwrap(),
378 };
379 sig.verify(&action, &committee).unwrap_err();
380 }
381
382 #[test]
383 fn test_bridge_authority_public_key_bytes_to_eth_address() {
384 let pub_key_bytes = BridgeAuthorityPublicKeyBytes::from_bytes(
385 &Hex::decode("02321ede33d2c2d7a8a152f275a1484edef2098f034121a602cb7d767d38680aa4")
386 .unwrap(),
387 )
388 .unwrap();
389 let addr = "0x68b43fd906c0b8f024a18c56e06744f7c6157c65"
390 .parse::<EthAddress>()
391 .unwrap();
392 assert_eq!(pub_key_bytes.to_eth_address(), addr);
393
394 let pub_key_bytes = BridgeAuthorityPublicKeyBytes::from_bytes(
396 &Hex::decode("0376698beebe8ee5c74d8cc50ab84ac301ee8f10af6f28d0ffd6adf4d6d3b9b762")
397 .unwrap(),
398 )
399 .unwrap();
400 let addr = "0Ac1dF02185025F65202660F8167210A80dD5086"
401 .parse::<EthAddress>()
402 .unwrap();
403 assert_eq!(pub_key_bytes.to_eth_address(), addr);
404 }
405}