clementine_tx_sender/citrea/
mod.rs

1//! Provides functions to build Bitcoin transactions
2//! related to commit-reveal pattern for Citrea rollup.
3
4pub mod data_serialization;
5mod reveal_scripts;
6pub mod sync;
7
8#[cfg(all(test, feature = "citrea", feature = "testing"))]
9mod tests;
10
11use bitcoin::absolute::LockTime;
12use bitcoin::blockdata::script;
13use bitcoin::secp256k1::{Message, PublicKey, SecretKey};
14use bitcoin::{Address, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid, Witness};
15use clementine_primitives::MIN_TAPROOT_AMOUNT;
16use sha2::{Digest, Sha256};
17
18use crate::signer::SECP;
19pub use tx_sender_types::CitreaTxRequest;
20
21/// Maximum raw BatchProof chunk size.
22///
23/// This is intentionally below the 400,000 WU standard transaction limit. The
24/// raw chunk is Borsh-wrapped and then pushed into a tapscript in 520-byte
25/// pieces, so the final reveal transaction is larger than the raw chunk bytes.
26/// see test `max_chunk_reveal_transaction_stays_under_standard_weight`
27pub(crate) const MAX_CHUNK_SIZE: u32 = 390_000;
28
29/// Type represents a typed enum for transaction kind
30/// Conversion to u16 (to_bytes) should be same as used in citrea repo.
31/// citrea/crates/bitcoin-da/src/helpers/mod.rs
32#[derive(Debug, Clone, Copy)]
33#[repr(u16)]
34pub enum TransactionKind {
35    /// This type of transaction includes full body (< 400kb)
36    Complete = 0,
37    /// This type of transaction includes txids of chunks (>= 400kb)
38    Aggregate = 1,
39    /// This type of transaction includes chunk parts of body (>= 400kb)
40    Chunks = 2,
41    /// This type of transaction includes a new batch proof method_id
42    BatchProofMethodId = 3,
43    /// SequencerCommitment
44    SequencerCommitment = 4,
45    // /// ForcedTransaction
46    // ForcedTransaction, // = ?,
47    /// An unknown type of transaction
48    Unknown(u16),
49}
50
51impl TransactionKind {
52    /// Serialize itself into bytes.
53    fn to_bytes(self) -> [u8; 2] {
54        match self {
55            TransactionKind::Complete => 0u16.to_le_bytes(),
56            TransactionKind::Aggregate => 1u16.to_le_bytes(),
57            TransactionKind::Chunks => 2u16.to_le_bytes(),
58            TransactionKind::BatchProofMethodId => 3u16.to_le_bytes(),
59            TransactionKind::SequencerCommitment => 4u16.to_le_bytes(),
60            TransactionKind::Unknown(n) => n.to_le_bytes(),
61        }
62    }
63
64    /// Construct a `TransactionKind` from its numeric representation.
65    pub(crate) fn from_u16(value: u16) -> TransactionKind {
66        match value {
67            0 => TransactionKind::Complete,
68            1 => TransactionKind::Aggregate,
69            2 => TransactionKind::Chunks,
70            3 => TransactionKind::BatchProofMethodId,
71            4 => TransactionKind::SequencerCommitment,
72            n => TransactionKind::Unknown(n),
73        }
74    }
75
76    /// Returns the numeric transaction kind as an `i16` for storage.
77    pub(crate) fn as_i16(&self) -> i16 {
78        match self {
79            TransactionKind::Complete => 0,
80            TransactionKind::Aggregate => 1,
81            TransactionKind::Chunks => 2,
82            TransactionKind::BatchProofMethodId => 3,
83            TransactionKind::SequencerCommitment => 4,
84            TransactionKind::Unknown(n) => *n as i16,
85        }
86    }
87}
88
89/// Build the commit part of commit-reveal pair
90/// Multiple commits can be in the same tx (if chunks are used, each commit needs a different nonce so that the addresses are different)
91pub(crate) fn build_commit_transaction(recipients: &[Address]) -> Transaction {
92    let outputs = recipients
93        .iter()
94        .map(|recipient| TxOut {
95            value: MIN_TAPROOT_AMOUNT,
96            script_pubkey: recipient.script_pubkey(),
97        })
98        .collect();
99
100    Transaction {
101        lock_time: LockTime::ZERO,
102        version: bitcoin::transaction::Version(2),
103        input: vec![],
104        output: outputs,
105    }
106}
107
108/// Build the reveal part of commit-reveal pair
109pub(crate) fn build_reveal_transaction(input_txid: Txid, input_vout: u32) -> Transaction {
110    let inputs = vec![TxIn {
111        previous_output: OutPoint {
112            txid: input_txid,
113            vout: input_vout,
114        },
115        script_sig: script::Builder::new().into_script(),
116        witness: Witness::new(),
117        sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
118    }];
119
120    Transaction {
121        lock_time: LockTime::ZERO,
122        version: bitcoin::transaction::Version(2),
123        input: inputs,
124        output: vec![],
125    }
126}
127
128/// Signs a message with a private key
129/// Returns (signature, public_key)
130pub fn sign_blob_with_private_key(blob: &[u8], private_key: &SecretKey) -> (Vec<u8>, Vec<u8>) {
131    let message = calculate_sha256(blob);
132    let public_key = PublicKey::from_secret_key(&SECP, private_key);
133    let msg = Message::from_digest(message);
134    let sig = SECP.sign_ecdsa(&msg, private_key);
135    (
136        sig.serialize_compact().to_vec(),
137        public_key.serialize().to_vec(),
138    )
139}
140
141pub(crate) fn calculate_sha256(input: &[u8]) -> [u8; 32] {
142    let mut hasher = Sha256::default();
143    hasher.update(input);
144    hasher.finalize().into()
145}