Skip to content

Instantly share code, notes, and snippets.

@futurepaul
Last active December 12, 2022 21:08
Show Gist options
  • Save futurepaul/7c09ce3491dcfb9ca103bc46127435d4 to your computer and use it in GitHub Desktop.
Save futurepaul/7c09ce3491dcfb9ca103bc46127435d4 to your computer and use it in GitHub Desktop.
example usage of miniscript
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());
}
@futurepaul
Copy link
Author

Here's my cargo.toml:

[dependencies]
miniscript = { git="https://github.com/apoelstra/rust-miniscript", features=["compiler"] }
bitcoin = "0.18"
secp256k1 = "0.12"

@sanket1729
Copy link

sanket1729 commented Jun 7, 2019

Hey Paul,
sigfn = oracle for the signature function which takes the public key as input and gives the corresponding signature
hashfn = oracle for the hash function which takes hash as the input and gives back the preimage.

Finally, the miniscript you are compiling does not match the scriptsig. If you want the last assert to satisfy, you must use miniscript= Miniscript(astelem::AstElem::Pk(pk)). Or Alternatively, you can supply a witness for and(pk(),or(pk(),aor(pk(),time(1000)))) which should be

let manual_txin = bitcoin::TxIn {
        previous_output: bitcoin::OutPoint::default(),
        script_sig: script::Builder::new()
            .push_slice(&sigser[..])
            .push_slice(&sigser[..])  //note the second push
            .push_slice(&miniscript.encode()[..])
            .into_script(),
        sequence: 100,
        witness: vec![],
    };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment