spark_rust/wallet/internal_handlers/implementations/
deposit.rs

1use std::collections::HashMap;
2use std::str::FromStr;
3
4use crate::error::validation::ValidationError;
5use crate::error::SparkSdkError;
6use crate::signer::traits::SparkSigner;
7use crate::wallet::client::SparkSdk;
8use crate::wallet::internal_handlers::traits::deposit::DepositInternalHandlers;
9use crate::wallet::utils::bitcoin::compute_taproot_key_no_script;
10use crate::wallet::utils::ecdsa::verify_operator_ecdsa_signature;
11use crate::wallet::utils::proof_of_possession::proof_of_possession_message_hash_for_deposit_address;
12use crate::SparkNetwork;
13use bitcoin::secp256k1;
14use bitcoin::secp256k1::PublicKey;
15use sha256;
16use tonic::async_trait;
17
18pub(crate) struct DepositAddressProof {
19    pub(crate) address_signatures: HashMap<String, secp256k1::ecdsa::Signature>,
20    pub(crate) proof_of_possession_signature: secp256k1::schnorr::Signature,
21}
22
23impl TryFrom<spark_protos::spark::DepositAddressProof> for DepositAddressProof {
24    type Error = SparkSdkError;
25
26    fn try_from(value: spark_protos::spark::DepositAddressProof) -> Result<Self, Self::Error> {
27        let mut address_signatures = HashMap::new();
28        for (key, value) in value.address_signatures.iter() {
29            let sig = secp256k1::ecdsa::Signature::from_der(value)?;
30            address_signatures.insert(key.clone(), sig);
31        }
32
33        let proof_of_possession_signature =
34            secp256k1::schnorr::Signature::from_slice(&value.proof_of_possession_signature)?;
35
36        Ok(DepositAddressProof {
37            address_signatures,
38            proof_of_possession_signature,
39        })
40    }
41}
42
43/// Wrapper around [spark_protos::spark::Address]
44pub(crate) struct Address {
45    pub(crate) address: bitcoin::Address,
46    pub(crate) verifying_key: PublicKey,
47    pub(crate) deposit_address_proof: Option<DepositAddressProof>,
48}
49
50impl Address {
51    pub(crate) fn try_from_address(
52        value: spark_protos::spark::Address,
53        network: SparkNetwork,
54    ) -> Result<Self, SparkSdkError> {
55        let address = bitcoin::Address::from_str(&value.address)
56            .map_err(|_| {
57                SparkSdkError::from(ValidationError::InvalidAddress {
58                    address: value.address.clone(),
59                })
60            })
61            .and_then(|addr| {
62                addr.require_network(network.to_bitcoin_network())
63                    .map_err(|_| {
64                        SparkSdkError::from(ValidationError::InvalidAddress {
65                            address: value.address.clone(),
66                        })
67                    })
68            })?;
69
70        let verifying_key = PublicKey::from_slice(&value.verifying_key)?;
71
72        let deposit_address_proof = match value.deposit_address_proof {
73            Some(deposit_address_proof) => {
74                Some(DepositAddressProof::try_from(deposit_address_proof)?)
75            }
76            None => None,
77        };
78
79        Ok(Address {
80            address,
81            verifying_key,
82            deposit_address_proof,
83        })
84    }
85}
86
87#[async_trait]
88impl<S: SparkSigner + Send + Sync + Clone + 'static> DepositInternalHandlers<S> for SparkSdk<S> {
89    #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
90    async fn validate_deposit_address(
91        &self,
92        address: &Address,
93        user_pubkey: &PublicKey,
94    ) -> Result<(), SparkSdkError> {
95        use spark_cryptography::key_arithmetic::subtract_public_keys;
96
97        let deposit_address_proof = match &address.deposit_address_proof {
98            Some(deposit_address_proof) => deposit_address_proof,
99            None => {
100                return Err(SparkSdkError::from(
101                    ValidationError::DepositAddressValidationFailed {
102                        address: format!(
103                            "{} (error: Deposit address proof is empty)",
104                            address.address
105                        ),
106                    },
107                ));
108            }
109        };
110
111        let se_pubkey = subtract_public_keys(&address.verifying_key, user_pubkey)?;
112
113        let message = proof_of_possession_message_hash_for_deposit_address(
114            &self.get_spark_address()?,
115            &se_pubkey,
116            &address.address,
117        );
118
119        let taproot_key = compute_taproot_key_no_script(se_pubkey)?;
120
121        let secp = bitcoin::secp256k1::Secp256k1::new();
122        let verified = secp.verify_schnorr(
123            &deposit_address_proof.proof_of_possession_signature,
124            &bitcoin::secp256k1::Message::from_digest_slice(&message)?,
125            // &bitcoin::XOnlyPublicKey::from_slice(&se_pubkey.serialize())?,
126            &taproot_key,
127        );
128
129        if verified.is_err() {
130            return Err(SparkSdkError::from(
131                ValidationError::DepositAddressValidationFailed {
132                    address: format!("{} (error: signature verification failed)", address.address),
133                },
134            ));
135        }
136
137        let addr_hash = sha256::digest(address.address.to_string().as_bytes());
138        let secp = bitcoin::secp256k1::Secp256k1::verification_only();
139        for operator in self.config.spark_config.spark_operators.iter() {
140            if operator.id == self.config.spark_config.coordinator_index {
141                continue;
142            }
143
144            let operator_frost_id = operator.frost_identifier_str();
145            let operator_sig = deposit_address_proof
146                .address_signatures
147                .get(&operator_frost_id)
148                .ok_or_else(|| {
149                    SparkSdkError::from(ValidationError::DepositAddressValidationFailed {
150                        address: format!(
151                            "{} (error: Missing signature for operator {} with frost identifier: {})",
152                            address.address, operator.id, operator_frost_id
153                        ),
154                    })
155                })?;
156
157            // Convert hex string addr_hash to bytes
158            let addr_hash_bytes = hex::decode(&addr_hash).map_err(|e| {
159                SparkSdkError::from(ValidationError::DepositAddressValidationFailed {
160                    address: format!(
161                        "{} (error: Failed to decode addr_hash: {})",
162                        address.address, e
163                    ),
164                })
165            })?;
166
167            verify_operator_ecdsa_signature(
168                operator_sig,
169                &operator.identity_public_key,
170                &addr_hash_bytes,
171                &secp,
172            )?;
173        }
174
175        Ok(())
176    }
177}