|
| 1 | +// BIP322 Generic Signature Algorithm |
| 2 | +// Written in 2019 by |
| 3 | +// Rajarshi Maitra <[email protected]>] |
| 4 | +// |
| 5 | +// To the extent possible under law, the author(s) have dedicated all |
| 6 | +// copyright and related and neighboring rights to this software to |
| 7 | +// the public domain worldwide. This software is distributed without |
| 8 | +// any warranty. |
| 9 | +// |
| 10 | +// You should have received a copy of the CC0 Public Domain Dedication |
| 11 | +// along with this software. |
| 12 | +// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>. |
| 13 | +// |
| 14 | + |
| 15 | +//! # BIP322 Generic Signed Message Structure |
| 16 | +//! |
| 17 | +//! This module implements the BIP322 Generic Message Signer and Validator |
| 18 | +//! |
| 19 | +//! `https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki` |
| 20 | +//! |
| 21 | +
|
| 22 | +use crate::{Descriptor, DescriptorTrait, MiniscriptKey, ToPublicKey}; |
| 23 | +use bitcoin::blockdata::{opcodes, script::Builder}; |
| 24 | +use bitcoin::hashes::{ |
| 25 | + borrow_slice_impl, hex_fmt_impl, index_impl, serde_impl, sha256t_hash_newtype, Hash, |
| 26 | +}; |
| 27 | +use bitcoin::secp256k1::{Secp256k1, Signature}; |
| 28 | +use bitcoin::{OutPoint, PublicKey, SigHashType, Transaction, TxIn, TxOut}; |
| 29 | + |
| 30 | +use crate::interpreter::{Error as InterpreterError, Interpreter}; |
| 31 | +use std::convert::From; |
| 32 | + |
| 33 | +// BIP322 message tag = sha256("BIP0322-signed-message") |
| 34 | +static MIDSTATE: [u8; 32] = [ |
| 35 | + 116, 101, 132, 161, 135, 47, 161, 0, 65, 85, 78, 255, 160, 56, 214, 18, 73, 66, 221, 121, 180, |
| 36 | + 229, 138, 76, 218, 24, 78, 19, 219, 230, 44, 73, |
| 37 | +]; |
| 38 | + |
| 39 | +// BIP322 Tagged Hash |
| 40 | +sha256t_hash_newtype!( |
| 41 | + MessageHash, |
| 42 | + MessageTag, |
| 43 | + MIDSTATE, |
| 44 | + 64, |
| 45 | + doc = "test hash", |
| 46 | + true |
| 47 | +); |
| 48 | + |
| 49 | +/// BIP322 Error types |
| 50 | +#[derive(Debug)] |
| 51 | +pub enum BIP322Error { |
| 52 | + /// BIP322 Internal Error |
| 53 | + InternalError(String), |
| 54 | + |
| 55 | + /// Signature Validation Error |
| 56 | + ValidationError(InterpreterError), |
| 57 | +} |
| 58 | + |
| 59 | +#[doc(hidden)] |
| 60 | +impl From<InterpreterError> for BIP322Error { |
| 61 | + fn from(e: InterpreterError) -> BIP322Error { |
| 62 | + BIP322Error::ValidationError(e) |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +/// Bip322 Signatures |
| 67 | +#[derive(Debug, Clone, Eq, PartialEq)] |
| 68 | +pub enum Bip322Signature { |
| 69 | + /// Legacy style. Only applicable for P2PKH message_challenge |
| 70 | + Legacy(Signature, PublicKey), |
| 71 | + |
| 72 | + /// Simple witness structure |
| 73 | + Simple(Vec<Vec<u8>>), |
| 74 | + |
| 75 | + /// Full `to_sign` transaction structure |
| 76 | + Full(Transaction), |
| 77 | +} |
| 78 | + |
| 79 | +/// BIP322 validator structure |
| 80 | +/// A standard for interoperable signed messages based on the Bitcoin Script format, |
| 81 | +/// either for proving fund availability, or committing to a message as the intended |
| 82 | +/// recipient of funds sent to the invoice address. |
| 83 | +#[derive(Debug, Clone, Eq, PartialEq)] |
| 84 | +pub struct Bip322<T: MiniscriptKey + ToPublicKey> { |
| 85 | + /// Message to be signed |
| 86 | + message: Vec<u8>, |
| 87 | + |
| 88 | + /// Signature to verify the message |
| 89 | + /// Optional value is used here because a validator structure can be |
| 90 | + /// created without a BIP322Signature. Such structure can only produce |
| 91 | + /// to_spend (or empty to_sign) transaction, but cannot validate them. |
| 92 | + signature: Option<Bip322Signature>, |
| 93 | + |
| 94 | + /// script_pubkey to define the challenge script inside to_spend transaction |
| 95 | + /// here we take in descriptors to derive the resulting script_pubkey |
| 96 | + message_challenge: Descriptor<T>, |
| 97 | +} |
| 98 | + |
| 99 | +impl<T: MiniscriptKey + ToPublicKey> Bip322<T> { |
| 100 | + /// Create a new BIP322 validator |
| 101 | + pub fn new(msg: &[u8], sig: Option<Bip322Signature>, addr: Descriptor<T>) -> Self { |
| 102 | + Bip322 { |
| 103 | + message: msg.to_vec(), |
| 104 | + signature: sig, |
| 105 | + message_challenge: addr, |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + /// Insert Signature inside BIP322 structure |
| 110 | + pub fn insert_sig(&mut self, sig: Bip322Signature) { |
| 111 | + self.signature = Some(sig) |
| 112 | + } |
| 113 | + |
| 114 | + /// create the to_spend transaction |
| 115 | + pub fn to_spend(&self) -> Transaction { |
| 116 | + // create default input and output |
| 117 | + let mut vin = TxIn::default(); |
| 118 | + let mut vout = TxOut::default(); |
| 119 | + |
| 120 | + // calculate the message tagged hash |
| 121 | + let msg_hash = MessageHash::hash(&self.message[..]).into_inner(); |
| 122 | + |
| 123 | + // mutate the input with appropriate script_sig and sequence |
| 124 | + vin.script_sig = Builder::new() |
| 125 | + .push_int(0) |
| 126 | + .push_slice(&msg_hash[..]) |
| 127 | + .into_script(); |
| 128 | + vin.sequence = 0; |
| 129 | + |
| 130 | + // mutate the value and script_pubkey as appropriate |
| 131 | + vout.value = 0; |
| 132 | + vout.script_pubkey = self.message_challenge.script_pubkey(); |
| 133 | + |
| 134 | + // create and return final transaction |
| 135 | + Transaction { |
| 136 | + version: 0, |
| 137 | + lock_time: 0, |
| 138 | + input: vec![vin], |
| 139 | + output: vec![vout], |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + /// Create to_sign transaction |
| 144 | + /// This will create a transaction structure with empty signature and witness field |
| 145 | + /// its up to the user of the library to fill the Tx with appropriate signature and witness |
| 146 | + pub fn to_sign(&self) -> Transaction { |
| 147 | + // create the appropriate input |
| 148 | + let outpoint = OutPoint::new(self.to_spend().txid(), 0); |
| 149 | + let mut input = TxIn::default(); |
| 150 | + input.previous_output = outpoint; |
| 151 | + input.sequence = 0; |
| 152 | + |
| 153 | + // create the output |
| 154 | + let output = TxOut { |
| 155 | + value: 0, |
| 156 | + script_pubkey: Builder::new() |
| 157 | + .push_opcode(opcodes::all::OP_RETURN) |
| 158 | + .into_script(), |
| 159 | + }; |
| 160 | + |
| 161 | + // return resulting transaction |
| 162 | + Transaction { |
| 163 | + version: 0, |
| 164 | + lock_time: 0, |
| 165 | + input: vec![input], |
| 166 | + output: vec![output], |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + /// Validate a BIP322 Signature against the message and challenge script |
| 171 | + /// This will require a BIP322Signature inside the structure |
| 172 | + pub fn validate(&self) -> Result<bool, BIP322Error> { |
| 173 | + match &self.signature { |
| 174 | + None => Err(BIP322Error::InternalError( |
| 175 | + "Signature required for validation".to_string(), |
| 176 | + )), |
| 177 | + Some(sig) => { |
| 178 | + match sig { |
| 179 | + // A Full signature can be validated directly against the `to_sign` transaction |
| 180 | + Bip322Signature::Full(to_sign) => self.tx_validation(to_sign), |
| 181 | + |
| 182 | + // If Simple Signature is provided, the resulting `to_sign` Tx will be computed |
| 183 | + Bip322Signature::Simple(witness) => { |
| 184 | + // create empty to_sign transaction |
| 185 | + let mut to_sign = self.to_sign(); |
| 186 | + |
| 187 | + to_sign.input[0].witness = witness.to_owned(); |
| 188 | + |
| 189 | + self.tx_validation(&to_sign) |
| 190 | + } |
| 191 | + |
| 192 | + // Legacy Signature can only be used to validate against P2PKH message_challenge |
| 193 | + Bip322Signature::Legacy(sig, pubkey) => { |
| 194 | + if !self.message_challenge.script_pubkey().is_p2pkh() { |
| 195 | + return Err(BIP322Error::InternalError("Legacy style signature is only applicable for P2PKH message_challenge".to_string())); |
| 196 | + } else { |
| 197 | + let mut sig_ser = sig.serialize_der()[..].to_vec(); |
| 198 | + |
| 199 | + // By default SigHashType is ALL |
| 200 | + sig_ser.push(SigHashType::All as u8); |
| 201 | + |
| 202 | + let script_sig = Builder::new() |
| 203 | + .push_slice(&sig_ser[..]) |
| 204 | + .push_key(&pubkey) |
| 205 | + .into_script(); |
| 206 | + |
| 207 | + let mut to_sign = self.to_sign(); |
| 208 | + |
| 209 | + to_sign.input[0].script_sig = script_sig; |
| 210 | + |
| 211 | + self.tx_validation(&to_sign) |
| 212 | + } |
| 213 | + } |
| 214 | + } |
| 215 | + } |
| 216 | + } |
| 217 | + } |
| 218 | + |
| 219 | + // Internal helper function to perform transaction validation |
| 220 | + fn tx_validation(&self, to_sign: &Transaction) -> Result<bool, BIP322Error> { |
| 221 | + let secp = Secp256k1::new(); |
| 222 | + |
| 223 | + // create an Interpreter to validate to_spend transaction |
| 224 | + let mut interpreter = Interpreter::from_txdata( |
| 225 | + &self.message_challenge.script_pubkey(), |
| 226 | + &to_sign.input[0].script_sig, |
| 227 | + &to_sign.input[0].witness, |
| 228 | + 0, |
| 229 | + 0, |
| 230 | + )?; |
| 231 | + |
| 232 | + // create the signature verification function |
| 233 | + let vfyfn = interpreter.sighash_verify(&secp, &to_sign, 0, 0); |
| 234 | + |
| 235 | + let mut result = false; |
| 236 | + |
| 237 | + for elem in interpreter.iter(vfyfn) { |
| 238 | + match elem { |
| 239 | + Ok(_) => result = true, |
| 240 | + Err(e) => return Err(BIP322Error::ValidationError(e)), |
| 241 | + } |
| 242 | + } |
| 243 | + |
| 244 | + Ok(result) |
| 245 | + } |
| 246 | +} |
| 247 | + |
| 248 | +#[cfg(test)] |
| 249 | +mod test { |
| 250 | + use super::*; |
| 251 | + use bitcoin::secp256k1::{Message, Secp256k1}; |
| 252 | + use bitcoin::util::bip143; |
| 253 | + use bitcoin::PrivateKey; |
| 254 | + use bitcoin::SigHashType; |
| 255 | + |
| 256 | + #[test] |
| 257 | + fn test_bip322_validation() { |
| 258 | + // Create key pairs and secp context |
| 259 | + let sk = |
| 260 | + PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap(); |
| 261 | + |
| 262 | + let ctx = Secp256k1::new(); |
| 263 | + let pk = sk.public_key(&ctx); |
| 264 | + |
| 265 | + // wpkh descriptor from pubkey |
| 266 | + let desc = Descriptor::new_wpkh(pk).unwrap(); |
| 267 | + |
| 268 | + // Corresponding p2pkh script. used for sighash calculation |
| 269 | + let p2pkh_script = bitcoin::Script::new_p2pkh(&pk.pubkey_hash()); |
| 270 | + |
| 271 | + // Create BIP322 structures with empty signature |
| 272 | + let mut bip322_1 = Bip322 { |
| 273 | + message: b"Hello World".to_vec(), |
| 274 | + message_challenge: desc.clone(), |
| 275 | + signature: None, |
| 276 | + }; |
| 277 | + let mut bip322_2 = bip322_1.clone(); |
| 278 | + let mut bip322_3 = bip322_1.clone(); |
| 279 | + |
| 280 | + // -------------------------------------------------------------- |
| 281 | + // Check BIP322Signature::FUll |
| 282 | + |
| 283 | + // Generate to_sign transaction |
| 284 | + let mut to_sign = bip322_1.to_sign(); |
| 285 | + |
| 286 | + // Generate witness for above wpkh pubkey |
| 287 | + let mut sighash_cache = bip143::SigHashCache::new(&to_sign); |
| 288 | + let message = sighash_cache.signature_hash(0, &p2pkh_script, 0, SigHashType::All.into()); |
| 289 | + let message = Message::from_slice(&message[..]).unwrap(); |
| 290 | + |
| 291 | + let signature = ctx.sign(&message, &sk.key); |
| 292 | + let der = signature.serialize_der(); |
| 293 | + let mut sig_with_hash = der[..].to_vec(); |
| 294 | + sig_with_hash.push(SigHashType::All as u8); |
| 295 | + |
| 296 | + let witness: Vec<Vec<u8>> = vec![sig_with_hash, pk.to_bytes()]; |
| 297 | + to_sign.input[0].witness = witness.clone(); |
| 298 | + |
| 299 | + // Insert signature inside BIP322 structure |
| 300 | + let bip322_signature = Bip322Signature::Full(to_sign); |
| 301 | + bip322_1.insert_sig(bip322_signature); |
| 302 | + |
| 303 | + // Check validation |
| 304 | + assert_eq!(bip322_1.validate().unwrap(), true); |
| 305 | + |
| 306 | + // ------------------------------------------------------------ |
| 307 | + // Check Bip322Signature::Simple |
| 308 | + |
| 309 | + // Same structure can be validated with Simple type signature |
| 310 | + bip322_2.insert_sig(Bip322Signature::Simple(witness)); |
| 311 | + |
| 312 | + assert_eq!(bip322_2.validate().unwrap(), true); |
| 313 | + |
| 314 | + // ------------------------------------------------------------ |
| 315 | + // Check Bip322Signature::Legacy |
| 316 | + |
| 317 | + let desc = Descriptor::new_pkh(pk); |
| 318 | + |
| 319 | + // Replace previous message_challenge with p2pkh |
| 320 | + bip322_3.message_challenge = desc.clone(); |
| 321 | + |
| 322 | + // Create empty to_sign |
| 323 | + let to_sign = bip322_3.to_sign(); |
| 324 | + |
| 325 | + // Compute SigHash and Signature |
| 326 | + let message = to_sign.signature_hash(0, &desc.script_pubkey(), SigHashType::All as u32); |
| 327 | + let message = Message::from_slice(&message[..]).unwrap(); |
| 328 | + let signature = ctx.sign(&message, &sk.key); |
| 329 | + |
| 330 | + // Create Bip322Signature::Legacy |
| 331 | + let bip322_sig = Bip322Signature::Legacy(signature, pk); |
| 332 | + bip322_3.insert_sig(bip322_sig); |
| 333 | + |
| 334 | + // Check validation |
| 335 | + assert_eq!(bip322_3.validate().unwrap(), true); |
| 336 | + } |
| 337 | +} |
0 commit comments