lib.rs (5553B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022-2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 use std::str::FromStr; 17 18 use bitcoin::{Address, Amount, Network, Txid, hashes::hex::FromHex}; 19 use rpc::{Category, Rpc, Transaction}; 20 use rpc_utils::{segwit_min_amount, sender_address}; 21 use segwit::{decode_segwit_msg, encode_segwit_key}; 22 use taler_common::{api_common::EddsaPublicKey, config::parser::ConfigSource}; 23 24 pub mod api; 25 pub mod cli; 26 pub mod config; 27 pub mod db; 28 mod fail_point; 29 mod loops; 30 pub mod payto; 31 pub mod rpc; 32 pub mod rpc_utils; 33 pub mod segwit; 34 pub mod setup; 35 pub mod sql; 36 pub mod taler_utils; 37 38 pub const CONFIG_SOURCE: ConfigSource = ConfigSource::simple("depolymerizer-bitcoin"); 39 pub const DB_SCHEMA: &str = "depolymerizer_bitcoin"; 40 41 #[derive(Debug, thiserror::Error)] 42 pub enum GetSegwitErr { 43 #[error(transparent)] 44 Decode(#[from] segwit::DecodeSegWitErr), 45 #[error(transparent)] 46 RPC(#[from] rpc::Error), 47 } 48 49 #[derive(Debug, thiserror::Error)] 50 pub enum GetOpReturnErr { 51 #[error("Missing opreturn")] 52 MissingOpReturn, 53 #[error(transparent)] 54 RPC(#[from] rpc::Error), 55 } 56 57 /// An extended bitcoincore JSON-RPC api client who can send and retrieve metadata with their transaction 58 impl Rpc { 59 /// Send a transaction with a 32B key as metadata encoded using fake segwit addresses 60 pub async fn send_segwit_key( 61 &mut self, 62 to: &Address, 63 amount: &Amount, 64 metadata: &[u8; 32], 65 ) -> rpc::Result<Txid> { 66 let network = guess_network(to); 67 let hrp = match network { 68 Network::Bitcoin => bech32::hrp::BC, 69 Network::Testnet | Network::Signet => bech32::hrp::TB, 70 Network::Regtest => bech32::hrp::BCRT, 71 _ => unimplemented!(), 72 }; 73 let addresses = encode_segwit_key(hrp, metadata); 74 let addresses = [ 75 Address::from_str(&addresses[0]).unwrap().assume_checked(), 76 Address::from_str(&addresses[1]).unwrap().assume_checked(), 77 ]; 78 let mut recipients = vec![(to, amount)]; 79 let min = segwit_min_amount(); 80 recipients.extend(addresses.iter().map(|addr| (addr, &min))); 81 self.send_many(recipients).await 82 } 83 84 /// Get detailed information about an in-wallet transaction and it's 32B metadata key encoded using fake segwit addresses 85 pub async fn get_tx_segwit_key( 86 &mut self, 87 id: &Txid, 88 ) -> Result<(Transaction, EddsaPublicKey), GetSegwitErr> { 89 let full = self.get_tx(id).await?; 90 91 let addresses: Vec<String> = full 92 .decoded 93 .vout 94 .iter() 95 .filter_map(|it| { 96 it.script_pub_key 97 .address 98 .as_ref() 99 .map(|addr| addr.clone().assume_checked().to_string()) 100 }) 101 .collect(); 102 103 let metadata = decode_segwit_msg(&addresses)?; 104 105 Ok((full, metadata)) 106 } 107 108 /// Get detailed information about an in-wallet transaction and its op_return metadata 109 pub async fn get_tx_op_return( 110 &mut self, 111 id: &Txid, 112 ) -> Result<(Transaction, Vec<u8>), GetOpReturnErr> { 113 let full = self.get_tx(id).await?; 114 115 let op_return_out = full 116 .decoded 117 .vout 118 .iter() 119 .find(|it| it.script_pub_key.asm.starts_with("OP_RETURN")) 120 .ok_or(GetOpReturnErr::MissingOpReturn)?; 121 122 let hex = op_return_out.script_pub_key.asm.split_once(' ').unwrap().1; 123 // Op return payload is always encoded in hexadecimal 124 let metadata = Vec::from_hex(hex).unwrap(); 125 126 Ok((full, metadata)) 127 } 128 129 /// Bounce a transaction bask to its sender 130 /// 131 /// There is no reliable way to bounce a transaction as you cannot know if the addresses 132 /// used are shared or come from a third-party service. We only send back to the first input 133 /// address as a best-effort gesture. 134 pub async fn bounce( 135 &mut self, 136 id: &Txid, 137 bounce_fee: &Amount, 138 metadata: Option<&[u8]>, 139 ) -> Result<Txid, rpc::Error> { 140 let full = self.get_tx(id).await?; 141 let detail = &full.details[0]; 142 assert!(detail.category == Category::Receive); 143 144 let amount = detail.amount.to_unsigned().unwrap(); 145 let sender = sender_address(self, &full).await?; 146 let bounce_amount = Amount::from_sat(amount.to_sat().saturating_sub(bounce_fee.to_sat())); 147 // Send refund making recipient pay the transaction fees 148 self.send(&sender, &bounce_amount, metadata, true).await 149 } 150 } 151 152 pub fn guess_network(address: &Address) -> Network { 153 let addr = address.as_unchecked(); 154 for network in [ 155 Network::Bitcoin, 156 Network::Regtest, 157 Network::Signet, 158 Network::Regtest, 159 ] { 160 if addr.is_valid_for_network(network) { 161 return network; 162 } 163 } 164 unreachable!() 165 }