Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
49 changes: 49 additions & 0 deletions bitcoin/examples/inquisition_bip448_script.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: CC0-1.0

//! Build a Bitcoin Inquisition/BIP448 Taproot script output.
//!
//! This is construction-only support. Current Bitcoin consensus still treats these bytes as
//! `OP_SUCCESSx`; Bitcoin Inquisition signet gives them the BIP448 meanings.

use bitcoin::opcodes::all::{OP_CHECKSIGFROMSTACK, OP_INTERNALKEY, OP_TEMPLATEHASH};
use bitcoin::script::Builder;
use bitcoin::secp256k1::{Keypair, Secp256k1, SecretKey};
use bitcoin::taproot::{LeafVersion, TaprootBuilder};
use bitcoin::ScriptBuf;
use hex::DisplayHex;

fn main() {
let secp = Secp256k1::new();
let keypair = example_keypair();
let internal_key = keypair.x_only_public_key().0;

let script = bip448_rebindable_script();
assert_eq!(script.as_bytes(), &[0xce, 0xcb, 0xcc]);

let spend_info = TaprootBuilder::new()
.add_leaf(0, script.clone())
.expect("valid taproot tree")
.finalize(&secp, internal_key)
.expect("finalizable taproot tree");

let script_pubkey = ScriptBuf::new_p2tr_tweaked(spend_info.output_key());
let control_block =
spend_info.control_block(&(script, LeafVersion::TapScript)).expect("script is in tree");

println!("scriptPubKey: {script_pubkey:x}");
println!("control block: {:x}", control_block.serialize().as_hex());
}

fn bip448_rebindable_script() -> ScriptBuf {
Builder::new()
.push_opcode(OP_TEMPLATEHASH)
.push_opcode(OP_INTERNALKEY)
.push_opcode(OP_CHECKSIGFROMSTACK)
.into_script()
}

fn example_keypair() -> Keypair {
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&[1u8; 32]).expect("valid secret key");
Keypair::from_secret_key(&secp, &secret_key)
}
79 changes: 79 additions & 0 deletions bitcoin/examples/inquisition_bip448_templatehash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: CC0-1.0

//! Sign a BIP446 `TemplateHash` for a Bitcoin Inquisition/BIP448 tapscript spend.
//!
//! The common script pattern is `OP_TEMPLATEHASH OP_INTERNALKEY OP_CHECKSIGFROMSTACK`. The signature
//! is a raw 64-byte BIP340 Schnorr signature over the 32-byte template hash, with no Taproot sighash
//! byte appended.

use bitcoin::hashes::Hash;
use bitcoin::locktime::absolute;
use bitcoin::opcodes::all::{OP_CHECKSIGFROMSTACK, OP_INTERNALKEY, OP_TEMPLATEHASH};
use bitcoin::script::Builder;
use bitcoin::secp256k1::{Keypair, Message, Secp256k1, SecretKey};
use bitcoin::sighash::SighashCache;
use bitcoin::taproot::{LeafVersion, TaprootBuilder};
use bitcoin::{
transaction, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness,
};

fn main() {
let secp = Secp256k1::new();
let keypair = example_keypair();
let internal_key = keypair.x_only_public_key().0;
let script = bip448_rebindable_script();

let spend_info = TaprootBuilder::new()
.add_leaf(0, script.clone())
.expect("valid taproot tree")
.finalize(&secp, internal_key)
.expect("finalizable taproot tree");
let control_block = spend_info
.control_block(&(script.clone(), LeafVersion::TapScript))
.expect("script is in tree");

let input = TxIn {
previous_output: OutPoint { txid: Txid::from_byte_array([0x11; 32]), vout: 0 },
script_sig: ScriptBuf::new(),
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
witness: Witness::new(),
};

let destination = ScriptBuf::new_p2tr(&secp, internal_key, None);
let output = TxOut { value: Amount::from_sat(49_000), script_pubkey: destination };

let mut tx = Transaction {
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![input],
output: vec![output],
};

let mut cache = SighashCache::new(&mut tx);
let template_hash = cache.template_hash(0, None).expect("valid input index");
let signature = secp.sign_schnorr_no_aux_rand(&Message::from(template_hash), &keypair);

let mut witness = Witness::new();
witness.push(signature.serialize());
witness.push(script.as_bytes());
witness.push(control_block.serialize());
*cache.witness_mut(0).expect("valid input index") = witness;

let signed_tx = cache.into_transaction();
println!("template hash: {template_hash:x}");
println!("signed transaction: {signed_tx:#?}");
}

fn bip448_rebindable_script() -> ScriptBuf {
Builder::new()
.push_opcode(OP_TEMPLATEHASH)
.push_opcode(OP_INTERNALKEY)
.push_opcode(OP_CHECKSIGFROMSTACK)
.into_script()
}

fn example_keypair() -> Keypair {
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&[1u8; 32]).expect("valid secret key");
Keypair::from_secret_key(&secp, &secret_key)
}
48 changes: 37 additions & 11 deletions bitcoin/src/blockdata/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ all_opcodes! {
OP_NOP8 => 0xb7, "Does nothing.";
OP_NOP9 => 0xb8, "Does nothing.";
OP_NOP10 => 0xb9, "Does nothing.";

// BIP 448 opcodes.
OP_INTERNALKEY => 0xcb, "Push the Taproot internal key onto the stack. (Tapscript-only.)";
OP_CHECKSIGFROMSTACK => 0xcc, "Pop a signature (bottom), message and public key (top) from the stack. Push 1 if the signature is valid, 0 otherwise. (Tapscript-only.)";
OP_TEMPLATEHASH => 0xce, "Push the hash of the spending transaction onto the stack. (Tapscript-only.)";

// Every other opcode acts as OP_RETURN
OP_CHECKSIGADD => 0xba, "OP_CHECKSIGADD post tapscript.";
OP_RETURN_187 => 0xbb, "Synonym for OP_RETURN.";
Expand All @@ -282,10 +288,7 @@ all_opcodes! {
OP_RETURN_200 => 0xc8, "Synonym for OP_RETURN.";
OP_RETURN_201 => 0xc9, "Synonym for OP_RETURN.";
OP_RETURN_202 => 0xca, "Synonym for OP_RETURN.";
OP_RETURN_203 => 0xcb, "Synonym for OP_RETURN.";
OP_RETURN_204 => 0xcc, "Synonym for OP_RETURN.";
OP_RETURN_205 => 0xcd, "Synonym for OP_RETURN.";
OP_RETURN_206 => 0xce, "Synonym for OP_RETURN.";
OP_RETURN_207 => 0xcf, "Synonym for OP_RETURN.";
OP_RETURN_208 => 0xd0, "Synonym for OP_RETURN.";
OP_RETURN_209 => 0xd1, "Synonym for OP_RETURN.";
Expand Down Expand Up @@ -367,7 +370,7 @@ impl Opcode {
| (OP_MUL, ctx) | (OP_DIV, ctx) | (OP_MOD, ctx)
| (OP_LSHIFT, ctx) | (OP_RSHIFT, ctx) if ctx == ClassifyContext::Legacy => Class::IllegalOp,

// 87 opcodes of SuccessOp class only in TapScript context
// 84 opcodes of SuccessOp class only in TapScript context
(op, ClassifyContext::TapScript)
if op.code == 80
|| op.code == 98
Expand All @@ -376,7 +379,9 @@ impl Opcode {
|| (op.code >= 137 && op.code <= 138)
|| (op.code >= 141 && op.code <= 142)
|| (op.code >= 149 && op.code <= 153)
|| (op.code >= 187 && op.code <= 254) =>
|| (op.code >= 187 && op.code <= 202)
|| (op.code == 205)
|| (op.code >= 207 && op.code <= 254) =>
Class::SuccessOp,

// 11 opcodes of NoOp class
Expand Down Expand Up @@ -408,7 +413,7 @@ impl Opcode {
// 76 opcodes of PushBytes class
(op, _) if op.code <= OP_PUSHBYTES_75.code => Class::PushBytes(self.code as u32),

// opcodes of Ordinary class: 61 for Legacy and 60 for TapScript context
// opcodes of Ordinary class: 61 for Legacy and 63 for TapScript context
(_, _) => Class::Ordinary(Ordinary::with(self)),
}
}
Expand Down Expand Up @@ -507,7 +512,7 @@ macro_rules! ordinary_opcode {
);
}

// "Ordinary" opcodes -- should be 61 of these
// "Ordinary" opcodes -- should be 64 of these
ordinary_opcode! {
// pushdata
OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4,
Expand All @@ -530,7 +535,8 @@ ordinary_opcode! {
OP_RIPEMD160, OP_SHA1, OP_SHA256, OP_HASH160, OP_HASH256,
OP_CODESEPARATOR, OP_CHECKSIG, OP_CHECKSIGVERIFY,
OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY,
OP_CHECKSIGADD
OP_CHECKSIGADD,
OP_INTERNALKEY, OP_CHECKSIGFROMSTACK, OP_TEMPLATEHASH
}

impl Ordinary {
Expand Down Expand Up @@ -564,6 +570,26 @@ mod tests {
assert_eq!(s, " OP_NOP");
}

#[test]
fn inquisition_bip448_aliases() {
use crate::script::Builder;

assert_eq!(OP_INTERNALKEY.to_u8(), 0xcb);
assert_eq!(OP_CHECKSIGFROMSTACK.to_u8(), 0xcc);
assert_eq!(OP_TEMPLATEHASH.to_u8(), 0xce);

let script = Builder::new()
.push_opcode(OP_TEMPLATEHASH)
.push_opcode(OP_INTERNALKEY)
.push_opcode(OP_CHECKSIGFROMSTACK)
.into_script();
assert_eq!(script.as_bytes(), &[0xce, 0xcb, 0xcc]);

assert_eq!(OP_INTERNALKEY.classify(ClassifyContext::TapScript), Class::Ordinary(Ordinary::OP_INTERNALKEY));
assert_eq!(OP_CHECKSIGFROMSTACK.classify(ClassifyContext::TapScript), Class::Ordinary(Ordinary::OP_CHECKSIGFROMSTACK));
assert_eq!(OP_TEMPLATEHASH.classify(ClassifyContext::TapScript), Class::Ordinary(Ordinary::OP_TEMPLATEHASH));
}

#[test]
fn decode_pushnum() {
// Test all possible opcodes
Expand Down Expand Up @@ -836,10 +862,10 @@ mod tests {
roundtrip!(unique, OP_RETURN_200);
roundtrip!(unique, OP_RETURN_201);
roundtrip!(unique, OP_RETURN_202);
roundtrip!(unique, OP_RETURN_203);
roundtrip!(unique, OP_RETURN_204);
roundtrip!(unique, OP_INTERNALKEY);
roundtrip!(unique, OP_CHECKSIGFROMSTACK);
roundtrip!(unique, OP_RETURN_205);
roundtrip!(unique, OP_RETURN_206);
roundtrip!(unique, OP_TEMPLATEHASH);
roundtrip!(unique, OP_RETURN_207);
roundtrip!(unique, OP_RETURN_208);
roundtrip!(unique, OP_RETURN_209);
Expand Down
Loading
Loading