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::ext::*;
use bitcoin::key::{Keypair, PrivateKey};
use bitcoin::opcodes::all::{OP_CHECKSIGFROMSTACK, OP_INTERNALKEY, OP_TEMPLATEHASH};
use bitcoin::script::Builder;
use bitcoin::taproot::{LeafVersion, TaprootBuilder};
use bitcoin::{ScriptPubKeyBuf, TapScriptBuf};
use hex::DisplayHex;

fn main() {
let keypair = example_keypair();
let internal_key = keypair.to_x_only_public_key();

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(internal_key)
.expect("finalizable taproot tree");

let script_pubkey = ScriptPubKeyBuf::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() -> TapScriptBuf {
Builder::new()
.push_opcode(OP_TEMPLATEHASH)
.push_opcode(OP_INTERNALKEY)
.push_opcode(OP_CHECKSIGFROMSTACK)
.into_script()
}

fn example_keypair() -> Keypair {
let secret = [1u8; 32];
let private_key = PrivateKey::from_secret_bytes(&secret).expect("valid secret key");
Keypair::from_private_key(&private_key)
}
78 changes: 78 additions & 0 deletions bitcoin/examples/inquisition_bip448_templatehash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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::ext::*;
use bitcoin::key::{Keypair, PrivateKey};
use bitcoin::locktime::absolute;
use bitcoin::opcodes::all::{OP_CHECKSIGFROMSTACK, OP_INTERNALKEY, OP_TEMPLATEHASH};
use bitcoin::script::Builder;
use bitcoin::sighash::SighashCache;
use bitcoin::taproot::{LeafVersion, TaprootBuilder};
use bitcoin::{
transaction, Amount, OutPoint, ScriptPubKeyBuf, ScriptSigBuf, Sequence, TapScriptBuf,
Transaction, TxIn, TxOut, Txid, Witness,
};

fn main() {
let keypair = example_keypair();
let internal_key = keypair.to_x_only_public_key();
let script = bip448_rebindable_script();

let spend_info = TaprootBuilder::new()
.add_leaf(0, script.clone())
.expect("valid taproot tree")
.finalize(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: ScriptSigBuf::new(),
sequence: Sequence::ENABLE_LOCKTIME_AND_RBF,
witness: Witness::new(),
};

let destination = ScriptPubKeyBuf::new_p2tr(internal_key, None);
let output = TxOut { amount: Amount::from_sat_u32(49_000), script_pubkey: destination };

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

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

let mut witness = Witness::new();
witness.push(signature.to_byte_array());
witness.push_p2tr_script_spend(&script, &control_block, None);
*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() -> TapScriptBuf {
Builder::new()
.push_opcode(OP_TEMPLATEHASH)
.push_opcode(OP_INTERNALKEY)
.push_opcode(OP_CHECKSIGFROMSTACK)
.into_script()
}

fn example_keypair() -> Keypair {
let secret = [1u8; 32];
let private_key = PrivateKey::from_secret_bytes(&secret).expect("valid secret key");
Keypair::from_private_key(&private_key)
}
29 changes: 29 additions & 0 deletions bitcoin/src/blockdata/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ macro_rules! all_opcodes {
/// Previously called OP_NOP3.
pub const OP_NOP3: Opcode = OP_CSV;

/// Bitcoin Inquisition/BIP349 alias for `OP_SUCCESS203`.
pub const OP_INTERNALKEY: Opcode = OP_RETURN_203;
/// Bitcoin Inquisition/BIP348 alias for `OP_SUCCESS204`.
pub const OP_CHECKSIGFROMSTACK: Opcode = OP_RETURN_204;
/// Bitcoin Inquisition/BIP446 alias for `OP_SUCCESS206`.
pub const OP_TEMPLATEHASH: Opcode = OP_RETURN_206;

/// Push the array `0x81` onto the stack.
#[deprecated(since = "TBD", note = "use OP_1NEGATE instead")]
pub const OP_PUSHNUM_NEG1: Opcode = OP_1NEGATE;
Expand Down Expand Up @@ -642,6 +649,28 @@ mod tests {
assert_eq!(ordinary_op.to_u8(), 0x4C_u8);
}

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

type Tag = primitives::script::TapScriptTag;

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

let script = Builder::<Tag>::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::SuccessOp);
assert_eq!(OP_CHECKSIGFROMSTACK.classify(ClassifyContext::TapScript), Class::SuccessOp);
assert_eq!(OP_TEMPLATEHASH.classify(ClassifyContext::TapScript), Class::SuccessOp);
}

#[test]
fn decode_pushnum() {
// Test all possible opcodes
Expand Down
Loading