reth_seismic_primitives/
test_utils.rs

1//! Test utils for seismic primitives, e.g. SeismicTransactionSigned
2
3use crate::SeismicTransactionSigned;
4use alloy_consensus::SignableTransaction;
5use alloy_dyn_abi::TypedData;
6use alloy_eips::eip2718::Encodable2718;
7use alloy_network::{EthereumWallet, TransactionBuilder};
8use alloy_primitives::{aliases::U96, hex_literal, Address, Bytes, Signature, TxKind, U256};
9use alloy_rpc_types::{TransactionInput, TransactionRequest};
10use alloy_signer_local::PrivateKeySigner;
11use core::str::FromStr;
12use enr::EnrKey;
13use k256::ecdsa::SigningKey;
14use reth_enclave::MockEnclaveServer;
15use secp256k1::{PublicKey, SecretKey};
16use seismic_alloy_consensus::{
17    SeismicTxEnvelope, SeismicTypedTransaction, TxSeismic, TxSeismicElements, TypedDataRequest,
18};
19use seismic_alloy_rpc_types::SeismicTransactionRequest;
20use seismic_enclave::keys::GetPurposeKeysRequest;
21
22/// Get the network public key
23pub fn get_network_public_key() -> PublicKey {
24    let purpose_keys = MockEnclaveServer::get_purpose_keys(GetPurposeKeysRequest { epoch: 0 });
25    purpose_keys.tx_io_pk
26}
27
28/// Get the client's sk for tx io
29pub fn get_client_io_sk() -> SecretKey {
30    let private_key_bytes =
31        hex_literal::hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
32    SecretKey::from_slice(&private_key_bytes).expect("Invalid private key")
33}
34
35/// Get the client's signing private key
36pub fn get_signing_private_key() -> SigningKey {
37    let private_key_bytes =
38        hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
39    let signing_key =
40        SigningKey::from_bytes(&private_key_bytes.into()).expect("Invalid private key");
41    signing_key
42}
43
44/// Get a wrong private secp256k1 key
45pub fn get_wrong_private_key() -> SecretKey {
46    let private_key_bytes =
47        hex_literal::hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1e");
48    SecretKey::from_slice(&private_key_bytes).expect("Invalid private key")
49}
50
51/// Get the encryption nonce
52pub fn get_encryption_nonce() -> U96 {
53    U96::MAX
54}
55
56/// Get the seismic elements
57pub fn get_seismic_elements() -> TxSeismicElements {
58    TxSeismicElements {
59        encryption_pubkey: get_client_io_sk().public(),
60        encryption_nonce: get_encryption_nonce(),
61        message_version: 0,
62    }
63}
64
65/// Encrypt plaintext using network public key and client private key
66pub fn client_encrypt(plaintext: &Bytes) -> Result<Bytes, anyhow::Error> {
67    get_seismic_elements().client_encrypt(plaintext, &get_network_public_key(), &get_client_io_sk())
68}
69
70/// Decrypt ciphertext using network public key and client private key
71pub fn client_decrypt(ciphertext: &Bytes) -> Result<Bytes, anyhow::Error> {
72    get_seismic_elements().client_decrypt(
73        ciphertext,
74        &get_network_public_key(),
75        &get_client_io_sk(),
76    )
77}
78
79/// Get the plaintext for a seismic transaction
80pub fn get_plaintext() -> Bytes {
81    Bytes::from_str("24a7f0b7000000000000000000000000000000000000000000000000000000000000000b")
82        .unwrap()
83}
84
85/// Encrypt plaintext using network public key and client private key
86pub fn get_ciphertext() -> Bytes {
87    let encrypted_data = client_encrypt(&get_plaintext()).unwrap();
88    encrypted_data
89}
90
91/// Get a seismic transaction
92pub fn get_seismic_tx() -> TxSeismic {
93    let ciphertext = get_ciphertext();
94    TxSeismic {
95        chain_id: 5123, // seismic chain id
96        nonce: 1,
97        gas_price: 20000000000,
98        gas_limit: 210000,
99        to: alloy_primitives::TxKind::Call(
100            Address::from_str("0x5fbdb2315678afecb367f032d93f642f64180aa3").unwrap(),
101        ),
102        value: U256::ZERO,
103        input: Bytes::copy_from_slice(&ciphertext),
104        seismic_elements: get_seismic_elements(),
105    }
106}
107
108/// Sign a seismic transaction
109pub fn sign_seismic_tx(tx: &TxSeismic, signing_sk: &SigningKey) -> Signature {
110    let _signature = signing_sk
111        .clone()
112        .sign_prehash_recoverable(tx.signature_hash().as_slice())
113        .expect("Failed to sign");
114
115    let recoverid = _signature.1;
116    let _signature = _signature.0;
117
118    let signature = Signature::new(
119        U256::from_be_slice(_signature.r().to_bytes().as_slice()),
120        U256::from_be_slice(_signature.s().to_bytes().as_slice()),
121        recoverid.is_y_odd(),
122    );
123
124    signature
125}
126
127/// signes a [`SeismicTypedTransaction`] using the provided [`SigningKey`]
128pub fn sign_seismic_typed_tx(
129    typed_data: &SeismicTypedTransaction,
130    signing_sk: &SigningKey,
131) -> Signature {
132    let sig_hash = typed_data.signature_hash();
133    let sig = signing_sk.sign_prehash_recoverable(&sig_hash.as_slice()).unwrap();
134    let recoverid = sig.1;
135
136    let signature = Signature::new(
137        U256::from_be_slice(sig.0.r().to_bytes().as_slice()),
138        U256::from_be_slice(sig.0.s().to_bytes().as_slice()),
139        recoverid.is_y_odd(),
140    );
141    signature
142}
143
144/// Get a signed seismic transaction
145pub fn get_signed_seismic_tx() -> SeismicTransactionSigned {
146    let signing_sk = get_signing_private_key();
147    let tx = get_seismic_tx();
148    let signature = sign_seismic_tx(&tx, &signing_sk);
149    SignableTransaction::into_signed(tx, signature).into()
150}
151
152/// Get the encoding of a signed seismic transaction
153pub fn get_signed_seismic_tx_encoding() -> Vec<u8> {
154    let signed_tx = get_signed_seismic_tx();
155    let mut encoding = Vec::new();
156
157    signed_tx.encode_2718(&mut encoding);
158    encoding
159}
160
161/// Get an unsigned seismic transaction request
162pub async fn get_unsigned_seismic_tx_request(
163    sk_wallet: &PrivateKeySigner,
164    nonce: u64,
165    to: TxKind,
166    chain_id: u64,
167    plaintext: Bytes,
168) -> SeismicTransactionRequest {
169    SeismicTransactionRequest {
170        inner: TransactionRequest {
171            from: Some(sk_wallet.address()),
172            nonce: Some(nonce),
173            value: Some(U256::from(0)),
174            to: Some(to),
175            gas: Some(6000000),
176            gas_price: Some(20e9 as u128),
177            chain_id: Some(chain_id),
178            input: TransactionInput {
179                input: Some(client_encrypt(&plaintext).unwrap()),
180                data: None,
181            },
182            transaction_type: Some(TxSeismic::TX_TYPE),
183            ..Default::default()
184        },
185        seismic_elements: Some(get_seismic_elements()),
186    }
187}
188
189/// Signs an arbitrary [`TransactionRequest`] using the provided wallet
190pub async fn sign_tx(wallet: PrivateKeySigner, tx: SeismicTransactionRequest) -> SeismicTxEnvelope {
191    let signer = EthereumWallet::from(wallet);
192    <SeismicTransactionRequest as TransactionBuilder<seismic_alloy_network::Seismic>>::build(
193        tx, &signer,
194    )
195    .await
196    .unwrap()
197}
198
199/// Create a seismic transaction
200pub async fn get_signed_seismic_tx_bytes(
201    sk_wallet: &PrivateKeySigner,
202    nonce: u64,
203    to: TxKind,
204    chain_id: u64,
205    plaintext: Bytes,
206) -> Bytes {
207    let tx = get_unsigned_seismic_tx_request(sk_wallet, nonce, to, chain_id, plaintext).await;
208    let signed_inner = sign_tx(sk_wallet.clone(), tx).await;
209    <SeismicTxEnvelope as Encodable2718>::encoded_2718(&signed_inner).into()
210}
211
212/// Get an unsigned seismic transaction typed data
213pub async fn get_unsigned_seismic_tx_typed_data(
214    sk_wallet: &PrivateKeySigner,
215    nonce: u64,
216    to: TxKind,
217    chain_id: u64,
218    decrypted_input: Bytes,
219) -> TypedData {
220    let tx_request =
221        get_unsigned_seismic_tx_request(sk_wallet, nonce, to, chain_id, decrypted_input).await;
222    let typed_tx = tx_request.build_typed_tx().unwrap();
223    match typed_tx {
224        SeismicTypedTransaction::Seismic(seismic) => seismic.eip712_to_type_data(),
225        _ => panic!("Typed transaction is not a seismic transaction"),
226    }
227}
228
229/// Create a seismic transaction with typed data
230pub async fn get_signed_seismic_tx_typed_data(
231    sk_wallet: &PrivateKeySigner,
232    nonce: u64,
233    to: TxKind,
234    chain_id: u64,
235    plaintext: Bytes,
236) -> TypedDataRequest {
237    let tx = get_unsigned_seismic_tx_request(sk_wallet, nonce, to, chain_id, plaintext).await;
238    tx.seismic_elements.unwrap().message_version = 2;
239    let signed = sign_tx(sk_wallet.clone(), tx).await;
240
241    match signed {
242        SeismicTxEnvelope::Seismic(tx) => tx.into(),
243        _ => panic!("Signed transaction is not a seismic transaction"),
244    }
245}