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