Skip to content

Commit

Permalink
Merge pull request #12 from monlovesmango/add-checksig
Browse files Browse the repository at this point in the history
Add checksig for vault keypair
  • Loading branch information
rot13maxi authored Sep 27, 2024
2 parents 48b7ee9 + 4d2206c commit 9e41e4d
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 27 deletions.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,6 @@ input amounts add up to the vault output amounts. For that we need either 64-bit
by doing a mini big-num implementation), or you need to have different tapscripts with pre-defined amounts, or you just only use vaults with 32-bit amounts.
I think doing 64-bit add in CAT is the most interesting, but I haven't done it yet.

There are not currently any (normal) signature checks in the vault scripts. There isn't a reason they can't be there. It would actually be very easy:
commit to a pubkey, push a signature in the witness, checksig. I just didn't do it. The point of the demo was to play with transaction introspection.
If you want to add signature checking, I'm more than happy to review the PR!

Right now the vault always spends back to itself when you cancel. In real life you might want to have it go to a different script with different keys.
That would be an easy change to make, but was elided for simplicity in this demo.

Expand Down
75 changes: 60 additions & 15 deletions src/vault/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ use bitcoin::absolute::LockTime;
use bitcoin::consensus::Encodable;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::hex::{Case, DisplayHex};
use bitcoin::key::Secp256k1;
use bitcoin::secp256k1::ThirtyTwoByteHash;
use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo};
use bitcoin::key::{Secp256k1, Keypair};
use bitcoin::secp256k1::{ThirtyTwoByteHash, rand, Message};
use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo, Signature};
use bitcoin::transaction::Version;
use bitcoin::{
Address, Amount, Network, OutPoint, Sequence, TapLeafHash, TapSighashType, Transaction, TxIn,
TxOut, XOnlyPublicKey,
};
use bitcoin::sighash::{SighashCache, Prevouts};
use bitcoincore_rpc::jsonrpc::serde_json;
use log::{debug, info};
use secp256kfun::marker::{EvenY, NonZero, Public};
Expand Down Expand Up @@ -57,10 +58,13 @@ pub(crate) struct VaultCovenant {
withdrawal_address: Option<String>,
trigger_transaction: Option<Transaction>,
state: VaultState,
keypair: Keypair,
}

impl Default for VaultCovenant {
fn default() -> Self {
let secp = Secp256k1::new();
let keypair = Keypair::new(&secp, &mut rand::thread_rng());
Self {
current_outpoint: None,
amount: Amount::ZERO,
Expand All @@ -69,6 +73,7 @@ impl Default for VaultCovenant {
withdrawal_address: None,
trigger_transaction: None,
state: VaultState::Inactive,
keypair,
}
}
}
Expand Down Expand Up @@ -161,13 +166,32 @@ impl VaultCovenant {
let nums_key = XOnlyPublicKey::from_slice(point.to_xonly_bytes().as_slice())?;
let secp = Secp256k1::new();
Ok(TaprootBuilder::new()
.add_leaf(1, vault_trigger_withdrawal())?
.add_leaf(2, vault_complete_withdrawal(self.timelock_in_blocks))?
.add_leaf(2, vault_cancel_withdrawal())?
.add_leaf(1, vault_trigger_withdrawal(self.x_only_public_key()))?
.add_leaf(2, vault_complete_withdrawal(self.x_only_public_key(), self.timelock_in_blocks))?
.add_leaf(2, vault_cancel_withdrawal(self.x_only_public_key()))?
.finalize(&secp, nums_key)
.expect("finalizing taproot spend info with a NUMS point should always work"))
}

fn x_only_public_key(&self) -> XOnlyPublicKey {
return self.keypair.x_only_public_key().0
}

fn sign_transaction(&self, txn: &Transaction, prevouts: &[TxOut], leaf_hash: TapLeafHash) -> Vec<u8> {
let secp = Secp256k1::new();
let mut sighashcache = SighashCache::new(txn);
let sighash = sighashcache.taproot_script_spend_signature_hash(
0,
&Prevouts::All(prevouts),
leaf_hash,
TapSighashType::All
).unwrap();
let message = Message::from_digest_slice(sighash.as_byte_array()).unwrap();
let signature = secp.sign_schnorr(&message, &self.keypair);
let final_sig = Signature { sig: signature, hash_ty: TapSighashType::All };
return final_sig.to_vec()
}

pub(crate) fn create_trigger_tx(
&self,
fee_paying_utxo: &OutPoint,
Expand Down Expand Up @@ -209,7 +233,7 @@ impl VaultCovenant {
};

let leaf_hash =
TapLeafHash::from_script(&vault_trigger_withdrawal(), LeafVersion::TapScript);
TapLeafHash::from_script(&vault_trigger_withdrawal(self.x_only_public_key()), LeafVersion::TapScript);
let vault_txout = TxOut {
script_pubkey: self.address()?.script_pubkey().clone(),
value: self.amount,
Expand Down Expand Up @@ -276,12 +300,19 @@ impl VaultCovenant {
vault_txin.witness.push([computed_signature[63]]); // push the last byte of the signature
vault_txin.witness.push([computed_signature[63] + 1]); // push the last byte of the signature

let sig = self.sign_transaction(
&txn,
&[vault_txout.clone(), fee_paying_output.clone()],
leaf_hash
);
vault_txin.witness.push(sig);

vault_txin
.witness
.push(vault_trigger_withdrawal().to_bytes());
.push(vault_trigger_withdrawal(self.x_only_public_key()).to_bytes());
vault_txin.witness.push(
self.taproot_spend_info()?
.control_block(&(vault_trigger_withdrawal().clone(), LeafVersion::TapScript))
.control_block(&(vault_trigger_withdrawal(self.x_only_public_key()).clone(), LeafVersion::TapScript))
.expect("control block should work")
.serialize(),
);
Expand Down Expand Up @@ -328,7 +359,7 @@ impl VaultCovenant {
};

let leaf_hash = TapLeafHash::from_script(
&vault_complete_withdrawal(self.timelock_in_blocks),
&vault_complete_withdrawal(self.x_only_public_key(), self.timelock_in_blocks),
LeafVersion::TapScript,
);
let vault_txout = TxOut {
Expand Down Expand Up @@ -419,13 +450,20 @@ impl VaultCovenant {
vault_txin.witness.push([computed_signature[63]]); // push the last byte of the signature
vault_txin.witness.push([computed_signature[63] + 1]); // push the last byte of the signature

let sig = self.sign_transaction(
&txn,
&[vault_txout.clone(), fee_paying_output.clone()],
leaf_hash
);
vault_txin.witness.push(sig);

vault_txin
.witness
.push(vault_complete_withdrawal(self.timelock_in_blocks).to_bytes());
.push(vault_complete_withdrawal(self.x_only_public_key(), self.timelock_in_blocks).to_bytes());
vault_txin.witness.push(
self.taproot_spend_info()?
.control_block(&(
vault_complete_withdrawal(self.timelock_in_blocks).clone(),
vault_complete_withdrawal(self.x_only_public_key(), self.timelock_in_blocks).clone(),
LeafVersion::TapScript,
))
.expect("control block should work")
Expand Down Expand Up @@ -473,7 +511,7 @@ impl VaultCovenant {
};

let leaf_hash =
TapLeafHash::from_script(&vault_cancel_withdrawal(), LeafVersion::TapScript);
TapLeafHash::from_script(&vault_cancel_withdrawal(self.x_only_public_key()), LeafVersion::TapScript);
let vault_txout = TxOut {
script_pubkey: self.address()?.script_pubkey().clone(),
value: self.amount,
Expand Down Expand Up @@ -532,12 +570,19 @@ impl VaultCovenant {
vault_txin.witness.push([computed_signature[63]]); // push the last byte of the signature
vault_txin.witness.push([computed_signature[63] + 1]); // push the last byte of the signature

let sig = self.sign_transaction(
&txn,
&[vault_txout.clone(), fee_paying_output.clone()],
leaf_hash
);
vault_txin.witness.push(sig);

vault_txin
.witness
.push(vault_cancel_withdrawal().to_bytes());
.push(vault_cancel_withdrawal(self.x_only_public_key()).to_bytes());
vault_txin.witness.push(
self.taproot_spend_info()?
.control_block(&(vault_cancel_withdrawal().clone(), LeafVersion::TapScript))
.control_block(&(vault_cancel_withdrawal(self.x_only_public_key()).clone(), LeafVersion::TapScript))
.expect("control block should work")
.serialize(),
);
Expand Down
25 changes: 17 additions & 8 deletions src/vault/script.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
use crate::vault::signature_building::{BIP0340_CHALLENGE_TAG, DUST_AMOUNT, G_X, TAPSIGHASH_TAG};
use bitcoin::opcodes::all::{
OP_2DUP, OP_CAT, OP_CHECKSIG, OP_CSV, OP_DROP, OP_DUP, OP_EQUALVERIFY, OP_FROMALTSTACK,
OP_HASH256, OP_ROT, OP_SHA256, OP_SWAP, OP_TOALTSTACK,
OP_HASH256, OP_ROT, OP_SHA256, OP_SWAP, OP_TOALTSTACK, OP_CHECKSIGVERIFY
};
use bitcoin::script::Builder;
use bitcoin::{Script, ScriptBuf, Sequence};
use bitcoin::{Script, ScriptBuf, Sequence, XOnlyPublicKey};

pub(crate) fn vault_trigger_withdrawal() -> ScriptBuf {
pub(crate) fn vault_trigger_withdrawal(x_only_pubkey: XOnlyPublicKey) -> ScriptBuf {
let mut builder = Script::builder();
// The witness program needs to have the signature components except the outputs and the pre_scriptpubkeys and pre_amounts,
// followed by the target scriptpubkey (the amount for that output will be fixed)
// followed by the vault output amount, then the vault scriptpubkey,
// followed by the fee amount, then the fee-paying scriptpubkey
// and finally the mangled signature
// followed by the mangled signature
// and finally the a normal signature that signs with vault pubkey
builder = builder
.push_x_only_key(&x_only_pubkey) // push vault pubkey
.push_opcode(OP_CHECKSIGVERIFY) // checksig for pubkey
.push_opcode(OP_TOALTSTACK) // move pre-computed signature minus last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
Expand Down Expand Up @@ -72,14 +75,17 @@ pub(crate) fn vault_trigger_withdrawal() -> ScriptBuf {
builder.into_script()
}

pub(crate) fn vault_complete_withdrawal(timelock_in_blocks: u16) -> ScriptBuf {
pub(crate) fn vault_complete_withdrawal(x_only_pubkey: XOnlyPublicKey, timelock_in_blocks: u16) -> ScriptBuf {
let mut builder = Script::builder();
// The witness program needs to have the signature components except the outputs, prevouts,
// followed by the previous transaction version, inputs, and locktime
// followed by vault SPK, the vault amount, and the target SPK
// followed by the fee-paying txout
// and finally the mangled signature
// followed by the mangled signature
// and finally the a normal signature that signs with vault pubkey
builder = builder
.push_x_only_key(&x_only_pubkey) // push vault pubkey
.push_opcode(OP_CHECKSIGVERIFY) // checksig for pubkey
.push_sequence(Sequence::from_height(timelock_in_blocks))
.push_opcode(OP_CSV) // check relative timelock on withdrawal
.push_opcode(OP_DROP) // drop the result
Expand Down Expand Up @@ -143,13 +149,16 @@ pub(crate) fn vault_complete_withdrawal(timelock_in_blocks: u16) -> ScriptBuf {
builder.into_script()
}

pub(crate) fn vault_cancel_withdrawal() -> ScriptBuf {
pub(crate) fn vault_cancel_withdrawal(x_only_pubkey: XOnlyPublicKey) -> ScriptBuf {
let mut builder = Script::builder();
// The witness program needs to have the signature components except the outputs and the pre_scriptpubkeys and pre_amounts,
// followed by the output amount, then the script pubkey,
// followed by the fee amount, then the fee-paying scriptpubkey
// and finally the mangled signature
// followed by the mangled signature
// and finally the a normal signature that signs with vault pubkey
builder = builder
.push_x_only_key(&x_only_pubkey) // push vault pubkey
.push_opcode(OP_CHECKSIGVERIFY) // checksig for pubkey
.push_opcode(OP_TOALTSTACK) // move pre-computed signature minus last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
Expand Down

0 comments on commit 9e41e4d

Please sign in to comment.