use bitcoin::{
blockdata::script::Script, blockdata::transaction::TxIn, network::constants::Network,
util::address::Address, PublicKey,
};
use secp256k1::{self, All, Secp256k1, SecretKey};
use crate::{
multisig::RedeemScript, sign, InputSignature, InputSignatureRef, Sha256dHash, TxInRef,
UnspentTxOutValue,
};
pub fn address(redeem_script: &RedeemScript, network: Network) -> Address {
Address::p2wsh(&redeem_script.0, network)
}
pub fn script_pubkey(redeem_script: &RedeemScript) -> Script {
redeem_script.0.to_v0_p2wsh()
}
#[derive(Debug)]
pub struct InputSigner {
context: Secp256k1<All>,
script: RedeemScript,
}
impl InputSigner {
pub fn new(script: RedeemScript) -> InputSigner {
InputSigner {
context: Secp256k1::new(),
script,
}
}
pub fn secp256k1_context(&self) -> &Secp256k1<All> {
&self.context
}
pub fn secp256k1_context_mut(&mut self) -> &mut Secp256k1<All> {
&mut self.context
}
pub fn signature_hash<'a, 'b, V: Into<UnspentTxOutValue<'b>>>(
&mut self,
txin: TxInRef<'a>,
value: V,
) -> Sha256dHash {
sign::signature_hash(txin, &self.script.0, value)
}
pub fn sign_input<'a, 'b, V: Into<UnspentTxOutValue<'b>>>(
&mut self,
txin: TxInRef<'a>,
value: V,
secret_key: &SecretKey,
) -> Result<InputSignature, secp256k1::Error> {
sign::sign_input(&mut self.context, txin, &self.script.0, value, secret_key)
}
pub fn verify_input<'a, 'b, 'c, V, S>(
&self,
txin: TxInRef<'a>,
value: V,
public_key: &PublicKey,
signature: S,
) -> Result<(), secp256k1::Error>
where
V: Into<UnspentTxOutValue<'b>>,
S: Into<InputSignatureRef<'c>>,
{
sign::verify_input_signature(
&self.context,
txin,
&self.script.0,
value,
public_key,
signature.into().content(),
)
}
pub fn spend_input<I: IntoIterator<Item = InputSignature>>(
&self,
input: &mut TxIn,
signatures: I,
) {
input.witness = self.witness_data(signatures.into_iter().map(Into::into));
}
fn witness_data<I: IntoIterator<Item = Vec<u8>>>(&self, signatures: I) -> Vec<Vec<u8>> {
let mut witness_stack = vec![Vec::default()];
witness_stack.extend(signatures);
witness_stack.push(self.script.0[..].to_vec());
witness_stack
}
}
#[cfg(test)]
mod tests {
use bitcoin::{
blockdata::opcodes::all::OP_RETURN,
blockdata::script::{Builder, Script},
blockdata::transaction::{OutPoint, Transaction, TxIn, TxOut},
};
use crate::{
multisig::RedeemScriptBuilder,
p2wsh,
test_data::{btc_tx_from_hex, keypair_from_wif},
InputSignatureRef, TxInRef,
};
#[test]
fn test_multisig_native_segwit() {
let keypairs = vec![
"cPHmynxvqfr7sXsJcohiGzoPGBShggxL6VWUdW14skohFZ1LQoeV",
"cTtSTL1stvg2tmK349WTmQDfHLMLqkkxwuo8ZJeQov9zEhtYtb4u",
"cQZZ7WvJUb6hXxCq9SF6516vb9bavQRPn2t3g9LFUtAoZuY7vNFk",
"cQoWXdtbsRP9nu6i8qX8h3qQmM25XDF7XJMBjgnqqr5MqDxmmkjN",
"cUccK257yZdqHDvGxNCqogdA87vz8C1FU1hDjQ35YuCy1mshCF5P",
"cVYXjWXoXbFAP3gWZc2zz5UzQV6Z9CX8myPijDutVPSZbX5bnv6h",
"cPZL4ehXEuB6mNniHrwDMCLKPSVKwwStTLkYg4gTtuUAAEPWyGzT",
"cTuBNbrnxkv55aHvJA4vTbCnqhAcc9vSNeVjwvR7t7255xY6SiHx",
"cTDddY9hBLv2R3twww1J85A8zUdpWjkjmKeTcxt1VuyHN6sv8sSf",
"cUfTuu8CHqrv6PKY8jp1CUrdaAKdqTtjWRP1v3fuXHvVkxoHACtA",
"cMs8EwSJwfQ5DrVqYcDgjKV52k3DrGZhK1MDNrabY16WxPjvACgG",
"cVwwcsdqRGV1cV1HLX1y7ccg2iu7aSHvSVRW3sPZpgZGr6Wzg9VR",
"cNqiotwcBrkLsFMC5wwehvSQ6CcjXu74U4mEeZn6vx3ZLYH2k3QY",
"cSAyWaxS6SwWQ5REE1LuNp1Vqi771JsTFRU1ZisUHkKRiYLg6grq",
"cTzJsaKdspquqTnGqz2iayMsm2r6CTkeC4KtjoZqXUu6TPzGcAoB",
"cRujmDjvBVbmjV4PzY2e9u8C7D5UABxFnyPyVe9XhPCfBTcRM5DX",
"cV8eYCREAdvSgLA5Pv1Q2L8UZwAmU5Gdan3dUUVDDGwnQqT5U9Ki",
"cUAfUuEojwoxkYsBNHjdYjEqKawwSieMGGqvtHjJVQj3Q4EaB8Bd",
]
.into_iter()
.map(keypair_from_wif)
.collect::<Vec<_>>();
let quorum = 12;
let redeem_script = RedeemScriptBuilder::with_public_keys(keypairs.iter().map(|x| x.0))
.quorum(quorum)
.to_script()
.unwrap();
let prev_tx = btc_tx_from_hex(
"02000000000101f8c16000cc59f9505046303944d42a6c264a322f80b46bb436115b6e306ba9950000000\
000feffffff02f07dc81600000000160014f65eb9d72a8475dd8e26f4fa748796e211aa88691027000000\
00000022002001fb25c3db04ca5580da43a7d38dd994650d9aa6d6ee075b4578388deed338ed024730440\
2206b5f211cd7f9b89e80c734b61113c33f437ba153e7ba6bc275eed857e54fcb260220038562e88b805f\
0cdfd4873ab3579d52268babe6af9c49086c00343187cdf28a012103979dff5cd9045f4b6fa454d2bc535\
7586a85d4789123df45f83522963d94e3217fb91300",
);
assert_eq!(
prev_tx.output[1].script_pubkey,
p2wsh::script_pubkey(&redeem_script)
);
let mut transaction = Transaction {
version: 2,
lock_time: 0,
input: vec![TxIn {
previous_output: OutPoint {
txid: prev_tx.txid(),
vout: 1,
},
script_sig: Script::default(),
sequence: 0xFFFF_FFFF,
witness: Vec::default(),
}],
output: vec![TxOut {
value: 0,
script_pubkey: Builder::new()
.push_opcode(OP_RETURN)
.push_slice(b"Hello Exonum with multisig!")
.into_script(),
}],
};
let mut signer = p2wsh::InputSigner::new(redeem_script.clone());
let signatures = keypairs[0..quorum]
.iter()
.map(|keypair| {
let txin = TxInRef::new(&transaction, 0);
let signature = signer.sign_input(txin, &prev_tx, &keypair.1.key).unwrap();
signer
.verify_input(txin, &prev_tx, &keypair.0, &signature)
.unwrap();
signature
})
.collect::<Vec<_>>();
signer.spend_input(&mut transaction.input[0], signatures);
assert_eq!(
transaction,
btc_tx_from_hex(
"02000000000101c4eb8102889b009f55cca8a1a07f3ea388843d6afa4bd77990d2190bb9248f92010\
0000000ffffffff0100000000000000001d6a1b48656c6c6f2045786f6e756d2077697468206d756c\
7469736967210e0047304402200fbebae9eabf9b2c33bb6112a821317e0f676c44aa7814d145bb604\
5bfb77bce022044df3307ce0a1a70d1d0b62432dba42d7e4aeaabe0290f5474509658a1196e330147\
30440220494baff1971f5a9b3ec63015466551a0acc185de8fc44228e3c4726550749abe02203d72b\
5032b8b88d3294b547b7bfe4e82f75d4e8268e64f5751b65ba0cc66efbb01473044022058eee8c7fb\
b81a471e4c71f7722e933297dfeaccc9fa86cac2a3aa7f0a78c17b022050fd3566953cb433471284c\
061285f51a118c51a46d62d92fe7c7d89434dc8f30147304402203c1e6392beda5bd01cf3ce8e4b78\
f6e21687113137a791739f53ba5459171d5302200362ee0d9981797b89133c5ade65f9bcb46af169a\
fea15d303d1b55eabbdedfb01483045022100f4be0af94fb9fa74893439378e836b3e4b81aeb0edd4\
7769d0177c1db6f37fdb02202652bcea0a3e0b2df754354c45d4fe349c5fd44e47e98daec9202d8dd\
2bb89490147304402204238210b1719108ea514ed59d3a56136ddb1cd3c99227156db45aa583881b2\
c3022039cfc5801d9b785ea544651dcf22e39c2ed6f3f4b3f5bda4858236404934285a01473044022\
02741c8bbffb09432276d80fcfafb6e3c294a635fb09f27002084a1086fab19e202203bdaec272515\
9c9d9a7126525345ae51838d0052acdba6b070fba096aa46680e01473044022032894b2a78f9f0cd9\
5543f9d56a233a469ce2448f994bd966ca0ff38b18eac880220101689725199b25946f19eed1437d4\
8f6f6714c062c22ce94a6e222e4fe01b6c01483045022100acee27e70ac1dbd4fb07e25bb3095cba8\
58c2cee8169ac55d4ee8da10a00742b02200b5a6ecdf5d375d561a53862e792136c10ed0287ffc9a8\
de52f45ab1fc3dbf770147304402204f3778dae6b8166bd667a59c8d30b9eb3b8572a2aa3c1197873\
94ce4bbb58ae5022064b4df41b2de281082fde6b2d1a4e25212c10f6011bbb048d1c54ddb91e33dfa\
01473044022060873642a76f8dfc36afcb5bd15a07da341ab0407f880e6a99d9ece22bca825902207\
cfb5ce2fea244355d0c9e589e91f626b1ba90ebfe5eff33a2e8e5706f5f36970147304402201ffe90\
90290e0a1a3cad7bc276a35da219528dd1b82c25b1a9ed190453925a59022004caee4a37ffe322fd5\
e45f25d42c65565be70a326edb38ba6d6f1a60332154e01fd68025c21031cf96b4fef362af7d86ee6\
c7159fa89485730dac8e3090163dd0c282dbc84f2221028839757bba9bdf46ae553c124479e5c3ded\
609495f3e93e88ab23c0f559e8be521035c70ffb21d1b454ec650e511e76f6bd3fe76f49c471522ee\
187abac8d0131a18210234acd7dee22bc23688beed0c7e42c0930cfe024204b7298b0b59d0e76a464\
76521033897e8dd88ee04cb42b69838c3167471880da23944c10eb9f67de2b5ca32a9d121027a715c\
f0aeec55482c1d42bfeb75c8f54348ec8b0ca0f9b535ed50a739b8ad632103a2be0380e248ec36401\
e99680e0fb4f8c03a0a5e00d5dda107aee6cba77b639521038bdb47da82981776e8b0e5d4175f2793\
0339a32e77ee7052ec51a1f2f0a46e88210312c4fb516caeb5eaec8ffdeecd4a507b69d6808651ae0\
2a4a61165cc56bfe55121039e021ca4d7969e5db181e0905b9baab2afe395e84587b588a6b039207c\
911355210259c9f752846c7bd514a042d53ea305f2d4ca7873cb21937dc6b5e82afbb8fb922102c52\
c3dc6e080ea4e74ba2e6797548bd79a692a01baeba1c757a18fd0ef519fb42102f5010ab66dd7a8dc\
06caefeceb9bb7e6e42c5d4afdab527a2f02d87b758920612103efbcec8bcc6ea4e58b44214b14eae\
2677399c28df8bb81fcd120cb4c88ce3bd92103e88aa50f0d7f43cb3171a69675385f130c6abafaca\
dde87fc84d5a194da5ad9c21025ed88603b59882c3ec6ef43c0b33ac9db315ecca8e7073e60d9b561\
45fc0efa02103643277862c4a8ab27913e3d2bcea109b6637c7454a03410aac8ccad445e81a502103\
380785c3e1c105e366ff445227cdde68e6a6461d6793a1437db847ecd04129dc0112ae00000000"
)
);
let public_key = redeem_script.content().public_keys[0];
let signature = &transaction.input[0].witness[1];
let signer = p2wsh::InputSigner::new(redeem_script);
signer
.verify_input(
TxInRef::new(&transaction, 0),
&prev_tx,
&public_key,
InputSignatureRef::from_bytes(signature.as_ref()).unwrap(),
)
.expect("Signature should be correct");
}
}