Skip to content

Commit c740598

Browse files
BIP322 Implementation
This PR implements a BIP322 generic message signer with Bitcoin Scripts.
1 parent 86a63d8 commit c740598

File tree

2 files changed

+338
-0
lines changed

2 files changed

+338
-0
lines changed

src/bip322.rs

+337
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
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+
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ extern crate test;
108108
#[macro_use]
109109
mod macros;
110110

111+
pub mod bip322;
111112
pub mod descriptor;
112113
pub mod expression;
113114
pub mod interpreter;

0 commit comments

Comments
 (0)