Skip to content

Instantly share code, notes, and snippets.

@RandyMcMillan
Forked from futurepaul/example.rs
Last active December 12, 2022 21:20
Show Gist options
  • Save RandyMcMillan/3dc43efd409109ce1720e5355e83cc60 to your computer and use it in GitHub Desktop.
Save RandyMcMillan/3dc43efd409109ce1720e5355e83cc60 to your computer and use it in GitHub Desktop.
example usage of miniscript
[dependencies]
miniscript = { git="https://github.com/apoelstra/rust-miniscript", features=["compiler"] }
bitcoin = "0.18"
secp256k1 = "0.12"
use bitcoin::blockdata::{opcodes, script};
use miniscript::{Descriptor, Miniscript, Policy};
use std::str::FromStr;
use secp256k1;
fn key_from_secret_key(sk: secp256k1::SecretKey, secp: &secp256k1::Secp256k1<secp256k1::All>) -> bitcoin::PublicKey {
bitcoin::PublicKey {
key: secp256k1::PublicKey::from_secret_key(
&secp,
&secp256k1::SecretKey::from_slice(&sk[..]).expect("secret key"),
),
compressed: true,
}
}
fn main() {
//Let's get a public key and a secret key to get started
let secp = secp256k1::Secp256k1::new();
let sk = secp256k1::SecretKey::from_slice(&b"sally was a secret key, she said"[..]).unwrap();
let pk = key_from_secret_key(sk, &secp);
// Here are three ways to represent the same spending policy
// this policy can be written in Miniscript notation as:
// and(pk(C),or(pk(C),aor(pk(C),time(1000))))
// where C is a compressed publickey
// Bitcoin Script
let policy_script = script::Builder::new()
.push_key(&pk)
.push_opcode(opcodes::all::OP_CHECKSIG)
.push_opcode(opcodes::all::OP_NOTIF)
.push_key(&pk)
.push_opcode(opcodes::all::OP_CHECKSIG)
.push_opcode(opcodes::all::OP_NOTIF)
.push_int(1000)
.push_opcode(opcodes::OP_CSV)
.push_opcode(opcodes::all::OP_DROP)
.push_opcode(opcodes::all::OP_ENDIF)
.push_opcode(opcodes::all::OP_ENDIF)
.push_key(&pk)
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script();
// Miniscript AST
let policy_ast = Policy::And(
Box::new(Policy::Key(pk.clone())),
Box::new(Policy::Or(
Box::new(Policy::Key(pk.clone())),
Box::new(Policy::AsymmetricOr(
Box::new(Policy::Key(pk.clone())),
Box::new(Policy::Time(1000)),
)),
)),
);
// Parsed Miniscript String
let pk_string = pk.to_string();
let policy_string = format!(
"and(pk({}),or(pk({}),aor(pk({}),time(1000))))",
pk_string, pk_string, pk_string
);
// Once the Miniscript policies are encoded to Script, all three scripts are identical
let policy_ast_encoded = policy_ast.compile().encode();
let policy_string_encoded = Policy::<bitcoin::PublicKey>::from_str(&policy_string)
.unwrap()
.compile()
.encode();
assert_eq!(policy_script, policy_ast_encoded);
assert_eq!(policy_script, policy_string_encoded);
// If you don't need a real key, you can use DummyKey
use miniscript::DummyKey;
let policy_string_dummy =
Policy::<DummyKey>::from_str("and(pk(),or(pk(),aor(pk(),time(1000))))").unwrap();
// Or you can make an abstract policy and ask it questions
let policy_string_abstract =
Policy::<String>::from_str("and(pk(),or(pk(),aor(pk(),time(1000))))").unwrap();
let policy_abstract = policy_string_abstract.abstract_policy();
// Now you can learn all sorts of things!
dbg!(policy_abstract.minimum_n_keys());
dbg!(policy_abstract.timelocks());
dbg!(policy_abstract.n_keys());
// While we can write any Script we want, there are standard ways to embed Scripts in transaction outputs
// These are called [Output Descriptors](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md)
// First we create a policy and compile it to a Miniscript
let policy = Policy::<bitcoin::PublicKey>::from_str(&policy_string).unwrap();
let miniscript = policy.compile();
// Then we wrap it in a Descriptor. This one is Pay To Script Hash.
let descriptor_p2sh = Descriptor::Sh(miniscript.clone());
// As a quick sanity check, we can make sure the P2SH address starts with a 3
let address = descriptor_p2sh.address(bitcoin::Network::Bitcoin).unwrap();
dbg!(address);
// We can also estimate the satisfaction cost
dbg!(descriptor_p2sh.max_satisfaction_weight());
// Here's our pubkey, ready to share with the world (or put on the blockchain)
let script_pubkey = descriptor_p2sh.script_pubkey();
// Here's another way to get the same script_pubkey, just so you know what's happening
let same_script_pubkey = miniscript.encode().to_p2sh();
assert_eq!(script_pubkey, same_script_pubkey);
// Now let's create a transaction and a scriptSig to satisfy our Script
let mut txin = bitcoin::TxIn {
previous_output: bitcoin::OutPoint::default(),
script_sig: bitcoin::Script::new(),
sequence: 100,
witness: vec![],
};
let msg =
secp256k1::Message::from_slice(&b"michael was a message, amusingly"[..]).expect("32 bytes");
// We're finally using our secret key from earlier!
let sig = secp.sign(&msg, &sk);
// Given a public key, return the corresponding signature
let sigfn = |key: &bitcoin::PublicKey| {
if *key == pk {
Some((sig, bitcoin::SigHashType::All))
} else {
None
}
};
// NO_HASHES is the "None" type, because we have no hash preimage in this scenario
// Alternatively, we could provide a hashfn which would take a hash and return the preimage
use miniscript::NO_HASHES;
// The satisfy method creates a scriptSig or witness as appropriate and adds it to the transaction
descriptor_p2sh
.satisfy(&mut txin, Some(&sigfn), NO_HASHES, 0)
.expect("satisfaction to succeed");
dbg!(txin.clone());
//
//
// Now for something (slightly more) practical: let's make a two of three multisig that becomes one of three after 100 blocks
// First we need some keys
let sk1 = secp256k1::SecretKey::from_slice(&b"alice's key plus pad for valid k"[..]).unwrap();
let sk2 = secp256k1::SecretKey::from_slice(&b"bob's key plus pad for valid key"[..]).unwrap();
let sk3 = secp256k1::SecretKey::from_slice(&b"carol's key plus pad for valid k"[..]).unwrap();
let pk1 = key_from_secret_key(sk1, &secp);
let pk2 = key_from_secret_key(sk2, &secp);
let pk3 = key_from_secret_key(sk3, &secp);
// Here's our Policy. Pretty straightforward
let multi_miniscript = Policy::<bitcoin::PublicKey>::Or(
Box::new(Policy::Multi(2, vec![pk1, pk2, pk3])),
Box::new(Policy::And(
Box::new(Policy::Time(100)),
Box::new(Policy::Multi(1, vec![pk1, pk2, pk3]))
))).compile();
// Let's check to see if we wrote the policy correctly
let mut abs = multi_miniscript.abstract_policy();
// The least keys necessary to unlock is one
assert_eq!(abs.minimum_n_keys(), 1);
// But at time < 100 we need two keys
abs = abs.before_time(99).unwrap();
assert_eq!(abs.minimum_n_keys(), 2);
// We're going to use p2sh again because it's good for multi-sig
let multi_p2sh = Descriptor::Sh(multi_miniscript);
// Another elegant Bitcoin Script achieved:
let multi_script_pubkey = multi_p2sh.script_pubkey();
// QUESTION just to make sure I'm not crazy: this is what we send the Bitcoin to, correct?
dbg!(multi_p2sh.address(bitcoin::Network::Bitcoin).unwrap());
//Now let's try to move the bitcoin
let mut multi_txin = bitcoin::TxIn {
previous_output: bitcoin::OutPoint::default(),
script_sig: bitcoin::Script::new(),
sequence: 100,
witness: vec![],
};
//QUESTION does it ever matter what the message is?
let msg =
secp256k1::Message::from_slice(&b"michael was a message, amusingly"[..]).expect("32 bytes");
let sig1 = secp.sign(&msg, &sk1);
let sig2 = secp.sign(&msg, &sk2);
let sig3 = secp.sign(&msg, &sk3);
// Given a public key, return the corresponding signature
let sigfn_multi = |key: &bitcoin::PublicKey| {
if *key == pk1 {
Some((sig1, bitcoin::SigHashType::All))
} else if *key == pk2 {
Some((sig2, bitcoin::SigHashType::All))
} else if *key == pk3 {
Some((sig3, bitcoin::SigHashType::All))
} else {
None
}
};
// Try commenting out different signatures to see what works
// At age 0-99 you should need two, at 100+ you should only need one
multi_p2sh
.satisfy(&mut multi_txin, Some(&sigfn_multi), NO_HASHES, 99)
.expect("satisfaction to succeed");
dbg!(multi_txin.clone());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment