lib.rs (5885B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022-2025, 2026 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, Transaction}; 20 use rpc_utils::segwit_min_amount; 21 use segwit::{decode_segwit_msg, encode_segwit_key}; 22 use taler_common::{api::EddsaPublicKey, config::parser::ConfigSource}; 23 24 use crate::{rpc::RpcApi, segwit::DecodeSegWitErr}; 25 26 pub mod api; 27 pub mod cli; 28 pub mod config; 29 pub mod db; 30 pub mod loops; 31 pub mod payto; 32 pub mod rpc; 33 pub mod rpc_utils; 34 pub mod segwit; 35 pub mod setup; 36 pub mod sql; 37 pub mod taler_utils; 38 39 pub const CONFIG_SOURCE: ConfigSource = ConfigSource::simple("depolymerizer-bitcoin"); 40 41 #[derive(Debug, thiserror::Error)] 42 pub enum GetOpReturnErr { 43 #[error("Missing opreturn")] 44 MissingOpReturn, 45 #[error(transparent)] 46 RPC(#[from] rpc::Error), 47 } 48 49 /// An extended bitcoincore JSON-RPC api client who can send and retrieve metadata with their transaction 50 #[allow(async_fn_in_trait)] 51 pub trait RpcApiExtended: RpcApi { 52 /// Send a transaction with a 32B key as metadata encoded using fake segwit addresses 53 async fn send_segwit_key( 54 &mut self, 55 to: &Address, 56 amount: Amount, 57 metadata: &[u8; 32], 58 ) -> rpc::Result<Txid> { 59 let network = guess_network(to); 60 let hrp = match network { 61 Network::Bitcoin => bech32::hrp::BC, 62 Network::Testnet | Network::Signet => bech32::hrp::TB, 63 Network::Regtest => bech32::hrp::BCRT, 64 _ => unimplemented!(), 65 }; 66 let addresses = encode_segwit_key(hrp, metadata); 67 let addresses = [ 68 Address::from_str(&addresses[0]).unwrap().assume_checked(), 69 Address::from_str(&addresses[1]).unwrap().assume_checked(), 70 ]; 71 let mut recipients = vec![(to, amount)]; 72 let min = segwit_min_amount(); 73 recipients.extend(addresses.iter().map(|addr| (addr, min))); 74 self.send_many(recipients).await 75 } 76 77 /// Get detailed information about an in-wallet transaction and it's 32B metadata key encoded using fake segwit addresses 78 async fn get_tx_segwit_key( 79 &mut self, 80 id: &Txid, 81 ) -> Result<(Transaction, Result<EddsaPublicKey, DecodeSegWitErr>), rpc::Error> { 82 let full = self.get_tx(id).await?; 83 84 let addresses: Vec<String> = full 85 .decoded 86 .vout 87 .iter() 88 .filter_map(|it| { 89 it.script_pub_key 90 .address 91 .as_ref() 92 .map(|addr| addr.clone().assume_checked().to_string()) 93 }) 94 .collect(); 95 96 Ok((full, decode_segwit_msg(&addresses))) 97 } 98 99 /// Get detailed information about an in-wallet transaction and its op_return metadata 100 async fn get_tx_op_return( 101 &mut self, 102 id: &Txid, 103 ) -> Result<(Transaction, Vec<u8>), GetOpReturnErr> { 104 let full = self.get_tx(id).await?; 105 106 let op_return_out = full 107 .decoded 108 .vout 109 .iter() 110 .find(|it| it.script_pub_key.asm.starts_with("OP_RETURN")) 111 .ok_or(GetOpReturnErr::MissingOpReturn)?; 112 113 let hex = op_return_out.script_pub_key.asm.split_once(' ').unwrap().1; 114 // Op return payload is always encoded in hexadecimal 115 let metadata = Vec::from_hex(hex).unwrap(); 116 117 Ok((full, metadata)) 118 } 119 120 /// Get the first sender address from a raw transaction 121 async fn sender_address(&mut self, full: &Transaction) -> rpc::Result<Address> { 122 let first = &full.decoded.vin[0]; 123 let tx = self.get_input_output(&first.txid.unwrap()).await?; 124 Ok(tx 125 .vout 126 .into_iter() 127 .find(|it| it.n == first.vout.unwrap()) 128 .unwrap() 129 .script_pub_key 130 .address 131 .unwrap() 132 .assume_checked()) 133 } 134 135 /// Bounce a transaction bask to its sender 136 /// 137 /// There is no reliable way to bounce a transaction as you cannot know if the addresses 138 /// used are shared or come from a third-party service. We only send back to the first input 139 /// address as a best-effort gesture. 140 async fn bounce( 141 &mut self, 142 id: &Txid, 143 bounce_fee: &Amount, 144 metadata: Option<&[u8]>, 145 ) -> Result<Txid, rpc::Error> { 146 let full = self.get_tx(id).await?; 147 let detail = &full.details[0]; 148 assert!(detail.category == Category::Receive); 149 150 let amount = detail.amount.to_unsigned().unwrap(); 151 let sender = self.sender_address(&full).await?; 152 let bounce_amount = Amount::from_sat(amount.to_sat().saturating_sub(bounce_fee.to_sat())); 153 // Send refund making recipient pay the transaction fees 154 self.send(&sender, bounce_amount, metadata, true).await 155 } 156 } 157 158 impl<T: RpcApi> RpcApiExtended for T {} 159 160 pub fn guess_network(address: &Address) -> Network { 161 let addr = address.as_unchecked(); 162 for network in [ 163 Network::Bitcoin, 164 Network::Regtest, 165 Network::Signet, 166 Network::Regtest, 167 ] { 168 if addr.is_valid_for_network(network) { 169 return network; 170 } 171 } 172 unreachable!() 173 }