bdk_wallet/wallet/
utils.rs

1// Bitcoin Dev Kit
2// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
3//
4// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
5//
6// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
7// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
9// You may not use this file except in accordance with one or both of these
10// licenses.
11
12use alloc::sync::Arc;
13use bitcoin::secp256k1::{All, Secp256k1};
14use bitcoin::{
15    absolute, relative, Amount, FeeRate, Script, Sequence, SignedAmount, Transaction, Txid,
16};
17use chain::{ChainPosition, ConfirmationBlockTime};
18use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
19
20use rand_core::RngCore;
21
22/// Trait to check if a value is below the dust limit.
23/// We are performing dust value calculation for a given script public key using rust-bitcoin to
24/// keep it compatible with network dust rate
25// we implement this trait to make sure we don't mess up the comparison with off-by-one like a <
26// instead of a <= etc.
27pub trait IsDust {
28    /// Check whether or not a value is below dust limit
29    fn is_dust(&self, script: &Script) -> bool;
30}
31
32impl IsDust for Amount {
33    fn is_dust(&self, script: &Script) -> bool {
34        *self < script.minimal_non_dust()
35    }
36}
37
38impl IsDust for u64 {
39    fn is_dust(&self, script: &Script) -> bool {
40        Amount::from_sat(*self).is_dust(script)
41    }
42}
43
44pub struct After {
45    pub current_height: Option<u32>,
46    pub assume_height_reached: bool,
47}
48
49impl After {
50    pub(crate) fn new(current_height: Option<u32>, assume_height_reached: bool) -> After {
51        After {
52            current_height,
53            assume_height_reached,
54        }
55    }
56}
57
58pub(crate) fn check_nsequence_rbf(sequence: Sequence, csv: Sequence) -> bool {
59    // The nSequence value must enable relative timelocks
60    if !sequence.is_relative_lock_time() {
61        return false;
62    }
63
64    // Both values should be represented in the same unit (either time-based or
65    // block-height based)
66    if sequence.is_time_locked() != csv.is_time_locked() {
67        return false;
68    }
69
70    // The value should be at least `csv`
71    if sequence < csv {
72        return false;
73    }
74
75    true
76}
77
78impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
79    fn check_after(&self, n: absolute::LockTime) -> bool {
80        if let Some(current_height) = self.current_height {
81            current_height >= n.to_consensus_u32()
82        } else {
83            self.assume_height_reached
84        }
85    }
86}
87
88pub struct Older {
89    pub current_height: Option<u32>,
90    pub create_height: Option<u32>,
91    pub assume_height_reached: bool,
92}
93
94impl Older {
95    pub(crate) fn new(
96        current_height: Option<u32>,
97        create_height: Option<u32>,
98        assume_height_reached: bool,
99    ) -> Older {
100        Older {
101            current_height,
102            create_height,
103            assume_height_reached,
104        }
105    }
106}
107
108impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Older {
109    fn check_older(&self, n: relative::LockTime) -> bool {
110        if let Some(current_height) = self.current_height {
111            // TODO: test >= / >
112            current_height
113                >= self
114                    .create_height
115                    .unwrap_or(0)
116                    .checked_add(n.to_consensus_u32())
117                    .expect("Overflowing addition")
118        } else {
119            self.assume_height_reached
120        }
121    }
122}
123
124// The Knuth shuffling algorithm based on the original [Fisher-Yates method](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)
125pub(crate) fn shuffle_slice<T>(list: &mut [T], rng: &mut impl RngCore) {
126    if list.is_empty() {
127        return;
128    }
129    let mut current_index = list.len() - 1;
130    while current_index > 0 {
131        let random_index = rng.next_u32() as usize % (current_index + 1);
132        list.swap(current_index, random_index);
133        current_index -= 1;
134    }
135}
136
137pub(crate) type SecpCtx = Secp256k1<All>;
138
139/// Details about a transaction affecting the wallet (relevant and canonical).
140#[derive(Debug)]
141pub struct TxDetails {
142    /// The transaction id.
143    pub txid: Txid,
144    /// The sum of the transaction input amounts that spend from previous outputs tracked by this
145    /// wallet.
146    pub sent: Amount,
147    /// The sum of the transaction outputs that send to script pubkeys tracked by this wallet.
148    pub received: Amount,
149    /// The fee paid for the transaction. Note that to calculate the fee for a transaction with
150    /// inputs not owned by this wallet you must manually insert the TxOut(s) into the tx graph
151    /// using the insert_txout function. If those are not available, the field will be `None`.
152    pub fee: Option<Amount>,
153    /// The fee rate paid for the transaction. Note that to calculate the fee rate for a
154    /// transaction with inputs not owned by this wallet you must manually insert the TxOut(s) into
155    /// the tx graph using the insert_txout function. If those are not available, the field will be
156    /// `None`.
157    pub fee_rate: Option<FeeRate>,
158    /// The net effect of the transaction on the balance of the wallet.
159    pub balance_delta: SignedAmount,
160    /// The position of the transaction in the chain.
161    pub chain_position: ChainPosition<ConfirmationBlockTime>,
162    /// The complete [`Transaction`].
163    pub tx: Arc<Transaction>,
164}
165
166#[cfg(test)]
167mod test {
168    // When nSequence is lower than this flag the timelock is interpreted as block-height-based,
169    // otherwise it's time-based
170    pub(crate) const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;
171
172    use super::{check_nsequence_rbf, shuffle_slice, IsDust};
173    use crate::bitcoin::{Address, Network, Sequence};
174    use alloc::vec::Vec;
175    use core::str::FromStr;
176    use rand::{rngs::StdRng, thread_rng, SeedableRng};
177
178    #[test]
179    fn test_is_dust() {
180        let script_p2pkh = Address::from_str("1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe")
181            .unwrap()
182            .require_network(Network::Bitcoin)
183            .unwrap()
184            .script_pubkey();
185        assert!(script_p2pkh.is_p2pkh());
186        assert!(545.is_dust(&script_p2pkh));
187        assert!(!546.is_dust(&script_p2pkh));
188
189        let script_p2wpkh = Address::from_str("bc1qxlh2mnc0yqwas76gqq665qkggee5m98t8yskd8")
190            .unwrap()
191            .require_network(Network::Bitcoin)
192            .unwrap()
193            .script_pubkey();
194        assert!(script_p2wpkh.is_p2wpkh());
195        assert!(293.is_dust(&script_p2wpkh));
196        assert!(!294.is_dust(&script_p2wpkh));
197    }
198
199    #[test]
200    fn test_check_nsequence_rbf_msb_set() {
201        let result = check_nsequence_rbf(Sequence(0x80000000), Sequence(5000));
202        assert!(!result);
203    }
204
205    #[test]
206    fn test_check_nsequence_rbf_lt_csv() {
207        let result = check_nsequence_rbf(Sequence(4000), Sequence(5000));
208        assert!(!result);
209    }
210
211    #[test]
212    fn test_check_nsequence_rbf_different_unit() {
213        let result =
214            check_nsequence_rbf(Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000), Sequence(5000));
215        assert!(!result);
216    }
217
218    #[test]
219    fn test_check_nsequence_rbf_mask() {
220        let result = check_nsequence_rbf(Sequence(0x3f + 10_000), Sequence(5000));
221        assert!(result);
222    }
223
224    #[test]
225    fn test_check_nsequence_rbf_same_unit_blocks() {
226        let result = check_nsequence_rbf(Sequence(10_000), Sequence(5000));
227        assert!(result);
228    }
229
230    #[test]
231    fn test_check_nsequence_rbf_same_unit_time() {
232        let result = check_nsequence_rbf(
233            Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 10_000),
234            Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000),
235        );
236        assert!(result);
237    }
238
239    #[test]
240    #[cfg(feature = "std")]
241    fn test_shuffle_slice_empty_vec() {
242        let mut test: Vec<u8> = vec![];
243        shuffle_slice(&mut test, &mut thread_rng());
244    }
245
246    #[test]
247    #[cfg(feature = "std")]
248    fn test_shuffle_slice_single_vec() {
249        let mut test: Vec<u8> = vec![0];
250        shuffle_slice(&mut test, &mut thread_rng());
251    }
252
253    #[test]
254    fn test_shuffle_slice_duple_vec() {
255        let seed = [0; 32];
256        let mut rng: StdRng = SeedableRng::from_seed(seed);
257        let mut test: Vec<u8> = vec![0, 1];
258        shuffle_slice(&mut test, &mut rng);
259        assert_eq!(test, &[0, 1]);
260        let seed = [6; 32];
261        let mut rng: StdRng = SeedableRng::from_seed(seed);
262        let mut test: Vec<u8> = vec![0, 1];
263        shuffle_slice(&mut test, &mut rng);
264        assert_eq!(test, &[1, 0]);
265    }
266
267    #[test]
268    fn test_shuffle_slice_multi_vec() {
269        let seed = [0; 32];
270        let mut rng: StdRng = SeedableRng::from_seed(seed);
271        let mut test: Vec<u8> = vec![0, 1, 2, 4, 5];
272        shuffle_slice(&mut test, &mut rng);
273        assert_eq!(test, &[2, 1, 0, 4, 5]);
274        let seed = [25; 32];
275        let mut rng: StdRng = SeedableRng::from_seed(seed);
276        let mut test: Vec<u8> = vec![0, 1, 2, 4, 5];
277        shuffle_slice(&mut test, &mut rng);
278        assert_eq!(test, &[0, 4, 1, 2, 5]);
279    }
280}