mod types;
use core::fmt;
use crate::types::{c_int64, c_uchar, c_uint};
pub const VERIFY_NONE: c_uint = 0;
pub const VERIFY_P2SH: c_uint = 1 << 0;
pub const VERIFY_DERSIG: c_uint = 1 << 2;
pub const VERIFY_NULLDUMMY: c_uint = 1 << 4;
pub const VERIFY_CHECKLOCKTIMEVERIFY: c_uint = 1 << 9;
pub const VERIFY_CHECKSEQUENCEVERIFY: c_uint = 1 << 10;
pub const VERIFY_WITNESS: c_uint = 1 << 11;
pub const VERIFY_TAPROOT: c_uint = 1 << 17;
pub const VERIFY_ALL_PRE_TAPROOT: c_uint = VERIFY_P2SH
| VERIFY_DERSIG
| VERIFY_NULLDUMMY
| VERIFY_CHECKLOCKTIMEVERIFY
| VERIFY_CHECKSEQUENCEVERIFY
| VERIFY_WITNESS;
pub fn height_to_flags(height: u32) -> u32 {
let mut flag = VERIFY_NONE;
if height >= 173805 {
flag |= VERIFY_P2SH;
}
if height >= 363725 {
flag |= VERIFY_DERSIG;
}
if height >= 388381 {
flag |= VERIFY_CHECKLOCKTIMEVERIFY;
}
if height >= 419328 {
flag |= VERIFY_CHECKSEQUENCEVERIFY;
}
if height >= 481824 {
flag |= VERIFY_NULLDUMMY | VERIFY_WITNESS
}
if height > 709632 {
flag |= VERIFY_TAPROOT
}
flag
}
pub fn version() -> u32 { unsafe { ffi::bitcoinconsensus_version() as u32 } }
pub fn verify(
spent_output: &[u8],
amount: u64, spending_transaction: &[u8], spent_outputs: Option<&[Utxo]>, input_index: usize,
) -> Result<(), Error> {
let flags = match spent_outputs {
Some(_) => VERIFY_ALL_PRE_TAPROOT | VERIFY_TAPROOT,
None => VERIFY_ALL_PRE_TAPROOT,
};
verify_with_flags(spent_output, amount, spending_transaction, spent_outputs, input_index, flags)
}
pub fn verify_with_flags(
spent_output_script: &[u8],
amount: u64,
spending_transaction: &[u8],
spent_outputs: Option<&[Utxo]>,
input_index: usize,
flags: u32,
) -> Result<(), Error> {
match spent_outputs {
Some(spent_outputs) => unsafe {
let mut error = Error::ERR_SCRIPT;
let ret = ffi::bitcoinconsensus_verify_script_with_spent_outputs(
spent_output_script.as_ptr(),
spent_output_script.len() as c_uint,
amount,
spending_transaction.as_ptr(),
spending_transaction.len() as c_uint,
spent_outputs.as_ptr() as *const c_uchar,
spent_outputs.len() as c_uint,
input_index as c_uint,
flags as c_uint,
&mut error,
);
if ret != 1 {
Err(error)
} else {
Ok(())
}
},
None => unsafe {
let mut error = Error::ERR_SCRIPT;
let ret = ffi::bitcoinconsensus_verify_script_with_amount(
spent_output_script.as_ptr(),
spent_output_script.len() as c_uint,
amount,
spending_transaction.as_ptr(),
spending_transaction.len() as c_uint,
input_index as c_uint,
flags as c_uint,
&mut error,
);
if ret != 1 {
Err(error)
} else {
Ok(())
}
},
}
}
#[repr(C)]
pub struct Utxo {
pub script_pubkey: *const c_uchar,
pub script_pubkey_len: c_uint,
pub value: c_int64,
}
pub mod ffi {
use super::*;
use crate::types::c_int;
extern "C" {
pub fn bitcoinconsensus_version() -> c_int;
pub fn bitcoinconsensus_verify_script_with_amount(
script_pubkey: *const c_uchar,
script_pubkeylen: c_uint,
amount: u64,
tx_to: *const c_uchar,
tx_tolen: c_uint,
n_in: c_uint,
flags: c_uint,
err: *mut Error,
) -> c_int;
pub fn bitcoinconsensus_verify_script_with_spent_outputs(
script_pubkey: *const c_uchar,
script_pubkeylen: c_uint,
amount: u64,
tx_to: *const c_uchar,
tx_tolen: c_uint,
spent_outputs: *const c_uchar,
num_spent_outputs: c_uint,
n_in: c_uint,
flags: c_uint,
err: *mut Error,
) -> c_int;
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(C)]
pub enum Error {
ERR_SCRIPT = 0, ERR_TX_INDEX,
ERR_TX_SIZE_MISMATCH,
ERR_TX_DESERIALIZE,
ERR_AMOUNT_REQUIRED,
ERR_INVALID_FLAGS,
ERR_SPENT_OUTPUTS_REQUIRED,
ERR_SPENT_OUTPUTS_MISMATCH,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
let s = match *self {
ERR_SCRIPT => "error value was not set (value still 0)",
ERR_TX_INDEX => "an invalid index for txTo",
ERR_TX_SIZE_MISMATCH => "txToLen did not match with the size of txTo",
ERR_TX_DESERIALIZE => "an error deserializing txTo",
ERR_AMOUNT_REQUIRED => "input amount is required if WITNESS is used",
ERR_INVALID_FLAGS => "script verification flags are invalid",
ERR_SPENT_OUTPUTS_REQUIRED => "verifying taproot input requires previous outputs",
ERR_SPENT_OUTPUTS_MISMATCH => "taproot outputs don't match",
};
f.write_str(s)
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use self::Error::*;
match *self {
ERR_SCRIPT
| ERR_TX_INDEX
| ERR_TX_SIZE_MISMATCH
| ERR_TX_DESERIALIZE
| ERR_AMOUNT_REQUIRED
| ERR_INVALID_FLAGS
| ERR_SPENT_OUTPUTS_REQUIRED
| ERR_SPENT_OUTPUTS_MISMATCH => None,
}
}
}
#[cfg(test)]
mod tests {
extern crate rustc_serialize as serialize;
use self::serialize::hex::FromHex;
use super::*;
#[test]
fn bitcoinconsensus_test() {
verify_test (
"76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac",
"02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700",
0, 0
).unwrap();
verify_test (
"a91434c06f8c87e355e123bdc6dda4ffabc64b6989ef87",
"01000000000101d9fd94d0ff0026d307c994d0003180a5f248146efb6371d040c5973f5f66d9df0400000017160014b31b31a6cb654cfab3c50567bcf124f48a0beaecffffffff012cbd1c000000000017a914233b74bf0823fa58bbbd26dfc3bb4ae715547167870247304402206f60569cac136c114a58aedd80f6fa1c51b49093e7af883e605c212bdafcd8d202200e91a55f408a021ad2631bc29a67bd6915b2d7e9ef0265627eabd7f7234455f6012103e7e802f50344303c76d12c089c8724c1b230e3b745693bbe16aad536293d15e300000000",
1900000, 0
).unwrap();
verify_test(
"0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d",
"010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000",
18393430 , 0
).unwrap();
assert!(verify_test (
"76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ff",
"02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700",
0, 0
).is_err());
assert!(verify_test (
"a91434c06f8c87e355e123bdc6dda4ffabc64b6989ef87",
"01000000000101d9fd94d0ff0026d307c994d0003180a5f248146efb6371d040c5973f5f66d9df0400000017160014b31b31a6cb654cfab3c50567bcf124f48a0beaecffffffff012cbd1c000000000017a914233b74bf0823fa58bbbd26dfc3bb4ae715547167870247304402206f60569cac136c114a58aedd80f6fa1c51b49093e7af883e605c212bdafcd8d202200e91a55f408a021ad2631bc29a67bd6915b2d7e9ef0265627eabd7f7234455f6012103e7e802f50344303c76d12c089c8724c1b230e3b745693bbe16aad536293d15e300000000",
900000, 0).is_err());
assert!(verify_test(
"0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58f",
"010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000",
18393430 , 0
).is_err());
}
fn verify_test(spent: &str, spending: &str, amount: u64, input: usize) -> Result<(), Error> {
verify(
spent.from_hex().unwrap().as_slice(),
amount,
spending.from_hex().unwrap().as_slice(),
None,
input,
)
}
#[test]
fn invalid_flags_test() {
verify_with_flags(&[], 0, &[], None, 0, VERIFY_ALL_PRE_TAPROOT + 1).unwrap_err();
}
}