Analyze rawtx msg and add sp_tweak

This commit is contained in:
Sosthene00 2024-03-07 09:36:59 +01:00
parent eb6699baca
commit 2d044ec2c2
5 changed files with 629 additions and 24 deletions

92
Cargo.lock generated
View File

@ -26,6 +26,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
[[package]]
name = "atty"
version = "0.2.14"
@ -58,24 +64,43 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bech32"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
[[package]]
name = "bech32"
version = "0.10.0-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea"
[[package]]
name = "bimap"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
[[package]]
name = "bitcoin"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd00f3c09b5f21fb357abe32d29946eb8bb7a0862bae62c0b5e4a692acbbe73c"
dependencies = [
"bech32",
"bech32 0.10.0-beta",
"bitcoin-internals",
"bitcoin_hashes",
"hex-conservative",
"hex_lit",
"secp256k1",
"serde",
]
[[package]]
@ -83,6 +108,9 @@ name = "bitcoin-internals"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
dependencies = [
"serde",
]
[[package]]
name = "bitcoin_hashes"
@ -92,6 +120,31 @@ checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
dependencies = [
"bitcoin-internals",
"hex-conservative",
"serde",
]
[[package]]
name = "bitcoincore-rpc"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eb70725a621848c83b3809913d5314c0d20ca84877d99dd909504b564edab00"
dependencies = [
"bitcoincore-rpc-json",
"jsonrpc",
"log",
"serde",
"serde_json",
]
[[package]]
name = "bitcoincore-rpc-json"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "856ffbee2e492c23bca715d72ea34aae80d58400f2bda26a82015d6bc2ec3662"
dependencies = [
"bitcoin",
"serde",
"serde_json",
]
[[package]]
@ -386,6 +439,12 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-conservative"
version = "0.1.1"
@ -456,6 +515,17 @@ dependencies = [
"libc",
]
[[package]]
name = "jsonrpc"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8128f36b47411cd3f044be8c1f5cc0c9e24d1d1bfdc45f0a57897b32513053f2"
dependencies = [
"base64",
"serde",
"serde_json",
]
[[package]]
name = "jwalk"
version = "0.8.1"
@ -675,12 +745,16 @@ dependencies = [
name = "sdk_relay"
version = "0.1.0"
dependencies = [
"anyhow",
"bitcoin",
"bitcoincore-rpc",
"bitcoincore-zmq",
"env_logger",
"futures-util",
"log",
"serde",
"serde_json",
"silentpayments",
"tokio",
"tokio-stream",
"tokio-tungstenite",
@ -693,7 +767,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
dependencies = [
"bitcoin_hashes",
"rand",
"secp256k1-sys",
"serde",
]
[[package]]
@ -756,6 +832,20 @@ dependencies = [
"digest",
]
[[package]]
name = "silentpayments"
version = "0.1.0"
source = "git+https://github.com/cygnet3/rust-silentpayments?branch=master#e915f5f8daef4b39ea32902963c143a5c0ed1746"
dependencies = [
"bech32 0.9.1",
"bimap",
"bitcoin_hashes",
"hex",
"secp256k1",
"serde",
"serde_json",
]
[[package]]
name = "slab"
version = "0.4.9"

View File

@ -4,12 +4,16 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
bitcoin = { version = "0.31.1", features = ["serde"] }
bitcoincore-rpc = { version = "0.18" }
bitcoincore-zmq = "1.4.0"
env_logger = "0.9"
futures-util = { version = "0.3.28", default-features = false, features = ["sink", "std"] }
log = "0.4.20"
serde = { version = "1.0.193", features = ["derive"]}
serde_json = "1.0"
silentpayments = { git = "https://github.com/cygnet3/rust-silentpayments", branch = "master", features = ['utils'] }
tokio = { version = "1.0.0", features = ["io-util", "rt-multi-thread", "macros", "sync"] }
tokio-stream = "0.1"
tokio-tungstenite = "0.21.0"

304
src/daemon.rs Normal file
View File

@ -0,0 +1,304 @@
use anyhow::{Context, Result};
use bitcoin::{consensus::deserialize, hashes::hex::FromHex};
use bitcoin::{Amount, BlockHash, Transaction, Txid};
use bitcoincore_rpc::{json, jsonrpc, Auth, Client, RpcApi};
// use crossbeam_channel::Receiver;
// use parking_lot::Mutex;
use serde_json::{json, Value};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
pub struct SensitiveAuth(pub Auth);
impl SensitiveAuth {
pub(crate) fn get_auth(&self) -> Auth {
self.0.clone()
}
}
enum PollResult {
Done(Result<()>),
Retry,
}
fn rpc_poll(client: &mut Client, skip_block_download_wait: bool) -> PollResult {
match client.get_blockchain_info() {
Ok(info) => {
if skip_block_download_wait {
// bitcoind RPC is available, don't wait for block download to finish
return PollResult::Done(Ok(()));
}
let left_blocks = info.headers - info.blocks;
if info.initial_block_download || left_blocks > 0 {
log::info!(
"waiting for {} blocks to download{}",
left_blocks,
if info.initial_block_download {
" (IBD)"
} else {
""
}
);
return PollResult::Retry;
}
PollResult::Done(Ok(()))
}
Err(err) => {
if let Some(e) = extract_bitcoind_error(&err) {
if e.code == -28 {
log::debug!("waiting for RPC warmup: {}", e.message);
return PollResult::Retry;
}
}
PollResult::Done(Err(err).context("daemon not available"))
}
}
}
fn read_cookie(path: &Path) -> Result<(String, String)> {
// Load username and password from bitcoind cookie file:
// * https://github.com/bitcoin/bitcoin/pull/6388/commits/71cbeaad9a929ba6a7b62d9b37a09b214ae00c1a
// * https://bitcoin.stackexchange.com/questions/46782/rpc-cookie-authentication
let mut file = File::open(path)
.with_context(|| format!("failed to open bitcoind cookie file: {}", path.display()))?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.with_context(|| format!("failed to read bitcoind cookie from {}", path.display()))?;
let parts: Vec<&str> = contents.splitn(2, ':').collect();
anyhow::ensure!(
parts.len() == 2,
"failed to parse bitcoind cookie - missing ':' separator"
);
Ok((parts[0].to_owned(), parts[1].to_owned()))
}
fn rpc_connect() -> Result<Client> {
let rpc_url = "http://127.0.0.1:39332";
// Allow `wait_for_new_block` to take a bit longer before timing out.
// See https://github.com/romanz/electrs/issues/495 for more details.
let builder = jsonrpc::simple_http::SimpleHttpTransport::builder()
.url(&rpc_url)?
.timeout(Duration::from_secs(30));
let daemon_auth = SensitiveAuth(Auth::CookieFile(PathBuf::from_str("/home/sosthene/.bitcoin/signet/.cookie").unwrap()));
let builder = match daemon_auth.get_auth() {
Auth::None => builder,
Auth::UserPass(user, pass) => builder.auth(user, Some(pass)),
Auth::CookieFile(path) => {
let (user, pass) = read_cookie(&path)?;
builder.auth(user, Some(pass))
}
};
Ok(Client::from_jsonrpc(jsonrpc::Client::with_transport(
builder.build(),
)))
}
pub struct Daemon {
// p2p: Mutex<Connection>,
rpc: Client,
}
impl Daemon {
pub(crate) fn connect(
// config: &Config,
// exit_flag: &ExitFlag,
// metrics: &Metrics,
) -> Result<Self> {
let mut rpc = rpc_connect()?;
loop {
match rpc_poll(&mut rpc, true) {
PollResult::Done(result) => {
result.context("bitcoind RPC polling failed")?;
break; // on success, finish polling
}
PollResult::Retry => {
std::thread::sleep(std::time::Duration::from_secs(1)); // wait a bit before polling
}
}
}
let network_info = rpc.get_network_info()?;
// if network_info.version < 21_00_00 {
// bail!("electrs requires bitcoind 0.21+");
// }
if !network_info.network_active {
anyhow::bail!("electrs requires active bitcoind p2p network");
}
let info = rpc.get_blockchain_info()?;
if info.pruned {
anyhow::bail!("electrs requires non-pruned bitcoind node");
}
// let p2p = tokio::sync::Mutex::new(Connection::connect(
// config.network,
// config.daemon_p2p_addr,
// metrics,
// config.signet_magic,
// )?);
Ok(Self { rpc })
}
pub(crate) fn estimate_fee(&self, nblocks: u16) -> Result<Option<Amount>> {
Ok(self
.rpc
.estimate_smart_fee(nblocks, None)
.context("failed to estimate fee")?
.fee_rate)
}
pub(crate) fn get_relay_fee(&self) -> Result<Amount> {
Ok(self
.rpc
.get_network_info()
.context("failed to get relay fee")?
.relay_fee)
}
pub(crate) fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
self.rpc
.send_raw_transaction(tx)
.context("failed to broadcast transaction")
}
pub(crate) fn get_transaction_info(
&self,
txid: &Txid,
blockhash: Option<BlockHash>,
) -> Result<Value> {
// No need to parse the resulting JSON, just return it as-is to the client.
self.rpc
.call(
"getrawtransaction",
&[json!(txid), json!(true), json!(blockhash)],
)
.context("failed to get transaction info")
}
pub(crate) fn get_transaction_hex(
&self,
txid: &Txid,
blockhash: Option<BlockHash>,
) -> Result<Value> {
use bitcoin::consensus::serde::{hex::Lower, Hex, With};
let tx = self.get_transaction(txid, blockhash)?;
#[derive(serde::Serialize)]
#[serde(transparent)]
struct TxAsHex(#[serde(with = "With::<Hex<Lower>>")] Transaction);
serde_json::to_value(TxAsHex(tx)).map_err(Into::into)
}
pub(crate) fn get_transaction(
&self,
txid: &Txid,
blockhash: Option<BlockHash>,
) -> Result<Transaction> {
self.rpc
.get_raw_transaction(txid, blockhash.as_ref())
.context("failed to get transaction")
}
pub(crate) fn get_block_txids(&self, blockhash: BlockHash) -> Result<Vec<Txid>> {
Ok(self
.rpc
.get_block_info(&blockhash)
.context("failed to get block txids")?
.tx)
}
pub(crate) fn get_mempool_txids(&self) -> Result<Vec<Txid>> {
self.rpc
.get_raw_mempool()
.context("failed to get mempool txids")
}
pub(crate) fn get_mempool_entries(
&self,
txids: &[Txid],
) -> Result<Vec<Result<json::GetMempoolEntryResult>>> {
let client = self.rpc.get_jsonrpc_client();
log::debug!("getting {} mempool entries", txids.len());
let args: Vec<_> = txids
.iter()
.map(|txid| vec![serde_json::value::to_raw_value(txid).unwrap()])
.collect();
let reqs: Vec<_> = args
.iter()
.map(|a| client.build_request("getmempoolentry", a))
.collect();
let res = client.send_batch(&reqs).context("batch request failed")?;
log::debug!("got {} mempool entries", res.len());
Ok(res
.into_iter()
.map(|r| {
r.context("missing response")?
.result::<json::GetMempoolEntryResult>()
.context("invalid response")
})
.collect())
}
pub(crate) fn get_mempool_transactions(
&self,
txids: &[Txid],
) -> Result<Vec<Result<Transaction>>> {
let client = self.rpc.get_jsonrpc_client();
log::debug!("getting {} transactions", txids.len());
let args: Vec<_> = txids
.iter()
.map(|txid| vec![serde_json::value::to_raw_value(txid).unwrap()])
.collect();
let reqs: Vec<_> = args
.iter()
.map(|a| client.build_request("getrawtransaction", a))
.collect();
let res = client.send_batch(&reqs).context("batch request failed")?;
log::debug!("got {} mempool transactions", res.len());
Ok(res
.into_iter()
.map(|r| -> Result<Transaction> {
let tx_hex = r
.context("missing response")?
.result::<String>()
.context("invalid response")?;
let tx_bytes = Vec::from_hex(&tx_hex).context("non-hex transaction")?;
deserialize(&tx_bytes).context("invalid transaction")
})
.collect())
}
// pub(crate) fn get_new_headers(&self, chain: &Chain) -> Result<Vec<NewHeader>> {
// self.p2p.lock().get_new_headers(chain)
// }
// pub(crate) fn for_blocks<B, F>(&self, blockhashes: B, func: F) -> Result<()>
// where
// B: IntoIterator<Item = BlockHash>,
// F: FnMut(BlockHash, SerBlock),
// {
// self.p2p.lock().for_blocks(blockhashes, func)
// }
// pub(crate) fn new_block_notification(&self) -> Receiver<()> {
// self.p2p.lock().new_block_notification()
// }
}
pub(crate) type RpcError = bitcoincore_rpc::jsonrpc::error::RpcError;
pub(crate) fn extract_bitcoind_error(err: &bitcoincore_rpc::Error) -> Option<&RpcError> {
use bitcoincore_rpc::{
jsonrpc::error::Error::Rpc as ServerError, Error::JsonRpc as JsonRpcError,
};
match err {
JsonRpcError(ServerError(e)) => Some(e),
_ => None,
}
}

View File

@ -6,6 +6,7 @@ use std::{
sync::{Arc, Mutex},
};
use bitcoin::{consensus::deserialize, secp256k1::PublicKey};
use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt};
use log::{debug, error};
@ -14,27 +15,16 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
use tokio_stream::wrappers::UnboundedReceiverStream;
use tokio_tungstenite::tungstenite::Message;
mod sp;
mod daemon;
use crate::daemon::Daemon;
use crate::sp::VinData;
type Tx = UnboundedSender<Message>;
type PeerMap = Arc<Mutex<HashMap<SocketAddr, Tx>>>;
// #[derive(Deserialize, Serialize, Debug)]
// struct AnkMessage {
// dest: SilentPaymentId,
// payload: String
// }
// impl TryFrom<Message> for AnkMessage {
// type Error = serde_json::Error;
// fn try_from(value: Message) -> Result<Self, Self::Error> {
// match value {
// Message::Text(s) => serde_json::from_str(&s),
// _ => Err(serde_json::Error::custom("Unsupported message type")),
// }
// }
// }
async fn handle_connection(peer_map: PeerMap, raw_stream: TcpStream, addr: SocketAddr) {
debug!("Incoming TCP connection from: {}", addr);
@ -82,7 +72,17 @@ async fn handle_connection(peer_map: PeerMap, raw_stream: TcpStream, addr: Socke
peer_map.lock().unwrap().remove(&addr);
}
async fn handle_zmq(peer_map: PeerMap) {
fn flatten_msg(parts: &[Vec<u8>]) -> Vec<u8> {
let total_len = parts.iter().fold(0, |acc, v| acc + v.len());
let mut final_vec = Vec::with_capacity(total_len);
for p in parts {
final_vec.extend(p);
}
final_vec
}
async fn handle_zmq(peer_map: PeerMap, daemon: Daemon) {
tokio::task::spawn_blocking(move || {
debug!("Starting listening on Core");
for msg in bitcoincore_zmq::subscribe_receiver(&["tcp://127.0.0.1:29000"]).unwrap() {
@ -91,11 +91,60 @@ async fn handle_zmq(peer_map: PeerMap) {
debug!("Received a {} message", core_msg.topic_str());
let peers = peer_map.lock().unwrap();
let vectors = core_msg.serialize_to_vecs();
let payload: Vec<u8>;
match core_msg.topic_str() {
"rawtx" => {
let tx: bitcoin::Transaction = deserialize(&core_msg.serialize_data_to_vec()).unwrap();
let mut payload: Vec<u8> = Vec::with_capacity(vectors[0].len() + vectors[1].len() + vectors[2].len());
for v in vectors {
payload.extend(v);
if tx.is_coinbase() {
continue;
}
let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len());
let mut pubkeys: Vec<PublicKey> = Vec::with_capacity(tx.input.len());
for input in tx.input {
outpoints.push((input.previous_output.txid.to_string(), input.previous_output.vout));
let prev_tx = daemon.get_transaction(&input.previous_output.txid, None).unwrap();
if let Some(output) = prev_tx.output.get(input.previous_output.vout as usize) {
let vin_data = VinData {
script_sig: input.script_sig.to_bytes().to_vec(),
txinwitness: input.witness.to_vec(),
script_pub_key: output.script_pubkey.to_bytes()
};
match sp::get_pubkey_from_input(&vin_data) {
Ok(res) => {
if let Some(pubkey) = res {
pubkeys.push(pubkey);
} else {
continue;
}
},
Err(e) => {
log::error!("Can't extract pubkey from input: {}", e.to_string());
continue;
}
}
} else {
log::error!("Transaction with a non existing input");
continue;
}
}
let input_pub_keys: Vec<&PublicKey> = pubkeys.iter().collect();
match silentpayments::utils::receiving::recipient_calculate_tweak_data(&input_pub_keys, &outpoints) {
Ok(partial_tweak) => {
let mut vecs = core_msg.serialize_to_vecs().to_vec();
vecs.push(partial_tweak.serialize().to_vec());
payload = flatten_msg(&vecs);
},
Err(e) => {
log::error!("Failed to compute tweak data: {}", e.to_string());
continue;
}
}
},
_ => {
payload = flatten_msg(&core_msg.serialize_to_vecs());
}
}
for tx in peers.values() {
@ -121,8 +170,11 @@ async fn main() -> Result<(), IoError> {
let state = PeerMap::new(Mutex::new(HashMap::new()));
// Connect the rpc daemon
let daemon = Daemon::connect().unwrap();
// Subscribe to Bitcoin Core
tokio::spawn(handle_zmq(state.clone()));
tokio::spawn(handle_zmq(state.clone(), daemon));
// Create the event loop and TCP listener we'll accept connections on.
let try_socket = TcpListener::bind(&addr).await;

155
src/sp.rs Normal file
View File

@ -0,0 +1,155 @@
use bitcoin::secp256k1::{Parity::Even, PublicKey, XOnlyPublicKey};
use bitcoin::hashes::{hash160, Hash};
use anyhow::Error;
// ** Putting all the pubkey extraction logic in the test utils for now. **
// NUMS_H (defined in BIP340)
const NUMS_H: [u8; 32] = [
0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e,
0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0,
];
// Define OP_CODES used in script template matching for readability
const OP_1: u8 = 0x51;
const OP_0: u8 = 0x00;
const OP_PUSHBYTES_20: u8 = 0x14;
const OP_PUSHBYTES_32: u8 = 0x20;
const OP_HASH160: u8 = 0xA9;
const OP_EQUAL: u8 = 0x87;
const OP_DUP: u8 = 0x76;
const OP_EQUALVERIFY: u8 = 0x88;
const OP_CHECKSIG: u8 = 0xAC;
// Only compressed pubkeys are supported for silent payments
const COMPRESSED_PUBKEY_SIZE: usize = 33;
pub struct VinData {
pub script_sig: Vec<u8>,
pub txinwitness: Vec<Vec<u8>>,
pub script_pub_key: Vec<u8>,
}
// script templates for inputs allowed in BIP352 shared secret derivation
pub fn is_p2tr(spk: &[u8]) -> bool {
matches!(spk, [OP_1, OP_PUSHBYTES_32, ..] if spk.len() == 34)
}
fn is_p2wpkh(spk: &[u8]) -> bool {
matches!(spk, [OP_0, OP_PUSHBYTES_20, ..] if spk.len() == 22)
}
fn is_p2sh(spk: &[u8]) -> bool {
matches!(spk, [OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUAL] if spk.len() == 23)
}
fn is_p2pkh(spk: &[u8]) -> bool {
matches!(spk, [OP_DUP, OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUALVERIFY, OP_CHECKSIG] if spk.len() == 25)
}
pub fn get_pubkey_from_input(vin: &VinData) -> Result<Option<PublicKey>, Error> {
if is_p2pkh(&vin.script_pub_key) {
match (&vin.txinwitness.is_empty(), &vin.script_sig.is_empty()) {
(true, false) => {
let spk_hash = &vin.script_pub_key[3..23];
for i in (COMPRESSED_PUBKEY_SIZE..=vin.script_sig.len()).rev() {
if let Some(pubkey_bytes) = &vin.script_sig.get(i - COMPRESSED_PUBKEY_SIZE..i) {
let pubkey_hash = hash160::Hash::hash(pubkey_bytes);
if pubkey_hash.to_byte_array() == spk_hash {
return Ok(Some(PublicKey::from_slice(pubkey_bytes)?));
}
} else {
return Ok(None);
}
}
}
(_, true) => return Err(Error::msg("Empty script_sig for spending a p2pkh")),
(false, _) => return Err(Error::msg("non empty witness for spending a p2pkh")),
}
} else if is_p2sh(&vin.script_pub_key) {
match (&vin.txinwitness.is_empty(), &vin.script_sig.is_empty()) {
(false, false) => {
let redeem_script = &vin.script_sig[1..];
if is_p2wpkh(redeem_script) {
if let Some(value) = vin.txinwitness.last() {
if let Ok(pubkey) = PublicKey::from_slice(value) {
return Ok(Some(pubkey));
} else {
return Ok(None);
}
}
}
}
(_, true) => {
return Err(Error::msg(
"Empty script_sig for spending a p2sh".to_owned(),
))
}
(true, false) => {
return Ok(None);
}
}
} else if is_p2wpkh(&vin.script_pub_key) {
match (&vin.txinwitness.is_empty(), &vin.script_sig.is_empty()) {
(false, true) => {
if let Some(value) = vin.txinwitness.last() {
if let Ok(pubkey) = PublicKey::from_slice(value) {
return Ok(Some(pubkey));
} else {
return Ok(None);
}
} else {
return Err(Error::msg("Empty witness".to_owned()));
}
}
(_, false) => {
return Err(Error::msg(
"Non empty script sig for spending a segwit output".to_owned(),
))
}
(true, _) => {
return Err(Error::msg(
"Empty witness for spending a segwit output".to_owned(),
))
}
}
} else if is_p2tr(&vin.script_pub_key) {
match (&vin.txinwitness.is_empty(), &vin.script_sig.is_empty()) {
(false, true) => {
// check for the optional annex
let annex = match vin.txinwitness.last().and_then(|value| value.get(0)) {
Some(&0x50) => 1,
Some(_) => 0,
None => return Err(Error::msg("Empty or invalid witness".to_owned())),
};
// Check for script path
let stack_size = vin.txinwitness.len();
if stack_size > annex && vin.txinwitness[stack_size - annex - 1][1..33] == NUMS_H {
return Ok(None);
}
// Return the pubkey from the script pubkey
return XOnlyPublicKey::from_slice(&vin.script_pub_key[2..34])
.map_err(|e| Error::new(e))
.map(|x_only_public_key| {
Some(PublicKey::from_x_only_public_key(x_only_public_key, Even))
});
}
(_, false) => {
return Err(Error::msg(
"Non empty script sig for spending a segwit output".to_owned(),
))
}
(true, _) => {
return Err(Error::msg(
"Empty witness for spending a segwit output".to_owned(),
))
}
}
} else {
// We don't support this kind of output
return Err(Error::msg("Unsupported script pubkey type"));
}
Ok(None)
}