1use 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 let pubkey = VerifyingKey::from_sec1_bytes(self.as_bytes()).unwrap();
40 let affine: ðers::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 fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
58 let pk = BridgeAuthorityPublicKey::from_bytes(bytes)?;
59 Ok(Self::from(&pk))
60 }
61
62 fn as_bytes(&self) -> &[u8] {
64 self.as_ref()
65 }
66}
67
68impl 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#[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 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();
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
156pub 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(®istry);
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 let _ =
228 verify_signed_bridge_action(&action, signed_action.clone(), &pubkey_bytes, &committee)
229 .unwrap();
230
231 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 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 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 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 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(®istry);
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 &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 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}