1use crate::taprpc;
2use taproot_assets_types as types;
3
4use bitcoin::hashes::{Hash, sha256::Hash as Sha256Hash};
5use bitcoin::{BlockHash, OutPoint, Witness};
6use std::convert::{TryFrom, TryInto};
7use std::str::FromStr;
8
9#[derive(Debug)]
10pub enum ConversionError {
11 InvalidEnumValue(String),
12 MissingField(String), InvalidStringFormat(String),
14 InvalidHashBytes(String),
15 InvalidWitnessData(String), RecursiveError(Box<ConversionError>),
17 Other(String), }
19
20pub type Result<T> = std::result::Result<T, ConversionError>;
21
22impl std::fmt::Display for ConversionError {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 ConversionError::InvalidEnumValue(s) => write!(f, "Invalid enum value: {}", s),
26 ConversionError::MissingField(s) => write!(f, "Missing field: {}", s),
27 ConversionError::InvalidStringFormat(s) => write!(f, "Invalid string format: {}", s),
28 ConversionError::InvalidHashBytes(s) => write!(f, "Invalid hash bytes: {}", s),
29 ConversionError::InvalidWitnessData(s) => write!(f, "Invalid witness data: {}", s),
30 ConversionError::RecursiveError(e) => write!(f, "Recursive conversion error: {}", e),
31 ConversionError::Other(s) => write!(f, "Conversion error: {}", s),
32 }
33 }
34}
35
36impl std::error::Error for ConversionError {
37 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
38 match self {
39 ConversionError::RecursiveError(e) => Some(e.as_ref()),
40 _ => None,
41 }
42 }
43}
44
45impl From<bitcoin::hashes::FromSliceError> for ConversionError {
46 fn from(e: bitcoin::hashes::FromSliceError) -> Self {
47 ConversionError::InvalidHashBytes(format!("Failed to convert from slice: {}", e))
48 }
49}
50
51impl From<bitcoin::consensus::encode::Error> for ConversionError {
52 fn from(e: bitcoin::consensus::encode::Error) -> Self {
53 ConversionError::Other(format!("Bitcoin consensus decoding error: {}", e))
54 }
55}
56
57fn vec_to_sha256hash(bytes: Vec<u8>, field_name: &str) -> Result<Sha256Hash> {
59 Sha256Hash::from_slice(&bytes).map_err(|err| {
60 ConversionError::InvalidHashBytes(format!(
61 "Invalid {} hash bytes (expected 32 for {}): {}, (got {} bytes)",
62 field_name,
63 field_name,
64 err,
65 bytes.len()
66 ))
67 })
68}
69
70fn string_to_outpoint(s: String, field_name: &str) -> Result<OutPoint> {
72 OutPoint::from_str(&s).map_err(|e| {
73 ConversionError::InvalidStringFormat(format!(
74 "Failed to parse {} from '{}': {}",
75 field_name, s, e
76 ))
77 })
78}
79
80fn string_to_blockhash(s: String, field_name: &str) -> Result<BlockHash> {
82 BlockHash::from_str(&s).map_err(|e| {
83 ConversionError::InvalidStringFormat(format!(
84 "Failed to parse {} from '{}': {}",
85 field_name, s, e
86 ))
87 })
88}
89
90fn try_option<F, R>(opt_f: Option<F>) -> Result<Option<R>>
92where
93 F: TryInto<R, Error = ConversionError>,
94{
95 opt_f
96 .map(TryInto::try_into)
97 .transpose()
98 .map_err(|err| ConversionError::RecursiveError(Box::new(err)))
99}
100
101fn try_vec<F, R>(vec_f: Vec<F>) -> Result<Vec<R>>
103where
104 F: TryInto<R, Error = ConversionError>,
105{
106 vec_f
107 .into_iter()
108 .map(TryInto::try_into)
109 .collect::<std::result::Result<Vec<R>, ConversionError>>()
110 .map_err(|err| ConversionError::RecursiveError(Box::new(err)))
111}
112
113impl TryFrom<taprpc::DecimalDisplay> for types::asset::DecimalDisplay {
116 type Error = ConversionError;
117
118 fn try_from(value: taprpc::DecimalDisplay) -> Result<Self> {
119 Ok(types::asset::DecimalDisplay {
120 decimal_display: value.decimal_display,
121 })
122 }
123}
124
125impl TryFrom<taprpc::GenesisInfo> for types::asset::GenesisInfo {
126 type Error = ConversionError;
127
128 fn try_from(value: taprpc::GenesisInfo) -> Result<Self> {
129 Ok(types::asset::GenesisInfo {
130 genesis_point: string_to_outpoint(value.genesis_point, "GenesisInfo.genesis_point")?,
131 name: value.name,
132 meta_hash: vec_to_sha256hash(value.meta_hash, "GenesisInfo.meta_hash")?,
133 asset_id: vec_to_sha256hash(value.asset_id, "GenesisInfo.asset_id")?,
134 asset_type: types::asset::AssetType::try_from(value.asset_type).map_err(|e| {
135 ConversionError::InvalidEnumValue(format!(
136 "GenesisInfo.asset_type (value: {}): {}",
137 value.asset_type, e
138 ))
139 })?,
140 output_index: value.output_index,
141 })
142 }
143}
144
145impl TryFrom<taprpc::AssetGroup> for types::asset::AssetGroup {
146 type Error = ConversionError;
147
148 fn try_from(value: taprpc::AssetGroup) -> Result<Self> {
149 let tapscript_root = if value.tapscript_root.is_empty() {
150 None
151 } else {
152 Some(vec_to_sha256hash(
153 value.tapscript_root,
154 "AssetGroup.tapscript_root",
155 )?)
156 };
157
158 Ok(types::asset::AssetGroup {
159 raw_group_key: value.raw_group_key,
160 tweaked_group_key: value.tweaked_group_key,
161 asset_witness: value.asset_witness,
162 tapscript_root: tapscript_root,
163 })
164 }
165}
166
167impl TryFrom<taprpc::AnchorInfo> for types::asset::AnchorInfo {
168 type Error = ConversionError;
169
170 fn try_from(value: taprpc::AnchorInfo) -> Result<Self> {
171 let tx = bitcoin::consensus::encode::deserialize(&value.anchor_tx)?;
172
173 Ok(types::asset::AnchorInfo {
174 anchor_tx: tx,
175 anchor_block_hash: string_to_blockhash(
176 value.anchor_block_hash,
177 "AnchorInfo.anchor_block_hash",
178 )?,
179 anchor_outpoint: string_to_outpoint(
180 value.anchor_outpoint,
181 "AnchorInfo.anchor_outpoint",
182 )?,
183 internal_key: value.internal_key,
184 merkle_root: vec_to_sha256hash(value.merkle_root, "AnchorInfo.merkle_root")?,
185 tapscript_sibling: value.tapscript_sibling,
186 block_height: value.block_height,
187 block_timestamp: value.block_timestamp,
188 })
189 }
190}
191
192impl TryFrom<taprpc::PrevInputAsset> for types::asset::PrevInputAsset {
193 type Error = ConversionError;
194
195 fn try_from(value: taprpc::PrevInputAsset) -> Result<Self> {
196 Ok(types::asset::PrevInputAsset {
197 anchor_point: string_to_outpoint(value.anchor_point, "PrevInputAsset.anchor_point")?,
198 asset_id: vec_to_sha256hash(value.asset_id, "PrevInputAsset.asset_id")?,
199 script_key: value.script_key,
200 amount: value.amount,
201 })
202 }
203}
204
205impl TryFrom<taprpc::Asset> for types::asset::Asset {
206 type Error = ConversionError;
207
208 fn try_from(value: taprpc::Asset) -> Result<Self> {
209 Ok(types::asset::Asset {
210 version: types::asset::AssetVersion::try_from(value.version).map_err(|e| {
211 ConversionError::InvalidEnumValue(format!(
212 "Asset.version (value: {}): {}",
213 value.version, e
214 ))
215 })?,
216 asset_genesis: try_option(value.asset_genesis)?,
217 amount: value.amount,
218 lock_time: value.lock_time,
219 relative_lock_time: value.relative_lock_time,
220 script_version: value.script_version,
221 script_key: value.script_key,
222 script_key_is_local: value.script_key_is_local,
223 asset_group: try_option(value.asset_group)?,
224 chain_anchor: try_option(value.chain_anchor)?,
225 prev_witnesses: try_vec(value.prev_witnesses)?,
226 split_commitment_root: None,
227 is_spent: value.is_spent,
228 lease_owner: value.lease_owner,
229 lease_expiry: value.lease_expiry,
230 is_burn: value.is_burn,
231 script_key_declared_known: value.script_key_declared_known,
232 script_key_has_script_path: value.script_key_has_script_path,
233 decimal_display: try_option(value.decimal_display)?,
234 script_key_type: types::asset::ScriptKeyType::try_from(value.script_key_type).map_err(
235 |e| {
236 ConversionError::InvalidEnumValue(format!(
237 "Asset.script_key_type (value: {}): {}",
238 value.script_key_type, e
239 ))
240 },
241 )?,
242 })
243 }
244}
245
246impl TryFrom<taprpc::SplitCommitment> for types::asset::SplitCommitment {
247 type Error = ConversionError;
248
249 fn try_from(value: taprpc::SplitCommitment) -> Result<Self> {
250 let rpc_asset = value.root_asset.ok_or_else(|| {
251 ConversionError::MissingField("SplitCommitment.root_asset".to_string())
252 })?;
253 let domain_asset: types::asset::Asset = rpc_asset
254 .try_into()
255 .map_err(|e: ConversionError| ConversionError::RecursiveError(Box::new(e)))?;
256 let proof = types::mssmt::MssmtProof { nodes: Vec::new() };
257 Ok(types::asset::SplitCommitment {
258 proof,
259 root_asset: Box::new(domain_asset),
260 })
261 }
262}
263
264impl TryFrom<taprpc::PrevWitness> for types::asset::PrevWitness {
265 type Error = ConversionError;
266
267 fn try_from(value: taprpc::PrevWitness) -> Result<Self> {
268 let prev_id = value
269 .prev_id
270 .map(|prev| {
271 let out_point =
272 string_to_outpoint(prev.anchor_point, "PrevInputAsset.anchor_point")?;
273 let asset_id = vec_to_sha256hash(prev.asset_id, "PrevInputAsset.asset_id")?;
274 if prev.script_key.len() != 33 {
275 return Err(ConversionError::InvalidHashBytes(format!(
276 "Invalid PrevInputAsset.script_key length: {}",
277 prev.script_key.len()
278 )));
279 }
280 let mut key_bytes = [0u8; 33];
281 key_bytes.copy_from_slice(&prev.script_key);
282 Ok(types::asset::PrevId {
283 out_point,
284 asset_id,
285 script_key: types::asset::SerializedKey { bytes: key_bytes },
286 })
287 })
288 .transpose()?;
289
290 Ok(types::asset::PrevWitness {
291 prev_id,
292 tx_witness: Witness::from(value.tx_witness),
293 split_commitment: try_option(value.split_commitment)?,
294 })
295 }
296}
297
298impl TryFrom<taprpc::ListAssetResponse> for crate::taprpc::types::ListAssetsResponse {
299 type Error = ConversionError;
300
301 fn try_from(value: taprpc::ListAssetResponse) -> Result<Self> {
302 let assets = value
303 .assets
304 .into_iter()
305 .map(types::asset::Asset::try_from)
306 .collect::<Result<Vec<types::asset::Asset>>>()
307 .map_err(|e| ConversionError::RecursiveError(Box::new(e)))?;
308
309 Ok(crate::taprpc::types::ListAssetsResponse {
310 assets,
311 unconfirmed_transfers: value.unconfirmed_transfers,
312 unconfirmed_mints: value.unconfirmed_mints,
313 })
314 }
315}
316
317impl TryFrom<taprpc::ProofFile> for crate::taprpc::types::ExportProofResponse {
318 type Error = ConversionError;
319
320 fn try_from(value: taprpc::ProofFile) -> Result<Self> {
321 let genesis_point = if value.genesis_point.is_empty() {
322 None
323 } else {
324 Some(string_to_outpoint(
325 value.genesis_point,
326 "ProofFile.genesis_point",
327 )?)
328 };
329
330 Ok(crate::taprpc::types::ExportProofResponse {
331 raw_proof_file: value.raw_proof_file,
332 genesis_point: genesis_point,
333 })
334 }
335}
336
337impl TryFrom<taprpc::VerifyProofResponse> for crate::taprpc::types::VerifyProofResponse {
338 type Error = ConversionError;
339
340 fn try_from(value: taprpc::VerifyProofResponse) -> Result<Self> {
341 Ok(crate::taprpc::types::VerifyProofResponse {
342 valid: value.valid,
343 decoded_proof: try_option(value.decoded_proof)?,
344 })
345 }
346}
347
348impl TryFrom<taprpc::AssetMeta> for types::asset::AssetMeta {
351 type Error = ConversionError;
352
353 fn try_from(value: taprpc::AssetMeta) -> Result<Self> {
354 Ok(types::asset::AssetMeta {
355 data: value.data,
356 meta_type: types::asset::AssetMetaType::try_from(value.r#type).map_err(|e| {
357 ConversionError::InvalidEnumValue(format!(
358 "AssetMeta.type (value: {}): {}",
359 value.r#type, e
360 ))
361 })?,
362 })
363 }
364}
365
366impl TryFrom<taprpc::GenesisReveal> for types::asset::GenesisReveal {
368 type Error = ConversionError;
369
370 fn try_from(value: taprpc::GenesisReveal) -> Result<Self> {
371 Ok(types::asset::GenesisReveal {
375 genesis_base: try_option(value.genesis_base_reveal)?,
376 asset_type: types::asset::AssetType::Normal, amount: 0, meta_reveal: None, })
380 }
381}
382
383impl TryFrom<taprpc::GroupKeyReveal> for types::asset::GroupKeyReveal {
384 type Error = ConversionError;
385
386 fn try_from(value: taprpc::GroupKeyReveal) -> Result<Self> {
387 if value.raw_group_key.len() != 33 {
388 return Err(ConversionError::InvalidHashBytes(format!(
389 "Invalid GroupKeyReveal.raw_group_key length: {}",
390 value.raw_group_key.len()
391 )));
392 }
393 let mut key_bytes = [0u8; 33];
394 key_bytes.copy_from_slice(&value.raw_group_key);
395 let tapscript_root = if value.tapscript_root.is_empty() {
396 None
397 } else {
398 Some(vec_to_sha256hash(
399 value.tapscript_root,
400 "GroupKeyReveal.tapscript_root",
401 )?)
402 };
403
404 Ok(types::asset::GroupKeyReveal {
405 raw_group_key: types::asset::SerializedKey { bytes: key_bytes },
406 tapscript_root,
407 version: None,
408 custom_subtree_root: None,
409 })
410 }
411}
412
413impl TryFrom<taprpc::DecodedProof> for crate::taprpc::types::DecodedProof {
414 type Error = ConversionError;
415
416 fn try_from(value: taprpc::DecodedProof) -> Result<Self> {
417 let challenge_witness = if value.challenge_witness.is_empty() {
418 None
419 } else {
420 Some(Witness::from(value.challenge_witness))
421 };
422
423 let inclusion_proof = types::proof::TaprootProof::from_bytes(&value.inclusion_proof)
424 .map_err(|e| {
425 ConversionError::Other(format!("Failed to decode inclusion proof: {}", e))
426 })?;
427
428 let exclusion_proofs = value
429 .exclusion_proofs
430 .into_iter()
431 .map(|proof_bytes| {
432 types::proof::TaprootProof::from_bytes(&proof_bytes).map_err(|e| {
433 ConversionError::Other(format!("Failed to decode exclusion proof: {}", e))
434 })
435 })
436 .collect::<Result<Vec<types::proof::TaprootProof>>>()?;
437
438 let split_root_proof = if value.split_root_proof.is_empty() {
439 None
440 } else {
441 Some(
442 types::proof::TaprootProof::from_bytes(&value.split_root_proof).map_err(|e| {
443 ConversionError::Other(format!("Failed to decode split root proof: {}", e))
444 })?,
445 )
446 };
447
448 let tx_merkle_proof = types::proof::TxMerkleProof::from_bytes(&value.tx_merkle_proof)
449 .map_err(|e| {
450 ConversionError::Other(format!("Failed to decode tx merkle proof: {}", e))
451 })?;
452
453 Ok(crate::taprpc::types::DecodedProof {
454 proof_at_depth: value.proof_at_depth,
455 number_of_proofs: value.number_of_proofs,
456 asset: value
457 .asset
458 .ok_or_else(|| ConversionError::MissingField("DecodedProof.asset".to_string()))?
459 .try_into()?,
460 meta_reveal: try_option(value.meta_reveal)?,
461 tx_merkle_proof,
462 inclusion_proof,
463 exclusion_proofs,
464 split_root_proof,
465 num_additional_inputs: value.num_additional_inputs,
466 challenge_witness,
467 is_burn: value.is_burn,
468 genesis_reveal: try_option(value.genesis_reveal)?,
469 group_key_reveal: try_option(value.group_key_reveal)?,
470 })
471 }
472}
473
474