1mod types;
17
18use core::fmt;
19
20use crate::types::{c_int64, c_uchar, c_uint};
21
22pub const VERIFY_NONE: c_uint = 0;
24pub const VERIFY_P2SH: c_uint = 1 << 0;
26pub const VERIFY_DERSIG: c_uint = 1 << 2;
28pub const VERIFY_NULLDUMMY: c_uint = 1 << 4;
30pub const VERIFY_CHECKLOCKTIMEVERIFY: c_uint = 1 << 9;
32pub const VERIFY_CHECKSEQUENCEVERIFY: c_uint = 1 << 10;
34pub const VERIFY_WITNESS: c_uint = 1 << 11;
36pub const VERIFY_TAPROOT: c_uint = 1 << 17;
38
39pub const VERIFY_ALL_PRE_TAPROOT: c_uint = VERIFY_P2SH
40 | VERIFY_DERSIG
41 | VERIFY_NULLDUMMY
42 | VERIFY_CHECKLOCKTIMEVERIFY
43 | VERIFY_CHECKSEQUENCEVERIFY
44 | VERIFY_WITNESS;
45
46pub fn height_to_flags(height: u32) -> u32 {
48 let mut flag = VERIFY_NONE;
49
50 if height >= 173805 {
51 flag |= VERIFY_P2SH;
52 }
53 if height >= 363725 {
54 flag |= VERIFY_DERSIG;
55 }
56 if height >= 388381 {
57 flag |= VERIFY_CHECKLOCKTIMEVERIFY;
58 }
59 if height >= 419328 {
60 flag |= VERIFY_CHECKSEQUENCEVERIFY;
61 }
62 if height >= 481824 {
63 flag |= VERIFY_NULLDUMMY | VERIFY_WITNESS
64 }
65 if height > 709632 {
66 flag |= VERIFY_TAPROOT
67 }
68
69 flag
70}
71
72pub fn version() -> u32 { unsafe { ffi::bitcoinconsensus_version() as u32 } }
74
75pub fn verify(
109 spent_output: &[u8],
111 amount: u64, spending_transaction: &[u8], spent_outputs: Option<&[Utxo]>, input_index: usize,
115) -> Result<(), Error> {
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)
122}
123
124pub fn verify_with_flags(
126 spent_output_script: &[u8],
127 amount: u64,
128 spending_transaction: &[u8],
129 spent_outputs: Option<&[Utxo]>,
130 input_index: usize,
131 flags: u32,
132) -> Result<(), Error> {
133 match spent_outputs {
134 Some(spent_outputs) => unsafe {
135 let mut error = Error::ERR_SCRIPT;
136
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 },
174 }
175}
176
177#[repr(C)]
180pub struct Utxo {
181 pub script_pubkey: *const c_uchar,
183 pub script_pubkey_len: c_uint,
185 pub value: c_int64,
187}
188
189pub mod ffi {
190 use super::*;
191 use crate::types::c_int;
192
193 extern "C" {
194 pub fn bitcoinconsensus_version() -> c_int;
196
197 pub fn bitcoinconsensus_verify_script_with_amount(
202 script_pubkey: *const c_uchar,
203 script_pubkeylen: c_uint,
204 amount: u64,
205 tx_to: *const c_uchar,
206 tx_tolen: c_uint,
207 n_in: c_uint,
208 flags: c_uint,
209 err: *mut Error,
210 ) -> c_int;
211
212 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;
228 }
229}
230
231#[allow(non_camel_case_types)]
237#[derive(Debug, Copy, Clone, PartialEq, Eq)]
238#[repr(C)]
239pub enum Error {
240 ERR_SCRIPT = 0, ERR_TX_INDEX,
244 ERR_TX_SIZE_MISMATCH,
246 ERR_TX_DESERIALIZE,
248 ERR_AMOUNT_REQUIRED,
250 ERR_INVALID_FLAGS,
252 ERR_SPENT_OUTPUTS_REQUIRED,
254 ERR_SPENT_OUTPUTS_MISMATCH,
256}
257
258impl fmt::Display for Error {
259 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
260 use self::Error::*;
261
262 let s = match *self {
263 ERR_SCRIPT => "error value was not set (value still 0)",
264 ERR_TX_INDEX => "an invalid index for txTo",
265 ERR_TX_SIZE_MISMATCH => "txToLen did not match with the size of txTo",
266 ERR_TX_DESERIALIZE => "an error deserializing txTo",
267 ERR_AMOUNT_REQUIRED => "input amount is required if WITNESS is used",
268 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",
271 };
272 f.write_str(s)
273 }
274}
275
276#[cfg(feature = "std")]
277impl std::error::Error for Error {
278 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
279 use self::Error::*;
280
281 match *self {
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,
290 }
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 extern crate rustc_serialize as serialize;
297
298 use self::serialize::hex::FromHex;
299 use super::*;
300
301 #[test]
302 fn bitcoinconsensus_test() {
303 verify_test (
305 "76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac",
306 "02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700",
307 0, 0
308 ).unwrap();
309
310 verify_test (
312 "a91434c06f8c87e355e123bdc6dda4ffabc64b6989ef87",
313 "01000000000101d9fd94d0ff0026d307c994d0003180a5f248146efb6371d040c5973f5f66d9df0400000017160014b31b31a6cb654cfab3c50567bcf124f48a0beaecffffffff012cbd1c000000000017a914233b74bf0823fa58bbbd26dfc3bb4ae715547167870247304402206f60569cac136c114a58aedd80f6fa1c51b49093e7af883e605c212bdafcd8d202200e91a55f408a021ad2631bc29a67bd6915b2d7e9ef0265627eabd7f7234455f6012103e7e802f50344303c76d12c089c8724c1b230e3b745693bbe16aad536293d15e300000000",
314 1900000, 0
315 ).unwrap();
316
317 verify_test(
319 "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d",
320 "010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000",
321 18393430 , 0
322 ).unwrap();
323
324 assert!(verify_test (
326 "76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ff",
327 "02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700",
328 0, 0
329 ).is_err());
330
331 assert!(verify_test (
333 "a91434c06f8c87e355e123bdc6dda4ffabc64b6989ef87",
334 "01000000000101d9fd94d0ff0026d307c994d0003180a5f248146efb6371d040c5973f5f66d9df0400000017160014b31b31a6cb654cfab3c50567bcf124f48a0beaecffffffff012cbd1c000000000017a914233b74bf0823fa58bbbd26dfc3bb4ae715547167870247304402206f60569cac136c114a58aedd80f6fa1c51b49093e7af883e605c212bdafcd8d202200e91a55f408a021ad2631bc29a67bd6915b2d7e9ef0265627eabd7f7234455f6012103e7e802f50344303c76d12c089c8724c1b230e3b745693bbe16aad536293d15e300000000",
335 900000, 0).is_err());
336
337 assert!(verify_test(
339 "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58f",
340 "010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000",
341 18393430 , 0
342 ).is_err());
343 }
344
345 fn verify_test(spent: &str, spending: &str, amount: u64, input: usize) -> Result<(), Error> {
346 verify(
347 spent.from_hex().unwrap().as_slice(),
348 amount,
349 spending.from_hex().unwrap().as_slice(),
350 None,
351 input,
352 )
353 }
354
355 #[test]
356 fn invalid_flags_test() {
357 verify_with_flags(&[], 0, &[], None, 0, VERIFY_ALL_PRE_TAPROOT + 1).unwrap_err();
358 }
359}