sui_bridge/
crypto.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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        // unwrap: the conversion should not fail
37        let pubkey = VerifyingKey::from_sec1_bytes(self.as_bytes()).unwrap();
38        let affine: &ethers::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    /// Parse an object from its byte representation
55    fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
56        let pk = BridgeAuthorityPublicKey::from_bytes(bytes)?;
57        Ok(Self::from(&pk))
58    }
59
60    /// Borrow a byte slice representing the serialized form of this object
61    fn as_bytes(&self) -> &[u8] {
62        self.as_ref()
63    }
64}
65
66/// implement `FromStr` for `BridgeAuthorityPublicKeyBytes`
67/// to convert a hex-string to public key bytes.
68impl 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// TODO: include epoch ID here to reduce race conditions?
113#[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        // 1. verify committee member is in the committee and not blocklisted
132        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        // 2. verify signature
139        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
158/// Verifies a SignedBridgeAction (response from bridge authority to bridge client)
159/// represents the right BridgeAction, and is signed by the right authority.
160pub 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(&registry);
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        // Verification should succeed
224        let _ =
225            verify_signed_bridge_action(&action, signed_action.clone(), &pubkey_bytes, &committee)
226                .unwrap();
227
228        // Verification should fail - mismatched signer
229        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        // Verification should fail - mismatched action
238        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        // Signature is invalid (signed over different message), verification should fail
250        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        // Signer is not in committee, verification should fail
259        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        // Authority is blocklisted, verification should fail
268        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(&registry);
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                // invalid sdig
376                &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        // Example from: https://github.com/gakonst/ethers-rs/blob/master/ethers-core/src/utils/mod.rs#L1235
395        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}