Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] BIP322 Implementation #240

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

rajarshimaitra
Copy link

Ref #159

This is a draft implementation of the BIP322 generic message signer using Bitcoin scripts.

There are a lot of rough edges to iron out. Looking for generic approach comments. The approach might be completely wrong, as it only reflects my own understanding after reading the BIP.

So far the signer can only validate message and message_challenge given the signature (of type Legacy/Simple/Full).

A simple test case for these three types of signature validation included.

Looking for comments to figure out the rest of the required details.

This PR implements a BIP322 generic message signer with Bitcoin Scripts.
Copy link
Member

@sanket1729 sanket1729 left a comment

Choose a reason for hiding this comment

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

Did not review the test. I think we should first focus on the BIP322Validtor struct which should only have msg, sig, and scriipt_pubkey.

Then, we can use the interpreter to automatically infer the descriptor. Look at from_txdata function.

The last step is correct where we interpret the descriptor.

Let us try to fix the above in this iteration.

Next, we can focus on extending to height and age and using satisfy API for signing logic.

src/bip322.rs Outdated Show resolved Hide resolved
src/bip322.rs Outdated
//! `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.

src/bip322.rs Outdated
use std::convert::From;

// BIP322 message tag = sha256("BIP0322-signed-message")
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..

src/bip322.rs Outdated
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..

);

/// 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 {

src/bip322.rs Outdated
/// Create a new BIP322 validator
pub fn new(msg: &[u8], sig: Option<Bip322Signature>, addr: Descriptor<T>) -> Self {
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..

src/bip322.rs Outdated

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

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.


// 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..

src/bip322.rs Outdated
/// 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.


/// 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.

@rajarshimaitra
Copy link
Author

Updated with changes. This is ready for the next review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants