Skip to main content

btc_transaction_utils/
p2wpk.rs

1// Copyright 2018 The Exonum Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A native `P2WPK` input signer.
16
17use bitcoin::{
18    blockdata::{
19        script::{Builder, Script},
20        transaction::TxIn,
21    },
22    network::constants::Network,
23    util::{address::Address, key::PublicKey, psbt::serialize::Serialize},
24};
25use secp256k1::{self, All, Secp256k1, SecretKey};
26
27use crate::{
28    sign, Hash, Hash160, InputSignature, InputSignatureRef, Sha256dHash, TxInRef, UnspentTxOutValue,
29};
30
31/// Creates a bitcoin address for the corresponding public key and the bitcoin network.
32pub fn address(pk: &PublicKey, network: Network) -> Address {
33    Address::p2wpkh(pk, network)
34}
35
36/// Creates a script pubkey for the corresponding public key.
37pub fn script_pubkey(pk: &PublicKey) -> Script {
38    let witness_version = 0;
39    let pk_hash = Hash160::hash(&pk.key.serialize()).into_inner().to_vec();
40    Builder::new()
41        .push_int(witness_version)
42        .push_slice(&pk_hash)
43        .into_script()
44}
45
46/// An input signer.
47#[derive(Debug)]
48pub struct InputSigner {
49    context: Secp256k1<All>,
50    public_key: PublicKey,
51    network: Network,
52}
53
54impl InputSigner {
55    /// Creates an input signer for the given public key and network.
56    pub fn new(public_key: PublicKey, network: Network) -> InputSigner {
57        InputSigner {
58            context: Secp256k1::new(),
59            public_key,
60            network,
61        }
62    }
63
64    /// Returns a reference to the secp256k1 engine, used to execute all signature operations.
65    pub fn secp256k1_context(&self) -> &Secp256k1<All> {
66        &self.context
67    }
68
69    /// Returns a mutable reference to the secp256k1 engine, used to execute all signature operations.
70    pub fn secp256k1_context_mut(&mut self) -> &mut Secp256k1<All> {
71        &mut self.context
72    }
73
74    /// Computes the [`BIP-143`][bip-143] compliant sighash for a [`SIGHASH_ALL`][sighash_all]
75    /// signature for the given input.
76    ///
77    /// [bip-143]: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
78    /// [sighash_all]: https://bitcoin.org/en/developer-guide#signature-hash-types
79    pub fn signature_hash<'a, 'b, V: Into<UnspentTxOutValue<'b>>>(
80        &mut self,
81        txin: TxInRef<'a>,
82        value: V,
83    ) -> Sha256dHash {
84        sign::signature_hash(txin, &self.witness_script(), value)
85    }
86
87    /// Computes the [`BIP-143`][bip-143] compliant signature for the given input.
88    /// Under the hood this method signs [`sighash`][signature-hash] for the given input with the
89    /// given secret key.
90    ///
91    /// [bip-143]: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
92    /// [signature-hash]: struct.InputSigner.html#signature_hash
93    pub fn sign_input<'a, 'b, V: Into<UnspentTxOutValue<'b>>>(
94        &mut self,
95        txin: TxInRef<'a>,
96        value: V,
97        secret_key: &SecretKey,
98    ) -> Result<InputSignature, secp256k1::Error> {
99        let script = self.witness_script();
100        sign::sign_input(&mut self.context, txin, &script, value, secret_key)
101    }
102
103    /// Checks correctness of the signature for the given input.
104    pub fn verify_input<'a, 'b, 'c, V, S>(
105        &self,
106        txin: TxInRef<'a>,
107        value: V,
108        public_key: &PublicKey,
109        signature: S,
110    ) -> Result<(), secp256k1::Error>
111    where
112        V: Into<UnspentTxOutValue<'b>>,
113        S: Into<InputSignatureRef<'c>>,
114    {
115        sign::verify_input_signature(
116            &self.context,
117            txin,
118            &self.witness_script(),
119            value,
120            public_key,
121            signature.into().content(),
122        )
123    }
124
125    /// Collects the witness data for the given transaction input. Thus, the input becomes spent.
126    pub fn spend_input(&self, input: &mut TxIn, signature: InputSignature) {
127        input.witness = self.witness_data(signature.into());
128    }
129
130    fn witness_data(&self, signature: Vec<u8>) -> Vec<Vec<u8>> {
131        vec![signature, self.public_key.serialize().to_vec()]
132    }
133
134    fn witness_script(&self) -> Script {
135        Address::p2pkh(&self.public_key, self.network).script_pubkey()
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use bitcoin::{
142        blockdata::opcodes::all::OP_RETURN,
143        blockdata::script::{Builder, Script},
144        blockdata::transaction::{OutPoint, Transaction, TxIn, TxOut},
145        network::constants::Network,
146    };
147
148    use crate::{
149        p2wpk,
150        test_data::{btc_tx_from_hex, keypair_from_wif},
151        TxInRef,
152    };
153
154    #[test]
155    fn test_native_segwit() {
156        let (pk, sk) = keypair_from_wif("cPHmynxvqfr7sXsJcohiGzoPGBShggxL6VWUdW14skohFZ1LQoeV");
157
158        let prev_tx = btc_tx_from_hex(
159            "02000000000101beccab33bc72bfc81b63fdec8a4a9a4719e4418bdb7b20e47b02074dc42f2d800000000\
160             017160014f3b1b3819c1290cd5d675c1319dc7d9d98d571bcfeffffff02dceffa0200000000160014368c\
161             6b7c38f0ff0839bf78d77544da96cb685bf28096980000000000160014284175e336fa10865fb4d1351c9\
162             e18e730f5d6f90247304402207c893c85d75e2230dde04f5a1e2c83c4f0b7d93213372746eb2227b06826\
163             0d840220705484b6ec70a8fc0d1f80c3a98079602595351b7a9bca7caddb9a6adb0a3440012103150514f\
164             05f3e3f40c7b404b16f8a09c2c71bad3ba8da5dd1e411a7069cc080a004b91300",
165        );
166        assert_eq!(prev_tx.output[1].script_pubkey, p2wpk::script_pubkey(&pk));
167
168        // Unsigned transaction.
169        let mut transaction = Transaction {
170            version: 2,
171            lock_time: 0,
172            input: vec![TxIn {
173                previous_output: OutPoint {
174                    txid: prev_tx.txid(),
175                    vout: 1,
176                },
177                script_sig: Script::default(),
178                sequence: 0xFFFF_FFFF,
179                witness: Vec::default(),
180            }],
181            output: vec![TxOut {
182                value: 0,
183                script_pubkey: Builder::new()
184                    .push_opcode(OP_RETURN)
185                    .push_slice(b"Hello Exonum!")
186                    .into_script(),
187            }],
188        };
189        // Makes signature.
190        let mut signer = p2wpk::InputSigner::new(pk, Network::Testnet);
191        let signature = signer
192            .sign_input(TxInRef::new(&transaction, 0), &prev_tx, &sk.key)
193            .unwrap();
194        // Verifies signature.
195        signer
196            .verify_input(TxInRef::new(&transaction, 0), &prev_tx, &pk, &signature)
197            .expect("Signature should be correct");
198        // Signs transaction.
199        signer.spend_input(&mut transaction.input[0], signature);
200        // Checks output.
201        let expected_tx = btc_tx_from_hex(
202            "0200000000010145f4a039a4bd6cc753ec02a22498b98427c6c288244340fff9d2abb5c63e48390100000\
203             000ffffffff0100000000000000000f6a0d48656c6c6f2045786f6e756d2102483045022100bdc1be9286\
204             2281061a14f7153dd57b7b3befa2b98fe85ae5d427d3921fe165ca02202f259a63f965f6d7f0503584b46\
205             3ce4b67c09b5a2e99c27f236f7a986743a94a0121031cf96b4fef362af7d86ee6c7159fa89485730dac8e\
206             3090163dd0c282dbc84f2200000000",
207        );
208        assert_eq!(transaction, expected_tx);
209    }
210}