spark_rust/wallet/internal_handlers/implementations/
deposit.rs1use 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
43pub(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 &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 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}