Skip to content

Commit

Permalink
Easier to use verification flags
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman committed Nov 15, 2024
1 parent 305af99 commit 5e62bba
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 43 deletions.
143 changes: 105 additions & 38 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ mod ffi {
scriptPubKeyLen: c_uint,
txTo: *const c_uchar,
txToLen: c_uint,
prev_outs: *const *const c_uchar,
prev_outs_lens: *const c_uint,
prev_outs_count: c_uint,
nIn: c_uint,
flags: c_uint,
amount: i64,
Expand All @@ -73,20 +76,42 @@ mod ffi {
}

#[allow(dead_code)]
pub fn mandatory_script_verify_flags() -> u32 {
fn mandatory_script_verify_flags() -> u32 {
unsafe { ffi::mandatory_script_verify_flags() }
}

#[allow(dead_code)]
pub fn standard_script_verify_flags() -> u32 {
fn standard_script_verify_flags() -> u32 {
unsafe { ffi::standard_script_verify_flags() }
}

pub fn op_cat_verify_flag() -> u32 {
fn op_cat_verify_flag() -> u32 {
unsafe { ffi::op_cat_verify_flag() }
}

pub fn verify_tapscript(
#[derive(Debug, Copy, Clone)]
pub enum VerifyFlags {
/// OP_CAT is enabled and enforced
OpCatEnabled,
/// OP_CAT is disabled and treated as an OP_SUCCESS
OpCatDisabled,
/// OP_CAT is disabled and will fail if seen
OpCatDiscouraged,
}

impl VerifyFlags {
fn flags(&self) -> u32 {
match self {
VerifyFlags::OpCatEnabled => {
mandatory_script_verify_flags() | op_cat_verify_flag()
}
VerifyFlags::OpCatDisabled => mandatory_script_verify_flags(),
VerifyFlags::OpCatDiscouraged => standard_script_verify_flags(),
}
}
}

fn verify_tapscript(
tx_to: &[u8],
n_in: u32,
prev_outs: &[Vec<u8>],
Expand All @@ -112,19 +137,27 @@ pub fn verify_tapscript(
.into()
}

pub fn verify(
fn verify(
script_pub_key: &[u8],
tx_to: &[u8],
n_in: u32,
prev_outs: &[Vec<u8>],
flags: u32,
amount: i64,
) -> Result<(), String> {
// Create arrays of pointers and lengths
let ptrs: Vec<*const u8> = prev_outs.iter().map(|s| s.as_ptr()).collect();
let lengths: Vec<u32> = prev_outs.iter().map(|s| s.len() as u32).collect();

unsafe {
&*ffi::verify_script(
script_pub_key.as_ptr(),
script_pub_key.len() as c_uint,
tx_to.as_ptr(),
tx_to.len() as c_uint,
ptrs.as_ptr(),
lengths.as_ptr(),
prev_outs.len() as c_uint,
n_in as c_uint,
flags as c_uint,
amount,
Expand All @@ -148,8 +181,9 @@ pub fn verify_tx_input_tapscript(
tx: &Transaction,
spent_outputs: &HashMap<OutPoint, TxOut>,
input_idx: usize,
flags: u32,
flags: VerifyFlags,
) -> Result<(), VerifyTxError> {
let flags = flags.flags();
if tx.input[input_idx].witness.tapscript().is_none() {
return Err(VerifyTxError {
input_idx,
Expand Down Expand Up @@ -199,16 +233,43 @@ pub fn verify_tx_input_tapscript(
pub fn verify_tx(
tx: &Transaction,
spent_outputs: &HashMap<OutPoint, TxOut>,
flags: u32,
flags: VerifyFlags,
) -> Result<(), VerifyTxError> {
let flags = flags.flags();
let tx_encoded = bitcoin::consensus::serialize(tx);

let prevouts: Vec<&TxOut> = tx
.input
.iter()
.enumerate()
.map(|(idx, i)| {
spent_outputs.get(&i.previous_output).ok_or(VerifyTxError {
input_idx: idx,
err_msg: "Missing previous output".to_string(),
})
})
.collect::<Result<_, _>>()?;

let prevouts_encoded = prevouts
.iter()
.map(|o| {
let mut bytes = Vec::new();
o.value
.consensus_encode(&mut bytes)
.expect("serialization failed");
bytes.extend_from_slice(o.script_pubkey.as_bytes());
bytes
})
.collect::<Vec<Vec<u8>>>();

for (input_idx, input) in tx.input.iter().enumerate() {
let spent_output = &spent_outputs[&input.previous_output];
let spk_encoded = &spent_output.script_pubkey.to_bytes();
if let Err(err_msg) = verify(
spk_encoded,
&tx_encoded,
input_idx as u32,
&prevouts_encoded,
flags,
spent_output.value.to_sat() as i64,
) {
Expand Down Expand Up @@ -320,7 +381,9 @@ mod test {
}

// verify tx
assert!(verify_tx(&tx, &spent_outputs, op_cat_verify_flag()).is_ok());
assert!(
verify_tx(&tx, &spent_outputs, VerifyFlags::OpCatEnabled).is_ok()
);
Ok(())
}

Expand Down Expand Up @@ -369,17 +432,10 @@ mod test {
source_tx.output[0].clone(),
)]);
// verify tx without OP_CAT enabled should work
let res = verify_tx(
&tx,
&spent_outputs,
standard_script_verify_flags() & op_cat_verify_flag(),
);
let res = verify_tx(&tx, &spent_outputs, VerifyFlags::OpCatDisabled);
assert_eq!(res, Ok(()));
// verify tx with OP_CAT enabled should fail
assert!(
verify_tx(&tx, &spent_outputs, standard_script_verify_flags())
.is_err()
);
assert!(verify_tx(&tx, &spent_outputs, VerifyFlags::OpCatEnabled).is_err());
Ok(())
}

Expand Down Expand Up @@ -429,11 +485,7 @@ mod test {
source_tx.output[0].clone(),
)]);
// verify tx with OP_CAT enabled should succeed
let res = verify_tx(
&tx,
&spent_outputs,
standard_script_verify_flags() & op_cat_verify_flag(),
);
let res = verify_tx(&tx, &spent_outputs, VerifyFlags::OpCatEnabled);
assert_eq!(res, Ok(()));
Ok(())
}
Expand Down Expand Up @@ -488,11 +540,7 @@ mod test {
source_tx.output[0].clone(),
)]);
// verify tx with OP_CAT enabled should succeed
let res = verify_tx(
&tx,
&spent_outputs,
standard_script_verify_flags() & op_cat_verify_flag(),
);
let res = verify_tx(&tx, &spent_outputs, VerifyFlags::OpCatEnabled);
assert_eq!(res, Ok(()));
Ok(())
}
Expand Down Expand Up @@ -548,15 +596,10 @@ mod test {
source_tx.output[0].clone(),
)]);
// verify tx with OP_CAT disabled should succeed
let res = verify_tx(
&tx,
&spent_outputs,
standard_script_verify_flags() & op_cat_verify_flag(),
);
let res = verify_tx(&tx, &spent_outputs, VerifyFlags::OpCatDisabled);
assert_eq!(res, Ok(()));
// verify tx with OP_CAT enabled should fail
let res =
verify_tx(&tx, &spent_outputs, standard_script_verify_flags());
let res = verify_tx(&tx, &spent_outputs, VerifyFlags::OpCatEnabled);
assert_ne!(res, Ok(()));

Ok(())
Expand All @@ -582,7 +625,7 @@ mod test {
&tx,
&prevouts,
0,
standard_script_verify_flags() & op_cat_verify_flag(),
VerifyFlags::OpCatEnabled,
);
assert_eq!(result, Ok(()));

Expand Down Expand Up @@ -622,12 +665,36 @@ mod test {
(tx.input[2].previous_output, prevout2),
]);

let result = verify_tx(
let result = verify_tx(&tx, &prevouts, VerifyFlags::OpCatEnabled);
assert_eq!(result, Ok(()));

Ok(())
}

#[test]
fn test_unsigned_tx_is_invalid() -> anyhow::Result<()> {
let tx_bytes: Vec<u8> = FromHex::from_hex("0200000000010194e2eed1469fb0aaa8bcb657ef5bd9213c4ea9cc01ecb34675ad78fd698940ba0000000000ffffffff01804a5d050000000022512049bda78532256ef251828cf06a294361061d44636bef532d9df43c8126906b13113f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817988dc934fcf372d23589f24421038a27115192b088676521b3a3f80843ac88f001000100040200000004290100002067c7507c94653d313b1818223e414e0e3fdb8903b31da00b450233b75c477fa1202c3417ea6a6d07f4a0218f5b1d380037c8b95c24eeb4e70299776a634a0dffa2204895f9f74569955d5f4b004d1a4fe0d9c57f2d3bc46e3fa26529df26d30cfb0820ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e010220b8dcbfe33ee00b3f465b72caa0ca1c3fac4220edb3b8ced42b2856ecf3c4d51c010004ffffffff08804a5d05000000002322512049bda78532256ef251828cf06a294361061d44636bef532d9df43c8126906b13c50063036361744c927ea804000000005e7a5e7a5e7a5e7a5e7a5e7a5e7a5e7a5e7a5a7a5e7a5b7a5e7a5e7a5e7a7e7e7e7e7e7e7e7e7e7e7e7e7e0a54617053696768617368a8767b7e7ea811424950303334302f6368616c6c656e6765a8767b2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817987676766b6b7b7e7e7e7ea86c7c7e7c7601007e7b88517e6cac686d6d6d6d6d6d6d7520d294d344d568526e925ff19cdb824ba9c5c946b6ea4bb43a6ab59b25e94fad7aac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac029010000").unwrap();
let mut tx_cursor = Cursor::new(tx_bytes);
let tx: Transaction = Transaction::consensus_decode(&mut tx_cursor)?;

let prevout: TxOut = TxOut {
script_pubkey: ScriptBuf::from_hex("512049bda78532256ef251828cf06a294361061d44636bef532d9df43c8126906b13")?,
value: Amount::from_sat(90_000_000),
};

let prevouts =
HashMap::from_iter([(tx.input[0].previous_output, prevout)]);

let result = verify_tx_input_tapscript(
&tx,
&prevouts,
standard_script_verify_flags() & op_cat_verify_flag(),
0,
VerifyFlags::OpCatDiscouraged,
);
assert_eq!(result, Ok(()));
assert!(result.is_err());

let result = verify_tx(&tx, &prevouts, VerifyFlags::OpCatDiscouraged);
assert!(result.is_err());

Ok(())
}
Expand Down
43 changes: 38 additions & 5 deletions stubs/bitcoin-script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ extern "C" {

VerifyScriptResult* verify_script(const uint8_t* scriptPubKey, uint32_t scriptPubKeyLen,
const uint8_t* txTo, uint32_t txToLen,
// Array of pointers to byte arrays
const uint8_t* const* prev_outs,
// Array of lengths corresponding to each byte array
const uint32_t* prev_out_lengths,
uint32_t prev_out_counts,
unsigned int nIn, unsigned int flags,
int64_t amount_in) {
// Convert inputs to appropriate types
Expand All @@ -69,19 +74,47 @@ extern "C" {
DataStream stream(vtxTo);
const CTransaction tx(deserialize, TX_WITH_WITNESS, stream);

std::vector<CTxOut> txouts;
txouts.reserve(prev_out_counts);

for (uint32_t i = 0; i < prev_out_counts; i++) {
const uint8_t* data = prev_outs[i];
uint32_t len = prev_out_lengths[i];

// First 8 bytes are value (amount)
if (len < 8) {
return new VerifyScriptResult(false, "TxOut data too short for value");
}

// Parse value (amount) - assuming little endian
CAmount value;
std::memcpy(&value, data, sizeof(value));

// Remaining bytes are scriptPubKey
CScript script(data + 8, data + len);


CTxOut txout(value, script);
txouts.emplace_back(txout);
}

// sig checker
PrecomputedTransactionData txdata(tx);
txdata.Init(tx, std::move(txouts), false);
TransactionSignatureChecker checker = TransactionSignatureChecker(&tx, nIn, amount_in, txdata, MissingDataBehavior::ASSERT_FAIL);

// Verify the script
ScriptError scriptErr;
bool success = VerifyScript(
tx.vin[nIn].scriptSig, CScript(vscriptPubKey.begin(), vscriptPubKey.end()),
&tx.vin[nIn].scriptWitness, flags,
TransactionSignatureChecker(&tx, nIn, amount_in, MissingDataBehavior::FAIL),
&scriptErr
checker, &scriptErr
);
if (success) {
return new VerifyScriptResult(success, "");
if (success && scriptErr == ScriptError::SCRIPT_ERR_OK) {
return new VerifyScriptResult(true, "");
} else {
std::string err_msg = ScriptErrorString(scriptErr);
return new VerifyScriptResult(success, err_msg);
return new VerifyScriptResult(false, err_msg);
}
}

Expand Down

0 comments on commit 5e62bba

Please sign in to comment.