Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
337 changes: 337 additions & 0 deletions src/bip322.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
// BIP322 Generic Signature Algorithm
// Written in 2019 by
// Rajarshi Maitra <[email protected]>]
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//

//! # BIP322 Generic Signed Message Structure
//!
//! This module implements the BIP322 Generic Message Signer and Validator
//!
//! `https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki`
//!

use crate::{Descriptor, DescriptorTrait, MiniscriptKey, ToPublicKey};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot use crate:: because that will cause rustc 1.29 to fail.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using super:: instead.

use bitcoin::blockdata::{opcodes, script::Builder};
use bitcoin::hashes::{
borrow_slice_impl, hex_fmt_impl, index_impl, serde_impl, sha256t_hash_newtype, Hash,
};
use bitcoin::secp256k1::{Secp256k1, Signature};
use bitcoin::{OutPoint, PublicKey, SigHashType, Transaction, TxIn, TxOut};

use crate::interpreter::{Error as InterpreterError, Interpreter};
use std::convert::From;

// BIP322 message tag = sha256("BIP0322-signed-message")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be BIP340 tagged hash. From the description, it appears to be just a regular hash

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating the description.

static MIDSTATE: [u8; 32] = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use const instead of static.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if we have a test similar to https://github.com/rust-bitcoin/rust-bitcoin/pull/259/files to verify the value is computed correctly

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done..

116, 101, 132, 161, 135, 47, 161, 0, 65, 85, 78, 255, 160, 56, 214, 18, 73, 66, 221, 121, 180,
229, 138, 76, 218, 24, 78, 19, 219, 230, 44, 73,
];

// BIP322 Tagged Hash
sha256t_hash_newtype!(
MessageHash,
MessageTag,
MIDSTATE,
64,
doc = "test hash",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more expressive doc comment

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done..

true
);

/// BIP322 Error types
#[derive(Debug)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to derive all possible things that we can derive.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's not much to drive here as InterpreterError only derives debug.

#[derive(Debug)]
pub enum Error {

pub enum BIP322Error {
/// BIP322 Internal Error
InternalError(String),

/// Signature Validation Error
ValidationError(InterpreterError),
}

#[doc(hidden)]
impl From<InterpreterError> for BIP322Error {
fn from(e: InterpreterError) -> BIP322Error {
BIP322Error::ValidationError(e)
}
}

/// Bip322 Signatures
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Bip322Signature {
/// Legacy style. Only applicable for P2PKH message_challenge
Legacy(Signature, PublicKey),

/// Simple witness structure
Simple(Vec<Vec<u8>>),

/// Full `to_sign` transaction structure
Full(Transaction),
}

/// BIP322 validator structure
/// A standard for interoperable signed messages based on the Bitcoin Script format,
/// either for proving fund availability, or committing to a message as the intended
/// recipient of funds sent to the invoice address.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Bip322<T: MiniscriptKey + ToPublicKey> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throughout the codebase, we use Pk as a generic for ToPublicKey + MiniscriptKey. It would be good if we stick to it for code readability for regular reviewers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done..

/// Message to be signed
message: Vec<u8>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Musing over String vs Vec. Indifferent over how we internally represent it, but we should have an API that accepts a String message.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. API should use String only. Changing accordingly.


/// Signature to verify the message
/// Optional value is used here because a validator structure can be
/// created without a BIP322Signature. Such structure can only produce
/// to_spend (or empty to_sign) transaction, but cannot validate them.
signature: Option<Bip322Signature>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better if we have two structs BIP322Signer and BIP322Validator without any optional fields.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes agreed. We don't need the optional signature. I used it to make my test workflow easier. Without it we just have to make to_spen and to_sign transactions for testing by hand. Updated.

For the Bip322Signer structure, I am leaving an empty structure for now, we can fix it once we are done with validation.


/// script_pubkey to define the challenge script inside to_spend transaction
/// here we take in descriptors to derive the resulting script_pubkey
message_challenge: Descriptor<T>,
}

impl<T: MiniscriptKey + ToPublicKey> Bip322<T> {
/// Create a new BIP322 validator
pub fn new(msg: &[u8], sig: Option<Bip322Signature>, addr: Descriptor<T>) -> Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that the addr variable for the descriptor. See detailed comment later for the suggestion of BIP322Validator struct

Copy link
Author

@rajarshimaitra rajarshimaitra Mar 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming addr to mesage_challenge.

Message challenge can be two things. Either descriptor or a Script structure. Throughout the logic currently, we are simply using descriptor.script_pubkey() to derive the message challenge script. We can as well use a script_pubkey directly here as no descriptor specific functionality is used anywhere. But I think it's better to keep the descriptor here to make the API easier for complex challenge scripts. Otherwise, lib users need to construct the script. Also, we can use the descriptor in the future if such functionality is required.

Will update if that's not acceptable for other reasons.

Bip322 {
message: msg.to_vec(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should consume the message instead of the new allocation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done..

signature: sig,
message_challenge: addr,
}
}

/// Insert Signature inside BIP322 structure
pub fn insert_sig(&mut self, sig: Bip322Signature) {
self.signature = Some(sig)
}

/// create the to_spend transaction
pub fn to_spend(&self) -> Transaction {
// create default input and output
let mut vin = TxIn::default();
let mut vout = TxOut::default();

// calculate the message tagged hash
let msg_hash = MessageHash::hash(&self.message[..]).into_inner();

// mutate the input with appropriate script_sig and sequence
vin.script_sig = Builder::new()
.push_int(0)
.push_slice(&msg_hash[..])
.into_script();
vin.sequence = 0;

// mutate the value and script_pubkey as appropriate
vout.value = 0;
vout.script_pubkey = self.message_challenge.script_pubkey();

// create and return final transaction
Transaction {
version: 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made a comment to BIP322 to make this always 2 and it was positively received. I think we should keep it always 2.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done..

lock_time: 0,
input: vec![vin],
output: vec![vout],
}
}

/// Create to_sign transaction
/// This will create a transaction structure with empty signature and witness field
/// its up to the user of the library to fill the Tx with appropriate signature and witness
pub fn to_sign(&self) -> Transaction {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should take a satisfier as an argument and should call satisfy to fill the witness and script sig.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also take in the age and height as arguments instead of hard coded zeeros

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing to_sign to empty_to_sign.

The purpose of this function is to create an empty to_sign transaction template, which then can be filled with the correct signature depending upon the BIP322Signature types. Which is currently happening inside the validate() function here.

Bip322Signature::Full(to_sign) => self.tx_validation(to_sign),
// If Simple Signature is provided, the resulting `to_sign` Tx will be computed
Bip322Signature::Simple(witness) => {
// create empty to_sign transaction
let mut to_sign = self.to_sign();
to_sign.input[0].witness = witness.to_owned();
self.tx_validation(&to_sign)
}
// Legacy Signature can only be used to validate against P2PKH message_challenge
Bip322Signature::Legacy(sig, pubkey) => {
if !self.message_challenge.script_pubkey().is_p2pkh() {
return Err(BIP322Error::InternalError("Legacy style signature is only applicable for P2PKH message_challenge".to_string()));
} else {
let mut sig_ser = sig.serialize_der()[..].to_vec();
// By default SigHashType is ALL
sig_ser.push(SigHashType::All as u8);
let script_sig = Builder::new()
.push_slice(&sig_ser[..])
.push_key(&pubkey)
.into_script();
let mut to_sign = self.to_sign();
to_sign.input[0].script_sig = script_sig;
self.tx_validation(&to_sign)

Here we are mutating the empty to_sign transaction with corresponding signatures. Which can be done with a satisfier too. But because a Bip322Signature::Full type signature will contain the entire to_sign transaction itself (which I think will be most of the cases, the other two cases are rather simplistic and more specific so can be hardcoded easily), using a separate satisfier to create the same to_sign feels like more roundabout than necessary.

Bip322Signer probably needs to use a satisfier in some way, but we can flesh that detail out later.

For age and height, I think it's better to provide these values in the validator structure itself. Though I am not sure if I have applied these values correctly in the empty_to_spend creation, please check.

// create the appropriate input
let outpoint = OutPoint::new(self.to_spend().txid(), 0);
let mut input = TxIn::default();
input.previous_output = outpoint;
input.sequence = 0;

// create the output
let output = TxOut {
value: 0,
script_pubkey: Builder::new()
.push_opcode(opcodes::all::OP_RETURN)
.into_script(),
};

// return resulting transaction
Transaction {
version: 0,
lock_time: 0,
input: vec![input],
output: vec![output],
}
}

/// Validate a BIP322 Signature against the message and challenge script
/// This will require a BIP322Signature inside the structure
pub fn validate(&self) -> Result<bool, BIP322Error> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should reproduce the errors states from the BIP. This should return Result<(T, S), BIP322Error> where BIP322Error should either be Inconclusive(..) or Invalid(..)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to somehow derive these error types from the IterpreterErrors?

Also if I understand it right, neither to_spend nor to_sign transactions are supposed to be mined. They are not validated against the chain. So what the purpose of T and S here and how the validator is supposed to validate Time and Age of an unmined transaction? Also, what does it mean to have a signature against a message only valid "after x time"? These are not transactions, but simple generic messages.

match &self.signature {
None => Err(BIP322Error::InternalError(
"Signature required for validation".to_string(),
)),
Some(sig) => {
match sig {
// A Full signature can be validated directly against the `to_sign` transaction
Bip322Signature::Full(to_sign) => self.tx_validation(to_sign),

// If Simple Signature is provided, the resulting `to_sign` Tx will be computed
Bip322Signature::Simple(witness) => {
// create empty to_sign transaction
let mut to_sign = self.to_sign();

to_sign.input[0].witness = witness.to_owned();

self.tx_validation(&to_sign)
}

// Legacy Signature can only be used to validate against P2PKH message_challenge
Bip322Signature::Legacy(sig, pubkey) => {
if !self.message_challenge.script_pubkey().is_p2pkh() {
return Err(BIP322Error::InternalError("Legacy style signature is only applicable for P2PKH message_challenge".to_string()));
} else {
let mut sig_ser = sig.serialize_der()[..].to_vec();

// By default SigHashType is ALL
sig_ser.push(SigHashType::All as u8);

let script_sig = Builder::new()
.push_slice(&sig_ser[..])
.push_key(&pubkey)
.into_script();

let mut to_sign = self.to_sign();

to_sign.input[0].script_sig = script_sig;

self.tx_validation(&to_sign)
}
}
}
}
}
}

// Internal helper function to perform transaction validation
fn tx_validation(&self, to_sign: &Transaction) -> Result<bool, BIP322Error> {
let secp = Secp256k1::new();

// create an Interpreter to validate to_spend transaction
let mut interpreter = Interpreter::from_txdata(
&self.message_challenge.script_pubkey(),
&to_sign.input[0].script_sig,
&to_sign.input[0].witness,
0,
0,
)?;

// create the signature verification function
let vfyfn = interpreter.sighash_verify(&secp, &to_sign, 0, 0);

let mut result = false;

for elem in interpreter.iter(vfyfn) {
match elem {
Ok(_) => result = true,
Err(e) => return Err(BIP322Error::ValidationError(e)),
}
}

Ok(result)
}
}

#[cfg(test)]
mod test {
use super::*;
use bitcoin::secp256k1::{Message, Secp256k1};
use bitcoin::util::bip143;
use bitcoin::PrivateKey;
use bitcoin::SigHashType;

#[test]
fn test_bip322_validation() {
// Create key pairs and secp context
let sk =
PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();

let ctx = Secp256k1::new();
let pk = sk.public_key(&ctx);

// wpkh descriptor from pubkey
let desc = Descriptor::new_wpkh(pk).unwrap();

// Corresponding p2pkh script. used for sighash calculation
let p2pkh_script = bitcoin::Script::new_p2pkh(&pk.pubkey_hash());

// Create BIP322 structures with empty signature
let mut bip322_1 = Bip322 {
message: b"Hello World".to_vec(),
message_challenge: desc.clone(),
signature: None,
};
let mut bip322_2 = bip322_1.clone();
let mut bip322_3 = bip322_1.clone();

// --------------------------------------------------------------
// Check BIP322Signature::FUll

// Generate to_sign transaction
let mut to_sign = bip322_1.to_sign();

// Generate witness for above wpkh pubkey
let mut sighash_cache = bip143::SigHashCache::new(&to_sign);
let message = sighash_cache.signature_hash(0, &p2pkh_script, 0, SigHashType::All.into());
let message = Message::from_slice(&message[..]).unwrap();

let signature = ctx.sign(&message, &sk.key);
let der = signature.serialize_der();
let mut sig_with_hash = der[..].to_vec();
sig_with_hash.push(SigHashType::All as u8);

let witness: Vec<Vec<u8>> = vec![sig_with_hash, pk.to_bytes()];
to_sign.input[0].witness = witness.clone();

// Insert signature inside BIP322 structure
let bip322_signature = Bip322Signature::Full(to_sign);
bip322_1.insert_sig(bip322_signature);

// Check validation
assert_eq!(bip322_1.validate().unwrap(), true);

// ------------------------------------------------------------
// Check Bip322Signature::Simple

// Same structure can be validated with Simple type signature
bip322_2.insert_sig(Bip322Signature::Simple(witness));

assert_eq!(bip322_2.validate().unwrap(), true);

// ------------------------------------------------------------
// Check Bip322Signature::Legacy

let desc = Descriptor::new_pkh(pk);

// Replace previous message_challenge with p2pkh
bip322_3.message_challenge = desc.clone();

// Create empty to_sign
let to_sign = bip322_3.to_sign();

// Compute SigHash and Signature
let message = to_sign.signature_hash(0, &desc.script_pubkey(), SigHashType::All as u32);
let message = Message::from_slice(&message[..]).unwrap();
let signature = ctx.sign(&message, &sk.key);

// Create Bip322Signature::Legacy
let bip322_sig = Bip322Signature::Legacy(signature, pk);
bip322_3.insert_sig(bip322_sig);

// Check validation
assert_eq!(bip322_3.validate().unwrap(), true);
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ extern crate test;
#[macro_use]
mod macros;

pub mod bip322;
pub mod descriptor;
pub mod expression;
pub mod interpreter;
Expand Down