mod cov;
mod error;
mod satisfy;
mod script_internals;
pub use self::cov::LegacyCSFSCov;
pub use self::error::CovError;
pub use self::satisfy::LegacyCovSatisfier;
pub use self::script_internals::CovOperations;
#[cfg(test)]
#[allow(unused_imports)]
mod tests {
use std::str::FromStr;
use bitcoin;
use elements::encode::serialize;
use elements::hex::ToHex;
use elements::opcodes::all::OP_PUSHNUM_1;
use elements::secp256k1_zkp::ZERO_TWEAK;
use elements::{
self, confidential, opcodes, script, secp256k1_zkp, AssetId, AssetIssuance,
EcdsaSighashType, LockTime, OutPoint, Script, Sequence, Transaction, TxIn, TxInWitness,
TxOut, Txid,
};
use super::cov::*;
use super::*;
use crate::descriptor::DescriptorType;
use crate::extensions::{CovExtArgs, CovenantExt, NoExtParam};
use crate::interpreter::SatisfiedConstraint;
use crate::util::{count_non_push_opcodes, witness_size};
use crate::{interpreter, Descriptor, ElementsSig, Error, Interpreter, Satisfier};
const BTC_ASSET: [u8; 32] = [
0x23, 0x0f, 0x4f, 0x5d, 0x4b, 0x7c, 0x6f, 0xa8, 0x45, 0x80, 0x6e, 0xe4, 0xf6, 0x77, 0x13,
0x45, 0x9e, 0x1b, 0x69, 0xe8, 0xe6, 0x0f, 0xce, 0xe2, 0xe4, 0x94, 0x0c, 0x7a, 0x0d, 0x5d,
0xe1, 0xb2,
];
fn string_rtt(desc_str: &str) {
let desc = Descriptor::<String>::from_str(desc_str).unwrap();
assert_eq!(format!("{:#}", desc), desc_str);
let cov_desc = desc.as_cov().unwrap();
assert_eq!(cov_desc.to_string(), desc.to_string());
}
#[test]
fn parse_cov() {
string_rtt("elcovwsh(A,pk(B))");
string_rtt("elcovwsh(A,or_i(pk(B),pk(C)))");
string_rtt("elcovwsh(A,multi(2,B,C,D))");
string_rtt("elcovwsh(A,and_v(v:pk(B),pk(C)))");
string_rtt("elcovwsh(A,thresh(2,l:ver_eq(1),s:pk(C),s:pk(B)))");
string_rtt("elcovwsh(A,outputs_pref(01020304))");
}
fn script_rtt(desc_str: &str) {
let desc = Descriptor::<bitcoin::PublicKey>::from_str(desc_str).unwrap();
assert_eq!(desc.desc_type(), DescriptorType::Cov);
let script = desc.as_cov().expect("Parsed as cov").encode();
let cov_desc =
LegacyCSFSCov::<bitcoin::PublicKey, CovenantExt<CovExtArgs>>::parse_insane(&script)
.unwrap();
assert_eq!(cov_desc.to_string(), desc.to_string());
}
#[test]
fn script_encode_test() {
let (pks, _sks) = setup_keys(5);
script_rtt(&format!("elcovwsh({},pk({}))", pks[0], pks[1]));
script_rtt(&format!(
"elcovwsh({},or_i(pk({}),pk({})))",
pks[0], pks[1], pks[2]
));
script_rtt(&format!(
"elcovwsh({},multi(2,{},{},{}))",
pks[0], pks[1], pks[2], pks[3]
));
script_rtt(&format!(
"elcovwsh({},and_v(v:pk({}),pk({})))",
pks[0], pks[1], pks[2]
));
script_rtt(&format!(
"elcovwsh({},and_v(v:ver_eq(2),pk({})))",
pks[0], pks[1]
));
script_rtt(&format!(
"elcovwsh({},and_v(v:outputs_pref(f2f233),pk({})))",
pks[0], pks[1]
));
}
fn setup_keys(n: usize) -> (Vec<bitcoin::PublicKey>, Vec<secp256k1_zkp::SecretKey>) {
let secp_sign = secp256k1_zkp::Secp256k1::signing_only();
let mut sks = vec![];
let mut pks = vec![];
let mut sk = [0; 32];
for i in 1..n + 1 {
sk[0] = i as u8;
sk[1] = (i >> 8) as u8;
sk[2] = (i >> 16) as u8;
let sk = secp256k1_zkp::SecretKey::from_slice(&sk[..]).expect("secret key");
let pk = bitcoin::PublicKey {
inner: secp256k1_zkp::PublicKey::from_secret_key(&secp_sign, &sk),
compressed: true,
};
sks.push(sk);
pks.push(pk);
}
(pks, sks)
}
#[test]
fn test_sanity_check_limits() {
let (pks, _sks) = setup_keys(1);
let cov_script = script::Builder::new().verify_cov(&pks[0]).into_script();
assert_eq!(
count_non_push_opcodes(&cov_script),
Ok(cov::COV_SCRIPT_OPCODE_COST)
);
assert_eq!(cov_script.len(), cov::COV_SCRIPT_SIZE);
let sighash_size = 4
+ 32
+ 32
+ 32
+ (32 + 4)
+ (5) + 4
+ 32
+ 4
+ 4;
assert_eq!(sighash_size, 185);
}
fn _satisfy_and_interpret(
desc: Descriptor<bitcoin::PublicKey, CovenantExt<CovExtArgs>>,
cov_sk: secp256k1_zkp::SecretKey,
) -> Result<(), Error> {
assert_eq!(desc.desc_type(), DescriptorType::Cov);
let desc = desc.as_cov().unwrap();
let mut spend_tx = Transaction {
version: 2,
lock_time: LockTime::ZERO,
input: vec![txin_from_txid_vout(
"141f79c7c254ee3a9a9bc76b4f60564385b784bdfc1882b25154617801fe2237",
1,
)],
output: vec![],
};
spend_tx.output.push(TxOut::default());
spend_tx.output[0].script_pubkey = script::Builder::new()
.push_opcode(opcodes::all::OP_PUSHNUM_1)
.into_script()
.to_v0_p2wsh();
spend_tx.output[0].value = confidential::Value::Explicit(99_000);
spend_tx.output[0].asset =
confidential::Asset::Explicit(AssetId::from_slice(&BTC_ASSET).unwrap());
let second_out = spend_tx.output[0].clone();
spend_tx.output.push(second_out);
spend_tx.output.push(TxOut::default());
spend_tx.output[2].asset =
confidential::Asset::Explicit(AssetId::from_slice(&BTC_ASSET).unwrap());
spend_tx.output[2].value = confidential::Value::Explicit(2_000);
let script_code = desc.cov_script_code();
let cov_sat = LegacyCovSatisfier::new_segwitv0(
&spend_tx,
0,
confidential::Value::Explicit(200_000),
&script_code,
EcdsaSighashType::All,
);
let sighash_u256 = cov_sat.segwit_sighash().unwrap();
let secp = secp256k1_zkp::Secp256k1::signing_only();
let sig = secp.sign_ecdsa(
&secp256k1_zkp::Message::from_digest_slice(&sighash_u256[..]).unwrap(),
&cov_sk,
);
let el_sig = (sig, EcdsaSighashType::All);
struct SimpleSat {
sig: ElementsSig,
pk: bitcoin::PublicKey,
}
impl Satisfier<bitcoin::PublicKey> for SimpleSat {
fn lookup_ecdsa_sig(&self, pk: &bitcoin::PublicKey) -> Option<ElementsSig> {
if *pk == self.pk {
Some(self.sig)
} else {
None
}
}
}
let pk_sat = SimpleSat {
sig: el_sig,
pk: desc.pk,
};
let (wit, ss) = desc.get_satisfaction((cov_sat, pk_sat))?;
let interpreter = Interpreter::from_txdata(
&desc.script_pubkey(),
&ss,
&wit,
Sequence::ZERO,
LockTime::ZERO,
)
.unwrap();
assert!(wit[0].len() <= 73);
assert!(wit[1].len() == 4);
let constraints = interpreter
.iter_assume_sigs()
.collect::<Result<Vec<_>, _>>()
.expect("If satisfy succeeds, interpret must succeed");
assert_eq!(
constraints.last().unwrap(),
&SatisfiedConstraint::PublicKey {
key_sig: interpreter::KeySigPair::Ecdsa(desc.pk, (sig, EcdsaSighashType::All))
}
);
Ok(())
}
#[test]
fn satisfy_and_interpret() {
let (pks, sks) = setup_keys(5);
_satisfy_and_interpret(
Descriptor::from_str(&format!("elcovwsh({},1)", pks[0])).unwrap(),
sks[0],
)
.unwrap();
_satisfy_and_interpret(
Descriptor::from_str(&format!("elcovwsh({},ver_eq(2))", pks[0])).unwrap(),
sks[0],
)
.unwrap();
_satisfy_and_interpret(
Descriptor::from_str(&format!("elcovwsh({},ver_eq(3))", pks[0])).unwrap(),
sks[0],
)
.unwrap_err();
let out = TxOut {
script_pubkey: script::Builder::new()
.push_opcode(opcodes::all::OP_PUSHNUM_1)
.into_script()
.to_v0_p2wsh(),
value: confidential::Value::Explicit(99_000),
asset: confidential::Asset::Explicit(AssetId::from_slice(&BTC_ASSET).unwrap()),
..Default::default()
};
let desc = Descriptor::<bitcoin::PublicKey>::from_str(&format!(
"elcovwsh({},outputs_pref({}))",
pks[0],
serialize(&out).to_hex(),
))
.unwrap();
_satisfy_and_interpret(desc, sks[0]).unwrap();
let out = TxOut {
script_pubkey: script::Builder::new()
.push_opcode(opcodes::all::OP_PUSHNUM_1)
.into_script()
.to_v0_p2wsh(),
value: confidential::Value::Explicit(99_001), asset: confidential::Asset::Explicit(AssetId::from_slice(&BTC_ASSET).unwrap()),
..Default::default()
};
let desc = Descriptor::<bitcoin::PublicKey>::from_str(&format!(
"elcovwsh({},outputs_pref({}))",
pks[0],
serialize(&out).to_hex(),
))
.unwrap();
_satisfy_and_interpret(desc, sks[0]).unwrap_err();
}
#[test]
fn fund_output() {
let (pks, _sks) = setup_keys(5);
let desc =
Descriptor::<bitcoin::PublicKey>::from_str(&format!("elcovwsh({},1)", pks[0])).unwrap();
assert_eq!(desc.desc_type(), DescriptorType::Cov);
assert_eq!(
desc.address(&elements::AddressParams::ELEMENTS)
.unwrap()
.to_string(),
"ert1qamjdykcfzkcsvc9z32a6qcz3mwr85a3k7z7qf2uaufem2q3lsjxqj4y4fy"
);
}
#[test]
fn spend_tx() {
let (pks, sks) = setup_keys(5);
let desc =
Descriptor::<bitcoin::PublicKey>::from_str(&format!("elcovwsh({},1)", pks[0])).unwrap();
assert_eq!(desc.desc_type(), DescriptorType::Cov);
assert_eq!(
desc.address(&elements::AddressParams::ELEMENTS)
.unwrap()
.to_string(),
"ert1qamjdykcfzkcsvc9z32a6qcz3mwr85a3k7z7qf2uaufem2q3lsjxqj4y4fy"
);
let mut spend_tx = Transaction {
version: 2,
lock_time: LockTime::ZERO,
input: vec![txin_from_txid_vout(
"7c8e615c8da947fefd2d9b6f83f313a9b59d249c93a5f232287633195b461cb7",
0,
)],
output: vec![],
};
spend_tx.output.push(TxOut::default());
spend_tx.output[0].script_pubkey = desc.script_pubkey(); spend_tx.output[0].value = confidential::Value::Explicit(99_000);
spend_tx.output[0].asset =
confidential::Asset::Explicit(AssetId::from_slice(&BTC_ASSET).unwrap());
let second_out = spend_tx.output[0].clone();
spend_tx.output.push(second_out);
spend_tx.output.push(TxOut::default());
spend_tx.output[2].asset =
confidential::Asset::Explicit(AssetId::from_slice(&BTC_ASSET).unwrap());
spend_tx.output[2].value = confidential::Value::Explicit(2_000);
let desc = desc.as_cov().unwrap();
let script_code = desc.cov_script_code();
let cov_sat = LegacyCovSatisfier::new_segwitv0(
&spend_tx,
0,
confidential::Value::Explicit(200_000),
&script_code,
EcdsaSighashType::All,
);
let sighash_u256 = cov_sat.segwit_sighash().unwrap();
let secp = secp256k1_zkp::Secp256k1::signing_only();
let sig = secp.sign_ecdsa(
&secp256k1_zkp::Message::from_digest_slice(&sighash_u256[..]).unwrap(),
&sks[0],
);
let sig = (sig, EcdsaSighashType::All);
struct SimpleSat {
sig: ElementsSig,
pk: bitcoin::PublicKey,
}
impl Satisfier<bitcoin::PublicKey> for SimpleSat {
fn lookup_ecdsa_sig(&self, pk: &bitcoin::PublicKey) -> Option<ElementsSig> {
if *pk == self.pk {
Some(self.sig)
} else {
None
}
}
}
let pk_sat = SimpleSat { sig, pk: pks[0] };
let (wit, ss) = desc.get_satisfaction((cov_sat, pk_sat)).unwrap();
let interpreter = Interpreter::from_txdata(
&desc.script_pubkey(),
&ss,
&wit,
Sequence::ZERO,
LockTime::ZERO,
)
.unwrap();
let constraints: Result<Vec<_>, _> = interpreter.iter_assume_sigs().collect();
constraints.expect("Covenant incorrect satisfaction");
assert_eq!(witness_size(&wit), 385);
assert_eq!(wit.len(), 13);
}
fn txin_from_txid_vout(txid: &str, vout: u32) -> TxIn {
TxIn {
previous_output: OutPoint {
txid: Txid::from_str(txid).unwrap(),
vout,
},
sequence: Sequence::MAX,
is_pegin: false,
asset_issuance: AssetIssuance {
asset_blinding_nonce: secp256k1_zkp::ZERO_TWEAK,
asset_entropy: [0; 32],
amount: confidential::Value::Null,
inflation_keys: confidential::Value::Null,
},
script_sig: Script::new(),
witness: TxInWitness::default(),
}
}
}