iota_bridge/
crypto.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::fmt::{Debug, Display, Formatter};
6
7use ethers::{
8    core::k256::{ecdsa::VerifyingKey, elliptic_curve::sec1::ToEncodedPoint},
9    types::Address as EthAddress,
10};
11use fastcrypto::{
12    encoding::{Encoding, Hex},
13    error::FastCryptoError,
14    hash::{HashFunction, Keccak256},
15    secp256k1::{
16        Secp256k1KeyPair, Secp256k1PublicKey, Secp256k1PublicKeyAsBytes,
17        recoverable::Secp256k1RecoverableSignature,
18    },
19    traits::{KeyPair, RecoverableSigner, ToFromBytes, VerifyRecoverable},
20};
21use iota_types::{base_types::ConciseableName, message_envelope::VerifiedEnvelope};
22use serde::{Deserialize, Serialize};
23use tap::TapFallible;
24
25use crate::{
26    error::{BridgeError, BridgeResult},
27    types::{BridgeAction, BridgeCommittee, SignedBridgeAction, VerifiedSignedBridgeAction},
28};
29pub type BridgeAuthorityKeyPair = Secp256k1KeyPair;
30pub type BridgeAuthorityPublicKey = Secp256k1PublicKey;
31pub type BridgeAuthorityRecoverableSignature = Secp256k1RecoverableSignature;
32
33#[derive(Ord, PartialOrd, PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
34pub struct BridgeAuthorityPublicKeyBytes(Secp256k1PublicKeyAsBytes);
35
36impl BridgeAuthorityPublicKeyBytes {
37    pub fn to_eth_address(&self) -> EthAddress {
38        // unwrap: the conversion should not fail
39        let pubkey = VerifyingKey::from_sec1_bytes(self.as_bytes()).unwrap();
40        let affine: &ethers::core::k256::AffinePoint = pubkey.as_ref();
41        let encoded = affine.to_encoded_point(false);
42        let pubkey = &encoded.as_bytes()[1..];
43        assert_eq!(pubkey.len(), 64, "raw public key must be 64 bytes");
44        let hash = Keccak256::digest(pubkey).digest;
45        EthAddress::from_slice(&hash[12..])
46    }
47}
48
49impl From<&BridgeAuthorityPublicKey> for BridgeAuthorityPublicKeyBytes {
50    fn from(pk: &BridgeAuthorityPublicKey) -> Self {
51        Self(Secp256k1PublicKeyAsBytes::from(pk))
52    }
53}
54
55impl ToFromBytes for BridgeAuthorityPublicKeyBytes {
56    /// Parse an object from its byte representation
57    fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
58        let pk = BridgeAuthorityPublicKey::from_bytes(bytes)?;
59        Ok(Self::from(&pk))
60    }
61
62    /// Borrow a byte slice representing the serialized form of this object
63    fn as_bytes(&self) -> &[u8] {
64        self.as_ref()
65    }
66}
67
68/// implement `FromStr` for `BridgeAuthorityPublicKeyBytes`
69/// to convert a hex-string to public key bytes.
70impl std::str::FromStr for BridgeAuthorityPublicKeyBytes {
71    type Err = FastCryptoError;
72    fn from_str(s: &str) -> Result<Self, Self::Err> {
73        let bytes = Hex::decode(s).map_err(|e| {
74            FastCryptoError::GeneralError(format!("Failed to decode hex string: {}", e))
75        })?;
76        Self::from_bytes(&bytes)
77    }
78}
79
80pub struct ConciseBridgeAuthorityPublicKeyBytesRef<'a>(&'a BridgeAuthorityPublicKeyBytes);
81
82impl Debug for ConciseBridgeAuthorityPublicKeyBytesRef<'_> {
83    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
84        let s = Hex::encode(self.0.0.0.get(0..4).ok_or(std::fmt::Error)?);
85        write!(f, "k#{}..", s)
86    }
87}
88
89impl Display for ConciseBridgeAuthorityPublicKeyBytesRef<'_> {
90    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
91        Debug::fmt(self, f)
92    }
93}
94
95impl AsRef<[u8]> for BridgeAuthorityPublicKeyBytes {
96    fn as_ref(&self) -> &[u8] {
97        self.0.0.as_ref()
98    }
99}
100
101impl<'a> ConciseableName<'a> for BridgeAuthorityPublicKeyBytes {
102    type ConciseTypeRef = ConciseBridgeAuthorityPublicKeyBytesRef<'a>;
103    type ConciseType = String;
104
105    fn concise(&'a self) -> ConciseBridgeAuthorityPublicKeyBytesRef<'a> {
106        ConciseBridgeAuthorityPublicKeyBytesRef(self)
107    }
108
109    fn concise_owned(&self) -> String {
110        format!("{:?}", ConciseBridgeAuthorityPublicKeyBytesRef(self))
111    }
112}
113
114// TODO: include epoch ID here to reduce race conditions?
115#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
116pub struct BridgeAuthoritySignInfo {
117    pub authority_pub_key: BridgeAuthorityPublicKey,
118    pub signature: BridgeAuthorityRecoverableSignature,
119}
120
121impl BridgeAuthoritySignInfo {
122    pub fn new(msg: &BridgeAction, secret: &BridgeAuthorityKeyPair) -> Self {
123        let msg_bytes = msg.to_bytes();
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();
140
141        self.authority_pub_key
142            .verify_recoverable_with_hash::<Keccak256>(&msg_bytes, &self.signature)
143            .map_err(|e| {
144                BridgeError::InvalidBridgeAuthoritySignature((
145                    self.authority_pub_key_bytes(),
146                    e.to_string(),
147                ))
148            })
149    }
150
151    pub fn authority_pub_key_bytes(&self) -> BridgeAuthorityPublicKeyBytes {
152        BridgeAuthorityPublicKeyBytes::from(&self.authority_pub_key)
153    }
154}
155
156/// Verifies a SignedBridgeAction (response from bridge authority to bridge
157/// client) represents the right BridgeAction, and is signed by the right
158/// authority.
159pub fn verify_signed_bridge_action(
160    expected_action: &BridgeAction,
161    signed_action: SignedBridgeAction,
162    expected_signer: &BridgeAuthorityPublicKeyBytes,
163    committee: &BridgeCommittee,
164) -> BridgeResult<VerifiedSignedBridgeAction> {
165    if signed_action.data() != expected_action {
166        return Err(BridgeError::MismatchedAction);
167    }
168
169    let sig = signed_action.auth_sig();
170    if &sig.authority_pub_key_bytes() != expected_signer {
171        return Err(BridgeError::MismatchedAuthoritySigner);
172    }
173    sig.verify(signed_action.data(), committee).tap_err(|e| {
174        tracing::error!(
175            "Failed to verify SignedBridgeEvent {:?}. Error {:?}",
176            signed_action,
177            e
178        )
179    })?;
180    Ok(VerifiedEnvelope::new_from_verified(signed_action))
181}
182
183#[cfg(test)]
184mod tests {
185    use std::{str::FromStr, sync::Arc};
186
187    use ethers::types::Address as EthAddress;
188    use fastcrypto::traits::{KeyPair, ToFromBytes};
189    use iota_types::{
190        base_types::IotaAddress,
191        bridge::{BridgeChainId, TOKEN_ID_ETH},
192        crypto::get_key_pair,
193        digests::TransactionDigest,
194    };
195    use prometheus::Registry;
196
197    use super::*;
198    use crate::{
199        events::EmittedIotaToEthTokenBridgeV1,
200        test_utils::{get_test_authority_and_key, get_test_iota_to_eth_bridge_action},
201        types::{BridgeAction, BridgeAuthority, IotaToEthBridgeAction, SignedBridgeAction},
202    };
203
204    #[test]
205    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
206    fn test_sign_and_verify_bridge_event_basic() -> anyhow::Result<()> {
207        telemetry_subscribers::init_for_testing();
208        let registry = Registry::new();
209        iota_metrics::init_metrics(&registry);
210
211        let (mut authority1, pubkey, secret) = get_test_authority_and_key(5000, 9999);
212        let pubkey_bytes = BridgeAuthorityPublicKeyBytes::from(&pubkey);
213
214        let (authority2, pubkey2, _secret) = get_test_authority_and_key(5000, 9999);
215        let pubkey_bytes2 = BridgeAuthorityPublicKeyBytes::from(&pubkey2);
216
217        let committee = BridgeCommittee::new(vec![authority1.clone(), authority2.clone()]).unwrap();
218
219        let action: BridgeAction =
220            get_test_iota_to_eth_bridge_action(None, Some(1), Some(1), Some(100), None, None, None);
221
222        let sig = BridgeAuthoritySignInfo::new(&action, &secret);
223
224        let signed_action = SignedBridgeAction::new_from_data_and_sig(action.clone(), sig.clone());
225
226        // Verification should succeed
227        let _ =
228            verify_signed_bridge_action(&action, signed_action.clone(), &pubkey_bytes, &committee)
229                .unwrap();
230
231        // Verification should fail - mismatched signer
232        assert!(matches!(
233            verify_signed_bridge_action(&action, signed_action.clone(), &pubkey_bytes2, &committee)
234                .unwrap_err(),
235            BridgeError::MismatchedAuthoritySigner
236        ));
237
238        let mismatched_action: BridgeAction =
239            get_test_iota_to_eth_bridge_action(None, Some(2), Some(3), Some(4), None, None, None);
240        // Verification should fail - mismatched action
241        assert!(matches!(
242            verify_signed_bridge_action(
243                &mismatched_action,
244                signed_action.clone(),
245                &pubkey_bytes2,
246                &committee
247            )
248            .unwrap_err(),
249            BridgeError::MismatchedAction,
250        ));
251
252        // Signature is invalid (signed over different message), verification should
253        // fail
254        let action2: BridgeAction =
255            get_test_iota_to_eth_bridge_action(None, Some(3), Some(5), Some(77), None, None, None);
256
257        let invalid_sig = BridgeAuthoritySignInfo::new(&action2, &secret);
258        let signed_action = SignedBridgeAction::new_from_data_and_sig(action.clone(), invalid_sig);
259        let _ = verify_signed_bridge_action(&action, signed_action, &pubkey_bytes, &committee)
260            .unwrap_err();
261
262        // Signer is not in committee, verification should fail
263        let (_, kp2): (_, fastcrypto::secp256k1::Secp256k1KeyPair) = get_key_pair();
264        let pubkey_bytes_2 = BridgeAuthorityPublicKeyBytes::from(kp2.public());
265        let secret2 = Arc::pin(kp2);
266        let sig2 = BridgeAuthoritySignInfo::new(&action, &secret2);
267        let signed_action2 = SignedBridgeAction::new_from_data_and_sig(action.clone(), sig2);
268        let _ = verify_signed_bridge_action(&action, signed_action2, &pubkey_bytes_2, &committee)
269            .unwrap_err();
270
271        // Authority is blocklisted, verification should fail
272        authority1.is_blocklisted = true;
273        let committee = BridgeCommittee::new(vec![authority1, authority2]).unwrap();
274        let signed_action = SignedBridgeAction::new_from_data_and_sig(action.clone(), sig);
275        let _ = verify_signed_bridge_action(&action, signed_action, &pubkey_bytes, &committee)
276            .unwrap_err();
277
278        Ok(())
279    }
280
281    #[test]
282    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
283    fn test_bridge_sig_verification_regression_test() {
284        telemetry_subscribers::init_for_testing();
285        let registry = Registry::new();
286        iota_metrics::init_metrics(&registry);
287
288        let public_key_bytes =
289            Hex::decode("02321ede33d2c2d7a8a152f275a1484edef2098f034121a602cb7d767d38680aa4")
290                .unwrap();
291        let pubkey1 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap();
292        let authority1 = BridgeAuthority {
293            pubkey: pubkey1.clone(),
294            voting_power: 2500,
295            is_blocklisted: false,
296            base_url: "".into(),
297        };
298
299        let public_key_bytes =
300            Hex::decode("027f1178ff417fc9f5b8290bd8876f0a157a505a6c52db100a8492203ddd1d4279")
301                .unwrap();
302        let pubkey2 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap();
303        let authority2 = BridgeAuthority {
304            pubkey: pubkey2.clone(),
305            voting_power: 2500,
306            is_blocklisted: false,
307            base_url: "".into(),
308        };
309
310        let public_key_bytes =
311            Hex::decode("026f311bcd1c2664c14277c7a80e4857c690626597064f89edc33b8f67b99c6bc0")
312                .unwrap();
313        let pubkey3 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap();
314        let authority3 = BridgeAuthority {
315            pubkey: pubkey3.clone(),
316            voting_power: 2500,
317            is_blocklisted: false,
318            base_url: "".into(),
319        };
320
321        let public_key_bytes =
322            Hex::decode("03a57b85771aedeb6d31c808be9a6e73194e4b70e679608f2bca68bcc684773736")
323                .unwrap();
324        let pubkey4 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap();
325        let authority4 = BridgeAuthority {
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::IotaToEthBridgeAction(IotaToEthBridgeAction {
341            iota_tx_digest: TransactionDigest::random(),
342            iota_tx_event_index: 0,
343            iota_bridge_event: EmittedIotaToEthTokenBridgeV1 {
344                nonce: 1,
345                iota_chain_id: BridgeChainId::IotaTestnet,
346                iota_address: IotaAddress::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_iota_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                // invalid sdig
377                &Hex::decode("8ba9ec92c2d5a44ecc123182f689b901a93921fd35f581354fea20b25a0ded6d055b96a64bdda77dd5a62b93d29abe93640aa3c1a136348093cd7a2418c6bfa302").unwrap(),
378            ).unwrap(),
379        };
380        sig.verify(&action, &committee).unwrap_err();
381    }
382
383    #[test]
384    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
385    fn test_bridge_authority_public_key_bytes_to_eth_address() {
386        let pub_key_bytes = BridgeAuthorityPublicKeyBytes::from_bytes(
387            &Hex::decode("02321ede33d2c2d7a8a152f275a1484edef2098f034121a602cb7d767d38680aa4")
388                .unwrap(),
389        )
390        .unwrap();
391        let addr = "0x68b43fd906c0b8f024a18c56e06744f7c6157c65"
392            .parse::<EthAddress>()
393            .unwrap();
394        assert_eq!(pub_key_bytes.to_eth_address(), addr);
395
396        // Example from: https://github.com/gakonst/ethers-rs/blob/master/ethers-core/src/utils/mod.rs#L1235
397        let pub_key_bytes = BridgeAuthorityPublicKeyBytes::from_bytes(
398            &Hex::decode("0376698beebe8ee5c74d8cc50ab84ac301ee8f10af6f28d0ffd6adf4d6d3b9b762")
399                .unwrap(),
400        )
401        .unwrap();
402        let addr = "0Ac1dF02185025F65202660F8167210A80dD5086"
403            .parse::<EthAddress>()
404            .unwrap();
405        assert_eq!(pub_key_bytes.to_eth_address(), addr);
406    }
407}