bip352/
lib.rs

1//! This library provides functions for working with sending and receiving
2//! Bitcoin Silent Payments according to BIP 352 proposal.
3use bitcoin::consensus::serialize;
4use bitcoin::hashes::sha256t::Tag;
5use bitcoin::hashes::{hash160, sha256t_hash_newtype, Hash, HashEngine};
6use bitcoin::key::TapTweak;
7use bitcoin::secp256k1::{
8    Error as SecpError, Keypair, Parity, PublicKey, Scalar, Secp256k1, SecretKey, Signing,
9    Verification, XOnlyPublicKey,
10};
11use bitcoin::{OutPoint, Script, ScriptBuf, TxIn};
12use label::Label;
13
14pub mod address;
15pub mod label;
16#[cfg(feature = "receive")]
17pub mod receive;
18#[cfg(feature = "send")]
19pub mod send;
20#[cfg(feature = "spend")]
21pub mod spend;
22
23sha256t_hash_newtype! {
24    pub struct LabelTag = hash_str("BIP0352/Label");
25    pub struct LabelHash(_);
26
27    pub struct InputsTag = hash_str("BIP0352/Inputs");
28    pub struct InputsHash(_);
29
30    pub struct SharedSecretTag = hash_str("BIP0352/SharedSecret");
31    pub struct SharedSecretHash(_);
32}
33
34#[derive(Clone, Copy, Debug, PartialEq)]
35pub struct SpendPublicKey(PublicKey);
36
37impl SpendPublicKey {
38    pub fn new(public_key: PublicKey) -> Self {
39        Self(public_key)
40    }
41
42    pub fn add_tweak<C: Verification>(
43        &self,
44        tweak: &Scalar,
45        secp: &Secp256k1<C>,
46    ) -> Result<SpendPublicKey, SecpError> {
47        self.0.add_exp_tweak(secp, tweak).map(SpendPublicKey)
48    }
49
50    pub fn serialize(&self) -> [u8; 33] {
51        self.0.serialize()
52    }
53}
54
55#[derive(Clone, Copy, Debug, PartialEq)]
56pub struct SpendSecretKey(SecretKey);
57
58impl SpendSecretKey {
59    pub fn new(secret_key: SecretKey) -> Self {
60        Self(secret_key)
61    }
62
63    pub fn add_tweak(&self, tweak: &Scalar) -> Result<SpendSecretKey, SecpError> {
64        self.0.add_tweak(tweak).map(SpendSecretKey)
65    }
66
67    pub fn to_keypair<C: Signing>(&self, secp: &Secp256k1<C>) -> Keypair {
68        Keypair::from_secret_key(secp, &self.0)
69    }
70}
71
72#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
73pub struct ScanPublicKey(PublicKey);
74
75impl ScanPublicKey {
76    pub fn serialize(&self) -> [u8; 33] {
77        self.0.serialize()
78    }
79
80    pub fn into_public_key(self) -> PublicKey {
81        self.0
82    }
83}
84
85#[derive(Clone, Copy, Debug, PartialEq)]
86pub struct ScanSecretKey(SecretKey);
87
88impl ScanSecretKey {
89    pub fn new(secret_key: SecretKey) -> Self {
90        Self(secret_key)
91    }
92
93    pub fn public_key<C: Signing>(&self, secp: &Secp256k1<C>) -> ScanPublicKey {
94        ScanPublicKey(self.0.public_key(secp))
95    }
96
97    pub fn to_secret_key(&self) -> SecretKey {
98        self.0
99    }
100}
101
102/// An output that has been detected as a Silent Payment together with
103/// all data that are needed to spend it. Wallets should index this.
104#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
105pub struct SilentPaymentOutput {
106    public_key: XOnlyPublicKey,
107    tweak: Scalar,
108    label: Option<Label>,
109}
110
111impl std::hash::Hash for SilentPaymentOutput {
112    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
113        self.public_key.hash(state);
114        self.tweak.to_be_bytes().hash(state);
115        self.label.hash(state);
116    }
117}
118
119impl SilentPaymentOutput {
120    pub fn new(public_key: XOnlyPublicKey, tweak: Scalar) -> Self {
121        Self {
122            public_key,
123            tweak,
124            label: None,
125        }
126    }
127
128    pub fn new_with_label(public_key: XOnlyPublicKey, tweak: Scalar, label: Label) -> Self {
129        Self {
130            public_key,
131            tweak,
132            label: Some(label),
133        }
134    }
135
136    pub fn public_key(&self) -> XOnlyPublicKey {
137        self.public_key
138    }
139
140    pub fn tweak(&self) -> Scalar {
141        self.tweak
142    }
143
144    pub fn label(&self) -> Option<Label> {
145        self.label
146    }
147}
148
149#[derive(Default)]
150pub struct InputHash {
151    /// Holds the least (so far) outpoint bytes.
152    least_outpoint: Option<[u8; 36]>,
153
154    /// Holds aggregated input public key.
155    public_key: Aggregate<PublicKey>,
156}
157
158impl InputHash {
159    /// Creates new, empty input hash.
160    pub fn new() -> Self {
161        Self::default()
162    }
163
164    /// Registers new outpoint that is subject of calculation of the input hash.
165    pub fn add_outpoint(&mut self, outpoint: &OutPoint) -> &mut InputHash {
166        let bytes: [u8; 36] = serialize(outpoint)
167            .try_into()
168            .expect("outpoint serializes to 36 bytes");
169        self.add_outpoint_serialized(bytes)
170    }
171
172    /// Registers new already-serialized outpoint that is subject of calculation of the input hash.
173    pub fn add_outpoint_serialized(&mut self, bytes: [u8; 36]) -> &mut InputHash {
174        match self.least_outpoint.as_ref() {
175            Some(least) if least <= &bytes => {}
176            _ => {
177                self.least_outpoint.replace(bytes);
178            }
179        };
180
181        self
182    }
183
184    pub fn add_input_public_key(&mut self, public_key: &PublicKey) -> Option<&mut InputHash> {
185        self.public_key.add_key(public_key)?;
186        Some(self)
187    }
188
189    /// Returns input hash.
190    pub fn hash(self) -> Result<Scalar, InputHashError> {
191        let mut engine = InputsTag::engine();
192
193        let outpoint = self.least_outpoint.ok_or(InputHashError::NoOutPoint)?;
194        engine.input(&outpoint);
195        let public_key = self.public_key.get().ok_or(InputHashError::NoPublicKey)?;
196        engine.input(&public_key.serialize());
197
198        let hash = InputsHash::from_engine(engine);
199        Scalar::from_be_bytes(hash.to_byte_array()).map_err(|_| InputHashError::InvalidValue)
200    }
201}
202
203#[derive(Debug)]
204pub enum InputHashError {
205    NoOutPoint,
206    NoPublicKey,
207    InvalidValue,
208}
209
210/// Marks keys, public or secret, that can be added together.
211trait Key: Sized {
212    /// Adds two keys together.
213    ///
214    /// # Errors
215    ///
216    /// Returns `None` if the result would not be a valid key.
217    fn plus(&self, other: &Self) -> Option<Self>;
218}
219
220impl Key for PublicKey {
221    fn plus(&self, other: &Self) -> Option<Self> {
222        self.combine(other).ok()
223    }
224}
225
226impl Key for SecretKey {
227    fn plus(&self, other: &Self) -> Option<Self> {
228        self.add_tweak(&(*other).into()).ok()
229    }
230}
231
232/// Holds a key that is being aggregated with other keys.
233struct Aggregate<K>(Option<K>);
234
235impl<K> Aggregate<K> {
236    /// Adds another key to the aggregate.
237    ///
238    /// # Errors
239    ///
240    /// Returns `None` if the new key could not be aggegated due to the result
241    /// not being a valid key.
242    fn add_key(&mut self, key: &K) -> Option<&mut Self>
243    where
244        K: Key + Clone,
245    {
246        match self.0.as_ref() {
247            Some(agg) => {
248                self.0.replace(agg.plus(key)?);
249                Some(self)
250            }
251            None => {
252                self.0.replace(key.clone());
253                Some(self)
254            }
255        }
256    }
257
258    /// Returns result of the aggregation.
259    ///
260    /// # Errors
261    ///
262    /// Returns `None` if no key was successfully added.
263    fn get(self) -> Option<K> {
264        self.0
265    }
266}
267
268impl<K> Default for Aggregate<K> {
269    fn default() -> Self {
270        Self(None)
271    }
272}
273
274/// ECDH shared secret serialized as compressed public key.
275pub struct SharedSecret([u8; 33]);
276
277impl SharedSecret {
278    /// Creates new ECDH shared secret from an input hash, a public key and a secret key.
279    ///
280    /// ### Under the hood
281    /// - input hash is derived from inputs equally for both sending and scanning
282    /// - when sending, public key is address's public scan key; when scanning, public key is aggregated public key of inputs
283    /// - when sending, secret key key is aggregated secret key of inputs; when scanning, secret key is address's secret scan key
284    ///
285    /// # Errors
286    ///
287    /// Fails when any operation results in a value that would not be a valid key.
288    pub fn new<C: Verification>(
289        input_hash: Scalar,
290        public_key: PublicKey,
291        secret_key: SecretKey,
292        secp: &Secp256k1<C>,
293    ) -> Result<SharedSecret, SecpError> {
294        Ok(SharedSecret(
295            public_key
296                .mul_tweak(secp, &secret_key.into())?
297                .mul_tweak(secp, &input_hash)?
298                .serialize(),
299        ))
300    }
301
302    pub fn destination_output<C: Verification>(
303        &self,
304        spend_key: SpendPublicKey,
305        k: u32,
306        secp: &Secp256k1<C>,
307    ) -> ScriptBuf {
308        let (p_k, _) = self.destination_public_key(spend_key, k, secp);
309
310        ScriptBuf::new_p2tr_tweaked(XOnlyPublicKey::from(p_k).dangerous_assume_tweaked())
311    }
312
313    pub fn destination_public_key<C: Verification>(
314        &self,
315        spend_key: SpendPublicKey,
316        k: u32,
317        secp: &Secp256k1<C>,
318    ) -> (PublicKey, Scalar) {
319        let mut engine = SharedSecretTag::engine();
320        engine.input(&self.0);
321        engine.input(&k.to_be_bytes());
322
323        let t_k =
324            Scalar::from_be_bytes(SharedSecretHash::from_engine(engine).to_byte_array()).unwrap();
325        let p_k = spend_key.add_tweak(&t_k, secp).unwrap().0;
326
327        if p_k.x_only_public_key().1 == Parity::Odd {
328            (p_k.negate(secp), t_k)
329        } else {
330            (p_k, t_k)
331        }
332    }
333}
334
335/// Attempts to extract public key from an input and the output it points to. Returns
336/// `None` if public key could not be extracted.
337///
338/// See section _Inputs For Shared Secret Derivation_ in BIP352 for details.
339// TODO: Add reason for rejection into result
340pub fn input_public_key(prevout: &Script, input: &TxIn) -> Option<PublicKey> {
341    if prevout.is_p2pkh() {
342        let hash = match prevout.as_bytes() {
343            [0x76, 0xA9, 0x14, hash @ .., 0x88, 0xAC] if hash.len() == 20 => Some(hash),
344            _ => None,
345        }?;
346
347        // Search for a public key matching given hash due to malleability.
348        // TODO: Improve comment
349        input
350            .script_sig
351            .as_bytes()
352            .windows(33)
353            .rev()
354            .find(|pk_candidate| {
355                hash160::Hash::hash(pk_candidate).as_byte_array().as_slice() == hash
356            })
357            .and_then(|b| PublicKey::from_slice(b).ok())
358    } else if prevout.is_p2wpkh() {
359        input
360            .witness
361            .nth(1)
362            .filter(|b| b.len() == 33)
363            .and_then(|b| PublicKey::from_slice(b).ok())
364    } else if prevout.is_p2tr() {
365        const NUMS_H: [u8; 32] = [
366            0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9,
367            0x7a, 0x5e, 0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, 0xee, 0x9a,
368            0xce, 0x80, 0x3a, 0xc0,
369        ];
370        let witness_stack = input.witness.iter().collect::<Vec<_>>();
371        let is_nums_h = match witness_stack[..] {
372            [.., _, cb, [0x50, ..]] | [.., _, cb] => cb.get(1..33) == Some(&NUMS_H),
373            _ => false,
374        };
375
376        if is_nums_h {
377            None
378        } else {
379            prevout
380                .as_bytes()
381                .get(2..)
382                .and_then(|b| XOnlyPublicKey::from_slice(b).ok())
383                .map(|k| k.public_key(Parity::Even))
384        }
385    } else if prevout.is_p2sh()
386        && matches!(input.script_sig.as_bytes(), [0x16, 0x0, 0x14, x@..] if x.len() == 20)
387    {
388        input
389            .witness
390            .last()
391            .filter(|b| b.len() == 33)
392            .and_then(|b| PublicKey::from_slice(b).ok())
393    } else {
394        None
395    }
396}
397
398#[cfg(test)]
399mod test {
400    use bitcoin::consensus::deserialize;
401    use bitcoin::hashes::hex::FromHex;
402    use bitcoin::Transaction;
403
404    use crate::input_public_key;
405
406    #[test]
407    fn extract_pk_from_pkh() {
408        let tx = bitcoin::consensus::deserialize::<Transaction>(&Vec::from_hex("01000000011856945d6a1ad35c2508c4a5f5ea66be5704db77f427a440f0ef334248e86443000000006b483045022100da9659a5697a2f98e1fa1979e090d8b1e3360cae211a9837a54068df517679f3022057d4885e3685e88ce348b5be1daeb7636aabb12a18eb561fc3a7a5306642c35c0121033586349f482008172d85d2ce5bd61995c864f8bf796c40f49bc6d5b8107df5c6ffffffff0220f661000000000017a9144acea97083d65ebacef745372786a3c3876d1d1b874ee71300000000001976a9144d0b5f8d4b1d89eddc8269bef5255017f44e003f88ac00000000").unwrap()).unwrap();
409
410        let prev = deserialize::<Transaction>(&Vec::from_hex("0100000001510d01348d6085971a5aeb1889dccfbc9c04066991e32e6212b6b8c98f01d23e000000006a473044022074ba5477db90089a264a981ccad24b17ed9e5a44256477a291069f66b532c8d6022012dc8ee766eb1470d22305da16b7e85f546a5fe017e6b15f45b7311d8de627b201210361b7dcaff946649a6e36d35fcd9f45edbb60c39be159a4796e99bcd66373774fffffffff0207317800000000001976a914dac493c1822e2bac7bb7d1308c8b5860ee4d3b4288acfc06d9000000000017a914454d3ad7b59d29b7dbdbee91fd7b209bdd39662a8700000000").unwrap()).unwrap();
411
412        assert!(input_public_key(&prev.output[0].script_pubkey, &tx.input[0],).is_some());
413    }
414
415    #[test]
416    fn extract_pk_from_wpkh() {
417        let tx = deserialize::<Transaction>(&Vec::from_hex("02000000000101269269bcac461236a1af6301f4f4bd33c2cd966d48c770a25406c4f5b3015f100000000000fdffffff02483b09000000000017a914e81d8995a7caa17a379967c37a79dc1ce80de2ea870872cb0000000000160014d0a2fc417c47010c2c0b5f192b2c6de07b1ec1e202473044022001216bd9d3ccf9f10a19df8b58a25bdde2a118f74679f7ca6e72cc9d6aceb68b022046e993d8b8a97962911e7db6f9dbb59a01e23bc63e787d16f52562cc5959e43f0121024bb3d2a761677e988221edd2a51220d325de43de4ff88409d2cf4eeb787f63e1ed8e0c00").unwrap()).unwrap();
418
419        let prev = deserialize::<Transaction>(&Vec::from_hex("02000000000104227ebdf59aa75eb65bc3666590b3b52b474678cbafa55e227137aa6f74e1bdde0000000000ffffffff1e5cfbbde34444f49d3e7c2b12bf369cdb062e1d5c14b52050c6db444a073e260000000000ffffffff57bc82c33cefd4b97bc10ba505bc37d9d7811b95743735ec6133dcadea13095e0000000000ffffffff556c8f583ab9cd9f0a5f475c768b75e265d5bb47d60f5ed39cc9e92d1670dde40000000000ffffffff02c753d500000000001600140d281cbad529b089c548f1c062587f32df8b2b7d949a7400000000001600147da639cdbbbc2cfc8d6a06bb3dae2a9a380ee6dc024730440220289e5a66676337e1d04cebd246f4495fdeb7f5a49a04e1b2987dcb14a8b4d8d702204414c22159c1d85521539b601a36c27ff6fe4f8f04ba5cce25f8242983569feb012102eea57d7f5af2c9ca13dcb3cfa7916fd6b005f9e10b78d3691d435b41402cb7ab02473044022018edcc3e25176f60e6b2c7d31f3534196772a0a5697adcc99165257952140fa902206d239be6d38c30dc7d9a6f434ea69d25d2256d69d8205aba422836384a7579f3012102a4a8b0750cbb33b007eb4d4f1db43c772964e71e354b650ce24e6b9ae8ac0a0d02473044022063e8ea69549f3fd85893963de3b64c6d8ada62a9add0306d6530c6749c32c0c002206698636cb4dcc5b6f898f97912b3ee83df4f239ffd5204138205a50244f62d630121031f40841d74b92963eb641a17be3ad9a9e244b88556e08d09fdb50531bd9ea8ca02473044022025090f5b98ae9b421db3aca367fee452f9c18e070446c2d4b88ff6b072408ea20220369d91093ff6be68cf1ae1304b0916cf830f9400358c2d1131b80dc88a26a8c7012102fb513f05ac31baeeec03eeb36b75e2724cb471c2585b25b76d5f26ffbfebdeca00000000").unwrap()).unwrap();
420
421        assert!(input_public_key(&prev.output[0].script_pubkey, &tx.input[0],).is_some());
422    }
423
424    #[test]
425    fn extract_pk_from_tr() {
426        let tx = deserialize::<Transaction>(&Vec::from_hex("020000000001011df72c869516a0c8addb12baa07eddce864d259a2455f87b3547a8117fc2196b0700000000fdffffff02cd50000000000000225120d0b9435a1c22f3d999f9af68ab510a9918ba34bf64be395ae5f605552fd5775cf16c0d0000000000225120c11979449d56f6066c0ffd622ca89213fbacc2f742653063cb120a24d7f2c1080140e6d3e4f8a3b74dea6597638411725128027fd352f260c42f44f07eaaf67aa481552b174df077ed33ffb85c48a4218bbc175b8c1a85c00647ad30fdbe222b8ee700000000").unwrap()).unwrap();
427
428        let prev = deserialize::<Transaction>(&Vec::from_hex("02000000000103d1f0417afbbbda5c5c6564bb456afb9fea3562d5b58547670ecb37f1dcfb91070100000000fdffffff4195e92c1d973266632b34e1ea025b1a88f57e13724b7100fefab1139f0a73bb0100000000fdffffffe157c6fe95807a2984c5f5975d7a5cb7bcca2057dd3fdbcabb6b3ea7ea51121d0100000000fdffffff0b0c9d4c0000000000160014c636fe9763effbdb212c74d9f8cf1e4e5c49f5bf1cf10b000000000016001459a449d01b453a02dd8f4e571593a4647640da3277700500000000001600143273c760e037bfbb56c0e65084c840febb1dafe560ea000000000000160014ed56e3e6bd0150f442803e2aadcfcefab51b36fb60ea000000000000160014285cf327a6623d590d0766175eac6cf4eebaf418c2970a0000000000160014cfd32ca45248f758ef6476c134a47aa00323328ef3b203000000000016001420f3c4a2f1502ae4237e15676984c0f933bd38aae0570e0000000000225120c11979449d56f6066c0ffd622ca89213fbacc2f742653063cb120a24d7f2c108fc81450000000000160014ab2159e5c0d88335623d18a02e2c94d4e3d332fa724b070000000000160014200d178390e34bd6b7bb283542387a2779493cd4a54abb5d00000000160014f60834ef165253c571b11ce9fa74e46692fc5ec10247304402205dde094af4fbbfca450686f75781f826cdfcdc46ebf2a993a1a7f871f49bd00e02205d8bb8d7549de0bc9e40f8db08a4013ab711a365c41c01490149369c0b18e60e0121026e5628506ecd33242e5ceb5fdafe4d3066b5c0f159b3c05a621ef65f177ea28602483045022100986016daece841e3f9c7a34f53bc12771dcc8a3c439b574c72bdc6568493570602205842ff775464a4966b11c65738662fa579098ad3aa8421fffa01243780386dbc0121026e5628506ecd33242e5ceb5fdafe4d3066b5c0f159b3c05a621ef65f177ea28602473044022017c3e474666b02b38fb7bffd7cb856e2c3332834fd9d1906757164175e85b6ce022005f04e95c380e005ad8c1d101ccb28d7714aa0bdc23e40dc7f93a6a10e18570b0121026e5628506ecd33242e5ceb5fdafe4d3066b5c0f159b3c05a621ef65f177ea28600000000").unwrap()).unwrap();
429
430        assert!(input_public_key(&prev.output[7].script_pubkey, &tx.input[0],).is_some());
431    }
432
433    #[test]
434    fn extract_pk_from_sh_wpkh() {
435        let tx = deserialize::<Transaction>(&Vec::from_hex("02000000000103bd1c1428173c1f50f6cce22c3fb64dbadea78b8c11942117a96a492f28be3640020000006a47304402203129d2a271e1786569777a74b7384af7c9617af89f0b6ece0b17402870a3f84702201ee2b3f8b7d788a92ad3205885b3d5dcbd993078484e5fc9fbdf5cd991760f7c012103786af4b32017ec640dba2d2a7e1fd5aa4a231a658e4cbc114d51c031576e19bcfdffffff153968ba0daf73e159223739a071316d5e9f5683262eb0d7c79f28d29cb396da0400000017160014bf0b3a458c7e3103c3c47ae79a0ed87ac4cac0c3fdfffffff6492a91c3e1d804a949b902a3a8c61253532307879e42875a5d2edaca89c23800000000171600149f33de9595d3f35c999afe440e24c4e6aae536a7fdffffff029c0209000000000017a914725545ba42319990559a1fe575801f3ddb01356d87f7c999d5000000001976a914cebb2851a9c7cfe2582c12ecaf7f3ff4383d1dc088ac00024730440220013409fe1d7afc06d6995d05bb7e1b41964d7eebd797b5de2d2ebd636db1bb590220034a23aa52f1d888587164347e9c4d33f97d2608380e31d7ef3f5f96af6b77e4012103f575b6be65db8c23595572c39deac371dcfb295df23d9794a585b98a94d143b10247304402207a51ecb60e0b3cdd4f841f5b1af23f16c7b9410b36716f875e15b535c2979b410220686b12b4e9d9b712a73d03cad75606d17241bc1d5124b6ef211eff08c767d2dd0121025ce070fd89c603855a4e97686a12652c4c17b658380078c5c44c300d5b06c90c00000000").unwrap()).unwrap();
436
437        let prev = deserialize::<Transaction>(&Vec::from_hex("010000000001015a7ba039bbdd1988b4426e928cf489b58889d75cf774010a967eda69a9b78d550000000000ffffffff1d607e10030000000016001468870b46c8214d13f33140dcd1f1962f05db0076a54d9f0100000000160014dc6bf86354105de2fcd9868a2b0376d6731cb92fb395060000000000160014583fef011b214f528b9e255cc0cb7f1764f20eb418ca02000000000017a91473a457d27346a2d0e8eb8a4387084f876a9b042e870a7bbd3b0000000017a914dd55b64160f3ec0d2a82af28d76524d368c124d587a8d801000000000017a91441c35ee5bcd7dd4c818d2556a870fe540ecc4a8987a02e63000000000016001403ee907981a26744ee915ab81254f736ac9792b58c0b0400000000002251206e1e6c0c6574ac2e4c8658481da1521b249932bfcd0bfc8e6b08e8fb56338183b850eb0b0000000017a9147ee44352cdc2cdc3036ae60f9eda0d51289d054c877cec2e0000000000160014d595129a2f0a832dfce5281beccbf824d9ed121c8071040000000000225120b4059360fd17d744417b713e4ada1808be8b73895716f749926073a0df4d91b43825980000000000160014fbf9fc56bfc3c43909ab064e280232cc1cffc7d9e1450500000000002251202ac197b7d66c494f45b01f3d177e88793b8d6c2d3f2a491cd9d56653a8d5c87863b204000000000016001488447d589ada506a048524b63da2cb5d27ecc4a814f426000000000016001487525cfaef66f71fb998ca775678020182f645967e7d08000000000017a9148ae9fa7b9ebdfc01c25b672ace9532cb6bd2e03a873a12300000000000160014158125b466f5c8282a5521203b2c2f0083382d0538370d0000000000160014b494268816787bbfb7dbcf309fee14535b3b82489a406e01000000001600147e715f78f0ecc163c524c9bea186d6905004a17985bc0200000000001976a914e2c2e39dd97c2ec71561263f879336f0d8a3645588ac80fc17e30000000017a914afb5676cc5e35befc0c345fa086e1a14c279cfcd874180290000000000160014092dede65e92bdbf28709efb9f807cc1e482f83cb82d2400000000001600146e08a5d80088f70e5f9be72d9159ed6b979ba7a6a149470100000000160014e8ddcdbad604b250e20009e7c9226e978cbf1210ec14060000000000225120a5777a3d41b7f981f3638cab65b1c2f9bb0555c3f5316d3e47df96995f534dcb41bc20000000000017a914ffdd09e91cfe0f169e14fbfb8a342c0eb420921587781b6c05000000001976a9141ac27e7b667a3195f2cac8a7f32438e1f5aadca588acb5e20f0000000000160014c95d561e7c8a7c60bffa80b800f5493b4dea26e618541b000000000017a914edc9a07756488398aa0264849a5d4a334b6142b58702483045022100b98a8716490de6a03d3f21b757fec8003a34d8c2d584c1ddcdcb72944fb33c29022075679082bda1e7d307d393014416248e60748fb46227c9a77a22df15204834cb012102174ee672429ff94304321cdae1fc1e487edf658b34bd1d36da03761658a2bb0900000000").unwrap()).unwrap();
438
439        assert!(input_public_key(&prev.output[4].script_pubkey, &tx.input[1],).is_some());
440    }
441}