Skip to content

Commit cd27ae8

Browse files
committed
Implement new Taproot verification
Now that we have upgraded to Bitcoin Core v26 we can support verification of taproot scripts. Add an initial API to do so - needs iterating upon.
1 parent 5af9417 commit cd27ae8

File tree

2 files changed

+112
-28
lines changed

2 files changed

+112
-28
lines changed

src/lib.rs

Lines changed: 110 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod types;
1717

1818
use core::fmt;
1919

20-
use crate::types::c_uint;
20+
use crate::types::{c_int64, c_uchar, c_uint};
2121

2222
/// Do not enable any verification.
2323
pub const VERIFY_NONE: c_uint = 0;
@@ -33,8 +33,10 @@ pub const VERIFY_CHECKLOCKTIMEVERIFY: c_uint = 1 << 9;
3333
pub const VERIFY_CHECKSEQUENCEVERIFY: c_uint = 1 << 10;
3434
/// Enable WITNESS (BIP141).
3535
pub const VERIFY_WITNESS: c_uint = 1 << 11;
36+
/// Enable TAPROOT (BIPs 341 & 342)
37+
pub const VERIFY_TAPROOT: c_uint = 1 << 17;
3638

37-
pub const VERIFY_ALL: c_uint = VERIFY_P2SH
39+
pub const VERIFY_ALL_PRE_TAPROOT: c_uint = VERIFY_P2SH
3840
| VERIFY_DERSIG
3941
| VERIFY_NULLDUMMY
4042
| VERIFY_CHECKLOCKTIMEVERIFY
@@ -60,6 +62,9 @@ pub fn height_to_flags(height: u32) -> u32 {
6062
if height >= 481824 {
6163
flag |= VERIFY_NULLDUMMY | VERIFY_WITNESS
6264
}
65+
if height > 709632 {
66+
flag |= VERIFY_TAPROOT
67+
}
6368

6469
flag
6570
}
@@ -101,53 +106,98 @@ pub fn version() -> u32 { unsafe { ffi::bitcoinconsensus_version() as u32 } }
101106
/// **Note** since the spent amount will only be checked for Segwit transactions and the above
102107
/// example is not segwit, `verify` will succeed with any amount.
103108
pub fn verify(
109+
// The script_pubkey of the output we are spending (e.g. `TxOut::script_pubkey.as_bytes()`).
104110
spent_output: &[u8],
105-
amount: u64,
106-
spending_transaction: &[u8],
111+
amount: u64, // The amount of the output is spending (e.g. `TxOut::value`)
112+
spending_transaction: &[u8], // Create using `tx.serialize().as_slice()`
113+
spent_outputs: Option<&[Utxo]>, // None for pre-taproot.
107114
input_index: usize,
108115
) -> Result<(), Error> {
109-
verify_with_flags(spent_output, amount, spending_transaction, input_index, VERIFY_ALL)
116+
let flags = match spent_outputs {
117+
Some(_) => VERIFY_ALL_PRE_TAPROOT | VERIFY_TAPROOT,
118+
None => VERIFY_ALL_PRE_TAPROOT,
119+
};
120+
121+
verify_with_flags(spent_output, amount, spending_transaction, spent_outputs, input_index, flags)
110122
}
111123

112124
/// Same as verify but with flags that turn past soft fork features on or off.
113125
pub fn verify_with_flags(
114126
spent_output_script: &[u8],
115127
amount: u64,
116128
spending_transaction: &[u8],
129+
spent_outputs: Option<&[Utxo]>,
117130
input_index: usize,
118131
flags: u32,
119132
) -> Result<(), Error> {
120-
unsafe {
121-
let mut error = Error::ERR_SCRIPT;
133+
match spent_outputs {
134+
Some(spent_outputs) => unsafe {
135+
let mut error = Error::ERR_SCRIPT;
122136

123-
let ret = ffi::bitcoinconsensus_verify_script_with_amount(
124-
spent_output_script.as_ptr(),
125-
spent_output_script.len() as c_uint,
126-
amount,
127-
spending_transaction.as_ptr(),
128-
spending_transaction.len() as c_uint,
129-
input_index as c_uint,
130-
flags as c_uint,
131-
&mut error,
132-
);
133-
if ret != 1 {
134-
Err(error)
135-
} else {
136-
Ok(())
137-
}
137+
let ret = ffi::bitcoinconsensus_verify_script_with_spent_outputs(
138+
spent_output_script.as_ptr(),
139+
spent_output_script.len() as c_uint,
140+
amount,
141+
spending_transaction.as_ptr(),
142+
spending_transaction.len() as c_uint,
143+
spent_outputs.as_ptr() as *const c_uchar,
144+
spent_outputs.len() as c_uint,
145+
input_index as c_uint,
146+
flags as c_uint,
147+
&mut error,
148+
);
149+
if ret != 1 {
150+
Err(error)
151+
} else {
152+
Ok(())
153+
}
154+
},
155+
None => unsafe {
156+
let mut error = Error::ERR_SCRIPT;
157+
158+
let ret = ffi::bitcoinconsensus_verify_script_with_amount(
159+
spent_output_script.as_ptr(),
160+
spent_output_script.len() as c_uint,
161+
amount,
162+
spending_transaction.as_ptr(),
163+
spending_transaction.len() as c_uint,
164+
input_index as c_uint,
165+
flags as c_uint,
166+
&mut error,
167+
);
168+
if ret != 1 {
169+
Err(error)
170+
} else {
171+
Ok(())
172+
}
173+
},
138174
}
139175
}
140176

177+
/// Mimics the Bitcoin Core UTXO typedef (bitcoinconsenus.h)
178+
// This is the TxOut data for utxos being spent, i.e., previous outputs.
179+
#[repr(C)]
180+
pub struct Utxo {
181+
/// Pointer to the scriptPubkey bytes.
182+
pub script_pubkey: *const c_uchar,
183+
/// The length of the scriptPubkey.
184+
pub script_pubkey_len: c_uint,
185+
/// The value in sats.
186+
pub value: c_int64,
187+
}
188+
141189
pub mod ffi {
142-
use crate::types::{c_int, c_uchar, c_uint};
143-
use crate::Error;
190+
use super::*;
191+
use crate::types::c_int;
144192

145193
extern "C" {
146194
/// Returns `libbitcoinconsensus` version.
147195
pub fn bitcoinconsensus_version() -> c_int;
148196

149197
/// Verifies that the transaction input correctly spends the previous
150198
/// output, considering any additional constraints specified by flags.
199+
///
200+
/// This function does not verify Taproot inputs.
151201
pub fn bitcoinconsensus_verify_script_with_amount(
152202
script_pubkey: *const c_uchar,
153203
script_pubkeylen: c_uint,
@@ -158,6 +208,23 @@ pub mod ffi {
158208
flags: c_uint,
159209
err: *mut Error,
160210
) -> c_int;
211+
212+
/// Verifies that the transaction input correctly spends the previous
213+
/// output, considering any additional constraints specified by flags.
214+
///
215+
/// This function verifies Taproot inputs.
216+
pub fn bitcoinconsensus_verify_script_with_spent_outputs(
217+
script_pubkey: *const c_uchar,
218+
script_pubkeylen: c_uint,
219+
amount: u64,
220+
tx_to: *const c_uchar,
221+
tx_tolen: c_uint,
222+
spent_outputs: *const c_uchar,
223+
num_spent_outputs: c_uint,
224+
n_in: c_uint,
225+
flags: c_uint,
226+
err: *mut Error,
227+
) -> c_int;
161228
}
162229
}
163230

@@ -171,7 +238,7 @@ pub mod ffi {
171238
#[repr(C)]
172239
pub enum Error {
173240
/// Default value, passed to `libbitcoinconsensus` as a return parameter.
174-
ERR_SCRIPT = 0,
241+
ERR_SCRIPT = 0, // This is ERR_OK in Bitcoin Core.
175242
/// An invalid index for `txTo`.
176243
ERR_TX_INDEX,
177244
/// `txToLen` did not match with the size of `txTo`.
@@ -182,6 +249,10 @@ pub enum Error {
182249
ERR_AMOUNT_REQUIRED,
183250
/// Script verification `flags` are invalid (i.e. not part of the libconsensus interface).
184251
ERR_INVALID_FLAGS,
252+
/// Verifying Taproot input requires previous outputs.
253+
ERR_SPENT_OUTPUTS_REQUIRED,
254+
/// Taproot outputs don't match.
255+
ERR_SPENT_OUTPUTS_MISMATCH,
185256
}
186257

187258
impl fmt::Display for Error {
@@ -195,6 +266,8 @@ impl fmt::Display for Error {
195266
ERR_TX_DESERIALIZE => "an error deserializing txTo",
196267
ERR_AMOUNT_REQUIRED => "input amount is required if WITNESS is used",
197268
ERR_INVALID_FLAGS => "script verification flags are invalid",
269+
ERR_SPENT_OUTPUTS_REQUIRED => "verifying taproot input requires previous outputs",
270+
ERR_SPENT_OUTPUTS_MISMATCH => "taproot outputs don't match",
198271
};
199272
f.write_str(s)
200273
}
@@ -206,8 +279,14 @@ impl std::error::Error for Error {
206279
use self::Error::*;
207280

208281
match *self {
209-
ERR_SCRIPT | ERR_TX_INDEX | ERR_TX_SIZE_MISMATCH | ERR_TX_DESERIALIZE
210-
| ERR_AMOUNT_REQUIRED | ERR_INVALID_FLAGS => None,
282+
ERR_SCRIPT
283+
| ERR_TX_INDEX
284+
| ERR_TX_SIZE_MISMATCH
285+
| ERR_TX_DESERIALIZE
286+
| ERR_AMOUNT_REQUIRED
287+
| ERR_INVALID_FLAGS
288+
| ERR_SPENT_OUTPUTS_REQUIRED
289+
| ERR_SPENT_OUTPUTS_MISMATCH => None,
211290
}
212291
}
213292
}
@@ -268,10 +347,13 @@ mod tests {
268347
spent.from_hex().unwrap().as_slice(),
269348
amount,
270349
spending.from_hex().unwrap().as_slice(),
350+
None,
271351
input,
272352
)
273353
}
274354

275355
#[test]
276-
fn invalid_flags_test() { verify_with_flags(&[], 0, &[], 0, VERIFY_ALL + 1).unwrap_err(); }
356+
fn invalid_flags_test() {
357+
verify_with_flags(&[], 0, &[], None, 0, VERIFY_ALL_PRE_TAPROOT + 1).unwrap_err();
358+
}
277359
}

src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
/// The C signed 32 bit integer type.
66
pub type c_int = i32;
7+
/// The C signed 64 bit integer type.
8+
pub type c_int64 = i64;
79
/// The C unsigned 8 bit integer type.
810
pub type c_uchar = u8;
911
/// The C unsigned 32 bit integer type.

0 commit comments

Comments
 (0)