clementine_tx_sender/
signer.rs

1use bitcoin::hashes::Hash;
2use bitcoin::secp256k1::schnorr;
3use bitcoin::secp256k1::{All, Keypair, Message, Secp256k1, SecretKey};
4use bitcoin::taproot::{TapNodeHash, TapTweakHash};
5use bitcoin::{Address, Network, TapSighash, XOnlyPublicKey};
6use clementine_errors::BridgeError;
7use clementine_utils::sign::TapTweakData;
8use eyre::Context;
9use std::sync::LazyLock;
10
11pub(crate) static SECP: LazyLock<Secp256k1<All>> = LazyLock::new(Secp256k1::new);
12
13#[cfg(feature = "citrea")]
14use sha2::{Digest, Sha256};
15
16fn calc_tweaked_keypair(
17    keypair: &Keypair,
18    merkle_root: Option<TapNodeHash>,
19) -> Result<Keypair, BridgeError> {
20    Ok(keypair
21        .add_xonly_tweak(
22            &SECP,
23            &TapTweakHash::from_key_and_tweak(keypair.x_only_public_key().0, merkle_root)
24                .to_scalar(),
25        )
26        .wrap_err("Failed to add tweak to keypair")?)
27}
28
29#[derive(Clone, Debug)]
30pub(crate) struct TxSenderSigningKey {
31    keypair: Keypair,
32    xonly_public_key: XOnlyPublicKey,
33    address: Address,
34}
35
36impl TxSenderSigningKey {
37    pub(crate) fn new(secret_key: SecretKey, network: Network) -> Self {
38        let keypair = Keypair::from_secret_key(&SECP, &secret_key);
39        let (xonly, _parity) = XOnlyPublicKey::from_keypair(&keypair);
40        let address = Address::p2tr(&SECP, xonly, None, network);
41
42        Self {
43            keypair,
44            xonly_public_key: xonly,
45            address,
46        }
47    }
48
49    pub(crate) fn address(&self) -> &Address {
50        &self.address
51    }
52
53    pub(crate) fn xonly_public_key(&self) -> XOnlyPublicKey {
54        self.xonly_public_key
55    }
56
57    pub(crate) fn sign_with_tweak_data(
58        &self,
59        sighash: TapSighash,
60        tweak_data: TapTweakData,
61    ) -> Result<schnorr::Signature, BridgeError> {
62        let keypair;
63        let keypair_ref = match tweak_data {
64            TapTweakData::KeyPath(merkle_root) => {
65                keypair = calc_tweaked_keypair(&self.keypair, merkle_root)?;
66                &keypair
67            }
68            TapTweakData::ScriptPath => &self.keypair,
69            TapTweakData::Unknown => return Err(eyre::eyre!("Spend Data Unknown").into()),
70        };
71
72        Ok(SECP
73            .sign_schnorr_no_aux_rand(&Message::from_digest(sighash.to_byte_array()), keypair_ref))
74    }
75
76    /// Signs a blob using ECDSA and returns (signature, public_key).
77    /// This is used for Citrea DA payload authentication.
78    /// Returns (signature, public_key) as serialized bytes.
79    #[cfg(feature = "citrea")]
80    pub(crate) fn sign_blob(&self, blob: &[u8]) -> (Vec<u8>, Vec<u8>) {
81        let secret_key = self.keypair.secret_key();
82        let message = {
83            let mut hasher = Sha256::default();
84            hasher.update(blob);
85            hasher.finalize().into()
86        };
87        let public_key = self.keypair.public_key();
88        let msg = Message::from_digest(message);
89        let sig = SECP.sign_ecdsa(&msg, &secret_key);
90        (
91            sig.serialize_compact().to_vec(),
92            public_key.serialize().to_vec(),
93        )
94    }
95}