bitcoinconsensus/
lib.rs

1// Copyright 2018 Tamas Blummer
2// SPDX-License-Identifier: Apache-2.0
3
4//! This project builds the `libbitcoinconsensus` library from Bitcoin's C++
5//! sources using Cargo and provides Rust bindings to its API.
6//!
7//! Quoting from [`bitcoin/doc/shared-libraries.md`]:
8//!
9//! > The purpose of this library is to make the verification functionality that is critical to
10//! > Bitcoin's consensus available to other applications, e.g. to language bindings.
11//!
12//! And that is exactly what this library is, the Rust bindings to `bitcoinconsensus`.
13//!
14//! [`bitcoin/doc/shared-libraries.md`]: <https://github.com/bitcoin/bitcoin/blob/master/doc/shared-libraries.md>
15
16mod types;
17
18use core::fmt;
19
20use crate::types::{c_int64, c_uchar, c_uint};
21
22/// Do not enable any verification.
23pub const VERIFY_NONE: c_uint = 0;
24/// Evaluate P2SH (BIP16) subscripts.
25pub const VERIFY_P2SH: c_uint = 1 << 0;
26/// Enforce strict DER (BIP66) compliance.
27pub const VERIFY_DERSIG: c_uint = 1 << 2;
28/// Enforce NULLDUMMY (BIP147).
29pub const VERIFY_NULLDUMMY: c_uint = 1 << 4;
30/// Enable CHECKLOCKTIMEVERIFY (BIP65).
31pub const VERIFY_CHECKLOCKTIMEVERIFY: c_uint = 1 << 9;
32/// Enable CHECKSEQUENCEVERIFY (BIP112).
33pub const VERIFY_CHECKSEQUENCEVERIFY: c_uint = 1 << 10;
34/// Enable WITNESS (BIP141).
35pub const VERIFY_WITNESS: c_uint = 1 << 11;
36/// Enable TAPROOT (BIPs 341 & 342)
37pub 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
46/// Computes flags for soft fork activation heights on the Bitcoin network.
47pub 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
72/// Returns `libbitcoinconsensus` version.
73pub fn version() -> u32 { unsafe { ffi::bitcoinconsensus_version() as u32 } }
74
75/// Verifies a single spend (input) of a Bitcoin transaction.
76///
77/// Note that amount will only be checked for Segwit transactions.
78///
79/// # Arguments
80///
81///  * `spend_output`: A Bitcoin transaction output script to be spent, serialized in Bitcoin's on wire format.
82///  * `amount`: The spent output amount in satoshis.
83///  * `spending_transaction`: The spending Bitcoin transaction, serialized in Bitcoin's on wire format.
84///  * `input_index`: The index of the input within spending_transaction.
85///
86/// # Examples
87///
88/// The (randomly choosen) Bitcoin transaction
89///
90///  `aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d`
91///
92/// spends one input, that is the first output of
93///
94///  `95da344585fcf2e5f7d6cbf2c3df2dcce84f9196f7a7bb901a43275cd6eb7c3f`
95///
96/// The spending transaction serialized is:
97///
98///  `spending = 02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700`
99///
100/// The script of the first output of the spent transaction is:
101///
102///  `spent = 76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac`
103///
104/// The (pseudo code) call: `verify(spent, 630482530, spending, 0)` should return `Ok(())`.
105///
106/// **Note** since the spent amount will only be checked for Segwit transactions and the above
107/// example is not segwit, `verify` will succeed with any amount.
108pub fn verify(
109    // The script_pubkey of the output we are spending (e.g. `TxOut::script_pubkey.as_bytes()`).
110    spent_output: &[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.
114    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
124/// Same as verify but with flags that turn past soft fork features on or off.
125pub 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/// 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)]
180pub 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
189pub mod ffi {
190    use super::*;
191    use crate::types::c_int;
192
193    extern "C" {
194        /// Returns `libbitcoinconsensus` version.
195        pub fn bitcoinconsensus_version() -> c_int;
196
197        /// Verifies that the transaction input correctly spends the previous
198        /// output, considering any additional constraints specified by flags.
199        ///
200        /// This function does not verify Taproot inputs.
201        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        /// 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;
228    }
229}
230
231/// Errors returned by [`libbitcoinconsensus`].
232///
233/// The error variant identifiers mimic those from `libbitcoinconsensus`.
234///
235/// [`libbitcoinconsensus`]: <https://github.com/bitcoin/bitcoin/blob/master/doc/shared-libraries.md#errors>
236#[allow(non_camel_case_types)]
237#[derive(Debug, Copy, Clone, PartialEq, Eq)]
238#[repr(C)]
239pub enum Error {
240    /// Default value, passed to `libbitcoinconsensus` as a return parameter.
241    ERR_SCRIPT = 0, // This is ERR_OK in Bitcoin Core.
242    /// An invalid index for `txTo`.
243    ERR_TX_INDEX,
244    /// `txToLen` did not match with the size of `txTo`.
245    ERR_TX_SIZE_MISMATCH,
246    /// An error deserializing `txTo`.
247    ERR_TX_DESERIALIZE,
248    /// Input amount is required if WITNESS is used.
249    ERR_AMOUNT_REQUIRED,
250    /// Script verification `flags` are invalid (i.e. not part of the libconsensus interface).
251    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,
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        // a random old-style transaction from the blockchain
304        verify_test (
305            "76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac",
306            "02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700",
307            0, 0
308        ).unwrap();
309
310        // a random segwit transaction from the blockchain using P2SH
311        verify_test (
312            "a91434c06f8c87e355e123bdc6dda4ffabc64b6989ef87",
313            "01000000000101d9fd94d0ff0026d307c994d0003180a5f248146efb6371d040c5973f5f66d9df0400000017160014b31b31a6cb654cfab3c50567bcf124f48a0beaecffffffff012cbd1c000000000017a914233b74bf0823fa58bbbd26dfc3bb4ae715547167870247304402206f60569cac136c114a58aedd80f6fa1c51b49093e7af883e605c212bdafcd8d202200e91a55f408a021ad2631bc29a67bd6915b2d7e9ef0265627eabd7f7234455f6012103e7e802f50344303c76d12c089c8724c1b230e3b745693bbe16aad536293d15e300000000",
314            1900000, 0
315        ).unwrap();
316
317        // a random segwit transaction from the blockchain using native segwit
318        verify_test(
319            "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d",
320            "010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000",
321            18393430 , 0
322        ).unwrap();
323
324        // a random old-style transaction from the blockchain - WITH WRONG SIGNATURE for the address
325        assert!(verify_test (
326            "76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ff",
327            "02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700",
328            0, 0
329        ).is_err());
330
331        // a random segwit transaction from the blockchain using P2SH - WITH WRONG AMOUNT
332        assert!(verify_test (
333            "a91434c06f8c87e355e123bdc6dda4ffabc64b6989ef87",
334            "01000000000101d9fd94d0ff0026d307c994d0003180a5f248146efb6371d040c5973f5f66d9df0400000017160014b31b31a6cb654cfab3c50567bcf124f48a0beaecffffffff012cbd1c000000000017a914233b74bf0823fa58bbbd26dfc3bb4ae715547167870247304402206f60569cac136c114a58aedd80f6fa1c51b49093e7af883e605c212bdafcd8d202200e91a55f408a021ad2631bc29a67bd6915b2d7e9ef0265627eabd7f7234455f6012103e7e802f50344303c76d12c089c8724c1b230e3b745693bbe16aad536293d15e300000000",
335            900000, 0).is_err());
336
337        // a random segwit transaction from the blockchain using native segwit - WITH WRONG SEGWIT
338        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}