diff --git a/Cargo.toml b/Cargo.toml index f8bda3d9..cf7451ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "miniscript" -version = "5.1.0" +name = "elements-miniscript" +version = "0.0.1" authors = ["Andrew Poelstra , Sanket Kanjalkar "] -repository = "https://github.com/apoelstra/miniscript" -description = "Miniscript: a subset of Bitcoin Script designed for analysis" +repository = "https://github.com/sanket1729/elements-miniscript" +description = "Elements Miniscript: Miniscript, but for elements" license = "CC0-1.0" [features] @@ -17,6 +17,12 @@ rand = ["bitcoin/rand"] [dependencies] bitcoin = "0.26" +elements = "0.16" +miniscript = "5.1.0" + +[dev-dependencies] +serde_json = "<=1.0.44" +ryu = "<1.0.5" [dependencies.serde] version = "1.0" @@ -35,8 +41,5 @@ name = "sign_multisig" [[example]] name = "verify_tx" -[[example]] -name = "psbt" - [[example]] name = "xpub_descriptors" diff --git a/README.md b/README.md index 31bba356..0d40e7e2 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,13 @@ -![Build](https://github.com/rust-bitcoin/rust-miniscript/workflows/Continuous%20integration/badge.svg) +![Build](https://github.com/sanket1729/elements-miniscript/workflows/Continuous%20integration/badge.svg) **Minimum Supported Rust Version:** 1.29.0 *This crate uses "2015" edition and won't be ported over "2018" edition in the near future as this will change the MSRV to 1.31.* -# Miniscript +# Elements Miniscript +This library is a fork of [rust-miniscript](https://github.com/rust-bitcoin/rust-miniscript) for elements. -Library for handling [Miniscript](http://bitcoin.sipa.be/miniscript/), -which is a subset of Bitcoin Script designed to support simple and general -tooling. Miniscripts represent threshold circuits of spending conditions, -and can therefore be easily visualized or serialized as human-readable -strings. ## High-Level Features @@ -27,12 +23,12 @@ public key types * Encoding and decoding Miniscript as Bitcoin Script, given key types that are convertible to `bitcoin::PublicKey` * Determining satisfiability, and optimal witnesses, for a given descriptor; -completing an unsigned `bitcoin::TxIn` with appropriate data +completing an unsigned `elements::TxIn` with appropriate data * Determining the specific keys, hash preimages and timelocks used to spend coins in a given Bitcoin transaction -More information can be found in [the documentation](https://docs.rs/miniscript) -or in [the `examples/` directory](https://github.com/apoelstra/rust-miniscript/tree/master/examples) +More information can be found in [the documentation](https://docs.rs/elemnents-miniscript) +or in [the `examples/` directory](https://github.com/sanket1729/elements-miniscript/tree/master/examples) ## Contributing Contributions are generally welcome. If you intend to make larger changes please diff --git a/examples/htlc.rs b/examples/htlc.rs index 0eb848b2..d068298d 100644 --- a/examples/htlc.rs +++ b/examples/htlc.rs @@ -15,9 +15,9 @@ //! Example: Create an HTLC with miniscript using the policy compiler extern crate bitcoin; -extern crate miniscript; +extern crate elements; +extern crate elements_miniscript as miniscript; -use bitcoin::Network; use miniscript::policy::{Concrete, Liftable}; use miniscript::{Descriptor, DescriptorTrait}; use std::str::FromStr; @@ -45,7 +45,7 @@ fn main() { assert!(htlc_descriptor.sanity_check().is_ok()); assert_eq!( format!("{}", htlc_descriptor), - "wsh(andor(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111),and_v(v:pkh(51814f108670aced2d77c1805ddd6634bc9d4731),older(4444))))#s0qq76ng" + "elwsh(andor(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111),and_v(v:pkh(51814f108670aced2d77c1805ddd6634bc9d4731),older(4444))))#dq09llks" ); assert_eq!( @@ -64,7 +64,12 @@ fn main() { ); assert_eq!( - format!("{}", htlc_descriptor.address(Network::Bitcoin).unwrap()), - "bc1qmpfcw7he9z5d9ftfe8qw699azmm2sr8fen903fs4plv007yx0t3qxfmqv5" + format!( + "{}", + htlc_descriptor + .address(&elements::AddressParams::ELEMENTS) + .unwrap() + ), + "ert1qmpfcw7he9z5d9ftfe8qw699azmm2sr8fen903fs4plv007yx0t3qdt0h29" ); } diff --git a/examples/parse.rs b/examples/parse.rs index f64d366c..1550c865 100644 --- a/examples/parse.rs +++ b/examples/parse.rs @@ -15,14 +15,14 @@ //! Example: Parsing a descriptor from a string extern crate bitcoin; -extern crate miniscript; +extern crate elements_miniscript as miniscript; use miniscript::{descriptor::DescriptorType, DescriptorTrait}; use std::str::FromStr; fn main() { let my_descriptor = miniscript::Descriptor::::from_str( - "wsh(c:pk_k(020202020202020202020202020202020202020202020202020202020202020202))", + "elwsh(c:pk_k(020202020202020202020202020202020202020202020202020202020202020202))", ) .unwrap(); @@ -47,7 +47,7 @@ fn main() { ); let desc = miniscript::Descriptor::::from_str( - "sh(wsh(c:pk_k(020202020202020202020202020202020202020202020202020202020202020202)))", + "elsh(wsh(c:pk_k(020202020202020202020202020202020202020202020202020202020202020202)))", ) .unwrap(); diff --git a/examples/psbt.rs b/examples/psbt.rs deleted file mode 100644 index e6eaf4c7..00000000 --- a/examples/psbt.rs +++ /dev/null @@ -1,32 +0,0 @@ -extern crate bitcoin; -extern crate miniscript; - -use bitcoin::consensus::encode::deserialize; -use bitcoin::hashes::hex::FromHex; - -use miniscript::psbt::{extract, finalize}; - -fn main() { - // Test vectors from BIP 174 - - let mut psbt: bitcoin::util::psbt::PartiallySignedTransaction = deserialize(&Vec::::from_hex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000").unwrap()).unwrap(); - // println!("{:?}", psbt); - - let expected_finalized_psbt: bitcoin::util::psbt::PartiallySignedTransaction = deserialize(&Vec::::from_hex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000").unwrap()).unwrap(); - - // Construct a secp context for transaction signature verification - let secp = bitcoin::secp256k1::Secp256k1::verification_only(); - // Assuming all partial sigs are filled in. - // Construct a generic finalizer - finalize(&mut psbt, &secp).unwrap(); - // println!("{:?}", psbt); - - assert_eq!(psbt, expected_finalized_psbt); - - // Extract the transaction from the psbt - let tx = extract(&psbt, &secp).unwrap(); - - let expected: bitcoin::Transaction = deserialize(&Vec::::from_hex("0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000").unwrap()).unwrap(); - // println!("{:?}", tx); - assert_eq!(tx, expected); -} diff --git a/examples/sign_multisig.rs b/examples/sign_multisig.rs index 9da4f000..b78d070c 100644 --- a/examples/sign_multisig.rs +++ b/examples/sign_multisig.rs @@ -15,7 +15,8 @@ //! Example: Signing a 2-of-3 multisignature extern crate bitcoin; -extern crate miniscript; +extern crate elements; +extern crate elements_miniscript as miniscript; use bitcoin::secp256k1; // secp256k1 re-exported from rust-bitcoin use miniscript::DescriptorTrait; @@ -27,18 +28,24 @@ fn main() { type BitcoinDescriptor = miniscript::Descriptor; // Transaction which spends some output - let mut tx = bitcoin::Transaction { + let mut tx = elements::Transaction { version: 2, lock_time: 0, - input: vec![bitcoin::TxIn { - previous_output: Default::default(), - script_sig: bitcoin::Script::new(), + input: vec![elements::TxIn { + previous_output: elements::OutPoint::default(), + script_sig: elements::Script::new(), sequence: 0xffffffff, - witness: vec![], + is_pegin: false, + has_issuance: false, + asset_issuance: elements::AssetIssuance::default(), + witness: elements::TxInWitness::default(), }], - output: vec![bitcoin::TxOut { - script_pubkey: bitcoin::Script::new(), - value: 100_000_000, + output: vec![elements::TxOut { + script_pubkey: elements::Script::new(), + value: elements::confidential::Value::Explicit(100_000_000), + witness: elements::TxOutWitness::default(), + asset: elements::confidential::Asset::default(), + nonce: elements::confidential::Nonce::default(), }], }; @@ -71,11 +78,11 @@ fn main() { 531d75c136272f127a5dc14acc0722301cbddc222262934151f140da345af177", ) .unwrap(), - bitcoin::SigHashType::All, + elements::SigHashType::All, ); let descriptor_str = format!( - "wsh(multi(2,{},{},{}))", + "elwsh(multi(2,{},{},{}))", public_keys[0], public_keys[1], public_keys[2], ); @@ -111,7 +118,7 @@ fn main() { // Attempt to satisfy at age 0, height 0 let original_txin = tx.input[0].clone(); - let mut sigs = HashMap::::new(); + let mut sigs = HashMap::::new(); // Doesn't work with no signatures assert!(my_descriptor.satisfy(&mut tx.input[0], &sigs).is_err()); @@ -126,11 +133,11 @@ fn main() { sigs.insert(public_keys[2], bitcoin_sig); assert!(my_descriptor.satisfy(&mut tx.input[0], &sigs).is_ok()); assert_ne!(tx.input[0], original_txin); - assert_eq!(tx.input[0].witness.len(), 4); // 0, sig, sig, witness script + assert_eq!(tx.input[0].witness.script_witness.len(), 4); // 0, sig, sig, witness script // ...and even if we give it a third signature, only two are used sigs.insert(public_keys[0], bitcoin_sig); assert!(my_descriptor.satisfy(&mut tx.input[0], &sigs).is_ok()); assert_ne!(tx.input[0], original_txin); - assert_eq!(tx.input[0].witness.len(), 4); // 0, sig, sig, witness script + assert_eq!(tx.input[0].witness.script_witness.len(), 4); // 0, sig, sig, witness script } diff --git a/examples/verify_tx.rs b/examples/verify_tx.rs index c881f223..43bb9951 100644 --- a/examples/verify_tx.rs +++ b/examples/verify_tx.rs @@ -15,79 +15,31 @@ //! Example: Verifying a signed transaction extern crate bitcoin; -extern crate miniscript; +extern crate elements; +extern crate elements_miniscript as miniscript; -use bitcoin::consensus::Decodable; -use bitcoin::secp256k1; // secp256k1 re-exported from rust-bitcoin +use elements::confidential; +use elements::encode::Decodable; +use elements::secp256k1; // secp256k1 re-exported from rust-bitcoin use std::str::FromStr; - fn main() { - // tx `f27eba163c38ad3f34971198687a3f1882b7ec818599ffe469a8440d82261c98` - #[cfg_attr(feature="cargo-fmt", rustfmt_skip)] - let tx_bytes = vec![ - 0x01, 0x00, 0x00, 0x00, 0x02, 0xc5, 0x11, 0x1d, 0xb7, 0x93, 0x50, 0xc1, - 0x70, 0x28, 0x41, 0x39, 0xe8, 0xe3, 0x4e, 0xb0, 0xed, 0xba, 0x64, 0x7b, - 0x6c, 0x88, 0x7e, 0x9f, 0x92, 0x8f, 0xfd, 0x9b, 0x5c, 0x4a, 0x4b, 0x52, - 0xd0, 0x01, 0x00, 0x00, 0x00, 0xda, 0x00, 0x47, 0x30, 0x44, 0x02, 0x20, - 0x1c, 0xcc, 0x1b, 0xe9, 0xaf, 0x73, 0x4a, 0x10, 0x9f, 0x66, 0xfb, 0xed, - 0xeb, 0x77, 0xb7, 0xa1, 0xf4, 0xb3, 0xc5, 0xff, 0x3d, 0x7f, 0x46, 0xf6, - 0xde, 0x50, 0x69, 0xbb, 0x52, 0x7f, 0x26, 0x9d, 0x02, 0x20, 0x75, 0x37, - 0x2f, 0x6b, 0xd7, 0x0c, 0xf6, 0x45, 0x7a, 0xc7, 0x0e, 0x82, 0x6f, 0xc6, - 0xa7, 0x5b, 0xf7, 0xcf, 0x10, 0x8c, 0x92, 0xea, 0xcf, 0xfc, 0xb5, 0xd9, - 0xfd, 0x77, 0x66, 0xa3, 0x58, 0xa9, 0x01, 0x48, 0x30, 0x45, 0x02, 0x21, - 0x00, 0xfe, 0x82, 0x5b, 0xe1, 0xd5, 0xfd, 0x71, 0x67, 0x83, 0xf4, 0x55, - 0xef, 0xe6, 0x6d, 0x61, 0x58, 0xff, 0xf8, 0xc3, 0x2b, 0x93, 0x1c, 0x5f, - 0x3f, 0xf9, 0x8e, 0x06, 0x65, 0xa9, 0xfd, 0x8e, 0x64, 0x02, 0x20, 0x22, - 0x01, 0x0f, 0xdb, 0x53, 0x8d, 0x0f, 0xa6, 0x8b, 0xd7, 0xf5, 0x20, 0x5d, - 0xc1, 0xdf, 0xa6, 0xc4, 0x28, 0x1b, 0x7b, 0xb7, 0x6f, 0xc2, 0x53, 0xf7, - 0x51, 0x4d, 0x83, 0x48, 0x52, 0x5f, 0x0d, 0x01, 0x47, 0x52, 0x21, 0x03, - 0xd0, 0xbf, 0x26, 0x7c, 0x93, 0x78, 0xb3, 0x18, 0xb5, 0x80, 0xc2, 0x10, - 0xa6, 0x78, 0xc4, 0xbb, 0x60, 0xd8, 0x44, 0x8b, 0x52, 0x0d, 0x21, 0x25, - 0xa1, 0xbd, 0x37, 0x2b, 0x23, 0xae, 0xa6, 0x49, 0x21, 0x02, 0x11, 0xa8, - 0x2a, 0xa6, 0x94, 0x63, 0x99, 0x0a, 0x6c, 0xdd, 0x48, 0x36, 0x76, 0x36, - 0x6a, 0x44, 0xac, 0x3c, 0x98, 0xe7, 0x68, 0x54, 0x69, 0x84, 0x0b, 0xf2, - 0x7a, 0x72, 0x4e, 0x40, 0x5a, 0x7e, 0x52, 0xae, 0xfd, 0xff, 0xff, 0xff, - 0xea, 0x51, 0x1f, 0x33, 0x7a, 0xf5, 0x72, 0xbb, 0xad, 0xcd, 0x2e, 0x03, - 0x07, 0x71, 0x62, 0x3a, 0x60, 0xcc, 0x71, 0x82, 0xad, 0x74, 0x53, 0x3e, - 0xa3, 0x2f, 0xc8, 0xaa, 0x47, 0xd2, 0x0e, 0x71, 0x01, 0x00, 0x00, 0x00, - 0xda, 0x00, 0x48, 0x30, 0x45, 0x02, 0x21, 0x00, 0xfa, 0x2b, 0xfb, 0x4d, - 0x49, 0xb7, 0x6d, 0x9f, 0xb4, 0xc6, 0x9c, 0xc7, 0x8c, 0x36, 0xd2, 0x66, - 0x92, 0x40, 0xe4, 0x57, 0x14, 0xc7, 0x19, 0x06, 0x85, 0xf7, 0xe5, 0x13, - 0x94, 0xac, 0x4e, 0x37, 0x02, 0x20, 0x04, 0x95, 0x2c, 0xf7, 0x75, 0x1c, - 0x45, 0x9d, 0x8a, 0x8b, 0x64, 0x76, 0x76, 0xce, 0x86, 0xf3, 0xbd, 0x69, - 0xff, 0x39, 0x17, 0xcb, 0x99, 0x85, 0x14, 0xbd, 0x73, 0xb7, 0xfc, 0x04, - 0xf6, 0x4c, 0x01, 0x47, 0x30, 0x44, 0x02, 0x20, 0x31, 0xae, 0x81, 0x1e, - 0x35, 0x7e, 0x80, 0x00, 0x01, 0xc7, 0x57, 0x27, 0x7a, 0x22, 0x44, 0xa7, - 0x2b, 0xd5, 0x9d, 0x0a, 0x00, 0xbe, 0xde, 0x49, 0x0a, 0x96, 0x12, 0x3e, - 0x54, 0xce, 0x03, 0x4c, 0x02, 0x20, 0x05, 0xa2, 0x9f, 0x14, 0x30, 0x1e, - 0x5e, 0x2f, 0xdc, 0x7c, 0xee, 0x49, 0x43, 0xec, 0x78, 0x78, 0xdf, 0x73, - 0xde, 0x96, 0x27, 0x00, 0xa4, 0xd9, 0x43, 0x6b, 0xce, 0x24, 0xd6, 0xc3, - 0xa3, 0x57, 0x01, 0x47, 0x52, 0x21, 0x03, 0x4e, 0x74, 0xde, 0x0b, 0x84, - 0x3f, 0xaa, 0x60, 0x44, 0x3d, 0xf4, 0x76, 0xf1, 0xf6, 0x14, 0x4a, 0x5b, - 0x0e, 0x76, 0x49, 0x9e, 0x8a, 0x26, 0x71, 0x07, 0x36, 0x5b, 0x32, 0xfa, - 0xd5, 0xd0, 0xfd, 0x21, 0x03, 0xb4, 0xa6, 0x82, 0xc8, 0x6a, 0xd9, 0x06, - 0x38, 0x8f, 0x99, 0x52, 0x76, 0xf0, 0x84, 0x92, 0x72, 0x3a, 0x8c, 0x5f, - 0x32, 0x3c, 0x6a, 0xf6, 0x92, 0x97, 0x17, 0x40, 0x5d, 0x2e, 0x1b, 0x2f, - 0x70, 0x52, 0xae, 0xfd, 0xff, 0xff, 0xff, 0x02, 0xa7, 0x32, 0x75, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, 0xfb, 0xf7, 0x76, 0xff, - 0xeb, 0x3b, 0xb8, 0x89, 0xb2, 0x01, 0xa5, 0x3f, 0x5f, 0xb0, 0x55, 0x4f, - 0x6e, 0x6f, 0xa2, 0x56, 0x88, 0xac, 0x19, 0x88, 0x56, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x17, 0xa9, 0x14, 0xd3, 0xb6, 0x1d, 0x34, 0xf6, 0x33, 0x7c, - 0xd7, 0xc0, 0x28, 0xb7, 0x90, 0xb0, 0xcf, 0x43, 0xe0, 0x27, 0xd9, 0x1d, - 0xe7, 0x87, 0x09, 0x5d, 0x07, 0x00, - ]; + // some random liquid tx from mempool(Dec 3rd 2020) + // txid: f23c8973027aa8c1e86580a729833914f5b1fa710367db07f1f5515aa3729f16 + let tx_bytes : Vec = elements::hashes::hex::FromHex::from_hex( + "02000000010232228caa1ed022bba919d55646169e6094fcff7580cfd51767c52ad1f1f7be1501000000171600142e7c87cd55d9163b58a0e98cb0c076e8fbc916c5feffffff28b3920a83b2507ca5f3993c84cf7b69021be6887e86e1ec8855ac432199de6b0100000017160014edfc058e1c059532d7aa90cfd47d1bf4f7d2d9b3feffffff030ae977a025f8432195181d77bc6523b7769b8b4d4c3cf8f5a45bdb940c7dfb561e0878ac4e6ab21b09f64a262d0342e88928cfa68bc9e8d004109f9caca1b305d37e0217abccf88d3488db1e40f79dfaf14ca816431441095bcb2570c9a0dcca01456c17a914b5687f4af9ba5dfb00ed22cb32a857e607bc844d870b892d93c6accf6253edd8340a6c27caaee3d5dc067e626d3c1647dd895299404a0936e562586398b4e95fb34c108afd4a9de4268bd5cd41749833e7a9265e383a0e02658aa567bb9656cd06b563855dfd63fb3e8b37b7991f86895c536af5692e819d17a9149f03346fa3e2a4fbeed0e5c379f2f2ba28025bc187016d521c38ec1ea15734ae22b7c46064412829c0d0579f0a713d1c04ede979026f0100000000000001060000e6bf1000000002473044022020d643f80c3cab90a76dfca198bdba119db97cf4fd8d8adfa2609cdbcf594f570220324c54a8e3170d06cea60511e5e8a84d1f41b21fc252181d69aa8b7d190c26a90121029e831ee2345cc698c730b713b82b99e8c16ffe5ddd51c9b36607bb1fa68b1395000000024730440220645f22a912344b4f8d165814c3513e9bfa06d42066fa4d392f783a57444eff0202200b98199177d9e13c9f3872f433154be430e82cc2e312791a47bea2a91167ff4c01210303a640e79b1e941f2d7867e4eabffd79f30daaabc80fe1c4d974775fc29339810063020003c7b8cf924dff6cef0c9f9b507abc741e431e1a793206b5fca623748f583e6ad3f3f7a4fd1b27d78bf3b4debc9a2c849b5f85722cab872a9c08f19e6eb59ceef74fcf9a7c38acd6fdb7821c33bc731011b6440e8aae7beff3ee6ffc1009c849bcfd4e10603300000000000000012361c401dc40cd92cf93029e64c20c463d17c6e15d2997ab43e8c4b1c412b99a1a337c3cff34c6e7269580727c27404a131606aa85dd8bdf6d46b6ddb23948a0507e31666b531e753eb8fece006050bc7b7e2e06db86833f9adc4f6b77c7b88537617c3accea246a8fe303a23697801181efa8bbcded7690aeac04b58e32f4cd94279cff059a5c9f679d973007042fa1e5e4268a884dbce7acba614d57e1f7a56f81354779ab353c04806ad280417f656a10bcbc683348608d7f6e08367d9c21318cfe6f03a0a60bb8269d667a3ddd57f77f8774364e6a946a6163896db4a8b7e2446d229d8d3718f9a8d7b57a2dfe43baa79bce705512635c775602404d55a2a61fee93c00f1e0cc466bf2cf0bc070591e228de578bc51a38b1927ae29f0e7daad95380f5be599f50367ae1028e57f2e1d9b35d45f0fd860491c2c03a46500f2fabf3c8c7cc9ede16af01eb3d42633ddb8ef87bc043c136f727fffcb4aebdb19f674864e2d2a20aa0d61f7e635c582a79320ff2dbbe8ee74c4c51ee09e03e89581378fcc4d2a6f44524add57be3d31d3054468662a805fb82bd047e5b52e13415ea92c57532940985ded222477f5bed2bdedbbe1f2471b0a85c32a70b0d07a5c734ce90e62b51babee95c63f2cd5ba7b6716a9ecb2f44e5f30f2bc07ea7fef5257aff812558385b23966608191436cbeb1f9fc6a907c1841e1dfcea0e5719749969d447bbb9b332389b2e046d107a873c1b557319250c6469ea03c7bd2c61e6a31963921f1854c5546479044dc992f09c2e781c782fa7dc116074787495bedd991b530a7a8b49bd0f85e32ea19bdfac28152bf63eef1a8bd9294b51eaba8b5bc4db1d89031e4dafffff9bae4329fce27eac3db83e6d5d22ec4e04850b7af49329828e087f2c998882249cb74234002c6ea20641f6b4dd0199e4d6d932e75cf0e59a07b25724826fdfedae239d8275c992456c53a2d4c469f08120554e86c4693c32284616279eeb048d434288f6f73dec16d3d37ef0ed2a98e5c11cf6ac046137bbd8b76c1ebb00e8804133e0e647751ab6b395cc49c92d5084bbac242cbd8369c8f56548ca92b8a1da586e12f6163d2d0b42b4774e6c8eac23240c51ec79d71dbd1a69441265c6d5e4974e3ef6358b30106649744c9799f6ec1de0cc98929f02be8396c4d807e87a767e3e2903633da774ff39141e14aba0c01fe3331d7b888360722ebb1c3e1fdb1b3226ecd2003cfe2400ec9ef29f431d6e5182eb98c8aa4384b60dca8dd0d28ea52fdb6b181c4788b6d0d2ce9c5dad4dad259a83eda33c21704d4230660f8962c69d290e77e60f16916d5e1bf811eadd054d464fe33232541070371a69b120402a9598ceeb9c8ffe0622c90fcffcf271eda70128ed21c5c1990c3361c11f7f697eb56a32bc15c5e79596274043eb80da990fb40e31a0d8a83d62e53e9d9f3f786de418ee3c9a1adc54ebb43b74db84d550dc8fb64aa5a0947f23d9e07fc370e141217b0766186315ad088c2ccbb426e3a184ad74b5469ff24b596670617bb967aaf8976438415d851bc89a98887e2388161c967b8ae9e5814af518097c4e0604b6ecf755527ade18ae0a50234a95f4b45c1bf68ada13991928dc88b61abf23445ec95d3f06830852bba27e09a7747b1c06a1d4948214be7e0bb47f2c14f6ac02715a250232ed15767da55717adb239149d8021f33bb14316622947d98d30506aaabadf90add3cd639f5e558450642ac7b4457da522efb710124518a0a3f41b6798127d209768cc526ecc9a6f70edbe3c3b9f45536295c2bcccc384b0d13bd6d741e8d1d01ac83e09cf35a9e7ff71ffbe2926d8b3c5d5eb131fe9b023d427c4c8c3e4310df6b3c45c2de0092df59e98176b1bcce17889b3667854785a668e3c08aec571506e5074ddc5e53eba12a5c2adde7a0fb7c1e5b86b1463e7634980985dff039d90abdb0d11cc701bc8122e22000d3eb4ee0ef422a08e85bf13d5bc856e79c0eb00f415a8a76d6d7bcb087b090954e0476c81ad1ba3ef89b8b5338c78a8c0b5b25cda36ff35c307900066fcf775cf71069a1e396b0efc611b21ecde8b5f080252d16690767137307aade91486ff23fd4ebd206d0b9bbe9f2221003967fb76e51925954fd5df40d0c331277c35bc7bd40c05ed32a4cf577ccd5219b113df6454258cf1cdf123e8ffb7fab3debb05ac9f3a3874221bc6d08db4c0e2676257ba26d2b780c6102014f980987c6826cdab6a713cbfeae703af2dd8838378dec58b406ce2e796a560e1bf1f7f4d09f32c0cad9ad2fc3720ce3963273b6768250c3119eb4d9c0c722cf64733432fa76abf07e845d55f089df83dfb784cd7bae06e1fa26b7464667945774f2c61afac025d4f5dcc909cfaf064ff9b622b680eab1e5ba31b8374fd2402ffae1481cb8ef180060c8ac01aac5a4f9df8b5e84b7872cdeda17cc5b37a4438b6c31b917a88336cbbd535f95710f1d4bda8dee8f9613c0e62e9914dbc8f2f812783b7b8a154c74c17ceb1df61880f61a9038ade4a15542a2258d0533988303e1d22910448fa3d8aa29660efc1af8d8c470da4071960ab55a83627da6c1d955e337fff0f18e6e4b0516ef21beabdd3174ffe816ac945ab7c54a60a56898fce13b782820ad6b6e4f72a82122366695b4d5c332490fe8caf98b3e77749ac06fcaa0a1be7e283201e7dd0ec43cb728da2597af4a4d1d20836d1d443c6fd24d57881ec9b9d0af6b99f04511ba2f526c1647f0c67328f7ea7952bbeaf41198ba56ddeca73e8f1e028d64c9e8bc9515aa9f16830add7c705d6aa4fbf20ba5aef213088d6e4ef4453dfe0b0c531ea19ba772969881e5945e353b43ce2940d9b6898855092f6426b32ff0f1037e1d9799e30d93b1b1df71b70bdc52fcd9db0cc87c55894244da38cc6534dae903d84f7b636cac2d26ee2ee3c60a60c9482dcf3ad679936b6fb4e6a16bd3275f6452a0a47dc0f5acdcf9fe17c9fb34ac669ff277964d5d77062d4dc69ffb3f376665972fc8300df0d3eca41c769891013cb14d70b7600300d030e3ad5d6f9394786551857c5beb49c0eab658b6253ad647365912062b1895ceb0e5ddcfbc73034414bb7eef9bc7a201f735d67edfc7c07c89f3ea8a98bdef324f606ed2b4b3989d0d019b73cd8ac9bdf062c53a40c13ca97f848e152756c388bc54d4ec86104962ea8df3a9430ee4f1bf6eb57862faf2cd49e21db5982dfb5386a23b47b4d010b4ad79f600161ba2d5307b9199977493c1e8a696d2aed4b85d7d52074438c76203baaa50bd96f5d2c9063b4e3033f090021c996261344e44aece16bf926614a5163b70d7f6a03d13462e0858b1911763700503b96505f471bf084d966dd7cd8603e3ffb9b22c572526526d8568e6bb55fa77bfe334ffee4edab2a58719aab70e9e8e8102eadb5391d6a188006a42daee2b2ca71da99b80360fadc4710e7a33d8c3173cc69b5824d93b5021718a276b0ab706029d4378df2fd1183cff056d463a2984c5aa397b9cafd1d3e4a209c04e499130980265d5e2930638bd12656145667bf0dfa09bb61c5c6801d158fead98b3ccc6619c5ff643bc629a57cd6e9b535033d29d4ebdfcff607c50b6ff979fd22fc1a07d796672d98d8b2be6c6c4a4f4911179d18cb29c4c8c490a091d334975033e2ae1a0db38f77ee252f22d4555d0202b39671b6ff29d564cf7b0252ed748cb1a5770bb5aa913d37a7e6e86814e1f34baa5ce474fdaca3bb316cebc4e7893c6452d856a4245434b26b52a37d8aab8f5da3a325e313858e40a9405307de7f4e80d25b3cddea6c75f8595a1c235005a3f0fc948019cfe9e68e3cd0a2affb2f073374d0a03ac55ce9644a92b28409173f763e6a1f7946c1d16ac14a792bd5aea57e404d8a684bdea0b430572d2e5034c949e6c9e74a989d0f91dd740b174e4853e2e693c011d2d29e50bef9fee62079eafe87516448c5a045a16eb569314c9410d4363619ea19aee6460e72440d1cc2a368429c99f10dc8707662d64262113d8eda7842a6233e9ec5c1f7d8c71dc6efedc4bfed53fa9646171caa26daaa0bf4ce50d7b02864ffa0d860014e4d08425f0c1fb840d69a55ec84a833b45dd4572d05d2bcd7dbc6a78974dd2ed0bd52a0343fbd5421c05900da0b7c4e9e42e59183bd2f6e5058482fdd165e346b3ca22f35934538e97110321709394ee446087d97630aa17305fd5d1fd697a4ff3477fc7d02a65afd16e2a5aeccf9402c9dc90f4550140b6bebe6554203e44b9b02472a47c524f4a9554dc25484eb8f173a878a975cdabdfb7993b5e097dc04ef93a7833e51a61b64a6b62743252bf60bf665b9c398688eb24db0d8d2acea848859864f426c08649d29aa7e1d1027318d55624ef750572616ca5b5a118afb83b6e0c872c29a5388f954fcaf8f6d0e67396bd54b9115abe02ffdf52878cd55cb0c5d97cedf41794880ca2b88f960a37d0241ad3e268b3ed142285cfb464c9b0917234df19aa37daa95b935b9f035687d1ac0b7334855e5bf0473175d54c31cf520735d04f5b4ac272cb57999fbb8cf49bd5de6b44616c6a103f53dfc86f874cd847c5adda25c50f3615c0c9c63b34c32142ed62e49f72921fd35753e0ec9333add42838b383684e5246c154cd3ff6310933a36798a4020a027e8f0f951f0dc2dcdb26f0922c69a8dee7183332aa534fa25523469e7bca5917f37fc822b4e7ea5123ef1b8250b2d3dfc9c24ade46394ade3694d528db41108801c3618fd98878e837794023e5ca756e584fd2853829c53380bbb8a281998db43e3ee44e4e6cd08a46c958fee413af351467b7c8275f4c74d4111614eb90b3b10c044c88e1c00f3c2b7959b71bbabb25afa0f564b1449ca585066cd77721fbde395a2c6337be3d546a58498c5f21abf8177c7299fca76e4ae951847858f50fb07cfb10744b770f115e35f57dc70858cafe3a9f66fa406b8a5da94a07699672eefb557fa6adbea267961fd9e4af73a1efdea261a249a573258dae670de24a3d96f36cd146d02370921cacdc155b30a80ad5b43c5db9390ee94e5fcb33b574f82e039371d25fe1128effcb536d5d53727ef341043dd7b07db4c41c117356b3c5379e486f19a4bdef1d3afd4cd0f5fced5a2fba436ee2b84d0b326cc4fe19e69ce05906d3e36c62d9b9a4695bd3ec0d0e5c5e8578dc36094fe879702cf95577a2616244fa3dd7f07c998836793f367d973f640138896f18461b877df9c5a86b046bfab5ac5692d85c5ae632ac51dd726aea59ff471e3f50579204ba6c5868530205faaf81821a57d462ffc697bdd82ba50edd8d3d7973235b57be2d0f9a84ded1d0f3cb68555c2300987f2d14768d2f0214abaf29a06d5d37c5296b5c2db62b2169e1a7bd425cef00db2745d5e246f66191c522f58f0c14fb1d1c8fe6b5b95a45570334125c190a3a9a3e77deb9ae7ca45d973fc64a89d2bb6420286ce01649d5de80ccf89d34bc0c2d9e49d8ec713d2c83286bded0d30a8c42740678c6538d8a7e491b2d1007df7d5037016018c6bd36a21247c13c1cccad168a6a0c2dab4c01185b6d61c31f0f2410b83c16a58d7cdcc0f2522fbe62fb27b1094f77229bff259433e9265d5656931b6d47210bd1e1a66a6c6a3dc9c6a2912a8fc1f05eecf40b751555e569ac2f92d1e5b540037c0cf5c9e658a17fb9dabdd23a087ebb90411f5e75f70bc7786855a2adbebd889d1946ecd6be827e2a624e256c36226ec87f2b92f978be8ffa98169f8dd4947390509d2397322da63757dff25a86f9872414209c50983c570558fc1a630d3caa8f6a59a16cd024f00b2323d2884187ec92feb19e078690f51940229c6076dea7064e71fcc6c55ffd643ccff5b63020003f6512e0b2e6dd0d8cf0a8cde59f4ae60632a902889a27e469b0e844d8477b471a779f720b7650e56645f8a044bebcf4660405a5185e75d13d0c15a221fc707860cfc260b9f9ab3c98e616cc574bd05a494faea98c1e06f9c7ff9643b72692a4afd4e1060330000000000000001cda9b801f96b13ea639d611a15e8f3e899ccf6718b7add1e0309d6c16a3f9b5699cea2fe90fb96147e22f3b025013bb9e7e824614a2c2c2e2e9a420a98b91d71170d89e7aebb414266337a636ec3031021a51701dda9113e9c1ce1291fa9ccab9dd8f30ab98d9c5ba12894af05b42573202c8a5a35d4a49dea9873c2f9aa51ee263a10e49cced91debeff76caca98e07cb7e7705fb647f3ea846cafa1ea4e8449022aedc5210ac18c0a2585a435cc2249cc997aa7e2e180fcee7ee92b1dfee29b3fb125c3d78938b8e6cec68f9fa16643460617013d0bf65b453a124fb843326020adba9ea96bb1832cefd6ac4126329187329b1f31f713b097d34cd2be2c42b70db5e5153a7c5508496f4608c8b7c1c3d8fa7dad87c76c06c278caf0a429d49f4d4fe367894ed493b1e91f331e0533b8364241527adbb867a29bb47fa8e8b5156d5c0ec9042c900f5b9bbe352488b0c671bcd381f090b7fd5ce469d21e01cf5b3f01fcdd51b3fb0eefb4145de49a9444ab161522dbd465169f6d9814b2646142fcc9020aee9742246442a63486e80aa341c81be71f8568080f7d700308f0f79dd8295345da0bd852f5aca16e5fc0e686d7642ef70f46f6d836f3a5bd2b14919fcd5e55ed2dfa8d6920681cc26d1a31f68bf6580c6693b8b877630746cf3a4bea53b4862872941f535345e69a7e7f0108cc72b741a952f4aee5436e918c6fae307277cf20b1fc4035f9235b82c18a24e3159563afd240b0cbe671f2138e6d3b9a4efb5e073b5428e4073a3610c79643b441165121d2d85a311f334b751fac62afec8e1bab82138c5ccd2154f1e00d11e00fae9fc1621ed68e1a32c71b7bd98d5d6328bf7bb3ab5227391eed36335fccd1f83a4b6854a2ddeca1942f8478391be3468693cec629918dd66f00f3a520b495c7f6eaa22aa90a3ee22f29c9ff4e473b739e7a7581411ae966f0d5aa9f157d33066f868a39a0af75199fe886d62c8112ba5242becc11cbbf038c28c41674c643d24448ec7336c98dec3ce09db944476df456e661dec9ffe471243b16888496d17093cb21f0cf5360c884107ed584a24ea151f6f91ddbf92848afe9ca936aa68f992d13aa42e470284454e3b7fe31ec28c33480cbd17d44b941f4f99fe269aa51319cdae2f454d548a41d801061beb6466cf3c27c0efdbf4ea556d57dfd9f52798214f73c3506525426a59820d037285f61465a141e394de9970b6c9b6297ada4d4d65fc5b9921a8f9e6aad8a9ab2d587f193b741c8e5ed879498e7374bf1744f6a72566745e21012cec8412ea8b8eab9712c740cc71c1c798c1d2df850426f69ce311ff1f537b87ae9a31935525eda54780f15dc73214043ece83f1629e36c55747af1ad8e84fa5e535422b09db9a3145022691e189c9ccbacae70ebea3f6b5f2e09376b6402767c85a2ede43761b4ee5edb436d9f711dfdd84e1ff0dbd35c607ea59d9d5c8adf7fa4bf08634e8cb5027da6bb8a8cd7ba1e87c40be3bd9c5554fae6bf7683d25a57320b63d20c66f0df976dbd688e8c4ccf0dbe755f0d31fd6d1b81c6f1ea0538f73c99cd239efbe92b32b946f07b8b53aba95f05d9e0686e188027061c0c912bf2c27cb83a46cff714e57e4f072bd1df08e23640bb64143ee7c866097f8d360800abbff28a94ef7b035563ce396c9c68ee5c7751c070c364e0fcfb015749b480a8bf4cd9626541e40af879213812612c2e04d688629239f609219a45657693c4cb81c85e156a0c9b377d6123340f009a265cec1a2fde0dfa4676b80dec9b6544712b202cec1467d8f440c2c2a1d13862fab29cb266e9185ed6bbb2c3f3e168bce4720ae079194948d41ed8bf7913fa1d9d8c0dbd39e4f7855f807f6192d3269fa0a98a3d6c27992798b70b59e04dd63918c7ca99054abe15fad0f567d9e993ca49eae695a820552575ee569c4aae328c5db0c3d04d1692ff561064345ed32571d072ea4a2613ce163022287b86b9797223619e556f978deb6711051eab698b43fcac2d62729addc0d699f775b516ed1952af9c5faad43601a4a23007ee2d6fbb10316bbb047e04596a57d5ee871496f85ea460f3f078e6c5a3f629aa6b67e4dbba3ace0a3ebf870a2800a5c7f27a866f6d58b33b1daf49c27a0e8057c169eb9be30d3a42e92676f45be824c12aee3f35a59e1cd23bb39e0ab5a87d43e5894e7aedfa7e74ee6554873db5646eb0e7d2b79f970c4210bd9be2b9294284aa26156ba9773c3422aac0489ba21878bf569c79cc548519221c3c90a706460a2d32e073564d7b6133ef25e14a6e327bc15e33555a299251ca49006225b18d8890b45b49e8c16aab18b6e7ba1e932750e81de679d7b6dff571a4cdf737e06eeec7cbac678b8a58e2142615b71cce7f26187a9c1490407b32c48783bf848a830a41ae89989fe68e50307044c34ac27f360b3679b1e4b76bf590cc131f2e25634c9979b4c98c392fa862cbda18e5c5ac0dd25f3ea6d9fe88691aa4d6399d4bc1da5218b95a8f8ab6683a1916eca043fbdb23137799f8e8e6e240ae4693e1896b942ae548a9e948ba77b2770ac8d98737bdb47d04e5c233da80d84c806e8585e73ab7d4f24ef5415a8ff01392e05b267159736f222a7e88652801cc7b9a3ce955494df7c556d1747255f9492da41ce8dc9a615c9b926f83436190e458f7b3994e425d560f35d3a79753e637c8f7dcfae559e8a76a956927c870e5f6da04a8fbb805253af55e52f3895811825d720e35e305c164ebad8a8f5535e6f1a9023bf6a6f1087361a93741a245b4b98920f539b21958c47b68e8f11bd764b10fba33c607fc70b126263a07216d72eef2d432083a6e373747e345cf6f9362a9bcd52e865062cb026484a21a5794dd4a1171560caec70cb607c324537007b74a36a1ec02033d8fe76196854c4158c47d0535a98eb15b74e347ec308728a32f7b75f06f3ee753cd0598ecc47a563978eba8535eaa012ae9555551a1c3f39c1fadbb66b08cf6f560a820fc4ae281f2bf57fb092f4d2c61568192483e86e65d5d58e86b1a873d06541227228209a98050728216b70185cb0243b893882db43e9a7aaeea366b2dbb13e9c3e707f1be4473d790d676695f1ae7bf1064c6f0a38e8706d37b652cff8e9e1979236a1482dcedb391f7f62da88843221043e472328e7b3fa57e7e06e4eaad1afe822f65002b18382ee5ccd890bd84f26ab467bef4265828e7331c31f119beadf4dca3773ff4a1f1b55b6377bfd9ff5277e740e2c60faa7111547f3ed0325a509582c4185de57a7f0b97b841736df3e69f7f73dfd831967ca615e29c4ffece4c229a3da65ce8e9e7bf12ae10b338290fd80f887cd11cc452ac0b89800c1694e38fbae1d83d7ca0112ad57797354315adceecef61c35fecf618144ed59f84c6f5b9be75de6aa218616bac4eade6dd148b9d692f5df6dcded45b3c3e4a3d679ac91a4eff540f608a86432a749aa119db5e3ea92ceec1ffea5c467bdd5dd3798dd8893e8e3972709025619cada7a880f675faff2a257e18164dbfb1752cfbd5dd8194cfca896298165c0dc4369724b59af981350c4514107c8ff09fa39eed4d76e636a21cdbb3e0c0e5e8f510d087a591135b52f974576432d3e3bf2f46b036c7e140547f96ebabfd0cb242102b3f66aee8560be27657d0900494c33eb8a02dd36b8d8b0e6df3d9931fceeefbc32590050838eec27fa778ae6347102442dd21e300b50faf8fdfabbc1161a6bfe5bc795fbcda8994091ee954edf66f1ba75c4acf7965b31d56db077396bb1e7e9c8286e63e54b34cf5ca11bd8998e40f2c4594c55d7fa8fc2cf9476612d567996307d21caf06e49daf16300bc1f70d70d145e64d92a0b307df6a3d3554d6ccd0c550ba193ff6b818c6f2b0008f17359e8617aa927b9f771203faf28c2a04116f8f048ab0228adad39c8aa3e8c8381b1e60886138face97ae7f3c02d2ef854fab2ef27425b23fcf05bcbeb3d752db854313084ddf7a8833369c17123c92d63c744df22ffaf10188b2f4f4cdb53e5f530279d4419b0858d44a8a8f76a26b78be327bd233bf12709ea04a4cc6b6ccfb20808519f316d293bb4d605e4cee8373202f4e95e28bfef9c42354672526a7a81391239783b64d74c2b04686438411614e7228d34e457151f71d742bc514aedfc96966ea35297d5efa513e8dbbc10e8e16eac2ae14848a20c165f59f1362b57b457f6786b0e1fd492ca47d6aba03774e58f4e09fb480156b9c96ddd13c441fc8202e40a12b30396482778ebc439b5abebcd140ad3848726b2908f6b594e0ac7b307857d68fad4ab05e4362140ad2e4b4606e365e894224dfc033f4c069dafdd7f819d340f6a202592243a27f67a38f20e5b07136f5b503c3ca209736c0a7b99749a9d36a82b2a6926a426c95b5f60c835ab85038469e1e5bce2b6bc2d47345d8f1beed8e678423ca3db106b554890e8a701e5d43f6358d957b8c799d1cf20da1f577df0261717227113e5d1127849bccc076947beab3e3aed964b83c94de9322591b67d6fad860d5a5c642a545aee2d1ee6756723a96d74ae2f431876fce6d8da50628fc51560760c97aa66a258f41a9d958a1e3bb0e27a58eda5f6806a115ac6aadab545aebe2d9c6c6c01cb2dc7e8b36c7b37609563f84f409b0813047fea9220872becbc9cd976d502576d1dc2a0a5612a7a1452e46b35149528ed2cb74b18a221c2281d7b766d28a30f763d7375f6242f4c361807efb711d21426696f77606cf1c37c4c162fe79e444c622297b0849c88995cf248a47c49d1932b6ee79c2c2f3dc77fe97389855c50cb797b0fddaa94c31d5b23f4b7f7d24131aa24d6d53e9a43a3e2c360c3254573355ddbe850299f7ad13f8d0676c8a39ee2a63ec700eb3b53d9abee536a6a2bef272527ede483a09913d8d839cf69c828654ccac0581552b342b0735f49cd5af77bca8e8926173e58320fd4738e574114736cd480900c001766cc72266f7c7ee34dd27a109ad4e932c28d0ce1292df46a0df42f23a48531da38ee14bae23d27e3370434efb85546c5e53c10e8407084e833a65a91d07df741174fbdd7e1e5475d273b1c30fc3db1227d32739889b45ac3ce208f82523d51e2c5a54b6c325ed1fbda4748b61090a838bdc5ebdbf1eb2280ad73028ed8ec40fda49cdf17ce0e158521ea5c99cb8fca79a91e7a4011e6aefa4235b4cdd5344356ed7fb31b1e575592fa2dd89c0a2ce151e07d049d67463d6fff3b2b830451fd89c596d9c807f57aba74fba0a24649e27c849376ab134c5c7f501fd1746b75866f3e74f854029e2e73455a60ec76df9df208b8ce5feccf2b6a2bb743e461924cbd22b91c9e89dcbe231474f26678dd91bda846034f4d55ae97aa1a569c5a3bbb399682ba6b066597a1be9d9a1612cf3faa7cd9b0dc9a3d47c9e382604d7004bb10b2e29fadfa2e374c806a1c7f95b169c4e461bcbe907d3a7ffa6af042f351fc908dd67c522802f0ef18eb57ecfc6f2e95322928e5b561d715a29667ee44382be6eba5ea8577fc926ba742d0d1f345263991610174ec46c5989d8546cd37904089d5327e0787d136d3b13412b536bbdc385990ed35cf6454874c9ce362acb0a8146f78b3f20140c34519c9be23051ad0e0620d50dc3319bfe072c46952cccfa44a62c7afeb9bd596673dcaead2a279175b9324c922a6c17959eef9e6b15a1d952bdbdd74e6eaca13735db8132e0c26fd8eaac1acc66feb76e62760e6746dba93492891a048babd276ae5c4ec2fdd0afc8c1e570a2be342e6d98c3ed004e627ce95e06770227f7b6976a0b2b8e2769e8a1061b15834582c581cc36c0e28909153881e4ee0f8770392a04b3459b1bea67a4f4a68270000" + ).unwrap(); let transaction = - bitcoin::Transaction::consensus_decode(&mut &tx_bytes[..]).expect("decode transaction"); + elements::Transaction::consensus_decode(&mut &tx_bytes[..]).expect("decode transaction"); - let spk_input_1 = bitcoin::Script::from(vec![ - 0xa9, 0x14, 0x92, 0x09, 0xa8, 0xf9, 0x0c, 0x58, 0x4b, 0xb5, 0x97, 0x4d, 0x58, 0x68, 0x72, - 0x49, 0xe5, 0x32, 0xde, 0x59, 0xf4, 0xbc, 0x87, + let spk_input_1 = elements::Script::from(vec![ + 0xa9, 0x14, 0x10, 0xc4, 0x65, 0x2c, 0x0d, 0x2d, 0xf7, 0xaf, 0xaa, 0xaf, 0x82, 0x0e, 0x48, + 0x9c, 0xb2, 0x7f, 0xae, 0x60, 0xd4, 0x86, 0x87, ]); let mut interpreter = miniscript::Interpreter::from_txdata( &spk_input_1, &transaction.input[0].script_sig, - &transaction.input[0].witness, + &transaction.input[0].witness.script_witness, 0, 0, ) @@ -126,19 +78,24 @@ fn main() { let mut interpreter = miniscript::Interpreter::from_txdata( &spk_input_1, &transaction.input[0].script_sig, - &transaction.input[0].witness, + &transaction.input[0].witness.script_witness, 0, 0, ) .unwrap(); - // We can set the amount passed to `sighash_verify` to 0 because this is a legacy - // transaction and so the amount won't actually be checked by the signature - let vfyfn = interpreter.sighash_verify(&secp, &transaction, 0, 0); + // Get the previous confidential amount + let conf_val: Vec = elements::hashes::hex::FromHex::from_hex( + "080e8899a3c271573359a179b27b59af180b36461f959ee00f762d9c2d84192a06", + ) + .unwrap(); + + let amount = confidential::Value::from_commitment(&conf_val).unwrap(); + let vfyfn = interpreter.sighash_verify(&secp, &transaction, 0, amount); // Restrict to sighash_all just to demonstrate how to add additional filters // `&_` needed here because of https://github.com/rust-lang/rust/issues/79187 - let vfyfn = move |pk: &_, bitcoinsig: miniscript::BitcoinSig| { - bitcoinsig.1 == bitcoin::SigHashType::All && vfyfn(pk, bitcoinsig) + let vfyfn = move |pk: &_, bitcoinsig: miniscript::ElementsSig| { + bitcoinsig.1 == elements::SigHashType::All && vfyfn(pk, bitcoinsig) }; println!("\nExample two"); @@ -159,14 +116,14 @@ fn main() { let mut interpreter = miniscript::Interpreter::from_txdata( &spk_input_1, &transaction.input[0].script_sig, - &transaction.input[0].witness, + &transaction.input[0].witness.script_witness, 0, 0, ) .unwrap(); let iter = interpreter.iter(|pk, (sig, sighashtype)| { - sighashtype == bitcoin::SigHashType::All && secp.verify(&message, &sig, &pk.key).is_ok() + sighashtype == elements::SigHashType::All && secp.verify(&message, &sig, &pk.key).is_ok() }); println!("\nExample three"); for elem in iter { diff --git a/examples/xpub_descriptors.rs b/examples/xpub_descriptors.rs index abd8e195..566476f8 100644 --- a/examples/xpub_descriptors.rs +++ b/examples/xpub_descriptors.rs @@ -14,9 +14,11 @@ //! Example: Parsing a xpub and getting address -extern crate miniscript; +extern crate bitcoin; +extern crate elements; +extern crate elements_miniscript as miniscript; -use miniscript::bitcoin::{self, secp256k1}; +use miniscript::bitcoin::secp256k1; use miniscript::{Descriptor, DescriptorPublicKey, DescriptorTrait, TranslatePk2}; use std::str::FromStr; @@ -25,22 +27,22 @@ fn main() { let secp_ctx = secp256k1::Secp256k1::verification_only(); // P2WSH and single xpubs let addr_one = Descriptor::::from_str( - "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))", + "elwsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))", ) .unwrap() .translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx)) .unwrap() - .address(bitcoin::Network::Bitcoin).unwrap(); + .address(&elements::AddressParams::ELEMENTS).unwrap(); let addr_two = Descriptor::::from_str( - "wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB))", + "elwsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB))", ) .unwrap() .translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx)) .unwrap() - .address(bitcoin::Network::Bitcoin).unwrap(); - let expected = bitcoin::Address::from_str( - "bc1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcspx5y6a", + .address(&elements::AddressParams::ELEMENTS).unwrap(); + let expected = elements::Address::from_str( + "ert1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcs2yqnuv", ) .unwrap(); assert_eq!(addr_one, expected); @@ -48,23 +50,23 @@ fn main() { // P2WSH-P2SH and ranged xpubs let addr_one = Descriptor::::from_str( - "sh(wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*)))", + "elsh(wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*)))", ) .unwrap() .derive(5) .translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx)) .unwrap() - .address(bitcoin::Network::Bitcoin).unwrap(); + .address(&elements::AddressParams::ELEMENTS).unwrap(); let addr_two = Descriptor::::from_str( - "sh(wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*)))", + "elsh(wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*)))", ) .unwrap() .derive(5) .translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx)) .unwrap() - .address(bitcoin::Network::Bitcoin).unwrap(); - let expected = bitcoin::Address::from_str("325zcVBN5o2eqqqtGwPjmtDd8dJRyYP82s").unwrap(); + .address( &elements::AddressParams::ELEMENTS).unwrap(); + let expected = elements::Address::from_str("XBkDY63XnRTz6BbwzJi3ifGhBwLTomEzkq").unwrap(); assert_eq!(addr_one, expected); assert_eq!(addr_two, expected); } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 37020e88..282c6141 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -15,7 +15,7 @@ honggfuzz_fuzz = ["honggfuzz"] honggfuzz = { version = "0.5", optional = true } afl = { version = "0.8", optional = true } regex = { version = "1.4"} -miniscript = { path = "..", features = ["fuzztarget", "compiler"] } +elements-miniscript = { path = "..", features = ["fuzztarget", "compiler"] } # Prevent this from interfering with workspaces [workspace] diff --git a/fuzz/fuzz_targets/compile_descriptor.rs b/fuzz/fuzz_targets/compile_descriptor.rs index 94e85652..03c7871f 100644 --- a/fuzz/fuzz_targets/compile_descriptor.rs +++ b/fuzz/fuzz_targets/compile_descriptor.rs @@ -1,4 +1,4 @@ -extern crate miniscript; +extern crate elements_miniscript as miniscript; use miniscript::Segwitv0; use miniscript::{policy, DummyKey, Miniscript}; diff --git a/fuzz/fuzz_targets/parse_descriptor.rs b/fuzz/fuzz_targets/parse_descriptor.rs index d8ae7d0b..90a3db4c 100644 --- a/fuzz/fuzz_targets/parse_descriptor.rs +++ b/fuzz/fuzz_targets/parse_descriptor.rs @@ -1,4 +1,4 @@ -extern crate miniscript; +extern crate elements_miniscript as miniscript; use miniscript::DescriptorPublicKey; use std::str::FromStr; @@ -31,3 +31,30 @@ fn main() { }); } } + +#[cfg(test)] +mod tests { + fn extend_vec_from_hex(hex: &str, out: &mut Vec) { + let mut b = 0; + for (idx, c) in hex.as_bytes().iter().enumerate() { + b <<= 4; + match *c { + b'A'...b'F' => b |= c - b'A' + 10, + b'a'...b'f' => b |= c - b'a' + 10, + b'0'...b'9' => b |= c - b'0', + _ => panic!("Bad hex"), + } + if (idx & 1) == 1 { + out.push(b); + b = 0; + } + } + } + + #[test] + fn duplicate_crash() { + let mut a = Vec::new(); + extend_vec_from_hex("6d75736967", &mut a); + super::do_test(&a); + } +} diff --git a/fuzz/fuzz_targets/parse_descriptor_secret.rs b/fuzz/fuzz_targets/parse_descriptor_secret.rs index a6054296..b598305e 100644 --- a/fuzz/fuzz_targets/parse_descriptor_secret.rs +++ b/fuzz/fuzz_targets/parse_descriptor_secret.rs @@ -1,4 +1,4 @@ -extern crate miniscript; +extern crate elements_miniscript as miniscript; use miniscript::descriptor::DescriptorSecretKey; use std::str::FromStr; diff --git a/fuzz/fuzz_targets/roundtrip_concrete.rs b/fuzz/fuzz_targets/roundtrip_concrete.rs index 76bcfde3..e2136067 100644 --- a/fuzz/fuzz_targets/roundtrip_concrete.rs +++ b/fuzz/fuzz_targets/roundtrip_concrete.rs @@ -1,4 +1,4 @@ -extern crate miniscript; +extern crate elements_miniscript as miniscript; extern crate regex; use miniscript::{policy, DummyKey}; use regex::Regex; diff --git a/fuzz/fuzz_targets/roundtrip_descriptor.rs b/fuzz/fuzz_targets/roundtrip_descriptor.rs index 5dd65fd1..054d9160 100644 --- a/fuzz/fuzz_targets/roundtrip_descriptor.rs +++ b/fuzz/fuzz_targets/roundtrip_descriptor.rs @@ -1,4 +1,4 @@ -extern crate miniscript; +extern crate elements_miniscript as miniscript; extern crate regex; use miniscript::{Descriptor, DummyKey}; @@ -26,9 +26,15 @@ fn do_test(data: &[u8]) { if normalize_aliases.len() == output.len() { let len = pre_checksum.len(); - assert_eq!(normalize_aliases[..len].to_lowercase(), pre_checksum.to_lowercase()); + assert_eq!( + normalize_aliases[..len].to_lowercase(), + pre_checksum.to_lowercase() + ); } else { - assert_eq!(normalize_aliases.to_lowercase(), pre_checksum.to_lowercase()); + assert_eq!( + normalize_aliases.to_lowercase(), + pre_checksum.to_lowercase() + ); } } } @@ -62,4 +68,3 @@ mod tests { do_test(b"pkh()"); } } - diff --git a/fuzz/fuzz_targets/roundtrip_miniscript_script.rs b/fuzz/fuzz_targets/roundtrip_miniscript_script.rs index 44f24623..4c9423c2 100644 --- a/fuzz/fuzz_targets/roundtrip_miniscript_script.rs +++ b/fuzz/fuzz_targets/roundtrip_miniscript_script.rs @@ -1,6 +1,6 @@ -extern crate miniscript; +extern crate elements_miniscript as miniscript; -use miniscript::bitcoin::blockdata::script; +use miniscript::elements::script; use miniscript::Miniscript; use miniscript::Segwitv0; diff --git a/fuzz/fuzz_targets/roundtrip_miniscript_str.rs b/fuzz/fuzz_targets/roundtrip_miniscript_str.rs index 467cde83..d20aea30 100644 --- a/fuzz/fuzz_targets/roundtrip_miniscript_str.rs +++ b/fuzz/fuzz_targets/roundtrip_miniscript_str.rs @@ -1,4 +1,4 @@ -extern crate miniscript; +extern crate elements_miniscript as miniscript; extern crate regex; use regex::Regex; diff --git a/fuzz/fuzz_targets/roundtrip_semantic.rs b/fuzz/fuzz_targets/roundtrip_semantic.rs index 3f75c197..f70abc5a 100644 --- a/fuzz/fuzz_targets/roundtrip_semantic.rs +++ b/fuzz/fuzz_targets/roundtrip_semantic.rs @@ -1,4 +1,4 @@ -extern crate miniscript; +extern crate elements_miniscript as miniscript; use miniscript::{policy, DummyKey}; use std::str::FromStr; diff --git a/src/descriptor/bare.rs b/src/descriptor/bare.rs index 61a44343..b8e9d269 100644 --- a/src/descriptor/bare.rs +++ b/src/descriptor/bare.rs @@ -20,7 +20,8 @@ use std::{fmt, str::FromStr}; -use bitcoin::{self, blockdata::script, Script}; +use bitcoin::secp256k1; +use elements::{self, script, Script}; use expression::{self, FromTree}; use miniscript::context::ScriptContext; @@ -33,7 +34,7 @@ use { use super::{ checksum::{desc_checksum, verify_checksum}, - DescriptorTrait, + DescriptorTrait, ElementsTrait, ELMTS_STR, }; /// Create a Bare Descriptor. That is descriptor that is @@ -65,13 +66,13 @@ impl Bare { impl fmt::Debug for Bare { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.ms) + write!(f, "{}{:?}", ELMTS_STR, self.ms) } } impl fmt::Display for Bare { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let desc = format!("{}", self.ms); + let desc = format!("{}{}", ELMTS_STR, self.ms); let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; write!(f, "{}#{}", &desc, &checksum) } @@ -91,9 +92,20 @@ where <::Hash as FromStr>::Err: ToString, { fn from_tree(top: &expression::Tree) -> Result { - let sub = Miniscript::::from_tree(&top)?; - BareCtx::top_level_checks(&sub)?; - Bare::new(sub) + // extra allocations to use the existing code as is. + if top.name.starts_with("el") { + let new_tree = expression::Tree { + name: top.name.split_at(2).1, + args: top.args.clone(), + }; + let sub = Miniscript::::from_tree(&new_tree)?; + BareCtx::top_level_checks(&sub)?; + Bare::new(sub) + } else { + Err(Error::Unexpected(String::from( + "Not an elements Descriptor", + ))) + } } } @@ -108,18 +120,36 @@ where fn from_str(s: &str) -> Result { let desc_str = verify_checksum(s)?; - let top = expression::Tree::from_str(desc_str)?; + let top = expression::Tree::from_str(&desc_str[2..])?; Self::from_tree(&top) } } -impl DescriptorTrait for Bare { +impl ElementsTrait for Bare { + fn blind_addr( + &self, + _blinder: Option, + _params: &'static elements::AddressParams, + ) -> Result + where + Pk: ToPublicKey, + { + Err(Error::BareDescriptorAddr) + } +} +impl DescriptorTrait for Bare +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ fn sanity_check(&self) -> Result<(), Error> { self.ms.sanity_check()?; Ok(()) } - fn address(&self, _network: bitcoin::Network) -> Result + fn address(&self, _network: &elements::AddressParams) -> Result where Pk: ToPublicKey, { @@ -229,13 +259,13 @@ impl Pkh { impl fmt::Debug for Pkh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "pkh({:?})", self.pk) + write!(f, "{}pkh({:?})", ELMTS_STR, self.pk) } } impl fmt::Display for Pkh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let desc = format!("pkh({})", self.pk); + let desc = format!("{}pkh({})", ELMTS_STR, self.pk); let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; write!(f, "{}#{}", &desc, &checksum) } @@ -255,7 +285,7 @@ where <::Hash as FromStr>::Err: ToString, { fn from_tree(top: &expression::Tree) -> Result { - if top.name == "pkh" && top.args.len() == 1 { + if top.name == "elpkh" && top.args.len() == 1 { Ok(Pkh::new(expression::terminal(&top.args[0], |pk| { Pk::from_str(pk) })?)) @@ -284,24 +314,48 @@ where Self::from_tree(&top) } } +impl ElementsTrait for Pkh { + fn blind_addr( + &self, + blinder: Option, + params: &'static elements::AddressParams, + ) -> Result + where + Pk: ToPublicKey, + { + Ok(elements::Address::p2pkh( + &self.pk.to_public_key(), + blinder, + params, + )) + } +} impl DescriptorTrait for Pkh { fn sanity_check(&self) -> Result<(), Error> { Ok(()) } - fn address(&self, network: bitcoin::Network) -> Result + fn address(&self, params: &'static elements::AddressParams) -> Result where Pk: ToPublicKey, { - Ok(bitcoin::Address::p2pkh(&self.pk.to_public_key(), network)) + Ok(elements::Address::p2pkh( + &self.pk.to_public_key(), + None, + params, + )) } fn script_pubkey(&self) -> Script where Pk: ToPublicKey, { - let addr = bitcoin::Address::p2pkh(&self.pk.to_public_key(), bitcoin::Network::Bitcoin); + let addr = elements::Address::p2pkh( + &self.pk.to_public_key(), + None, + &elements::AddressParams::ELEMENTS, + ); addr.script_pubkey() } diff --git a/src/descriptor/blinded.rs b/src/descriptor/blinded.rs new file mode 100644 index 00000000..31aa124b --- /dev/null +++ b/src/descriptor/blinded.rs @@ -0,0 +1,235 @@ +// Miniscript +// Written in 2020 by rust-miniscript developers +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # Bare Output Descriptors +//! +//! Implementation of Bare Descriptors (i.e descriptors that are) +//! wrapped inside wsh, or sh fragments. +//! Also includes pk, and pkh descriptors +//! + +use std::{fmt, str::FromStr}; + +use bitcoin::secp256k1; +use elements::{self, Script}; + +use expression::{self, FromTree}; +use policy::{semantic, Liftable}; +use {Error, MiniscriptKey, Satisfier, ToPublicKey}; + +use super::{ + checksum::{desc_checksum, strip_checksum, verify_checksum}, + Descriptor, DescriptorTrait, ElementsTrait, TranslatePk, +}; + +/// Create a Bare Descriptor. That is descriptor that is +/// not wrapped in sh or wsh. This covers the Pk descriptor +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct Blinded { + /// The blinding key + blinder: Pk, + /// underlying descriptor + /// Must be unblinded as blinding is only + /// permitted at the root level. + desc: Descriptor, +} + +impl Blinded { + /// Create a new blinded descriptor from a descriptor and blinder + pub fn new(blinder: Pk, desc: Descriptor) -> Self { + Self { blinder, desc } + } + + /// get the blinder + pub fn blinder(&self) -> &Pk { + &self.blinder + } + + /// get the unblinded descriptor + pub fn as_unblinded(&self) -> &Descriptor { + &self.desc + } + + /// get the unblinded descriptor + pub fn into_unblinded(self) -> Descriptor { + self.desc + } +} + +impl fmt::Debug for Blinded { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "blinded({:?},{:?})", self.blinder, self.desc) + } +} + +impl fmt::Display for Blinded { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // strip thec checksum from display + let desc = format!("{}", self.desc); + let desc = format!("blinded({},{})", self.blinder, strip_checksum(&desc)); + let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; + write!(f, "{}#{}", &desc, &checksum) + } +} + +impl Liftable for Blinded { + fn lift(&self) -> Result, Error> { + self.desc.lift() + } +} + +impl FromTree for Blinded +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn from_tree(top: &expression::Tree) -> Result { + if top.name == "blinded" && top.args.len() == 2 { + let blinder = expression::terminal(&top.args[0], |pk| Pk::from_str(pk))?; + let desc = Descriptor::::from_tree(&top.args[1])?; + if top.args[1].name == "blinded" { + return Err(Error::BadDescriptor(format!( + "Blinding only permitted at root level" + ))); + } + Ok(Blinded { blinder, desc }) + } else { + Err(Error::Unexpected(format!( + "{}({} args) while parsing sh descriptor", + top.name, + top.args.len(), + ))) + } + } +} + +impl FromStr for Blinded +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + type Err = Error; + + fn from_str(s: &str) -> Result { + let desc_str = verify_checksum(s)?; + let top = expression::Tree::from_str(desc_str)?; + Self::from_tree(&top) + } +} + +impl ElementsTrait for Blinded +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + /// Overides the blinding key in descriptor with the one + /// provided in the argument. + fn blind_addr( + &self, + blinder: Option, + params: &'static elements::AddressParams, + ) -> Result + where + Pk: ToPublicKey, + { + self.desc.blind_addr(blinder, params) + } +} + +impl DescriptorTrait for Blinded +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn sanity_check(&self) -> Result<(), Error> { + self.desc.sanity_check()?; + Ok(()) + } + + fn address(&self, params: &'static elements::AddressParams) -> Result + where + Pk: ToPublicKey, + { + self.desc + .blind_addr(Some(self.blinder.to_public_key().key), params) + } + + fn script_pubkey(&self) -> Script + where + Pk: ToPublicKey, + { + self.desc.script_pubkey() + } + + fn unsigned_script_sig(&self) -> Script + where + Pk: ToPublicKey, + { + self.desc.unsigned_script_sig() + } + + fn explicit_script(&self) -> Script + where + Pk: ToPublicKey, + { + self.desc.explicit_script() + } + + fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, Script), Error> + where + Pk: ToPublicKey, + S: Satisfier, + { + self.desc.get_satisfaction(satisfier) + } + + fn max_satisfaction_weight(&self) -> Result { + self.desc.max_satisfaction_weight() + } + + fn script_code(&self) -> Script + where + Pk: ToPublicKey, + { + self.script_pubkey() + } +} + +impl TranslatePk for Blinded

{ + type Output = Blinded; + + fn translate_pk( + &self, + mut translatefpk: Fpk, + mut translatefpkh: Fpkh, + ) -> Result + where + Fpk: FnMut(&P) -> Result, + Fpkh: FnMut(&P::Hash) -> Result, + Q: MiniscriptKey, + { + Ok(Blinded::new( + translatefpk(&self.blinder)?, + self.desc + .translate_pk(&mut translatefpk, &mut translatefpkh)?, + )) + } +} diff --git a/src/descriptor/checksum.rs b/src/descriptor/checksum.rs index b5e41da4..954c3c08 100644 --- a/src/descriptor/checksum.rs +++ b/src/descriptor/checksum.rs @@ -99,6 +99,13 @@ pub(super) fn verify_checksum(s: &str) -> Result<&str, Error> { } Ok(desc_str) } + +/// Helper function to strip checksum without verifying +#[allow(dead_code)] +pub(super) fn strip_checksum(s: &str) -> &str { + let mut parts = s.splitn(2, '#'); + parts.next().unwrap() +} #[cfg(test)] mod test { use super::*; @@ -113,22 +120,22 @@ mod test { #[test] fn test_valid_descriptor_checksum() { check_expected!( - "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", - "tqz0nc62" + "elwpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", + "hkvr2vkj" ); check_expected!( - "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)", - "lasegmfs" + "elpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)", + "g7zpd3we" ); // https://github.com/bitcoin/bitcoin/blob/7ae86b3c6845873ca96650fc69beb4ae5285c801/src/test/descriptor_tests.cpp#L352-L354 check_expected!( - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", - "ggrsrxfy" + "elsh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", + "9s2ngs7u" ); check_expected!( - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", - "tjg09x5t" + "elsh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", + "uklept69" ); } @@ -140,7 +147,7 @@ mod test { .chars() .next() .unwrap(); - let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart); + let invalid_desc = format!("elwpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart); assert_eq!( desc_checksum(&invalid_desc).err().unwrap().to_string(), diff --git a/src/descriptor/covenants/mod.rs b/src/descriptor/covenants/mod.rs new file mode 100644 index 00000000..cd592f11 --- /dev/null +++ b/src/descriptor/covenants/mod.rs @@ -0,0 +1,1165 @@ +// Miniscript +// Written in 2018 by +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Covenant Descriptor support +//! +//! Traits and implementations for Covenant descriptors +//! A cov() descriptor puts a context items required for +//! sighash onto the top of the stack in the required order +//! +//! ** WORKS only for Segwit sighash +//! A new transaction digest algorithm is defined, but only applicable to sigops in version 0 witness program: +//! Text from BIP 143: +//! Double SHA256 of the serialization of: +//! 1. nVersion of the transaction (4-byte little endian) +//! 2. hashPrevouts (32-byte hash) +//! 3. hashSequence (32-byte hash) +//! 3b. ELEMENTS EXTRA hashIssuances (32-byte hash) +//! 4. outpoint (32-byte hash + 4-byte little endian) +//! 5. scriptCode of the input (serialized as scripts inside CTxOuts) +//! 6. value of the output spent by this input (8-byte little endian) +//! 7. nSequence of the input (4-byte little endian) +//! 8. hashOutputs (32-byte hash) +//! 9. nLocktime of the transaction (4-byte little endian) +//! 10. sighash type of the signature (4-byte little endian) +//! +//! The miniscript fragments lookups all the relevant fragment +//! from the stack using using OP_PICK(specifying the relative) +//! position using OP_DEPTH. +//! After all the miniscript fragments are evaluated, we concat +//! all the items using OP_CAT to obtain a Sighash on which we +//! which we verify using CHECKSIGFROMSTACK +use std::{error, fmt, str::FromStr}; + +use bitcoin; +use elements::hashes::{sha256d, Hash}; +use elements::opcodes::all; +use elements::secp256k1; +use elements::sighash::SigHashCache; +use elements::{ + self, + encode::{serialize, Encodable}, + SigHash, +}; +use elements::{confidential, script}; +use elements::{OutPoint, Script, SigHashType, Transaction, TxOut}; +use miniscript::limits::{MAX_SCRIPT_SIZE, MAX_STANDARD_P2WSH_SCRIPT_SIZE}; + +use { + expression::{self, FromTree}, + miniscript::{ + decode, + lex::{lex, Token as Tk, TokenIter}, + limits::MAX_OPS_PER_SCRIPT, + types, + }, + util::varint_len, + ForEach, ForEachKey, Miniscript, ScriptContext, Segwitv0, TranslatePk, +}; + +use super::{ + checksum::{desc_checksum, verify_checksum}, + ElementsTrait, ELMTS_STR, +}; +use {MiniscriptKey, ToPublicKey}; + +use {DescriptorTrait, Error, Satisfier}; + +/// Additional operations requied on script builder +/// for Covenant operations support +pub trait CovOperations: Sized { + /// Assuming the 10 sighash components + 1 sig on the top of + /// stack for segwit sighash as created by [init_stack] + /// CAT all of them and check sig from stack + fn verify_cov(self, key: &bitcoin::PublicKey) -> Self; + + /// Get the script code for the covenant script + /// assuming the above construction of covenants + /// which uses OP_CODESEP + fn post_codesep_script(self) -> Self; +} + +impl CovOperations for script::Builder { + fn verify_cov(self, key: &bitcoin::PublicKey) -> Self { + let mut builder = self; + // The miniscript is of type B, which should have pushed 1 + // onto the stack if it satisfied correctly.(which it should) + // because this is a top level check + builder = builder.push_verify(); + // pick signature. stk_size = 12 + // Why can we pick have a fixed pick of 11. + // The covenant check enforces that the the next 12 elements + // of the stack must be elements from the sighash. + // We don't additionally need to check the depth because + // cleanstack is a consensus rule in segwit. + builder = builder.push_int(11).push_opcode(all::OP_PICK); + // convert sighash type into 1 byte + // OP_OVER copies the second to top element onto + // the top of the stack + builder = builder.push_opcode(all::OP_OVER); + builder = builder.push_int(1).push_opcode(all::OP_LEFT); + // create a bitcoinsig = cat the sig and hashtype + builder = builder.push_opcode(all::OP_CAT); + + // check the sig and push pk to alt stack + builder = builder + .push_key(key) + .push_opcode(all::OP_DUP) + .push_opcode(all::OP_TOALTSTACK); + + // Code separtor. Everything before this(and including this codesep) + // won't be used in script code calculation + builder = builder.push_opcode(all::OP_CODESEPARATOR); + builder.post_codesep_script() + } + + /// The second parameter decides whether the script code should + /// a hashlock verifying the entire script + fn post_codesep_script(self) -> Self { + let mut builder = self; + // let script_slice = builder.clone().into_script().into_bytes(); + builder = builder.push_opcode(all::OP_CHECKSIGVERIFY); + for _ in 0..10 { + builder = builder.push_opcode(all::OP_CAT); + } + + // Now sighash is on the top of the stack + builder = builder.push_opcode(all::OP_SHA256); + builder = builder.push_opcode(all::OP_FROMALTSTACK); + builder.push_opcode(all::OP_CHECKSIGFROMSTACK) + } +} + +/// Satisfaction related Errors +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum CovError { + /// Missing script code (segwit sighash) + MissingScriptCode, + /// Missing value (segwit sighash) + MissingValue, + /// Missing a sighash Item in satisfier, + MissingSighashItem(u8), + /// Missing Sighash Signature + /// This must be a secp signature serialized + /// in DER format *with* the sighash byte + MissingCovSignature, + /// Bad(Malformed) Covenant Descriptor + BadCovDescriptor, + /// Cannot lift a Covenant Descriptor + /// This is because the different components of the covenants + /// might interact across branches and thus is + /// not composable and could not be analyzed individually. + CovenantLift, + /// The Covenant Sighash type and the satisfier sighash + /// type must be the same + CovenantSighashTypeMismatch, +} + +impl fmt::Display for CovError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + CovError::MissingScriptCode => write!(f, "Missing Script code"), + CovError::MissingValue => write!(f, "Missing value"), + CovError::BadCovDescriptor => write!(f, "Bad or Malformed covenant descriptor"), + CovError::CovenantLift => write!(f, "Cannot lift a covenant descriptor"), + CovError::MissingSighashItem(i) => { + write!(f, "Missing sighash item # : {} in satisfier", i) + } + CovError::MissingCovSignature => write!(f, "Missing signature over the covenant pk"), + CovError::CovenantSighashTypeMismatch => write!( + f, + "The sighash type provided in the witness must the same \ + as the one used in signature" + ), + } + } +} + +impl error::Error for CovError {} + +#[doc(hidden)] +impl From for Error { + fn from(e: CovError) -> Error { + Error::CovError(e) + } +} + +// A simple utility function to serialize an array +// of elements and compute double sha2 on it +fn hash256_arr(sl: &[T]) -> sha256d::Hash { + let mut enc = sha256d::Hash::engine(); + for elem in sl { + elem.consensus_encode(&mut enc).unwrap(); + } + sha256d::Hash::from_engine(enc) +} + +/// The covenant descriptor +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CovenantDescriptor { + /// the pk constraining the Covenant + /// The key over which we want CHECKSIGFROMSTACK + pk: Pk, + /// the underlying Miniscript + /// Must be under segwit context + ms: Miniscript, +} + +impl CovenantDescriptor { + /// Get the pk from covenant + pub fn pk(&self) -> &Pk { + &self.pk + } + + /// Get a reference to Miniscript inside covenant + pub fn to_ms(&self) -> &Miniscript { + &self.ms + } + + /// Consume self and return inner miniscript + pub fn into_ms(self) -> Miniscript { + self.ms + } + + /// Create a new Self from components + pub fn new(pk: Pk, ms: Miniscript) -> Result { + // // 1) Check the 201 opcode count here + let ms_op_count = ms.ext.ops_count_sat; + // statically computed + // see cov_test_limits test for the test assert + let cov_script_ops = 24; + let total_ops = ms_op_count.ok_or(Error::ImpossibleSatisfaction)? + cov_script_ops + - if ms.ext.has_free_verify { 1 } else { 0 }; + if total_ops > MAX_OPS_PER_SCRIPT { + return Err(Error::ImpossibleSatisfaction); + } + // 2) TODO: Sighash never exceeds 520 bytes, but we check the + // witness script before the codesep is still under 520 + // bytes if the covenant relies on introspection of script + let ss = 58 - if ms.ext.has_free_verify { 1 } else { 0 }; + // 3) Check that the script size does not exceed 10_000 bytes + // global consensus rule + if ms.script_size() + ss > MAX_SCRIPT_SIZE { + Err(Error::ScriptSizeTooLarge) + } else { + Ok(Self { pk, ms }) + } + } + /// Encode + pub fn encode(&self) -> Script + where + Pk: ToPublicKey, + { + let builder = self.ms.node.encode(script::Builder::new()); + builder.verify_cov(&self.pk.to_public_key()).into_script() + } + + /// Create a satisfaction for the Covenant Descriptor + pub fn satisfy>(&self, s: S) -> Result>, Error> + where + Pk: ToPublicKey, + { + let mut wit = { + use descriptor::CovError::MissingSighashItem; + let n_version = s.lookup_nversion().ok_or(MissingSighashItem(1))?; + let hash_prevouts = s.lookup_hashprevouts().ok_or(MissingSighashItem(1))?; + let hash_sequence = s.lookup_hashsequence().ok_or(MissingSighashItem(3))?; + // note the 3 again, for elements + let hash_issuances = s.lookup_hashissuances().ok_or(MissingSighashItem(3))?; + let outpoint = s.lookup_outpoint().ok_or(MissingSighashItem(4))?; + let script_code = s.lookup_scriptcode().ok_or(MissingSighashItem(5))?; + let value = s.lookup_value().ok_or(MissingSighashItem(6))?; + let n_sequence = s.lookup_nsequence().ok_or(MissingSighashItem(7))?; + let outputs = s.lookup_outputs().ok_or(MissingSighashItem(8))?; + let hash_outputs = hash256_arr(outputs); + let n_locktime = s.lookup_nlocktime().ok_or(MissingSighashItem(9))?; + let sighash_ty = s.lookup_sighashu32().ok_or(MissingSighashItem(10))?; + + let (sig, hash_ty) = s + .lookup_sig(&self.pk) + .ok_or(CovError::MissingCovSignature)?; + // Hashtype must be the same + if sighash_ty != hash_ty.as_u32() { + return Err(CovError::CovenantSighashTypeMismatch)?; + } + + vec![ + Vec::from(sig.serialize_der().as_ref()), // The covenant sig + serialize(&n_version), // item 1 + serialize(&hash_prevouts), // item 2 + serialize(&hash_sequence), // item 3 + serialize(&hash_issuances), // ELEMENTS EXTRA: item 3b(4) + serialize(&outpoint), // item 4(5) + serialize(script_code), // item 5(6) + serialize(&value), // item 6(7) + serialize(&n_sequence), // item 7(8) + serialize(&hash_outputs), // item 8(9) + serialize(&n_locktime), // item 9(10) + serialize(&sighash_ty), // item 10(11) + ] + }; + + let ms_wit = self.ms.satisfy(s)?; + wit.extend(ms_wit); + Ok(wit) + } + + /// Script code for signing with covenant publickey. + /// Use this script_code for sighash method when signing + /// with the covenant pk. Use the [DescriptorTrait] script_code + /// method for getting sighash for regular miniscripts. + pub fn cov_script_code(&self) -> Script + where + Pk: ToPublicKey, + { + script::Builder::new().post_codesep_script().into_script() + } +} + +/// A satisfier for Covenant descriptors +/// that can do transaction introspection +/// 'tx denotes the lifetime of the transaction +/// being satisfied and 'ptx denotes the lifetime +/// of the previous transaction inputs +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CovSatisfier<'tx, 'ptx> { + // Common fields in Segwit and Taphash + /// The transaction being spent + tx: &'tx Transaction, + /// The script code required for + /// The input index being spent + idx: u32, + /// The sighash type + hash_type: SigHashType, + + // Segwitv0 + /// The script code required for segwit sighash + script_code: Option<&'ptx Script>, + /// The value of the output being spent + value: Option, + + // Taproot + /// The utxos used in transaction + /// This construction should suffice for Taproot + /// related covenant spends too. + spent_utxos: Option<&'ptx [TxOut]>, +} + +impl<'tx, 'ptx> CovSatisfier<'tx, 'ptx> { + /// Create a new CovSatisfier for taproot spends + /// **Panics** + /// 1) if number of spent_utxos is not equal to + /// number of transaction inputs. + /// 2) if idx is out of bounds + pub fn new_taproot( + tx: &'tx Transaction, + spent_utxos: &'ptx [TxOut], + idx: u32, + hash_type: SigHashType, + ) -> Self { + assert!(spent_utxos.len() == tx.input.len()); + assert!((idx as usize) < spent_utxos.len()); + Self { + tx, + idx, + hash_type, + script_code: None, + value: None, + spent_utxos: Some(spent_utxos), + } + } + + /// Create a new Covsatisfier for v0 spends + /// Panics if idx is out of bounds + pub fn new_segwitv0( + tx: &'tx Transaction, + idx: u32, + value: confidential::Value, + script_code: &'ptx Script, + hash_type: SigHashType, + ) -> Self { + assert!((idx as usize) < tx.input.len()); + Self { + tx, + idx, + hash_type, + script_code: Some(script_code), + value: Some(value), + spent_utxos: None, + } + } + + /// Easy way to get sighash since we already have + /// all the required information. + /// Note that this does not do any caching, so it + /// will be slightly inefficient as compared to + /// using sighash + pub fn segwit_sighash(&self) -> Result { + let mut cache = SigHashCache::new(self.tx); + // TODO: error types + let script_code = self.script_code.ok_or(CovError::MissingScriptCode)?; + let value = self.value.ok_or(CovError::MissingValue)?; + Ok(cache.segwitv0_sighash(self.idx as usize, script_code, value, self.hash_type)) + } +} + +impl<'tx, 'ptx, Pk: MiniscriptKey + ToPublicKey> Satisfier for CovSatisfier<'tx, 'ptx> { + fn lookup_nversion(&self) -> Option { + Some(self.tx.version) + } + + fn lookup_hashprevouts(&self) -> Option { + let mut enc = sha256d::Hash::engine(); + for txin in &self.tx.input { + txin.previous_output.consensus_encode(&mut enc).unwrap(); + } + Some(sha256d::Hash::from_engine(enc)) + } + + fn lookup_hashsequence(&self) -> Option { + let mut enc = sha256d::Hash::engine(); + for txin in &self.tx.input { + txin.sequence.consensus_encode(&mut enc).unwrap(); + } + Some(sha256d::Hash::from_engine(enc)) + } + + fn lookup_hashissuances(&self) -> Option { + let mut enc = sha256d::Hash::engine(); + for txin in &self.tx.input { + if txin.has_issuance() { + txin.asset_issuance.consensus_encode(&mut enc).unwrap(); + } else { + 0u8.consensus_encode(&mut enc).unwrap(); + } + } + Some(sha256d::Hash::from_engine(enc)) + } + + fn lookup_outpoint(&self) -> Option { + Some(self.tx.input[self.idx as usize].previous_output) + } + + fn lookup_scriptcode(&self) -> Option<&Script> { + self.script_code + } + + fn lookup_value(&self) -> Option { + self.value + } + + fn lookup_nsequence(&self) -> Option { + Some(self.tx.input[self.idx as usize].sequence) + } + + fn lookup_outputs(&self) -> Option<&[elements::TxOut]> { + Some(&self.tx.output) + } + + fn lookup_nlocktime(&self) -> Option { + Some(self.tx.lock_time) + } + + fn lookup_sighashu32(&self) -> Option { + Some(self.hash_type.as_u32()) + } +} + +impl CovenantDescriptor { + /// Check if the given script is a covenant descriptor + /// Consumes the iterator so that only remaining miniscript + /// needs to be parsed from the iterator + #[allow(unreachable_patterns)] + fn check_cov_script(tokens: &mut TokenIter) -> Result { + match_token!(tokens, + Tk::CheckSigFromStack, Tk::FromAltStack, Tk::Sha256, Tk::Cat, + Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat, + Tk::Cat, Tk::Cat, Tk::Verify, Tk::CheckSig, Tk::CodeSep, Tk::ToAltStack, + Tk::Dup, Tk::Pubkey(pk), Tk::Cat, Tk::Left, Tk::Num(1), + Tk::Over, Tk::Pick, Tk::Num(11), Tk::Verify => { + return Ok(pk); + }, + _ => return Err(Error::CovError(CovError::BadCovDescriptor)), + ); + } + + /// Parse a descriptor from script. While parsing + /// other descriptors, we only parse the inner miniscript + /// with ScriptContext. But Covenant descriptors only + /// applicable under Wsh context to avoid implementation + /// complexity. + // All code for covenants can thus be separated in a module + // This parsing is parse_insane + pub fn parse_insane(script: &script::Script) -> Result { + let (pk, ms) = Self::parse_cov_components(script)?; + Self::new(pk, ms) + } + + // Utility function to parse the components of cov + // descriptor. This allows us to parse Miniscript with + // it's context so that it can be used with NoChecks + // context while using the interpreter + pub(crate) fn parse_cov_components( + script: &script::Script, + ) -> Result<(bitcoin::PublicKey, Miniscript), Error> { + let tokens = lex(script)?; + let mut iter = TokenIter::new(tokens); + + let pk = CovenantDescriptor::::check_cov_script(&mut iter)?; + let ms = decode::parse(&mut iter)?; + Segwitv0::check_global_validity(&ms)?; + if ms.ty.corr.base != types::Base::B { + return Err(Error::NonTopLevel(format!("{:?}", ms))); + }; + if let Some(leading) = iter.next() { + Err(Error::Trailing(leading.to_string())) + } else { + Ok((pk, ms)) + } + } + + /// Parse a descriptor with additional local sanity checks. + /// See [Miniscript::sanity_check] for all the checks. Use + /// [parse_insane] to allow parsing insane scripts + pub fn parse(script: &script::Script) -> Result { + let cov = Self::parse_insane(script)?; + cov.ms.sanity_check()?; + Ok(cov) + } +} + +impl FromTree for CovenantDescriptor +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn from_tree(top: &expression::Tree) -> Result { + if top.name == "elcovwsh" && top.args.len() == 2 { + let pk = expression::terminal(&top.args[0], |pk| Pk::from_str(pk))?; + let top = &top.args[1]; + let sub = Miniscript::from_tree(&top)?; + Segwitv0::top_level_checks(&sub)?; + Ok(CovenantDescriptor { pk: pk, ms: sub }) + } else { + Err(Error::Unexpected(format!( + "{}({} args) while parsing elcovwsh descriptor", + top.name, + top.args.len(), + ))) + } + } +} +impl fmt::Debug for CovenantDescriptor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}covwsh({},{})", ELMTS_STR, self.pk, self.ms) + } +} + +impl fmt::Display for CovenantDescriptor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let desc = format!("{}covwsh({},{})", ELMTS_STR, self.pk, self.ms); + let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; + write!(f, "{}#{}", &desc, &checksum) + } +} + +impl FromStr for CovenantDescriptor +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + type Err = Error; + + fn from_str(s: &str) -> Result { + let desc_str = verify_checksum(s)?; + let top = expression::Tree::from_str(desc_str)?; + CovenantDescriptor::::from_tree(&top) + } +} + +impl ElementsTrait for CovenantDescriptor +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn blind_addr( + &self, + blinder: Option, + params: &'static elements::AddressParams, + ) -> Result + where + Pk: ToPublicKey, + { + Ok(elements::Address::p2wsh( + &self.explicit_script(), + blinder, + params, + )) + } +} + +impl DescriptorTrait for CovenantDescriptor +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn sanity_check(&self) -> Result<(), Error> { + self.ms.sanity_check()?; + // Additional local check for p2wsh script size + let ss = 58 - if self.ms.ext.has_free_verify { 1 } else { 0 }; + if self.ms.script_size() + ss > MAX_STANDARD_P2WSH_SCRIPT_SIZE { + Err(Error::ScriptSizeTooLarge) + } else { + Ok(()) + } + } + + fn address(&self, params: &'static elements::AddressParams) -> Result + where + Pk: ToPublicKey, + { + Ok(elements::Address::p2wsh( + &self.explicit_script(), + None, + params, + )) + } + + fn script_pubkey(&self) -> Script + where + Pk: ToPublicKey, + { + self.explicit_script().to_v0_p2wsh() + } + + fn unsigned_script_sig(&self) -> Script + where + Pk: ToPublicKey, + { + Script::new() + } + + fn explicit_script(&self) -> Script + where + Pk: ToPublicKey, + { + self.encode() + } + + fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, Script), Error> + where + Pk: ToPublicKey, + S: Satisfier, + { + let mut witness = self.satisfy(satisfier)?; + witness.push(self.explicit_script().into_bytes()); + let script_sig = Script::new(); + Ok((witness, script_sig)) + } + + fn max_satisfaction_weight(&self) -> Result { + let script_size = + self.ms.script_size() + 58 - if self.ms.ext.has_free_verify { 1 } else { 0 }; + let max_sat_elems = self.ms.max_satisfaction_witness_elements()? + 12; + let max_sat_size = self.ms.max_satisfaction_size()? + 275; + + Ok(4 + // scriptSig length byte + varint_len(script_size) + + script_size + + varint_len(max_sat_elems) + + max_sat_size) + } + + /// This returns the entire explicit script as the script code. + /// You will need this script code when singing with pks that + /// inside Miniscript. Use the [cov_script_code] method to + /// get the script code for signing with covenant pk + fn script_code(&self) -> Script + where + Pk: ToPublicKey, + { + self.explicit_script() + } +} + +impl ForEachKey for CovenantDescriptor { + fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, mut pred: F) -> bool + where + Pk: 'a, + Pk::Hash: 'a, + { + pred(ForEach::Key(&self.pk)) && self.ms.for_any_key(pred) + } +} + +impl TranslatePk for CovenantDescriptor

{ + type Output = CovenantDescriptor; + + fn translate_pk( + &self, + mut translatefpk: Fpk, + mut translatefpkh: Fpkh, + ) -> Result + where + Fpk: FnMut(&P) -> Result, + Fpkh: FnMut(&P::Hash) -> Result, + Q: MiniscriptKey, + { + Ok(CovenantDescriptor { + pk: translatefpk(&self.pk)?, + ms: self + .ms + .translate_pk(&mut translatefpk, &mut translatefpkh)?, + }) + } +} + +#[cfg(test)] +#[allow(unused_imports)] +mod tests { + + use super::*; + use elements::hashes::hex::ToHex; + use elements::{confidential, opcodes::all::OP_PUSHNUM_1}; + use elements::{opcodes, script}; + use elements::{AssetId, AssetIssuance, OutPoint, TxIn, TxInWitness, Txid}; + use interpreter::SatisfiedConstraint; + use std::str::FromStr; + use util::{count_non_push_opcodes, witness_size}; + use Interpreter; + use {descriptor::DescriptorType, Descriptor, ElementsSig}; + + 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::::from_str(desc_str).unwrap(); + assert_eq!(desc.to_string_no_chksum(), 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,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::::from_str(desc_str).unwrap(); + assert_eq!(desc.desc_type(), DescriptorType::Cov); + let script = desc.explicit_script(); + + let cov_desc = CovenantDescriptor::::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] + )); + } + + // Some deterministic keys for ease of testing + fn setup_keys(n: usize) -> (Vec, Vec) { + let secp_sign = secp256k1::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::SecretKey::from_slice(&sk[..]).expect("secret key"); + let pk = bitcoin::PublicKey { + key: secp256k1::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); + // Count of the opcodes without the + let cov_script = script::Builder::new().verify_cov(&pks[0]).into_script(); + assert_eq!(count_non_push_opcodes(&cov_script), Ok(24)); + assert_eq!(cov_script.len(), 58); + + let sighash_size = 4 + + 32 + + 32 + + 32 + + (32 + 4) + + (58) // script code size + + 4 + + 32 + + 4 + + 4; + assert_eq!(sighash_size, 238); + } + + fn _satisfy_and_interpret( + desc: Descriptor, + cov_sk: secp256k1::SecretKey, + ) -> Result<(), Error> { + assert_eq!(desc.desc_type(), DescriptorType::Cov); + let desc = desc.as_cov().unwrap(); + // Now create a transaction spending this. + let mut spend_tx = Transaction { + version: 2, + lock_time: 0, + 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()); + + // same second output + let second_out = spend_tx.output[0].clone(); + spend_tx.output.push(second_out); + + // Add a fee output + 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); + + // Try to satisfy the covenant part + let script_code = desc.cov_script_code(); + let cov_sat = CovSatisfier::new_segwitv0( + &spend_tx, + 0, + confidential::Value::Explicit(200_000), + &script_code, + SigHashType::All, + ); + + // Create a signature to sign the input + + let sighash_u256 = cov_sat.segwit_sighash().unwrap(); + let secp = secp256k1::Secp256k1::signing_only(); + let sig = secp.sign( + &secp256k1::Message::from_slice(&sighash_u256[..]).unwrap(), + &cov_sk, + ); + let el_sig = (sig, SigHashType::All); + + // For satisfying the Pk part of the covenant + struct SimpleSat { + sig: ElementsSig, + pk: bitcoin::PublicKey, + }; + + impl Satisfier for SimpleSat { + fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option { + if *pk == self.pk { + Some(self.sig) + } else { + None + } + } + } + + let pk_sat = SimpleSat { + sig: el_sig, + pk: desc.pk, + }; + + // A pair of satisfiers is also a satisfier + let (wit, ss) = desc.get_satisfaction((cov_sat, pk_sat))?; + let mut interpreter = + Interpreter::from_txdata(&desc.script_pubkey(), &ss, &wit, 0, 0).unwrap(); + + assert!(wit[0].len() <= 73); + assert!(wit[1].len() == 4); // version + + // Check that everything is executed correctly with correct sigs inside + // miniscript + let constraints = interpreter + .iter(|_, _| true) + .collect::, _>>() + .expect("If satisfy succeeds, interpret must succeed"); + + // The last constraint satisfied must be the covenant pk + assert_eq!( + constraints.last().unwrap(), + &SatisfiedConstraint::PublicKey { + key: &desc.pk, + sig: sig, + } + ); + 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(); + + // Version tests + // Satisfy with 2, err with 3 + _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(); + + // Outputs Pref test + // 1. Correct case + let mut out = TxOut::default(); + out.script_pubkey = script::Builder::new() + .push_opcode(opcodes::all::OP_PUSHNUM_1) + .into_script() + .to_v0_p2wsh(); + out.value = confidential::Value::Explicit(99_000); + out.asset = confidential::Asset::Explicit(AssetId::from_slice(&BTC_ASSET).unwrap()); + let desc = Descriptor::::from_str(&format!( + "elcovwsh({},outputs_pref({}))", + pks[0], + serialize(&out).to_hex(), + )) + .unwrap(); + _satisfy_and_interpret(desc, sks[0]).unwrap(); + + // 2. Chaning the amount should fail the test + let mut out = TxOut::default(); + out.script_pubkey = script::Builder::new() + .push_opcode(opcodes::all::OP_PUSHNUM_1) + .into_script() + .to_v0_p2wsh(); + out.value = confidential::Value::Explicit(99_001); // Changed to +1 + out.asset = confidential::Asset::Explicit(AssetId::from_slice(&BTC_ASSET).unwrap()); + let desc = Descriptor::::from_str(&format!( + "elcovwsh({},outputs_pref({}))", + pks[0], + serialize(&out).to_hex(), + )) + .unwrap(); + _satisfy_and_interpret(desc, sks[0]).unwrap_err(); + } + + // Fund output and spend tx are tests handy with code for + // running with regtest mode and testing that the scripts + // are accepted by elementsd + // Instructions for running: + // 1. Modify the descriptor script in fund_output and + // get the address to which we should spend the funds + // 2. Look up the spending transaction and update the + // spend tx test with outpoint for spending. + // 3. Uncomment the printlns at the end of spend_tx to get + // a raw tx that we can then check if it is accepted. + #[test] + fn fund_output() { + let (pks, _sks) = setup_keys(5); + let desc = + Descriptor::::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(), + "ert1ql8l6f3cytl5a849pcy7ycpqz9q9xqsd4mnq8wcms7mjlyr3mezpqz0vt3q" + ); + + println!( + "{}", + desc.address(&elements::AddressParams::ELEMENTS) + .unwrap() + .to_string() + ); + } + #[test] + fn spend_tx() { + let (pks, sks) = setup_keys(5); + let desc = + Descriptor::::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(), + "ert1ql8l6f3cytl5a849pcy7ycpqz9q9xqsd4mnq8wcms7mjlyr3mezpqz0vt3q" + ); + // Now create a transaction spending this. + let mut spend_tx = Transaction { + version: 2, + lock_time: 0, + input: vec![txin_from_txid_vout( + "141f79c7c254ee3a9a9bc76b4f60564385b784bdfc1882b25154617801fe2237", + 1, + )], + output: vec![], + }; + + spend_tx.output.push(TxOut::default()); + spend_tx.output[0].script_pubkey = desc.script_pubkey(); // send back to self + spend_tx.output[0].value = confidential::Value::Explicit(99_000); + spend_tx.output[0].asset = + confidential::Asset::Explicit(AssetId::from_slice(&BTC_ASSET).unwrap()); + + // same second output + let second_out = spend_tx.output[0].clone(); + spend_tx.output.push(second_out); + + // Add a fee output + 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); + + // Try to satisfy the covenant part + let desc = desc.as_cov().unwrap(); + let script_code = desc.cov_script_code(); + let cov_sat = CovSatisfier::new_segwitv0( + &spend_tx, + 0, + confidential::Value::Explicit(200_000), + &script_code, + SigHashType::All, + ); + + // Create a signature to sign the input + + let sighash_u256 = cov_sat.segwit_sighash().unwrap(); + let secp = secp256k1::Secp256k1::signing_only(); + let sig = secp.sign( + &secp256k1::Message::from_slice(&sighash_u256[..]).unwrap(), + &sks[0], + ); + let sig = (sig, SigHashType::All); + + // For satisfying the Pk part of the covenant + struct SimpleSat { + sig: ElementsSig, + pk: bitcoin::PublicKey, + }; + + impl Satisfier for SimpleSat { + fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option { + if *pk == self.pk { + Some(self.sig) + } else { + None + } + } + } + + let pk_sat = SimpleSat { sig, pk: pks[0] }; + + // A pair of satisfiers is also a satisfier + let (wit, ss) = desc.get_satisfaction((cov_sat, pk_sat)).unwrap(); + let mut interpreter = + Interpreter::from_txdata(&desc.script_pubkey(), &ss, &wit, 0, 0).unwrap(); + // Check that everything is executed correctly with dummysigs + let constraints: Result, _> = interpreter.iter(|_, _| true).collect(); + constraints.expect("Covenant incorrect satisfaction"); + // Commented Demo test code: + // 1) Send 0.002 btc to above address + // 2) Create a tx by filling up txid + // 3) Send the tx + assert_eq!(witness_size(&wit), 334); + assert_eq!(wit.len(), 13); + // spend_tx.input[0].witness.script_witness = wit; + // use elements::encode::serialize_hex; + // println!("{}", serialize_hex(&spend_tx)); + // println!("{}", serialize_hex(&desc.explicit_script())); + } + + fn txin_from_txid_vout(txid: &str, vout: u32) -> TxIn { + TxIn { + previous_output: OutPoint { + txid: Txid::from_str(txid).unwrap(), + vout: vout, + }, + sequence: 0xfffffffe, + is_pegin: false, + has_issuance: false, + // perhaps make this an option in elements upstream? + asset_issuance: AssetIssuance { + asset_blinding_nonce: [0; 32], + asset_entropy: [0; 32], + amount: confidential::Value::Null, + inflation_keys: confidential::Value::Null, + }, + script_sig: Script::new(), + witness: TxInWitness { + amount_rangeproof: vec![], + inflation_keys_rangeproof: vec![], + script_witness: vec![], + pegin_witness: vec![], + }, + } + } +} diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 4108d004..b9698c9b 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -588,6 +588,7 @@ impl DescriptorXKey { /// ## Examples /// /// ``` + /// # extern crate elements_miniscript as miniscript; /// # use std::str::FromStr; /// # fn body() -> Result<(), Box> { /// use miniscript::bitcoin::util::bip32; diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 9855f7f1..97263d9d 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -29,8 +29,13 @@ use std::{ str::{self, FromStr}, }; -use bitcoin::secp256k1; -use bitcoin::{self, Script}; +#[allow(unused_imports)] +pub mod pegin; + +// use bitcoin; +use elements; +use elements::secp256k1; +use elements::Script; use self::checksum::verify_checksum; use expression; @@ -42,17 +47,20 @@ use { }; mod bare; +mod blinded; +mod covenants; mod segwitv0; mod sh; mod sortedmulti; // Descriptor Exports pub use self::bare::{Bare, Pkh}; +pub use self::blinded::Blinded; pub use self::segwitv0::{Wpkh, Wsh, WshInner}; pub use self::sh::{Sh, ShInner}; pub use self::sortedmulti::SortedMultiVec; - mod checksum; mod key; +pub use self::covenants::{CovError, CovOperations, CovSatisfier, CovenantDescriptor}; pub use self::key::{ DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, Wildcard, @@ -66,13 +74,29 @@ pub use self::key::{ /// public key from the descriptor. pub type KeyMap = HashMap; +/// Elements Descriptor String Prefix +pub const ELMTS_STR: &str = "el"; +/// Elements specific additional features that +/// we want on DescriptorTrait from upstream. +// Maintained as a separate trait to avoid conflicts. +pub trait ElementsTrait { + /// Compute a blinded address + fn blind_addr( + &self, + blinder: Option, + params: &'static elements::AddressParams, + ) -> Result + where + Pk: ToPublicKey; +} + /// A general trait for Bitcoin descriptor. /// Offers function for witness cost estimation, script pubkey creation /// satisfaction using the [Satisfier] trait. // Unfortunately, the translation function cannot be added to trait // because of traits cannot know underlying generic of Self. // Thus, we must implement additional trait for translate function -pub trait DescriptorTrait { +pub trait DescriptorTrait: ElementsTrait { /// Whether the descriptor is safe /// Checks whether all the spend paths in the descriptor are possible /// on the bitcoin network under the current standardness and consensus rules @@ -85,7 +109,7 @@ pub trait DescriptorTrait { /// Computes the Bitcoin address of the descriptor, if one exists /// Some descriptors like pk() don't have any address. - fn address(&self, network: bitcoin::Network) -> Result + fn address(&self, params: &'static elements::AddressParams) -> Result where Pk: ToPublicKey; @@ -125,14 +149,14 @@ pub trait DescriptorTrait { /// Attempts to produce a satisfying witness and scriptSig to spend an /// output controlled by the given descriptor; add the data to a given /// `TxIn` output. - fn satisfy(&self, txin: &mut bitcoin::TxIn, satisfier: S) -> Result<(), Error> + fn satisfy(&self, txin: &mut elements::TxIn, satisfier: S) -> Result<(), Error> where Pk: ToPublicKey, S: Satisfier, { // easy default implementation let (witness, script_sig) = self.get_satisfaction(satisfier)?; - txin.witness = witness; + txin.witness.script_witness = witness; txin.script_sig = script_sig; Ok(()) } @@ -153,21 +177,6 @@ pub trait DescriptorTrait { Pk: ToPublicKey; } -/// Script descriptor -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Descriptor { - /// A raw scriptpubkey (including pay-to-pubkey) under Legacy context - Bare(Bare), - /// Pay-to-PubKey-Hash - Pkh(Pkh), - /// Pay-to-Witness-PubKey-Hash - Wpkh(Wpkh), - /// Pay-to-ScriptHash(includes nested wsh/wpkh/sorted multi) - Sh(Sh), - /// Pay-to-Witness-ScriptHash with Segwitv0 context - Wsh(Wsh), -} - /// Descriptor Type of the descriptor #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum DescriptorType { @@ -191,6 +200,133 @@ pub enum DescriptorType { WshSortedMulti, /// Sh Wsh Sorted Multi ShWshSortedMulti, + /// Legacy Pegin + LegacyPegin, + /// Dynafed Pegin + Pegin, + /// Covenant: Only supported in p2wsh context + Cov, +} + +impl FromStr for DescriptorType { + type Err = Error; + + /// Does not check if the Descriptor is well formed or not. + /// Such errors would be caught later while parsing the descriptor + fn from_str(s: &str) -> Result { + if s.len() >= 12 && &s[0..12] == "legacy_pegin" { + Ok(DescriptorType::LegacyPegin) + } else if s.len() >= 5 && &s[0..5] == "pegin" { + Ok(DescriptorType::Pegin) + } else if s.len() >= 3 && &s[0..3] == "pkh" { + Ok(DescriptorType::Pkh) + } else if s.len() >= 4 && &s[0..4] == "wpkh" { + Ok(DescriptorType::Wpkh) + } else if s.len() >= 6 && &s[0..6] == "sh(wsh" { + Ok(DescriptorType::ShWsh) + } else if s.len() >= 7 && &s[0..7] == "sh(wpkh" { + Ok(DescriptorType::ShWpkh) + } else if s.len() >= 14 && &s[0..14] == "sh(sortedmulti" { + Ok(DescriptorType::ShSortedMulti) + } else if s.len() >= 18 && &s[0..18] == "sh(wsh(sortedmulti" { + Ok(DescriptorType::ShWshSortedMulti) + } else if s.len() >= 2 && &s[0..2] == "sh" { + Ok(DescriptorType::Sh) + } else if s.len() >= 15 && &s[0..15] == "wsh(sortedmulti" { + Ok(DescriptorType::WshSortedMulti) + } else if s.len() >= 3 && &s[0..3] == "wsh" { + Ok(DescriptorType::Wsh) + } else if s.len() >= 6 && &s[0..6] == "covwsh" { + Ok(DescriptorType::Cov) + } else { + Ok(DescriptorType::Bare) + } + } +} +/// Method for determining Type of descriptor when parsing from String +pub enum DescriptorInfo { + /// Bitcoin Descriptor + Btc { + /// Whether descriptor has secret keys + has_secret: bool, + /// The type of descriptor + ty: DescriptorType, + }, + /// Elements Descriptor + Elements { + /// Whether descriptor has secret keys + has_secret: bool, + /// The type of descriptor + ty: DescriptorType, + }, + /// Pegin descriptor + /// Only provides information about the bitcoin side of descriptor + /// Use [DescriptorTrait::user_desc] method to obtain the user descriptor + /// and call DescriptorType method on it on to find information about + /// the user claim descriptor. + Pegin { + /// Whether the user descriptor has secret + has_secret: bool, + /// The type of descriptor + ty: DescriptorType, + }, +} + +impl DescriptorInfo { + /// Compute the [DescriptorInfo] for the given descriptor string + /// This method should when the user is unsure whether they are parsing + /// Bitcoin Descriptor, Elements Descriptor or Pegin Descriptors. + /// This also returns information whether the descriptor contains any secrets + /// of the type [DescriptorSecretKey]. If the descriptor contains secret, users + /// should use the method [DescriptorPublicKey::parse_descriptor] to obtain the + /// Descriptor and a secret key to public key mapping + pub fn from_desc_str(s: &str) -> Result { + let is_secret_key = |s: &String, has_secret: &mut bool| -> String { + *has_secret = match DescriptorSecretKey::from_str(s) { + Ok(_sk) => true, + Err(_) => false, + }; + String::from("") + }; + + // Parse as a string descriptor + let mut has_secret_pk = false; + let mut has_secret_pkh = false; + let descriptor = Descriptor::::from_str(s)?; + let _d = descriptor.translate_pk_infallible( + |pk| is_secret_key(pk, &mut has_secret_pk), + |pkh| is_secret_key(pkh, &mut has_secret_pkh), + ); + let has_secret = has_secret_pk || has_secret_pkh; + let ty = DescriptorType::from_str(s)?; + let is_pegin = match ty { + DescriptorType::Pegin | DescriptorType::LegacyPegin => true, + _ => false, + }; + // Todo: add elements later + if is_pegin { + Ok(DescriptorInfo::Pegin { has_secret, ty }) + } else { + Ok(DescriptorInfo::Btc { has_secret, ty }) + } + } +} + +/// Script descriptor +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Descriptor { + /// A raw scriptpubkey (including pay-to-pubkey) under Legacy context + Bare(Bare), + /// Pay-to-PubKey-Hash + Pkh(Pkh), + /// Pay-to-Witness-PubKey-Hash + Wpkh(Wpkh), + /// Pay-to-ScriptHash(includes nested wsh/wpkh/sorted multi) + Sh(Sh), + /// Pay-to-Witness-ScriptHash with Segwitv0 context + Wsh(Wsh), + /// Covenant descriptor + Cov(CovenantDescriptor), } impl Descriptor { @@ -296,8 +432,24 @@ impl Descriptor { WshInner::SortedMulti(ref _smv) => DescriptorType::WshSortedMulti, WshInner::Ms(ref _ms) => DescriptorType::Wsh, }, + Descriptor::Cov(ref _cov) => DescriptorType::Cov, + } + } + + /// Unwrap a descriptor as a covenant descriptor + /// Panics if the descriptor is not of [DescriptorType::Cov] + pub fn as_cov(&self) -> Result<&CovenantDescriptor, Error> { + if let Descriptor::Cov(cov) = self { + Ok(cov) + } else { + Err(Error::CovError(CovError::BadCovDescriptor)) } } + + /// Return a string without the checksum + pub fn to_string_no_chksum(&self) -> String { + format!("{:?}", self) + } } impl TranslatePk for Descriptor

{ @@ -332,12 +484,47 @@ impl TranslatePk for Descriptor

{ Descriptor::Wsh(ref wsh) => { Descriptor::Wsh(wsh.translate_pk(&mut translatefpk, &mut translatefpkh)?) } + Descriptor::Cov(ref cov) => { + Descriptor::Cov(cov.translate_pk(&mut translatefpk, &mut translatefpkh)?) + } }; Ok(desc) } } -impl DescriptorTrait for Descriptor { +impl ElementsTrait for Descriptor +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn blind_addr( + &self, + blinder: Option, + params: &'static elements::AddressParams, + ) -> Result + where + Pk: ToPublicKey, + { + match *self { + Descriptor::Bare(ref bare) => bare.blind_addr(blinder, params), + Descriptor::Pkh(ref pkh) => pkh.blind_addr(blinder, params), + Descriptor::Wpkh(ref wpkh) => wpkh.blind_addr(blinder, params), + Descriptor::Wsh(ref wsh) => wsh.blind_addr(blinder, params), + Descriptor::Sh(ref sh) => sh.blind_addr(blinder, params), + Descriptor::Cov(ref cov) => cov.blind_addr(blinder, params), + } + } +} + +impl DescriptorTrait for Descriptor +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ /// Whether the descriptor is safe /// Checks whether all the spend paths in the descriptor are possible /// on the bitcoin network under the current standardness and consensus rules @@ -353,19 +540,21 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.sanity_check(), Descriptor::Wsh(ref wsh) => wsh.sanity_check(), Descriptor::Sh(ref sh) => sh.sanity_check(), + Descriptor::Cov(ref cov) => cov.sanity_check(), } } /// Computes the Bitcoin address of the descriptor, if one exists - fn address(&self, network: bitcoin::Network) -> Result + fn address(&self, params: &'static elements::AddressParams) -> Result where Pk: ToPublicKey, { match *self { - Descriptor::Bare(ref bare) => bare.address(network), - Descriptor::Pkh(ref pkh) => pkh.address(network), - Descriptor::Wpkh(ref wpkh) => wpkh.address(network), - Descriptor::Wsh(ref wsh) => wsh.address(network), - Descriptor::Sh(ref sh) => sh.address(network), + Descriptor::Bare(ref bare) => bare.address(params), + Descriptor::Pkh(ref pkh) => pkh.address(params), + Descriptor::Wpkh(ref wpkh) => wpkh.address(params), + Descriptor::Wsh(ref wsh) => wsh.address(params), + Descriptor::Sh(ref sh) => sh.address(params), + Descriptor::Cov(ref cov) => cov.address(params), } } @@ -380,6 +569,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.script_pubkey(), Descriptor::Wsh(ref wsh) => wsh.script_pubkey(), Descriptor::Sh(ref sh) => sh.script_pubkey(), + Descriptor::Cov(ref cov) => cov.script_pubkey(), } } @@ -401,6 +591,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.unsigned_script_sig(), Descriptor::Wsh(ref wsh) => wsh.unsigned_script_sig(), Descriptor::Sh(ref sh) => sh.unsigned_script_sig(), + Descriptor::Cov(ref cov) => cov.unsigned_script_sig(), } } @@ -418,6 +609,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.explicit_script(), Descriptor::Wsh(ref wsh) => wsh.explicit_script(), Descriptor::Sh(ref sh) => sh.explicit_script(), + Descriptor::Cov(ref cov) => cov.explicit_script(), } } @@ -435,6 +627,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction(satisfier), Descriptor::Wsh(ref wsh) => wsh.get_satisfaction(satisfier), Descriptor::Sh(ref sh) => sh.get_satisfaction(satisfier), + Descriptor::Cov(ref cov) => cov.get_satisfaction(satisfier), } } @@ -449,6 +642,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.max_satisfaction_weight(), Descriptor::Wsh(ref wsh) => wsh.max_satisfaction_weight(), Descriptor::Sh(ref sh) => sh.max_satisfaction_weight(), + Descriptor::Cov(ref cov) => cov.max_satisfaction_weight(), } } @@ -466,6 +660,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.script_code(), Descriptor::Wsh(ref wsh) => wsh.script_code(), Descriptor::Sh(ref sh) => sh.script_code(), + Descriptor::Cov(ref cov) => cov.script_code(), } } } @@ -482,6 +677,7 @@ impl ForEachKey for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.for_each_key(pred), Descriptor::Wsh(ref wsh) => wsh.for_each_key(pred), Descriptor::Sh(ref sh) => sh.for_each_key(pred), + Descriptor::Cov(ref cov) => cov.for_any_key(pred), } } } @@ -568,10 +764,11 @@ where /// Parse an expression tree into a descriptor fn from_tree(top: &expression::Tree) -> Result, Error> { Ok(match (top.name, top.args.len() as u32) { - ("pkh", 1) => Descriptor::Pkh(Pkh::from_tree(top)?), - ("wpkh", 1) => Descriptor::Wpkh(Wpkh::from_tree(top)?), - ("sh", 1) => Descriptor::Sh(Sh::from_tree(top)?), - ("wsh", 1) => Descriptor::Wsh(Wsh::from_tree(top)?), + ("elpkh", 1) => Descriptor::Pkh(Pkh::from_tree(top)?), + ("elwpkh", 1) => Descriptor::Wpkh(Wpkh::from_tree(top)?), + ("elsh", 1) => Descriptor::Sh(Sh::from_tree(top)?), + ("elcovwsh", 2) => Descriptor::Cov(CovenantDescriptor::from_tree(top)?), + ("elwsh", 1) => Descriptor::Wsh(Wsh::from_tree(top)?), _ => Descriptor::Bare(Bare::from_tree(top)?), }) } @@ -587,7 +784,12 @@ where type Err = Error; fn from_str(s: &str) -> Result, Error> { - let desc_str = verify_checksum(s)?; + if !s.starts_with(ELMTS_STR) { + return Err(Error::BadDescriptor(String::from( + "Not an Elements Descriptor", + ))); + } + let desc_str = verify_checksum(&s)?; let top = expression::Tree::from_str(desc_str)?; expression::FromTree::from_tree(&top) } @@ -601,6 +803,7 @@ impl fmt::Debug for Descriptor { Descriptor::Wpkh(ref wpkh) => write!(f, "{:?}", wpkh), Descriptor::Sh(ref sub) => write!(f, "{:?}", sub), Descriptor::Wsh(ref sub) => write!(f, "{:?}", sub), + Descriptor::Cov(ref cov) => write!(f, "{:?}", cov), } } } @@ -613,6 +816,7 @@ impl fmt::Display for Descriptor { Descriptor::Wpkh(ref wpkh) => write!(f, "{}", wpkh), Descriptor::Sh(ref sub) => write!(f, "{}", sub), Descriptor::Wsh(ref sub) => write!(f, "{}", sub), + Descriptor::Cov(ref cov) => write!(f, "{}", cov), } } } @@ -623,19 +827,25 @@ serde_string_impl_pk!(Descriptor, "a script descriptor"); mod tests { use super::checksum::desc_checksum; use super::DescriptorTrait; - use bitcoin::blockdata::opcodes::all::{OP_CLTV, OP_CSV}; - use bitcoin::blockdata::script::Instruction; - use bitcoin::blockdata::{opcodes, script}; + use bitcoin; use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::{hash160, sha256}; use bitcoin::util::bip32; - use bitcoin::{self, secp256k1, PublicKey}; + use bitcoin::PublicKey; use descriptor::key::Wildcard; use descriptor::{ DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePub, DescriptorXKey, }; + + use elements::opcodes::{ + self, + all::{OP_CLTV, OP_CSV}, + }; + use elements::script::Instruction; + use elements::{self, secp256k1}; + use elements::{script, Script}; use hex_script; - use miniscript::satisfy::BitcoinSig; + use miniscript::satisfy::ElementsSig; use std::cmp; use std::collections::HashMap; use std::str::FromStr; @@ -646,7 +856,7 @@ mod tests { type StdDescriptor = Descriptor; const TEST_PK: &'static str = - "pk(020000000000000000000000000000000000000000000000000000000000000002)"; + "elpk(020000000000000000000000000000000000000000000000000000000000000002)"; impl cmp::PartialEq for DescriptorSecretKey { fn eq(&self, other: &Self) -> bool { @@ -680,12 +890,27 @@ mod tests { ); } + // helper function to create elements txin from scriptsig and witness + fn elements_txin(script_sig: Script, witness: Vec>) -> elements::TxIn { + let mut txin_witness = elements::TxInWitness::default(); + txin_witness.script_witness = witness; + elements::TxIn { + previous_output: elements::OutPoint::default(), + script_sig: script_sig, + sequence: 100, + is_pegin: false, + has_issuance: false, + asset_issuance: elements::AssetIssuance::default(), + witness: txin_witness, + } + } + #[test] fn desc_rtt_tests() { - roundtrip_descriptor("c:pk_k()"); - roundtrip_descriptor("wsh(pk())"); - roundtrip_descriptor("wsh(c:pk_k())"); - roundtrip_descriptor("c:pk_h()"); + roundtrip_descriptor("elc:pk_k()"); + roundtrip_descriptor("elwsh(pk())"); + roundtrip_descriptor("elwsh(c:pk_k())"); + roundtrip_descriptor("elc:pk_h()"); } #[test] fn parse_descriptor() { @@ -696,33 +921,35 @@ mod tests { StdDescriptor::from_str("nl:0").unwrap_err(); //issue 63 let compressed_pk = DummyKey.to_string(); assert_eq!( - StdDescriptor::from_str("sh(sortedmulti)") + StdDescriptor::from_str("elsh(sortedmulti)") .unwrap_err() .to_string(), "unexpected «no arguments given for sortedmulti»" ); //issue 202 assert_eq!( - StdDescriptor::from_str(&format!("sh(sortedmulti(2,{}))", compressed_pk)) + StdDescriptor::from_str(&format!("elsh(sortedmulti(2,{}))", compressed_pk)) .unwrap_err() .to_string(), "unexpected «higher threshold than there were keys in sortedmulti»" ); //issue 202 StdDescriptor::from_str(TEST_PK).unwrap(); + // fuzzer + StdDescriptor::from_str("slip77").unwrap_err(); let uncompressed_pk = "0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf"; // Context tests - StdDescriptor::from_str(&format!("pk({})", uncompressed_pk)).unwrap(); - StdDescriptor::from_str(&format!("pkh({})", uncompressed_pk)).unwrap(); - StdDescriptor::from_str(&format!("sh(pk({}))", uncompressed_pk)).unwrap(); - StdDescriptor::from_str(&format!("wpkh({})", uncompressed_pk)).unwrap_err(); - StdDescriptor::from_str(&format!("sh(wpkh({}))", uncompressed_pk)).unwrap_err(); - StdDescriptor::from_str(&format!("wsh(pk{})", uncompressed_pk)).unwrap_err(); - StdDescriptor::from_str(&format!("sh(wsh(pk{}))", uncompressed_pk)).unwrap_err(); + StdDescriptor::from_str(&format!("elpk({})", uncompressed_pk)).unwrap(); + StdDescriptor::from_str(&format!("elpkh({})", uncompressed_pk)).unwrap(); + StdDescriptor::from_str(&format!("elsh(pk({}))", uncompressed_pk)).unwrap(); + StdDescriptor::from_str(&format!("elwpkh({})", uncompressed_pk)).unwrap_err(); + StdDescriptor::from_str(&format!("elsh(wpkh({}))", uncompressed_pk)).unwrap_err(); + StdDescriptor::from_str(&format!("elwsh(pk{})", uncompressed_pk)).unwrap_err(); + StdDescriptor::from_str(&format!("elsh(wsh(pk{}))", uncompressed_pk)).unwrap_err(); StdDescriptor::from_str(&format!( - "or_i(pk({}),pk({}))", + "elor_i(pk({}),pk({}))", uncompressed_pk, uncompressed_pk )) .unwrap_err(); @@ -731,7 +958,7 @@ mod tests { #[test] pub fn script_pubkey() { let bare = StdDescriptor::from_str(&format!( - "multi(1,020000000000000000000000000000000000000000000000000000000000000002)" + "elmulti(1,020000000000000000000000000000000000000000000000000000000000000002)" )) .unwrap(); assert_eq!( @@ -741,7 +968,7 @@ mod tests { ) ); assert_eq!( - bare.address(bitcoin::Network::Bitcoin) + bare.address(&elements::AddressParams::ELEMENTS) .unwrap_err() .to_string(), "Bare descriptors don't have address" @@ -750,7 +977,7 @@ mod tests { let pk = StdDescriptor::from_str(TEST_PK).unwrap(); assert_eq!( pk.script_pubkey(), - bitcoin::Script::from(vec![ + elements::Script::from(vec![ 0x21, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xac, @@ -758,7 +985,7 @@ mod tests { ); let pkh = StdDescriptor::from_str( - "pkh(\ + "elpkh(\ 020000000000000000000000000000000000000000000000000000000000000002\ )", ) @@ -777,12 +1004,14 @@ mod tests { .into_script() ); assert_eq!( - pkh.address(bitcoin::Network::Bitcoin,).unwrap().to_string(), - "1D7nRvrRgzCg9kYBwhPH3j3Gs6SmsRg3Wq" + pkh.address(&elements::AddressParams::ELEMENTS,) + .unwrap() + .to_string(), + "2dmYXpSu8YP6aLcJYhHfB1C19mdzSx2GPB9" ); let wpkh = StdDescriptor::from_str( - "wpkh(\ + "elwpkh(\ 020000000000000000000000000000000000000000000000000000000000000002\ )", ) @@ -798,14 +1027,14 @@ mod tests { .into_script() ); assert_eq!( - wpkh.address(bitcoin::Network::Bitcoin,) + wpkh.address(&elements::AddressParams::ELEMENTS,) .unwrap() .to_string(), - "bc1qsn57m9drscflq5nl76z6ny52hck5w4x5wqd9yt" + "ert1qsn57m9drscflq5nl76z6ny52hck5w4x57m69k3" ); let shwpkh = StdDescriptor::from_str( - "sh(wpkh(\ + "elsh(wpkh(\ 020000000000000000000000000000000000000000000000000000000000000002\ ))", ) @@ -823,14 +1052,14 @@ mod tests { ); assert_eq!( shwpkh - .address(bitcoin::Network::Bitcoin,) + .address(&elements::AddressParams::ELEMENTS,) .unwrap() .to_string(), - "3PjMEzoveVbvajcnDDuxcJhsuqPHgydQXq" + "XZPaAbg6M83Fq5NqvbEGZ5kwy9RKSTke2s" ); let sh = StdDescriptor::from_str( - "sh(c:pk_k(\ + "elsh(c:pk_k(\ 020000000000000000000000000000000000000000000000000000000000000002\ ))", ) @@ -847,12 +1076,14 @@ mod tests { .into_script() ); assert_eq!( - sh.address(bitcoin::Network::Bitcoin,).unwrap().to_string(), - "3HDbdvM9CQ6ASnQFUkWw6Z4t3qNwMesJE9" + sh.address(&elements::AddressParams::ELEMENTS,) + .unwrap() + .to_string(), + "XSspZXDJu2XVh8AKC7qF3L7x79Qy67JhQb" ); let wsh = StdDescriptor::from_str( - "wsh(c:pk_k(\ + "elwsh(c:pk_k(\ 020000000000000000000000000000000000000000000000000000000000000002\ ))", ) @@ -873,12 +1104,14 @@ mod tests { .into_script() ); assert_eq!( - wsh.address(bitcoin::Network::Bitcoin,).unwrap().to_string(), - "bc1qlymeahyfsv2jm3upw3urqp6m65ufde9seedl7umh0lth6yjt5zzsk33tv6" + wsh.address(&elements::AddressParams::ELEMENTS,) + .unwrap() + .to_string(), + "ert1qlymeahyfsv2jm3upw3urqp6m65ufde9seedl7umh0lth6yjt5zzsan9u2t" ); let shwsh = StdDescriptor::from_str( - "sh(wsh(c:pk_k(\ + "elsh(wsh(c:pk_k(\ 020000000000000000000000000000000000000000000000000000000000000002\ )))", ) @@ -896,10 +1129,10 @@ mod tests { ); assert_eq!( shwsh - .address(bitcoin::Network::Bitcoin,) + .address(&elements::AddressParams::ELEMENTS,) .unwrap() .to_string(), - "38cTksiyPT2b1uGRVbVqHdDhW9vKs84N6Z" + "XJGggUb965TvGF2VCxp9EQGmZTxMeDjwQQ" ); } @@ -924,9 +1157,9 @@ mod tests { }; impl Satisfier for SimpleSat { - fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option { + fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option { if *pk == self.pk { - Some((self.sig, bitcoin::SigHashType::All)) + Some((self.sig, elements::SigHashType::All)) } else { None } @@ -936,54 +1169,48 @@ mod tests { let satisfier = SimpleSat { sig, pk }; let ms = ms_str!("c:pk_k({})", pk); - let mut txin = bitcoin::TxIn { - previous_output: bitcoin::OutPoint::default(), - script_sig: bitcoin::Script::new(), + let mut txin = elements::TxIn { + previous_output: elements::OutPoint::default(), + script_sig: Script::new(), sequence: 100, - witness: vec![], + is_pegin: false, + has_issuance: false, + asset_issuance: elements::AssetIssuance::default(), + witness: elements::TxInWitness::default(), }; let bare = Descriptor::new_bare(ms.clone()).unwrap(); bare.satisfy(&mut txin, &satisfier).expect("satisfaction"); assert_eq!( txin, - bitcoin::TxIn { - previous_output: bitcoin::OutPoint::default(), - script_sig: script::Builder::new().push_slice(&sigser[..]).into_script(), - sequence: 100, - witness: vec![], - } + elements_txin( + script::Builder::new().push_slice(&sigser[..]).into_script(), + vec![] + ), ); - assert_eq!(bare.unsigned_script_sig(), bitcoin::Script::new()); + assert_eq!(bare.unsigned_script_sig(), elements::Script::new()); let pkh = Descriptor::new_pkh(pk); pkh.satisfy(&mut txin, &satisfier).expect("satisfaction"); assert_eq!( txin, - bitcoin::TxIn { - previous_output: bitcoin::OutPoint::default(), - script_sig: script::Builder::new() + elements_txin( + script::Builder::new() .push_slice(&sigser[..]) .push_key(&pk) .into_script(), - sequence: 100, - witness: vec![], - } + vec![] + ) ); - assert_eq!(pkh.unsigned_script_sig(), bitcoin::Script::new()); + assert_eq!(pkh.unsigned_script_sig(), elements::Script::new()); let wpkh = Descriptor::new_wpkh(pk).unwrap(); wpkh.satisfy(&mut txin, &satisfier).expect("satisfaction"); assert_eq!( txin, - bitcoin::TxIn { - previous_output: bitcoin::OutPoint::default(), - script_sig: bitcoin::Script::new(), - sequence: 100, - witness: vec![sigser.clone(), pk.to_bytes(),], - } + elements_txin(Script::new(), vec![sigser.clone(), pk.to_bytes(),]) ); - assert_eq!(wpkh.unsigned_script_sig(), bitcoin::Script::new()); + assert_eq!(wpkh.unsigned_script_sig(), elements::Script::new()); let shwpkh = Descriptor::new_sh_wpkh(pk).unwrap(); shwpkh.satisfy(&mut txin, &satisfier).expect("satisfaction"); @@ -993,40 +1220,24 @@ mod tests { &hash160::Hash::from_hex("d1b2a1faf62e73460af885c687dee3b7189cd8ab").unwrap()[..], ) .into_script(); + let expected_ssig = script::Builder::new() + .push_slice(&redeem_script[..]) + .into_script(); assert_eq!( txin, - bitcoin::TxIn { - previous_output: bitcoin::OutPoint::default(), - script_sig: script::Builder::new() - .push_slice(&redeem_script[..]) - .into_script(), - sequence: 100, - witness: vec![sigser.clone(), pk.to_bytes(),], - } - ); - assert_eq!( - shwpkh.unsigned_script_sig(), - script::Builder::new() - .push_slice(&redeem_script[..]) - .into_script() + elements_txin(expected_ssig.clone(), vec![sigser.clone(), pk.to_bytes()]) ); + assert_eq!(shwpkh.unsigned_script_sig(), expected_ssig); let ms = ms_str!("c:pk_k({})", pk); let sh = Descriptor::new_sh(ms.clone()).unwrap(); sh.satisfy(&mut txin, &satisfier).expect("satisfaction"); - assert_eq!( - txin, - bitcoin::TxIn { - previous_output: bitcoin::OutPoint::default(), - script_sig: script::Builder::new() - .push_slice(&sigser[..]) - .push_slice(&ms.encode()[..]) - .into_script(), - sequence: 100, - witness: vec![], - } - ); - assert_eq!(sh.unsigned_script_sig(), bitcoin::Script::new()); + let expected_ssig = script::Builder::new() + .push_slice(&sigser[..]) + .push_slice(&ms.encode()[..]) + .into_script(); + assert_eq!(txin, elements_txin(expected_ssig, vec![])); + assert_eq!(sh.unsigned_script_sig(), Script::new()); let ms = ms_str!("c:pk_k({})", pk); @@ -1034,39 +1245,31 @@ mod tests { wsh.satisfy(&mut txin, &satisfier).expect("satisfaction"); assert_eq!( txin, - bitcoin::TxIn { - previous_output: bitcoin::OutPoint::default(), - script_sig: bitcoin::Script::new(), - sequence: 100, - witness: vec![sigser.clone(), ms.encode().into_bytes(),], - } + elements_txin( + Script::new(), + vec![sigser.clone(), ms.encode().into_bytes()] + ) ); - assert_eq!(wsh.unsigned_script_sig(), bitcoin::Script::new()); + assert_eq!(wsh.unsigned_script_sig(), Script::new()); let shwsh = Descriptor::new_sh_wsh(ms.clone()).unwrap(); shwsh.satisfy(&mut txin, &satisfier).expect("satisfaction"); + let expected_ssig = script::Builder::new() + .push_slice(&ms.encode().to_v0_p2wsh()[..]) + .into_script(); assert_eq!( txin, - bitcoin::TxIn { - previous_output: bitcoin::OutPoint::default(), - script_sig: script::Builder::new() - .push_slice(&ms.encode().to_v0_p2wsh()[..]) - .into_script(), - sequence: 100, - witness: vec![sigser.clone(), ms.encode().into_bytes(),], - } - ); - assert_eq!( - shwsh.unsigned_script_sig(), - script::Builder::new() - .push_slice(&ms.encode().to_v0_p2wsh()[..]) - .into_script() + elements_txin( + expected_ssig.clone(), + vec![sigser.clone(), ms.encode().into_bytes(),] + ) ); + assert_eq!(shwsh.unsigned_script_sig(), expected_ssig); } #[test] fn after_is_cltv() { - let descriptor = Descriptor::::from_str("wsh(after(1000))").unwrap(); + let descriptor = Descriptor::::from_str("elwsh(after(1000))").unwrap(); let script = descriptor.explicit_script(); let actual_instructions: Vec<_> = script.instructions().collect(); @@ -1077,7 +1280,7 @@ mod tests { #[test] fn older_is_csv() { - let descriptor = Descriptor::::from_str("wsh(older(1000))").unwrap(); + let descriptor = Descriptor::::from_str("elwsh(older(1000))").unwrap(); let script = descriptor.explicit_script(); let actual_instructions: Vec<_> = script.instructions().collect(); @@ -1088,7 +1291,7 @@ mod tests { #[test] fn roundtrip_tests() { - let descriptor = Descriptor::::from_str("multi"); + let descriptor = Descriptor::::from_str("elmulti"); assert_eq!( descriptor.unwrap_err().to_string(), "unexpected «no arguments given»" @@ -1097,7 +1300,7 @@ mod tests { #[test] fn empty_thresh() { - let descriptor = Descriptor::::from_str("thresh"); + let descriptor = Descriptor::::from_str("elthresh"); assert_eq!( descriptor.unwrap_err().to_string(), "unexpected «no arguments given»" @@ -1120,23 +1323,18 @@ mod tests { let sig_b = secp256k1::Signature::from_str("3044022075b7b65a7e6cd386132c5883c9db15f9a849a0f32bc680e9986398879a57c276022056d94d12255a4424f51c700ac75122cb354895c9f2f88f0cbb47ba05c9c589ba").unwrap(); let descriptor = Descriptor::::from_str(&format!( - "wsh(and_v(v:pk({A}),pk({B})))", + "elwsh(and_v(v:pk({A}),pk({B})))", A = a, B = b )) .unwrap(); - let mut txin = bitcoin::TxIn { - previous_output: bitcoin::OutPoint::default(), - script_sig: bitcoin::Script::new(), - sequence: 0, - witness: vec![], - }; + let mut txin = elements_txin(Script::new(), vec![]); let satisfier = { let mut satisfier = HashMap::with_capacity(2); - satisfier.insert(a, (sig_a.clone(), ::bitcoin::SigHashType::All)); - satisfier.insert(b, (sig_b.clone(), ::bitcoin::SigHashType::All)); + satisfier.insert(a, (sig_a.clone(), ::elements::SigHashType::All)); + satisfier.insert(b, (sig_b.clone(), ::elements::SigHashType::All)); satisfier }; @@ -1145,8 +1343,8 @@ mod tests { descriptor.satisfy(&mut txin, &satisfier).unwrap(); // assert - let witness0 = &txin.witness[0]; - let witness1 = &txin.witness[1]; + let witness0 = &txin.witness.script_witness[0]; + let witness1 = &txin.witness.script_witness[1]; let sig0 = secp256k1::Signature::from_der(&witness0[..witness0.len() - 1]).unwrap(); let sig1 = secp256k1::Signature::from_der(&witness1[..witness1.len() - 1]).unwrap(); @@ -1163,7 +1361,7 @@ mod tests { fn test_scriptcode() { // P2WPKH (from bip143 test vectors) let descriptor = Descriptor::::from_str( - "wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)", + "elwpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)", ) .unwrap(); assert_eq!( @@ -1173,7 +1371,7 @@ mod tests { // P2SH-P2WPKH (from bip143 test vectors) let descriptor = Descriptor::::from_str( - "sh(wpkh(03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873))", + "elsh(wpkh(03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873))", ) .unwrap(); assert_eq!( @@ -1183,7 +1381,7 @@ mod tests { // P2WSH (from bitcoind's `createmultisig`) let descriptor = Descriptor::::from_str( - "wsh(multi(2,03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd,03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626))", + "elwsh(multi(2,03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd,03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626))", ) .unwrap(); assert_eq!( @@ -1194,7 +1392,7 @@ mod tests { ); // P2SH-P2WSH (from bitcoind's `createmultisig`) - let descriptor = Descriptor::::from_str("sh(wsh(multi(2,03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd,03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626)))").unwrap(); + let descriptor = Descriptor::::from_str("elsh(wsh(multi(2,03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd,03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626)))").unwrap(); assert_eq!( *descriptor .script_code() @@ -1328,45 +1526,45 @@ mod tests { let addr_one = desc_one .translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx)) .unwrap() - .address(bitcoin::Network::Bitcoin) + .address(&elements::AddressParams::ELEMENTS) .unwrap(); let addr_two = desc_two .translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx)) .unwrap() - .address(bitcoin::Network::Bitcoin) + .address(&elements::AddressParams::ELEMENTS) .unwrap(); - let addr_expected = bitcoin::Address::from_str(raw_addr_expected).unwrap(); + let addr_expected = elements::Address::from_str(raw_addr_expected).unwrap(); assert_eq!(addr_one, addr_expected); assert_eq!(addr_two, addr_expected); } // P2SH and pubkeys _test_sortedmulti( - "sh(sortedmulti(1,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352))#uetvewm2", - "sh(sortedmulti(1,0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))#7l8smyg9", - "3JZJNxvDKe6Y55ZaF5223XHwfF2eoMNnoV", + "elsh(sortedmulti(1,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352))#tse3qz98", + "elsh(sortedmulti(1,0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))#ptnf05qc", + "XUDXJZnP2GXsKRKdxSLKzJM1iZ4gbbyrGh", ); // P2WSH and single-xpub descriptor _test_sortedmulti( - "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))#7etm7zk7", - "wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB))#ppmeel9k", - "bc1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcspx5y6a", + "elwsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))#a8h2v83d", + "elwsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB))#qfcn7ujk", + "ert1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcs2yqnuv", ); // P2WSH-P2SH and ranged descriptor _test_sortedmulti( - "sh(wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*)))#u60cee0u", - "sh(wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*)))#75dkf44w", - "325zcVBN5o2eqqqtGwPjmtDd8dJRyYP82s", + "elsh(wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*)))#l7qy253t", + "elsh(wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*)))#0gpee5cl", + "XBkDY63XnRTz6BbwzJi3ifGhBwLTomEzkq", ); } #[test] fn test_parse_descriptor() { let secp = &secp256k1::Secp256k1::signing_only(); - let (descriptor, key_map) = Descriptor::parse_descriptor(secp, "wpkh(tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/44'/0'/0'/0/*)").unwrap(); - assert_eq!(descriptor.to_string(), "wpkh([2cbe2a6d/44'/0'/0']tpubDCvNhURocXGZsLNqWcqD3syHTqPXrMSTwi8feKVwAcpi29oYKsDD3Vex7x2TDneKMVN23RbLprfxB69v94iYqdaYHsVz3kPR37NQXeqouVz/0/*)#nhdxg96s"); + let (descriptor, key_map) = Descriptor::parse_descriptor(secp, "elwpkh(tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/44'/0'/0'/0/*)").unwrap(); + assert_eq!(descriptor.to_string(), "elwpkh([2cbe2a6d/44'/0'/0']tpubDCvNhURocXGZsLNqWcqD3syHTqPXrMSTwi8feKVwAcpi29oYKsDD3Vex7x2TDneKMVN23RbLprfxB69v94iYqdaYHsVz3kPR37NQXeqouVz/0/*)#pznhhta9"); assert_eq!(key_map.len(), 1); // https://github.com/bitcoin/bitcoin/blob/7ae86b3c6845873ca96650fc69beb4ae5285c801/src/test/descriptor_tests.cpp#L355-L360 @@ -1382,21 +1580,21 @@ mod tests { }; } check_invalid_checksum!(secp, - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#", - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq", - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq", - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5", - "sh(multi(3,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", - "sh(multi(3,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjq09x4t", - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))##ggssrxfy", - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))##tjq09x4t" + "elsh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", + "elsh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#", + "elsh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", + "elsh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq", + "elsh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", + "elsh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5", + "elsh(multi(3,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", + "elsh(multi(3,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", + "elsh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjq09x4t", + "elsh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))##ggssrxfy", + "elsh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))##tjq09x4t" ); - Descriptor::parse_descriptor(&secp, "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy").expect("Valid descriptor with checksum"); - Descriptor::parse_descriptor(&secp, "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t").expect("Valid descriptor with checksum"); + Descriptor::parse_descriptor(&secp, "elsh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#9s2ngs7u").expect("Valid descriptor with checksum"); + Descriptor::parse_descriptor(&secp, "elsh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#uklept69").expect("Valid descriptor with checksum"); } #[test] @@ -1424,11 +1622,11 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; #[test] fn parse_with_secrets() { let secp = &secp256k1::Secp256k1::signing_only(); - let descriptor_str = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)#v20xlvm9"; + let descriptor_str = "elwpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)#xldrpn5u"; let (descriptor, keymap) = Descriptor::::parse_descriptor(&secp, descriptor_str).unwrap(); - let expected = "wpkh([a12b02f4/44'/0'/0']xpub6BzhLAQUDcBUfHRQHZxDF2AbcJqp4Kaeq6bzJpXrjrWuK26ymTFwkEFbxPra2bJ7yeZKbDjfDeFwxe93JMqpo5SsPJH6dZdvV9kMzJkAZ69/0/*)#u37l7u8u"; + let expected = "elwpkh([a12b02f4/44'/0'/0']xpub6BzhLAQUDcBUfHRQHZxDF2AbcJqp4Kaeq6bzJpXrjrWuK26ymTFwkEFbxPra2bJ7yeZKbDjfDeFwxe93JMqpo5SsPJH6dZdvV9kMzJkAZ69/0/*)#20ufqv7z"; assert_eq!(expected, descriptor.to_string()); assert_eq!(keymap.len(), 1); @@ -1438,12 +1636,12 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; #[test] fn checksum_for_nested_sh() { - let descriptor_str = "sh(wpkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL))"; + let descriptor_str = "elsh(wpkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL))"; let descriptor: Descriptor = descriptor_str.parse().unwrap(); - assert_eq!(descriptor.to_string(), "sh(wpkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL))#tjp2zm88"); + assert_eq!(descriptor.to_string(), "elsh(wpkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL))#2040pn7l"); - let descriptor_str = "sh(wsh(pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))"; + let descriptor_str = "elsh(wsh(pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))"; let descriptor: Descriptor = descriptor_str.parse().unwrap(); - assert_eq!(descriptor.to_string(), "sh(wsh(pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))#6c6hwr22"); + assert_eq!(descriptor.to_string(), "elsh(wsh(pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))#pqs0de7e"); } } diff --git a/src/descriptor/pegin/dynafed_pegin.rs b/src/descriptor/pegin/dynafed_pegin.rs new file mode 100644 index 00000000..d2733ca4 --- /dev/null +++ b/src/descriptor/pegin/dynafed_pegin.rs @@ -0,0 +1,255 @@ +// Miniscript +// Written in 2020 by +// Rust Elements developers +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! DynaFed Pegin Descriptor Support +//! +//! Traits and implementations for Dynafed Pegin descriptors. +//! Note that this is a bitcoin descriptor and thus cannot be +//! added to elements Descriptor. +//! Unlike Pegin descriptors these are Miniscript, so dealing +//! with these is easier. + +use bitcoin::hashes::Hash; +use bitcoin::{self, blockdata::script, hashes}; +#[allow(deprecated)] +use bitcoin::{blockdata::opcodes, util::contracthash}; +use bitcoin::{hashes::hash160, Address as BtcAddress}; +use bitcoin::{secp256k1, Script as BtcScript}; +use expression::{self, FromTree}; +use policy::{semantic, Liftable}; +use std::{ + fmt::Debug, + fmt::{self, Display}, + marker::PhantomData, + str::FromStr, + sync::Arc, +}; +use Descriptor; +use Error; +use Miniscript; +use { + BtcDescriptor, BtcDescriptorTrait, BtcError, BtcFromTree, BtcLiftable, BtcMiniscript, + BtcPolicy, BtcSatisfier, BtcSegwitv0, BtcTerminal, BtcTree, +}; + +use {DescriptorTrait, Segwitv0, TranslatePk}; + +use {tweak_key, util::varint_len}; + +use descriptor::checksum::{desc_checksum, verify_checksum}; + +use super::PeginTrait; +use {MiniscriptKey, ToPublicKey}; + +/// New Pegin Descriptor with Miniscript support +/// Useful with dynamic federations +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct Pegin { + /// The untweaked pegin bitcoin descriptor + pub fed_desc: BtcDescriptor, + /// The redeem elements descriptor + pub elem_desc: Descriptor, +} + +impl Pegin { + /// Create a new LegacyPegin descriptor + pub fn new(fed_desc: BtcDescriptor, elem_desc: Descriptor) -> Self { + Self { + fed_desc, + elem_desc, + } + } +} + +impl fmt::Debug for Pegin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "pegin({:?},{:?})", self.fed_desc, self.elem_desc) + } +} + +impl fmt::Display for Pegin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let desc = format!("pegin({},{})", self.fed_desc, self.elem_desc); + let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; + write!(f, "{}#{}", &desc, &checksum) + } +} + +impl Liftable for Pegin { + fn lift(&self) -> Result, Error> { + let btc_pol = BtcLiftable::lift(&self.fed_desc)?; + Liftable::lift(&btc_pol) + } +} + +impl BtcLiftable for Pegin { + fn lift(&self) -> Result, BtcError> { + self.fed_desc.lift() + } +} + +impl FromTree for Pegin +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn from_tree(top: &expression::Tree) -> Result { + if top.name == "pegin" && top.args.len() == 2 { + // a roundtrip hack to use FromTree from bitcoin::Miniscript from + // expression::Tree in elements. + let ms_str = top.args[0].to_string(); + let ms_expr = BtcTree::from_str(&ms_str)?; + // + // TODO: Confirm with Andrew about the descriptor type for dynafed + // Assuming sh(wsh) for now. + let fed_desc = BtcDescriptor::::from_tree(&ms_expr)?; + let elem_desc = Descriptor::::from_tree(&top.args[1])?; + Ok(Pegin::new(fed_desc, elem_desc)) + } else { + Err(Error::Unexpected(format!( + "{}({} args) while parsing legacy_pegin descriptor", + top.name, + top.args.len(), + ))) + } + } +} + +impl FromStr for Pegin +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + type Err = Error; + + fn from_str(s: &str) -> Result { + let desc_str = verify_checksum(s)?; + let top = expression::Tree::from_str(desc_str)?; + Self::from_tree(&top) + } +} + +impl PeginTrait for Pegin +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn sanity_check(&self) -> Result<(), Error> { + self.fed_desc + .sanity_check() + .map_err(|_| Error::Unexpected(format!("Federation script sanity check failed")))?; + self.elem_desc + .sanity_check() + .map_err(|_| Error::Unexpected(format!("Federation script sanity check failed")))?; + Ok(()) + } + + fn bitcoin_address( + &self, + network: bitcoin::Network, + secp: &secp256k1::Secp256k1, + ) -> Result + where + Pk: ToPublicKey, + { + Ok(bitcoin::Address::p2shwsh( + &self.bitcoin_witness_script(secp), + network, + )) + } + + fn bitcoin_script_pubkey( + &self, + secp: &secp256k1::Secp256k1, + ) -> BtcScript + where + Pk: ToPublicKey, + { + self.bitcoin_address(bitcoin::Network::Bitcoin, secp) + .expect("Address cannot fail for pegin") + .script_pubkey() + } + + fn bitcoin_unsigned_script_sig( + &self, + secp: &secp256k1::Secp256k1, + ) -> BtcScript + where + Pk: ToPublicKey, + { + let witness_script = self.bitcoin_witness_script(secp); + script::Builder::new() + .push_slice(&witness_script.to_v0_p2wsh()[..]) + .into_script() + } + + fn bitcoin_witness_script( + &self, + secp: &secp256k1::Secp256k1, + ) -> BtcScript + where + Pk: ToPublicKey, + { + let tweak_vec = self.elem_desc.explicit_script().into_bytes(); + let tweak = hashes::sha256::Hash::hash(&tweak_vec); + let tweaked_desc = self.fed_desc.translate_pk_infallible( + |pk| tweak_key(pk, secp, tweak.as_inner()), + |_| unreachable!("No keyhashes in elements descriptors"), + ); + // Hopefully, we never have to use this and dynafed is deployed + tweaked_desc.explicit_script() + } + + fn get_bitcoin_satisfaction( + &self, + secp: &secp256k1::Secp256k1, + satisfier: S, + ) -> Result<(Vec>, BtcScript), Error> + where + S: BtcSatisfier, + Pk: ToPublicKey, + { + let tweak_vec = self.elem_desc.explicit_script().into_bytes(); + let tweak = hashes::sha256::Hash::hash(&tweak_vec); + let tweaked_desc = self.fed_desc.translate_pk_infallible( + |pk| tweak_key(pk, secp, tweak.as_inner()), + |_| unreachable!("No keyhashes in elements descriptors"), + ); + let res = tweaked_desc.get_satisfaction(satisfier)?; + Ok(res) + } + + fn max_satisfaction_weight(&self) -> Result { + // tweaking does not change max satisfaction weight + let w = self.fed_desc.max_satisfaction_weight()?; + Ok(w) + } + + fn script_code(&self, secp: &secp256k1::Secp256k1) -> BtcScript + where + Pk: ToPublicKey, + { + self.bitcoin_witness_script(secp) + } + + fn into_user_descriptor(self) -> Descriptor { + self.elem_desc + } +} diff --git a/src/descriptor/pegin/legacy_pegin.rs b/src/descriptor/pegin/legacy_pegin.rs new file mode 100644 index 00000000..f85914f3 --- /dev/null +++ b/src/descriptor/pegin/legacy_pegin.rs @@ -0,0 +1,515 @@ +// Miniscript +// Written in 2018 by +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Pegin Descriptor Support +//! +//! Traits and implementations for Pegin descriptors +//! Note that this is a bitcoin descriptor and thus cannot be +//! added to elements Descriptor. Upstream rust-miniscript does +//! has a Descriptor enum which should ideally have it, but +//! bitcoin descriptors cannot depend on elements descriptors +//! Thus, as a simple solution we implement these as a separate +//! struct with it's own API. + +use bitcoin::hashes::Hash; +use bitcoin::{self, blockdata::script, hashes}; +#[allow(deprecated)] +use bitcoin::{blockdata::opcodes, util::contracthash}; +use bitcoin::{hashes::hash160, Address as BtcAddress}; +use bitcoin::{secp256k1, Script as BtcScript}; +use expression::{self, FromTree}; +use policy::{semantic, Liftable}; +use std::{ + fmt::Debug, + fmt::{self, Display}, + marker::PhantomData, + str::FromStr, + sync::Arc, +}; +use Descriptor; +use Error; +use Miniscript; +use { + BtcDescriptor, BtcDescriptorTrait, BtcError, BtcFromTree, BtcLiftable, BtcMiniscript, + BtcPolicy, BtcSatisfier, BtcSegwitv0, BtcTerminal, BtcTree, +}; + +use {DescriptorTrait, Segwitv0, TranslatePk}; + +use {tweak_key, util::varint_len}; + +use descriptor::checksum::{desc_checksum, verify_checksum}; + +use super::PeginTrait; +use {MiniscriptKey, ToPublicKey}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +/// MiniscriptKey used for Pegins +pub enum LegacyPeginKey { + /// Functionary Key that can be tweaked + Functionary(bitcoin::PublicKey), + /// Non functionary Key, cannot be tweaked + NonFunctionary(bitcoin::PublicKey), +} + +impl LegacyPeginKey { + /// Get the untweaked version of the LegacyPeginKey + pub fn as_untweaked(&self) -> &bitcoin::PublicKey { + match *self { + LegacyPeginKey::Functionary(ref pk) => pk, + LegacyPeginKey::NonFunctionary(ref pk) => pk, + } + } +} + +/// 'f' represents tweakable functionary keys and +/// 'u' represents untweakable keys +impl FromStr for LegacyPeginKey { + // only allow compressed keys in LegacyPegin + type Err = Error; + fn from_str(s: &str) -> Result { + if s.is_empty() { + Err(Error::BadDescriptor(format!("Empty Legacy pegin"))) + } else if &s[0..1] == "f" && s.len() == 67 { + Ok(LegacyPeginKey::Functionary(bitcoin::PublicKey::from_str( + &s[1..], + )?)) + } else if &s[0..1] == "u" && s.len() == 67 { + Ok(LegacyPeginKey::NonFunctionary( + bitcoin::PublicKey::from_str(&s[1..])?, + )) + } else { + Err(Error::BadDescriptor(format!( + "Invalid Legacy Pegin descriptor" + ))) + } + } +} + +impl fmt::Display for LegacyPeginKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + LegacyPeginKey::Functionary(ref pk) => write!(f, "f{}", pk), + LegacyPeginKey::NonFunctionary(ref pk) => write!(f, "u{}", pk), + } + } +} + +impl MiniscriptKey for LegacyPeginKey { + type Hash = hash160::Hash; + + fn is_uncompressed(&self) -> bool { + false + } + + fn serialized_len(&self) -> usize { + 33 + } + + fn to_pubkeyhash(&self) -> Self::Hash { + let pk = match *self { + LegacyPeginKey::Functionary(ref pk) => pk, + LegacyPeginKey::NonFunctionary(ref pk) => pk, + }; + MiniscriptKey::to_pubkeyhash(pk) + } +} + +/// Legacy Pegin Descriptor +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct LegacyPegin { + /// The federation pks + pub fed_pks: Vec, + /// The federation threshold + pub fed_k: usize, + /// The emergency pks + pub emer_pks: Vec, + /// The emergency threshold + pub emer_k: usize, + /// csv timelock + pub timelock: u32, + /// The elements descriptor required to redeem + pub desc: Descriptor, + // Representation of federation policy as a miniscript + // Allows for easier implementation + ms: BtcMiniscript, +} + +impl LegacyPegin { + /// Create a new LegacyPegin descriptor + pub fn new( + fed_pks: Vec, + fed_k: usize, + emer_pks: Vec, + emer_k: usize, + timelock: u32, + desc: Descriptor, + ) -> Self { + let fed_ms = BtcMiniscript::from_ast(BtcTerminal::Multi(fed_k, fed_pks.clone())) + .expect("Multi type check can't fail"); + let csv = BtcMiniscript::from_ast(BtcTerminal::Verify(Arc::new( + BtcMiniscript::from_ast(BtcTerminal::Older(timelock)).unwrap(), + ))) + .unwrap(); + let emer_ms = BtcMiniscript::from_ast(BtcTerminal::Multi(emer_k, emer_pks.clone())) + .expect("Multi type check can't fail"); + let emer_ms = + BtcMiniscript::from_ast(BtcTerminal::AndV(Arc::new(csv), Arc::new(emer_ms))).unwrap(); + let ms = BtcMiniscript::from_ast(BtcTerminal::OrD(Arc::new(fed_ms), Arc::new(emer_ms))) + .expect("Type check"); + Self { + fed_pks, + fed_k, + emer_pks, + emer_k, + timelock, + desc, + ms, + } + } + + // Internal function to set the fields of Self according to + // miniscript + fn from_ms_and_desc( + desc: Descriptor, + ms: BtcMiniscript, + ) -> Self { + // Miniscript is a bunch of Arc's. So, cloning is not as bad. + // Can we avoid this without NLL? + let ms_clone = ms.clone(); + let (fed_pks, fed_k, right) = if let BtcTerminal::OrD(ref a, ref b) = ms_clone.node { + if let (BtcTerminal::Multi(fed_k, fed_pks), right) = (&a.node, &b.node) { + (fed_pks, *fed_k, right) + } else { + unreachable!("Only valid pegin miniscripts"); + } + } else { + unreachable!("Only valid pegin miniscripts"); + }; + let (timelock, emer_pks, emer_k) = if let BtcTerminal::AndV(l, r) = right { + if let (BtcTerminal::Verify(csv), BtcTerminal::Multi(emer_k, emer_pks)) = + (&l.node, &r.node) + { + if let BtcTerminal::Older(timelock) = csv.node { + (timelock, emer_pks, *emer_k) + } else { + unreachable!("Only valid pegin miniscripts"); + } + } else { + unreachable!("Only valid pegin miniscripts"); + } + } else { + unreachable!("Only valid pegin miniscripts"); + }; + Self { + fed_pks: fed_pks.to_vec(), + fed_k, + emer_pks: emer_pks.to_vec(), + emer_k, + timelock, + desc, + ms, + } + } + + /// Create a new descriptor with hard coded values for the + /// legacy federation and emergency keys + pub fn new_legacy_fed(user_desc: Descriptor) -> Self { + // Taken from functionary codebase + // TODO: Verify the keys are correct + let pks = " + 020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261, + 02675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af99, + 02896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d48, + 029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c, + 02a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc4010, + 02f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf07, + 03079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b, + 03111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2, + 0318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa0840174, + 03230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de1, + 035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a6, + 03bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c, + 03cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d17546, + 03d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d424828, + 03ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a"; + let fed_pks: Vec = pks + .split(",") + .map(|pk| LegacyPeginKey::Functionary(bitcoin::PublicKey::from_str(pk).unwrap())) + .collect(); + + let emer_pks = " + 03aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79, + 0291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807, + 0386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb"; + let emer_pks: Vec = emer_pks + .split(",") + .map(|pk| LegacyPeginKey::Functionary(bitcoin::PublicKey::from_str(pk).unwrap())) + .collect(); + + Self::new(fed_pks, 11, emer_pks, 2, 4032, user_desc) + } +} + +impl fmt::Debug for LegacyPegin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "legacy_pegin({:?},{:?})", self.ms, self.desc) + } +} + +impl fmt::Display for LegacyPegin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let desc = format!("legacy_pegin({},{})", self.ms, self.desc); + let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; + write!(f, "{}#{}", &desc, &checksum) + } +} + +impl Liftable for LegacyPegin { + fn lift(&self) -> Result, Error> { + let btc_pol = BtcLiftable::lift(&self.ms)?; + Liftable::lift(&btc_pol) + } +} + +impl BtcLiftable for LegacyPegin { + fn lift(&self) -> Result, BtcError> { + self.ms.lift() + } +} + +impl FromTree for LegacyPegin +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn from_tree(top: &expression::Tree) -> Result { + if top.name == "legacy_pegin" && top.args.len() == 2 { + // a roundtrip hack to use FromTree from bitcoin::Miniscript from + // expression::Tree in elements. + let ms_str = top.args[0].to_string(); + let ms_expr = BtcTree::from_str(&ms_str)?; + // + let ms = BtcMiniscript::::from_tree(&ms_expr); + let desc = Descriptor::::from_tree(&top.args[1]); + Ok(LegacyPegin::from_ms_and_desc(desc?, ms?)) + } else { + Err(Error::Unexpected(format!( + "{}({} args) while parsing legacy_pegin descriptor", + top.name, + top.args.len(), + ))) + } + } +} + +impl FromStr for LegacyPegin +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + type Err = Error; + + fn from_str(s: &str) -> Result { + let desc_str = verify_checksum(s)?; + let top = expression::Tree::from_str(desc_str)?; + Self::from_tree(&top) + } +} + +impl PeginTrait for LegacyPegin +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn sanity_check(&self) -> Result<(), Error> { + self.ms + .sanity_check() + .map_err(|_| Error::Unexpected(format!("Federation script sanity check failed")))?; + self.desc + .sanity_check() + .map_err(|_| Error::Unexpected(format!("Federation script sanity check failed")))?; + Ok(()) + } + + fn bitcoin_address( + &self, + network: bitcoin::Network, + secp: &secp256k1::Secp256k1, + ) -> Result + where + Pk: ToPublicKey, + { + Ok(bitcoin::Address::p2shwsh( + &self.bitcoin_witness_script(secp), + network, + )) + } + + fn bitcoin_script_pubkey( + &self, + secp: &secp256k1::Secp256k1, + ) -> BtcScript + where + Pk: ToPublicKey, + { + self.bitcoin_address(bitcoin::Network::Bitcoin, secp) + .expect("Address cannot fail for pegin") + .script_pubkey() + } + + fn bitcoin_unsigned_script_sig( + &self, + secp: &secp256k1::Secp256k1, + ) -> BtcScript + where + Pk: ToPublicKey, + { + let witness_script = self.bitcoin_witness_script(secp); + script::Builder::new() + .push_slice(&witness_script.to_v0_p2wsh()[..]) + .into_script() + } + + fn bitcoin_witness_script( + &self, + secp: &secp256k1::Secp256k1, + ) -> BtcScript + where + Pk: ToPublicKey, + { + let tweak_vec = self.desc.explicit_script().into_bytes(); + let tweak = hashes::sha256::Hash::hash(&tweak_vec); + // Hopefully, we never have to use this and dynafed is deployed + let mut builder = script::Builder::new() + .push_opcode(opcodes::all::OP_DEPTH) + .push_int(self.fed_k as i64 + 1) + .push_opcode(opcodes::all::OP_EQUAL) + .push_opcode(opcodes::all::OP_IF) + // manually serialize the left CMS branch, without the OP_CMS + .push_int(self.fed_k as i64); + + for key in &self.fed_pks { + let tweaked_pk = tweak_key(key.as_untweaked(), secp, tweak.as_inner()); + builder = builder.push_key(&tweaked_pk); + } + let mut nearly_done = builder + .push_int(self.fed_pks.len() as i64) + .push_opcode(opcodes::all::OP_ELSE) + .into_script() + .to_bytes(); + + let right = if let BtcTerminal::OrD(_l, right) = &self.ms.node { + right + } else { + unreachable!("Only valid pegin descriptors should be created inside LegacyPegin") + }; + let right = right.translate_pk_infallible( + |pk| pk.as_untweaked().clone(), + |_| unreachable!("No Keyhashes in legacy pegins"), + ); + let mut rser = right.encode().into_bytes(); + // ...and we have an OP_VERIFY style checksequenceverify, which in + // Liquid production was encoded with OP_DROP instead... + assert_eq!(rser[4], opcodes::all::OP_VERIFY.into_u8()); + rser[4] = opcodes::all::OP_DROP.into_u8(); + // ...then we should serialize it by sharing the OP_CMS across + // both branches, and add an OP_DEPTH check to distinguish the + // branches rather than doing the normal cascade construction + nearly_done.extend(rser); + + let insert_point = nearly_done.len() - 1; + nearly_done.insert(insert_point, 0x68); + bitcoin::Script::from(nearly_done) + } + + fn get_bitcoin_satisfaction( + &self, + secp: &secp256k1::Secp256k1, + satisfier: S, + ) -> Result<(Vec>, BtcScript), Error> + where + S: BtcSatisfier, + Pk: ToPublicKey, + { + let tweak_vec = self.desc.explicit_script().into_bytes(); + let tweak = hashes::sha256::Hash::hash(&tweak_vec); + let unsigned_script_sig = self.bitcoin_unsigned_script_sig(secp); + let mut sigs = vec![]; + for key in &self.fed_pks { + let tweaked_pk = tweak_key(key.as_untweaked(), secp, tweak.as_inner()); + match satisfier.lookup_sig(&tweaked_pk) { + Some(sig) => { + let mut sig_vec = sig.0.serialize_der().to_vec(); + sig_vec.push(sig.1.as_u32() as u8); + sigs.push(sig_vec) + } + None => {} + } + } + sigs.sort_by(|a, b| a.len().cmp(&b.len())); + if sigs.len() >= self.fed_k { + // Prefer using federation keys over emergency paths + let mut sigs: Vec> = sigs.into_iter().take(self.fed_k).collect(); + sigs.push(vec![0]); // CMS extra value + Ok((sigs, unsigned_script_sig)) + } else { + let mut emer_sigs = vec![]; + for emer_key in &self.emer_pks { + match satisfier.lookup_sig(emer_key.as_untweaked()) { + Some(sig) => { + let mut sig_vec = sig.0.serialize_der().to_vec(); + sig_vec.push(sig.1.as_u32() as u8); + emer_sigs.push(sig_vec) + } + None => {} + } + } + emer_sigs.sort_by(|a, b| a.len().cmp(&b.len())); + if emer_sigs.len() >= self.emer_k { + let mut sigs: Vec> = emer_sigs.into_iter().take(self.emer_k).collect(); + sigs.push(vec![0]); // CMS extra value + Ok((sigs, unsigned_script_sig)) + } else { + Err(Error::CouldNotSatisfy) + } + } + } + + fn max_satisfaction_weight(&self) -> Result { + let script_size = 628; + Ok(4 * 36 + + varint_len(script_size) + + script_size + + varint_len(self.ms.max_satisfaction_witness_elements()?) + + self.ms.max_satisfaction_size()?) + } + + fn script_code(&self, secp: &secp256k1::Secp256k1) -> BtcScript + where + Pk: ToPublicKey, + { + self.bitcoin_witness_script(secp) + } + + fn into_user_descriptor(self) -> Descriptor { + self.desc + } +} diff --git a/src/descriptor/pegin/mod.rs b/src/descriptor/pegin/mod.rs new file mode 100644 index 00000000..fc7c39fe --- /dev/null +++ b/src/descriptor/pegin/mod.rs @@ -0,0 +1,191 @@ +// Miniscript +// Written in 2018 by +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Pegin Descriptor Support +//! +//! Traits and implementations for Pegin descriptors +//! Note that this is a bitcoin descriptor and thus cannot be +//! added to elements Descriptor. Upstream rust-miniscript does +//! has a Descriptor enum which should ideally have it, but +//! bitcoin descriptors cannot depend on elements descriptors +//! Thus, as a simple solution we implement these as a separate +//! struct with it's own API. + +use bitcoin::blockdata::opcodes; +use bitcoin::hashes::Hash; +use bitcoin::{self, blockdata::script, hashes}; +use bitcoin::{hashes::hash160, Address as BtcAddress}; +use bitcoin::{secp256k1, Script as BtcScript}; +use expression::{self, FromTree}; +use policy::{semantic, Liftable}; +use std::{ + fmt::Debug, + fmt::{self, Display}, + marker::PhantomData, + str::FromStr, + sync::Arc, +}; +use Descriptor; +use Error; +use Miniscript; +use { + BtcDescriptor, BtcDescriptorTrait, BtcError, BtcFromTree, BtcLiftable, BtcMiniscript, + BtcPolicy, BtcSatisfier, BtcSegwitv0, BtcTerminal, BtcTree, +}; + +use {DescriptorTrait, Segwitv0, TranslatePk}; + +use {tweak_key, util::varint_len}; + +use super::checksum::{desc_checksum, verify_checksum}; +use {MiniscriptKey, ToPublicKey}; + +mod dynafed_pegin; +mod legacy_pegin; +pub use self::legacy_pegin::{LegacyPegin, LegacyPeginKey}; +/// A general trait for Pegin Bitcoin descriptor. +/// It should also support FromStr, fmt::Display and should be liftable +/// to bitcoin Semantic Policy. +/// Offers function for witness cost estimation, script pubkey creation +/// satisfaction using the [Satisfier] trait. +// Unfortunately, the translation function cannot be added to trait +// because of traits cannot know underlying generic of Self. +// Thus, we must implement additional trait for translate function +pub trait PeginTrait { + /// Whether the descriptor is safe + /// Checks whether all the spend paths in the descriptor are possible + /// on the bitcoin network under the current standardness and consensus rules + /// Also checks whether the descriptor requires signauture on all spend paths + /// And whether the script is malleable. + /// In general, all the guarantees of miniscript hold only for safe scripts. + /// All the analysis guarantees of miniscript only hold safe scripts. + /// The signer may not be able to find satisfactions even if one exists + fn sanity_check(&self) -> Result<(), Error>; + + /// Computes the Bitcoin address of the pegin descriptor, if one exists. + /// Requires the secp context to compute the tweak + fn bitcoin_address( + &self, + network: bitcoin::Network, + secp: &secp256k1::Secp256k1, + ) -> Result + where + Pk: ToPublicKey; + + /// Computes the bitcoin scriptpubkey of the descriptor. + /// Requires the secp context to compute the tweak + fn bitcoin_script_pubkey( + &self, + secp: &secp256k1::Secp256k1, + ) -> BtcScript + where + Pk: ToPublicKey; + + /// Computes the scriptSig that will be in place for an unsigned + /// input spending an output with this descriptor. For pre-segwit + /// descriptors, which use the scriptSig for signatures, this + /// returns the empty script. + /// + /// This is used in Segwit transactions to produce an unsigned + /// transaction whose txid will not change during signing (since + /// only the witness data will change). + /// Requires the secp context to compute the tweak + fn bitcoin_unsigned_script_sig( + &self, + secp: &secp256k1::Secp256k1, + ) -> BtcScript + where + Pk: ToPublicKey; + + /// Computes the bitcoin "witness script" of the descriptor, i.e. the underlying + /// script before any hashing is done. For `Bare`, `Pkh` and `Wpkh` this + /// is the scriptPubkey; for `ShWpkh` and `Sh` this is the redeemScript; + /// for the others it is the witness script. + /// `to_pk_ctx` denotes the ToPkCtx required for deriving bitcoin::PublicKey + /// from MiniscriptKey using [ToPublicKey]. + /// If MiniscriptKey is already is [bitcoin::PublicKey], then the context + /// would be [NullCtx] and [descriptor.DescriptorPublicKeyCtx] if MiniscriptKey is [descriptor.DescriptorPublicKey] + /// + /// In general, this is defined by generic for the trait [ToPublicKey] + fn bitcoin_witness_script( + &self, + secp: &secp256k1::Secp256k1, + ) -> BtcScript + where + Pk: ToPublicKey; + + /// Returns satisfying witness and scriptSig to spend an + /// output controlled by the given descriptor if it possible to + /// construct one using the satisfier S. + /// `to_pk_ctx` denotes the ToPkCtx required for deriving bitcoin::PublicKey + /// from MiniscriptKey using [ToPublicKey]. + /// If MiniscriptKey is already is [bitcoin::PublicKey], then the context + /// would be [NullCtx] and [descriptor.DescriptorPublicKeyCtx] if MiniscriptKey is [descriptor.DescriptorPublicKey] + /// + /// In general, this is defined by generic for the trait [ToPublicKey] + fn get_bitcoin_satisfaction( + &self, + secp: &secp256k1::Secp256k1, + satisfier: S, + ) -> Result<(Vec>, BtcScript), Error> + where + S: BtcSatisfier, + Pk: ToPublicKey; + + /// Attempts to produce a satisfying witness and scriptSig to spend an + /// output controlled by the given descriptor; add the data to a given + /// `TxIn` output. + fn bitcoin_satisfy( + &self, + secp: &secp256k1::Secp256k1, + txin: &mut bitcoin::TxIn, + satisfier: S, + ) -> Result<(), Error> + where + Pk: ToPublicKey, + S: BtcSatisfier, + { + // easy default implementation + let (witness, script_sig) = self.get_bitcoin_satisfaction(secp, satisfier)?; + txin.witness = witness; + txin.script_sig = script_sig; + Ok(()) + } + + /// Computes an upper bound on the weight of a satisfying witness to the + /// transaction. Assumes all signatures are 73 bytes, including push opcode + /// and sighash suffix. Includes the weight of the VarInts encoding the + /// scriptSig and witness stack length. + fn max_satisfaction_weight(&self) -> Result; + + /// Get the `scriptCode` of a transaction output. + /// + /// The `scriptCode` is the Script of the previous transaction output being serialized in the + /// sighash when evaluating a `CHECKSIG` & co. OP code. + /// `to_pk_ctx` denotes the ToPkCtx required for deriving bitcoin::PublicKey + /// from MiniscriptKey using [ToPublicKey]. + /// If MiniscriptKey is already is [bitcoin::PublicKey], then the context + /// would be [NullCtx] and [descriptor.DescriptorPublicKeyCtx] if MiniscriptKey is [descriptor.DescriptorPublicKey] + /// + /// In general, this is defined by generic for the trait [ToPublicKey] + fn script_code(&self, secp: &secp256k1::Secp256k1) -> BtcScript + where + Pk: ToPublicKey; + + /// Get the corresponding elements descriptor that would be used + /// at redeem time by the user. + /// Users can use the DescrpitorTrait operations on the output Descriptor + /// to obtain the characteristics of the elements descriptor. + fn into_user_descriptor(self) -> Descriptor; +} diff --git a/src/descriptor/segwitv0.rs b/src/descriptor/segwitv0.rs index 79c7e8c3..a7f3fe3b 100644 --- a/src/descriptor/segwitv0.rs +++ b/src/descriptor/segwitv0.rs @@ -18,7 +18,8 @@ use std::{fmt, str::FromStr}; -use bitcoin::{self, Script}; +use bitcoin::secp256k1; +use elements::{self, Script}; use expression::{self, FromTree}; use miniscript::context::{ScriptContext, ScriptContextError}; @@ -31,7 +32,7 @@ use { use super::{ checksum::{desc_checksum, verify_checksum}, - DescriptorTrait, SortedMultiVec, + DescriptorTrait, ElementsTrait, SortedMultiVec, ELMTS_STR, }; /// A Segwitv0 wsh descriptor #[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] @@ -69,13 +70,42 @@ impl Wsh { }) } - /// Get the descriptor without the checksum - pub fn to_string_no_checksum(&self) -> String { + /// Get the descriptor without the checksum, without the el prefix + pub(crate) fn to_string_no_checksum(&self) -> String { match self.inner { WshInner::SortedMulti(ref smv) => format!("wsh({})", smv), WshInner::Ms(ref ms) => format!("wsh({})", ms), } } + + // Constructor for creating inner wsh for the sh fragment + pub(super) fn from_inner_tree(top: &expression::Tree) -> Result + where + Pk: FromStr, + Pk::Hash: FromStr, + <::Hash as FromStr>::Err: ToString, + ::Err: ToString, + { + if top.name == "wsh" && top.args.len() == 1 { + let top = &top.args[0]; + if top.name == "sortedmulti" { + return Ok(Wsh { + inner: WshInner::SortedMulti(SortedMultiVec::from_tree(&top)?), + }); + } + let sub = Miniscript::from_tree(&top)?; + Segwitv0::top_level_checks(&sub)?; + Ok(Wsh { + inner: WshInner::Ms(sub), + }) + } else { + Err(Error::Unexpected(format!( + "{}({} args) while parsing wsh descriptor", + top.name, + top.args.len(), + ))) + } + } } /// Wsh Inner @@ -104,7 +134,7 @@ where <::Hash as FromStr>::Err: ToString, { fn from_tree(top: &expression::Tree) -> Result { - if top.name == "wsh" && top.args.len() == 1 { + if top.name == "elwsh" && top.args.len() == 1 { let top = &top.args[0]; if top.name == "sortedmulti" { return Ok(Wsh { @@ -128,15 +158,15 @@ where impl fmt::Debug for Wsh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.inner { - WshInner::SortedMulti(ref smv) => write!(f, "wsh({:?})", smv), - WshInner::Ms(ref ms) => write!(f, "wsh({:?})", ms), + WshInner::SortedMulti(ref smv) => write!(f, "{}wsh({:?})", ELMTS_STR, smv), + WshInner::Ms(ref ms) => write!(f, "{}wsh({:?})", ELMTS_STR, ms), } } } impl fmt::Display for Wsh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let desc = self.to_string_no_checksum(); + let desc = format!("{}{}", ELMTS_STR, self.to_string_no_checksum()); let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; write!(f, "{}#{}", &desc, &checksum) } @@ -158,7 +188,31 @@ where } } -impl DescriptorTrait for Wsh { +impl ElementsTrait for Wsh { + fn blind_addr( + &self, + blinder: Option, + params: &'static elements::AddressParams, + ) -> Result + where + Pk: ToPublicKey, + { + match self.inner { + WshInner::SortedMulti(ref smv) => { + Ok(elements::Address::p2wsh(&smv.encode(), blinder, params)) + } + WshInner::Ms(ref ms) => Ok(elements::Address::p2wsh(&ms.encode(), blinder, params)), + } + } +} + +impl DescriptorTrait for Wsh +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ fn sanity_check(&self) -> Result<(), Error> { match self.inner { WshInner::SortedMulti(ref smv) => smv.sanity_check()?, @@ -167,13 +221,15 @@ impl DescriptorTrait for Wsh { Ok(()) } - fn address(&self, network: bitcoin::Network) -> Result + fn address(&self, params: &'static elements::AddressParams) -> Result where Pk: ToPublicKey, { match self.inner { - WshInner::SortedMulti(ref smv) => Ok(bitcoin::Address::p2wsh(&smv.encode(), network)), - WshInner::Ms(ref ms) => Ok(bitcoin::Address::p2wsh(&ms.encode(), network)), + WshInner::SortedMulti(ref smv) => { + Ok(elements::Address::p2wsh(&smv.encode(), None, params)) + } + WshInner::Ms(ref ms) => Ok(elements::Address::p2wsh(&ms.encode(), None, params)), } } @@ -309,21 +365,42 @@ impl Wpkh { &self.pk } - /// Get the descriptor without the checksum - pub fn to_string_no_checksum(&self) -> String { + /// Get the descriptor without the checksum without the el prefix + pub(crate) fn to_string_no_checksum(&self) -> String { format!("wpkh({})", self.pk) } + + // Parse a bitcoin style wpkh tree. Useful when parsing nested trees + pub(super) fn from_inner_tree(top: &expression::Tree) -> Result + where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, + { + if top.name == "wpkh" && top.args.len() == 1 { + Ok(Wpkh::new(expression::terminal(&top.args[0], |pk| { + Pk::from_str(pk) + })?)?) + } else { + Err(Error::Unexpected(format!( + "{}({} args) while parsing wpkh descriptor", + top.name, + top.args.len(), + ))) + } + } } impl fmt::Debug for Wpkh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "wpkh({:?})", self.pk) + write!(f, "{}wpkh({:?})", ELMTS_STR, self.pk) } } impl fmt::Display for Wpkh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let desc = self.to_string_no_checksum(); + let desc = format!("{}{}", ELMTS_STR, self.to_string_no_checksum()); let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; write!(f, "{}#{}", &desc, &checksum) } @@ -343,7 +420,7 @@ where <::Hash as FromStr>::Err: ToString, { fn from_tree(top: &expression::Tree) -> Result { - if top.name == "wpkh" && top.args.len() == 1 { + if top.name == "elwpkh" && top.args.len() == 1 { Ok(Wpkh::new(expression::terminal(&top.args[0], |pk| { Pk::from_str(pk) })?)?) @@ -373,7 +450,30 @@ where } } -impl DescriptorTrait for Wpkh { +impl ElementsTrait for Wpkh { + fn blind_addr( + &self, + blinder: Option, + params: &'static elements::AddressParams, + ) -> Result + where + Pk: ToPublicKey, + { + Ok(elements::Address::p2wpkh( + &self.pk.to_public_key(), + blinder, + params, + )) + } +} + +impl DescriptorTrait for Wpkh +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ fn sanity_check(&self) -> Result<(), Error> { if self.pk.is_uncompressed() { Err(Error::ContextError(ScriptContextError::CompressedOnly)) @@ -382,20 +482,26 @@ impl DescriptorTrait for Wpkh { } } - fn address(&self, network: bitcoin::Network) -> Result + fn address(&self, params: &'static elements::AddressParams) -> Result where Pk: ToPublicKey, { - Ok(bitcoin::Address::p2wpkh(&self.pk.to_public_key(), network) - .expect("Rust Miniscript types don't allow uncompressed pks in segwit descriptors")) + Ok(elements::Address::p2wpkh( + &self.pk.to_public_key(), + None, + params, + )) } fn script_pubkey(&self) -> Script where Pk: ToPublicKey, { - let addr = bitcoin::Address::p2wpkh(&self.pk.to_public_key(), bitcoin::Network::Bitcoin) - .expect("wpkh descriptors have compressed keys"); + let addr = elements::Address::p2wpkh( + &self.pk.to_public_key(), + None, + &elements::AddressParams::ELEMENTS, + ); addr.script_pubkey() } @@ -441,7 +547,11 @@ impl DescriptorTrait for Wpkh { // the previous txo's scriptPubKey. // The item 5: // - For P2WPKH witness program, the scriptCode is `0x1976a914{20-byte-pubkey-hash}88ac`. - let addr = bitcoin::Address::p2pkh(&self.pk.to_public_key(), bitcoin::Network::Bitcoin); + let addr = elements::Address::p2pkh( + &self.pk.to_public_key(), + None, + &elements::AddressParams::ELEMENTS, + ); addr.script_pubkey() } } diff --git a/src/descriptor/sh.rs b/src/descriptor/sh.rs index eab539fe..055de70c 100644 --- a/src/descriptor/sh.rs +++ b/src/descriptor/sh.rs @@ -20,7 +20,8 @@ use std::{fmt, str::FromStr}; -use bitcoin::{self, blockdata::script, Script}; +use bitcoin::secp256k1; +use elements::{self, script, Script}; use expression::{self, FromTree}; use miniscript::context::ScriptContext; @@ -34,7 +35,7 @@ use { use super::{ checksum::{desc_checksum, verify_checksum}, - DescriptorTrait, SortedMultiVec, Wpkh, Wsh, + DescriptorTrait, ElementsTrait, SortedMultiVec, Wpkh, Wsh, ELMTS_STR, }; /// A Legacy p2sh Descriptor @@ -71,10 +72,10 @@ impl Liftable for Sh { impl fmt::Debug for Sh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.inner { - ShInner::Wsh(ref wsh_inner) => write!(f, "sh({:?})", wsh_inner), - ShInner::Wpkh(ref pk) => write!(f, "sh({:?})", pk), - ShInner::SortedMulti(ref smv) => write!(f, "sh({:?})", smv), - ShInner::Ms(ref ms) => write!(f, "sh({:?})", ms), + ShInner::Wsh(ref wsh_inner) => write!(f, "{}sh({:?})", ELMTS_STR, wsh_inner), + ShInner::Wpkh(ref pk) => write!(f, "{}sh({:?})", ELMTS_STR, pk), + ShInner::SortedMulti(ref smv) => write!(f, "{}sh({:?})", ELMTS_STR, smv), + ShInner::Ms(ref ms) => write!(f, "{}sh({:?})", ELMTS_STR, ms), } } } @@ -82,10 +83,10 @@ impl fmt::Debug for Sh { impl fmt::Display for Sh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let desc = match self.inner { - ShInner::Wsh(ref wsh) => format!("sh({})", wsh.to_string_no_checksum()), - ShInner::Wpkh(ref pk) => format!("sh({})", pk.to_string_no_checksum()), - ShInner::SortedMulti(ref smv) => format!("sh({})", smv), - ShInner::Ms(ref ms) => format!("sh({})", ms), + ShInner::Wsh(ref wsh) => format!("{}sh({})", ELMTS_STR, wsh.to_string_no_checksum()), + ShInner::Wpkh(ref pk) => format!("{}sh({})", ELMTS_STR, pk.to_string_no_checksum()), + ShInner::SortedMulti(ref smv) => format!("{}sh({})", ELMTS_STR, smv), + ShInner::Ms(ref ms) => format!("{}sh({})", ELMTS_STR, ms), }; let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; write!(f, "{}#{}", &desc, &checksum) @@ -100,11 +101,11 @@ where <::Hash as FromStr>::Err: ToString, { fn from_tree(top: &expression::Tree) -> Result { - if top.name == "sh" && top.args.len() == 1 { + if top.name == "elsh" && top.args.len() == 1 { let top = &top.args[0]; let inner = match top.name { - "wsh" => ShInner::Wsh(Wsh::from_tree(&top)?), - "wpkh" => ShInner::Wpkh(Wpkh::from_tree(&top)?), + "wsh" => ShInner::Wsh(Wsh::from_inner_tree(&top)?), + "wpkh" => ShInner::Wpkh(Wpkh::from_inner_tree(&top)?), "sortedmulti" => ShInner::SortedMulti(SortedMultiVec::from_tree(&top)?), _ => { let sub = Miniscript::from_tree(&top)?; @@ -193,7 +194,47 @@ impl Sh { } } -impl DescriptorTrait for Sh { +impl ElementsTrait for Sh +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn blind_addr( + &self, + blinder: Option, + params: &'static elements::AddressParams, + ) -> Result + where + Pk: ToPublicKey, + { + match self.inner { + ShInner::Wsh(ref wsh) => Ok(elements::Address::p2sh( + &wsh.script_pubkey(), + blinder, + params, + )), + ShInner::Wpkh(ref wpkh) => Ok(elements::Address::p2sh( + &wpkh.script_pubkey(), + blinder, + params, + )), + ShInner::SortedMulti(ref smv) => { + Ok(elements::Address::p2sh(&smv.encode(), blinder, params)) + } + ShInner::Ms(ref ms) => Ok(elements::Address::p2sh(&ms.encode(), blinder, params)), + } + } +} + +impl DescriptorTrait for Sh +where + Pk: FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ fn sanity_check(&self) -> Result<(), Error> { match self.inner { ShInner::Wsh(ref wsh) => wsh.sanity_check()?, @@ -204,15 +245,21 @@ impl DescriptorTrait for Sh { Ok(()) } - fn address(&self, network: bitcoin::Network) -> Result + fn address(&self, params: &'static elements::AddressParams) -> Result where Pk: ToPublicKey, { match self.inner { - ShInner::Wsh(ref wsh) => Ok(bitcoin::Address::p2sh(&wsh.script_pubkey(), network)), - ShInner::Wpkh(ref wpkh) => Ok(bitcoin::Address::p2sh(&wpkh.script_pubkey(), network)), - ShInner::SortedMulti(ref smv) => Ok(bitcoin::Address::p2sh(&smv.encode(), network)), - ShInner::Ms(ref ms) => Ok(bitcoin::Address::p2sh(&ms.encode(), network)), + ShInner::Wsh(ref wsh) => { + Ok(elements::Address::p2sh(&wsh.script_pubkey(), None, params)) + } + ShInner::Wpkh(ref wpkh) => { + Ok(elements::Address::p2sh(&wpkh.script_pubkey(), None, params)) + } + ShInner::SortedMulti(ref smv) => { + Ok(elements::Address::p2sh(&smv.encode(), None, params)) + } + ShInner::Ms(ref ms) => Ok(elements::Address::p2sh(&ms.encode(), None, params)), } } diff --git a/src/descriptor/sortedmulti.rs b/src/descriptor/sortedmulti.rs index 64595010..4943e504 100644 --- a/src/descriptor/sortedmulti.rs +++ b/src/descriptor/sortedmulti.rs @@ -18,7 +18,7 @@ use std::{fmt, marker::PhantomData, str::FromStr}; -use bitcoin::blockdata::script; +use elements::script; use expression; use miniscript::{self, context::ScriptContext, decode::Terminal}; diff --git a/src/expression.rs b/src/expression.rs index fd31862a..8fda28db 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -15,14 +15,14 @@ //! # Function-like Expression Language //! -use std::str::FromStr; +use std::{fmt, str::FromStr}; use errstr; use Error; use MAX_RECURSION_DEPTH; -#[derive(Debug)] +#[derive(Debug, Clone)] /// A token of the form `x(...)` or `x` pub struct Tree<'a> { /// The name `x` @@ -37,6 +37,15 @@ pub trait FromTree: Sized { fn from_tree(top: &Tree) -> Result; } +impl<'a> fmt::Display for Tree<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({}", self.name)?; + for arg in &self.args { + write!(f, ",{}", arg)?; + } + write!(f, ")") + } +} impl<'a> Tree<'a> { fn from_slice(sl: &'a str) -> Result<(Tree<'a>, &'a str), Error> { Self::from_slice_helper(sl, 0u32) @@ -54,19 +63,44 @@ impl<'a> Tree<'a> { } let mut found = Found::Nothing; + // Decide whether we are parsing a key or not. + // When parsing a key ignore all the '(' and ')'. + // We keep count of lparan whenever we are inside a key context + // We exit the context whenever we find the corresponding ')' + // in which we entered the context. This allows to special case + // parse the '(' ')' inside key expressions.(slip77 and musig). + let mut key_ctx = false; + let mut key_lparan_count = 0; for (n, ch) in sl.char_indices() { match ch { '(' => { - found = Found::Lparen(n); - break; + // already inside a key context + if key_ctx { + key_lparan_count += 1; + } else if &sl[..n] == "slip77" || &sl[..n] == "musig" { + key_lparan_count = 1; + key_ctx = true; + } else { + found = Found::Lparen(n); + break; + } } ',' => { - found = Found::Comma(n); - break; + if !key_ctx { + found = Found::Comma(n); + break; + } } ')' => { - found = Found::Rparen(n); - break; + if key_ctx { + key_lparan_count -= 1; + if key_lparan_count == 0 { + key_ctx = false; + } + } else { + found = Found::Rparen(n); + break; + } } _ => {} } diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index 349f8190..282e098d 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -12,8 +12,9 @@ // If not, see . // -use bitcoin::hashes::hash160; -use bitcoin::{self, secp256k1}; +use bitcoin; +use elements::hashes::hash160; +use elements::secp256k1; use std::{error, fmt}; /// Detailed Error type for Interpreter @@ -84,6 +85,19 @@ pub enum Error { /// Verify expects stack top element exactly to be `stack::Element::Satisfied`. /// This error is raised even if the stack top is `stack::Element::Push`. VerifyFailed, + /// Incorrect Covenant Witness + IncorrectCovenantWitness, + /// Covenant witness size mismatch + /// eg: supplied a witness at + /// nVersion with 5 bytes instead of 4 + CovWitnessSizeErr { + /// Position of the item in sighash Msg + pos: usize, + /// Expected size + expected: usize, + /// Actual size + actual: usize, + }, } #[doc(hidden)] @@ -154,6 +168,19 @@ impl fmt::Display for Error { Error::VerifyFailed => { f.write_str("Expected Satisfied Boolean at stack top for VERIFY") } + Error::IncorrectCovenantWitness => f.write_str( + "Covenant witness incorrect, the initial stack supplied for \ + covenant global context is incorrect", + ), + Error::CovWitnessSizeErr { + pos, + expected, + actual, + } => write!( + f, + "At script code item position{}: Expected size{}, got size {}", + pos, expected, actual + ), } } } diff --git a/src/interpreter/inner.rs b/src/interpreter/inner.rs index 5c49c47e..54222ad4 100644 --- a/src/interpreter/inner.rs +++ b/src/interpreter/inner.rs @@ -13,9 +13,11 @@ // use bitcoin; -use bitcoin::hashes::{hash160, sha256, Hash}; +use elements::hashes::{hash160, sha256, Hash}; +use elements::{self, script}; use super::{stack, Error, Stack}; +use descriptor::{CovOperations, CovenantDescriptor}; use miniscript::context::NoChecks; use {Miniscript, MiniscriptKey}; @@ -50,7 +52,7 @@ fn script_from_stackelem<'a>( ) -> Result, Error> { match *elem { stack::Element::Push(sl) => { - Miniscript::parse_insane(&bitcoin::Script::from(sl.to_owned())).map_err(Error::from) + Miniscript::parse_insane(&elements::Script::from(sl.to_owned())).map_err(Error::from) } stack::Element::Satisfied => Miniscript::from_ast(::Terminal::True).map_err(Error::from), stack::Element::Dissatisfied => { @@ -59,6 +61,19 @@ fn script_from_stackelem<'a>( } } +// Try to parse covenant components from witness script +// stack element +fn cov_components_from_stackelem<'a>( + elem: &stack::Element<'a>, +) -> Option<(bitcoin::PublicKey, Miniscript)> { + match *elem { + stack::Element::Push(sl) => { + CovenantDescriptor::parse_cov_components(&elements::Script::from(sl.to_owned())).ok() + } + _ => None, + } +} + /// Helper type to indicate the origin of the bare pubkey that the interpereter uses #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum PubkeyType { @@ -85,6 +100,14 @@ pub enum Inner { PublicKey(bitcoin::PublicKey, PubkeyType), /// The script being evaluated is an actual script Script(Miniscript, ScriptType), + /// The Covenant Miniscript + /// Only Witnessv0 scripts are supported for now + CovScript( + bitcoin::PublicKey, + Miniscript, + // Add scriptType when we support additional things here + // ScriptType, + ), } // The `Script` returned by this method is always generated/cloned ... when @@ -94,10 +117,10 @@ pub enum Inner { /// Parses an `Inner` and appropriate `Stack` from completed transaction data, /// as well as the script that should be used as a scriptCode in a sighash pub fn from_txdata<'txin>( - spk: &bitcoin::Script, - script_sig: &'txin bitcoin::Script, + spk: &elements::Script, + script_sig: &'txin elements::Script, witness: &'txin [Vec], -) -> Result<(Inner, Stack<'txin>, bitcoin::Script), Error> { +) -> Result<(Inner, Stack<'txin>, elements::Script), Error> { let mut ssig_stack: Stack = script_sig .instructions_minimal() .map(stack::Element::from_instruction) @@ -131,7 +154,7 @@ pub fn from_txdata<'txin>( match ssig_stack.pop() { Some(elem) => { let pk = pk_from_stackelem(&elem, false)?; - if *spk == bitcoin::Script::new_p2pkh(&pk.to_pubkeyhash().into()) { + if *spk == elements::Script::new_p2pkh(&pk.to_pubkeyhash().into()) { Ok(( Inner::PublicKey(pk, PubkeyType::Pkh), ssig_stack, @@ -152,11 +175,11 @@ pub fn from_txdata<'txin>( match wit_stack.pop() { Some(elem) => { let pk = pk_from_stackelem(&elem, true)?; - if *spk == bitcoin::Script::new_v0_wpkh(&pk.to_pubkeyhash().into()) { + if *spk == elements::Script::new_v0_wpkh(&pk.to_pubkeyhash().into()) { Ok(( Inner::PublicKey(pk, PubkeyType::Wpkh), wit_stack, - bitcoin::Script::new_p2pkh(&pk.to_pubkeyhash().into()), // bip143, why.. + elements::Script::new_p2pkh(&pk.to_pubkeyhash().into()), // bip143, why.. )) } else { Err(Error::IncorrectWPubkeyHash) @@ -172,10 +195,15 @@ pub fn from_txdata<'txin>( } else { match wit_stack.pop() { Some(elem) => { + if let Some((pk, ms)) = cov_components_from_stackelem(&elem) { + let script_code = + script::Builder::new().post_codesep_script().into_script(); + return Ok((Inner::CovScript(pk, ms), wit_stack, script_code)); + } let miniscript = script_from_stackelem(&elem)?; let script = miniscript.encode(); let scripthash = sha256::Hash::hash(&script[..]); - if *spk == bitcoin::Script::new_v0_wsh(&scripthash.into()) { + if *spk == elements::Script::new_v0_wsh(&scripthash.into()) { Ok(( Inner::Script(miniscript, ScriptType::Wsh), wit_stack, @@ -194,7 +222,7 @@ pub fn from_txdata<'txin>( Some(elem) => { if let stack::Element::Push(slice) = elem { let scripthash = hash160::Hash::hash(slice); - if *spk != bitcoin::Script::new_p2sh(&scripthash.into()) { + if *spk != elements::Script::new_p2sh(&scripthash.into()) { return Err(Error::IncorrectScriptHash); } // ** p2sh-wrapped wpkh ** @@ -206,13 +234,14 @@ pub fn from_txdata<'txin>( } else { let pk = pk_from_stackelem(&elem, true)?; if slice - == &bitcoin::Script::new_v0_wpkh(&pk.to_pubkeyhash().into()) - [..] + == &elements::Script::new_v0_wpkh( + &pk.to_pubkeyhash().into(), + )[..] { Ok(( Inner::PublicKey(pk, PubkeyType::ShWpkh), wit_stack, - bitcoin::Script::new_p2pkh(&pk.to_pubkeyhash().into()), // bip143, why.. + elements::Script::new_p2pkh(&pk.to_pubkeyhash().into()), // bip143, why.. )) } else { Err(Error::IncorrectWScriptHash) @@ -231,7 +260,8 @@ pub fn from_txdata<'txin>( let miniscript = script_from_stackelem(&elem)?; let script = miniscript.encode(); let scripthash = sha256::Hash::hash(&script[..]); - if slice == &bitcoin::Script::new_v0_wsh(&scripthash.into())[..] + if slice + == &elements::Script::new_v0_wsh(&scripthash.into())[..] { Ok(( Inner::Script(miniscript, ScriptType::ShWsh), @@ -252,7 +282,7 @@ pub fn from_txdata<'txin>( let script = miniscript.encode(); if wit_stack.is_empty() { let scripthash = hash160::Hash::hash(&script[..]); - if *spk == bitcoin::Script::new_p2sh(&scripthash.into()) { + if *spk == elements::Script::new_p2sh(&scripthash.into()) { Ok(( Inner::Script(miniscript, ScriptType::Sh), ssig_stack, @@ -286,23 +316,23 @@ pub fn from_txdata<'txin>( mod tests { use super::*; - use bitcoin::blockdata::script; - use bitcoin::hashes::hex::FromHex; - use bitcoin::hashes::{hash160, sha256, Hash}; - use bitcoin::{self, Script}; + use elements::hashes::hex::FromHex; + use elements::hashes::{hash160, sha256, Hash}; + use elements::script; + use elements::{self, Script}; use std::str::FromStr; struct KeyTestData { - pk_spk: bitcoin::Script, - pk_sig: bitcoin::Script, - pkh_spk: bitcoin::Script, - pkh_sig: bitcoin::Script, - pkh_sig_justkey: bitcoin::Script, - wpkh_spk: bitcoin::Script, + pk_spk: elements::Script, + pk_sig: elements::Script, + pkh_spk: elements::Script, + pkh_sig: elements::Script, + pkh_sig_justkey: elements::Script, + wpkh_spk: elements::Script, wpkh_stack: Vec>, wpkh_stack_justkey: Vec>, - sh_wpkh_spk: bitcoin::Script, - sh_wpkh_sig: bitcoin::Script, + sh_wpkh_spk: elements::Script, + sh_wpkh_sig: elements::Script, sh_wpkh_stack: Vec>, sh_wpkh_stack_justkey: Vec>, } @@ -320,12 +350,12 @@ mod tests { let pkhash = key.to_pubkeyhash().into(); let wpkhash = key.to_pubkeyhash().into(); - let wpkh_spk = bitcoin::Script::new_v0_wpkh(&wpkhash); + let wpkh_spk = elements::Script::new_v0_wpkh(&wpkhash); let wpkh_scripthash = hash160::Hash::hash(&wpkh_spk[..]).into(); KeyTestData { - pk_spk: bitcoin::Script::new_p2pk(&key), - pkh_spk: bitcoin::Script::new_p2pkh(&pkhash), + pk_spk: elements::Script::new_p2pk(&key), + pkh_spk: elements::Script::new_p2pkh(&pkhash), pk_sig: script::Builder::new().push_slice(&dummy_sig).into_script(), pkh_sig: script::Builder::new() .push_slice(&dummy_sig) @@ -335,7 +365,7 @@ mod tests { wpkh_spk: wpkh_spk.clone(), wpkh_stack: vec![dummy_sig.clone(), key.to_bytes()], wpkh_stack_justkey: vec![key.to_bytes()], - sh_wpkh_spk: bitcoin::Script::new_p2sh(&wpkh_scripthash), + sh_wpkh_spk: elements::Script::new_p2sh(&wpkh_scripthash), sh_wpkh_sig: script::Builder::new() .push_slice(&wpkh_spk[..]) .into_script(), @@ -373,7 +403,7 @@ mod tests { let fixed = fixed_test_data(); let comp = KeyTestData::from_key(fixed.pk_comp); let uncomp = KeyTestData::from_key(fixed.pk_uncomp); - let blank_script = bitcoin::Script::new(); + let blank_script = elements::Script::new(); // Compressed pk, empty scriptsig let (inner, stack, script_code) = @@ -406,15 +436,15 @@ mod tests { // Scriptpubkey has invalid key let mut spk = comp.pk_spk.to_bytes(); spk[1] = 5; - let spk = bitcoin::Script::from(spk); - let err = from_txdata(&spk, &bitcoin::Script::new(), &[]).unwrap_err(); + let spk = elements::Script::from(spk); + let err = from_txdata(&spk, &elements::Script::new(), &[]).unwrap_err(); assert_eq!(err.to_string(), "could not parse pubkey"); // Scriptpubkey has invalid script let mut spk = comp.pk_spk.to_bytes(); spk[0] = 100; - let spk = bitcoin::Script::from(spk); - let err = from_txdata(&spk, &bitcoin::Script::new(), &[]).unwrap_err(); + let spk = elements::Script::from(spk); + let err = from_txdata(&spk, &elements::Script::new(), &[]).unwrap_err(); assert_eq!(&err.to_string()[0..12], "parse error:"); // Witness is nonempty @@ -429,7 +459,7 @@ mod tests { let uncomp = KeyTestData::from_key(fixed.pk_uncomp); // pkh, empty scriptsig; this time it errors out - let err = from_txdata(&comp.pkh_spk, &bitcoin::Script::new(), &[]).unwrap_err(); + let err = from_txdata(&comp.pkh_spk, &elements::Script::new(), &[]).unwrap_err(); assert_eq!(err.to_string(), "unexpected end of stack"); // pkh, wrong pubkey @@ -472,7 +502,7 @@ mod tests { let fixed = fixed_test_data(); let comp = KeyTestData::from_key(fixed.pk_comp); let uncomp = KeyTestData::from_key(fixed.pk_uncomp); - let blank_script = bitcoin::Script::new(); + let blank_script = elements::Script::new(); // wpkh, empty witness; this time it errors out let err = from_txdata(&comp.wpkh_spk, &blank_script, &[]).unwrap_err(); @@ -519,7 +549,7 @@ mod tests { let fixed = fixed_test_data(); let comp = KeyTestData::from_key(fixed.pk_comp); let uncomp = KeyTestData::from_key(fixed.pk_uncomp); - let blank_script = bitcoin::Script::new(); + let blank_script = elements::Script::new(); // sh_wpkh, missing witness or scriptsig let err = from_txdata(&comp.sh_wpkh_spk, &blank_script, &[]).unwrap_err(); @@ -587,7 +617,7 @@ mod tests { ::Miniscript::from_str_insane(&format!("hash160({})", hash)).unwrap(); let spk = miniscript.encode(); - let blank_script = bitcoin::Script::new(); + let blank_script = elements::Script::new(); // bare script has no validity requirements beyond being a sane script let (inner, stack, script_code) = @@ -618,7 +648,7 @@ mod tests { let script_sig = script::Builder::new() .push_slice(&redeem_script[..]) .into_script(); - let blank_script = bitcoin::Script::new(); + let blank_script = elements::Script::new(); // sh without scriptsig let err = from_txdata(&spk, &blank_script, &[]).unwrap_err(); @@ -652,7 +682,7 @@ mod tests { let wit_stack = vec![witness_script.to_bytes()]; let spk = Script::new_v0_wsh(&wit_hash); - let blank_script = bitcoin::Script::new(); + let blank_script = elements::Script::new(); // wsh without witness let err = from_txdata(&spk, &blank_script, &[]).unwrap_err(); @@ -692,7 +722,7 @@ mod tests { let script_sig = script::Builder::new() .push_slice(&redeem_script[..]) .into_script(); - let blank_script = bitcoin::Script::new(); + let blank_script = elements::Script::new(); let rs_hash = hash160::Hash::hash(&redeem_script[..]).into(); let spk = Script::new_p2sh(&rs_hash); diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index a65679e3..3a493905 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -19,14 +19,19 @@ //! assuming that the spent coin was descriptor controlled. //! -use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; -use bitcoin::util::bip143; -use bitcoin::{self, secp256k1}; +use bitcoin; +use elements::{self, secp256k1, SigHash}; +use elements::{confidential, sighash}; +use elements::{ + hashes::{hash160, ripemd160, sha256, sha256d, Hash, HashEngine}, + SigHashType, +}; use miniscript::context::NoChecks; use miniscript::ScriptContext; +use util; use Miniscript; use Terminal; -use {BitcoinSig, Descriptor, ToPublicKey}; +use {Descriptor, ElementsSig, ToPublicKey}; mod error; mod inner; @@ -39,7 +44,7 @@ use self::stack::Stack; pub struct Interpreter<'txin> { inner: inner::Inner, stack: Stack<'txin>, - script_code: bitcoin::Script, + script_code: elements::Script, age: u32, height: u32, } @@ -52,8 +57,8 @@ impl<'txin> Interpreter<'txin> { /// function; otherwise, it should be a closure containing a sighash and /// secp context, which can actually verify a given signature. pub fn from_txdata( - spk: &bitcoin::Script, - script_sig: &'txin bitcoin::Script, + spk: &elements::Script, + script_sig: &'txin elements::Script, witness: &'txin [Vec], age: u32, height: u32, @@ -81,7 +86,7 @@ impl<'txin> Interpreter<'txin> { /// /// Running the iterator through will consume the internal stack of the /// `Iterpreter`, and it should not be used again after this. - pub fn iter<'iter, F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool>( + pub fn iter<'iter, F: FnMut(&bitcoin::PublicKey, ElementsSig) -> bool>( &'iter mut self, verify_sig: F, ) -> Iter<'txin, 'iter, F> { @@ -92,18 +97,27 @@ impl<'txin> Interpreter<'txin> { } else { None }, - state: if let inner::Inner::Script(ref script, _) = self.inner { - vec![NodeEvaluationState { - node: script, + state: match self.inner { + inner::Inner::Script(ref ms, _) => vec![NodeEvaluationState { + node: ms, n_evaluated: 0, n_satisfied: 0, - }] - } else { - vec![] + }], + inner::Inner::CovScript(ref _pk, ref ms) => vec![NodeEvaluationState { + node: ms, + n_evaluated: 0, + n_satisfied: 0, + }], + inner::Inner::PublicKey(ref _pk, _) => vec![], }, stack: &mut self.stack, age: self.age, height: self.height, + cov: if let inner::Inner::CovScript(ref pk, ref _ms) = self.inner { + Some(pk) + } else { + None + }, has_errored: false, } } @@ -125,12 +139,16 @@ impl<'txin> Interpreter<'txin> { inner::Inner::PublicKey(ref pk, inner::PubkeyType::Pkh) => format!("pkh({})", pk), inner::Inner::PublicKey(ref pk, inner::PubkeyType::Wpkh) => format!("wpkh({})", pk), inner::Inner::PublicKey(ref pk, inner::PubkeyType::ShWpkh) => { - format!("sh(wpkh({}))", pk) + format!("elsh(wpkh({}))", pk) } inner::Inner::Script(ref ms, inner::ScriptType::Bare) => format!("{}", ms), - inner::Inner::Script(ref ms, inner::ScriptType::Sh) => format!("sh({})", ms), - inner::Inner::Script(ref ms, inner::ScriptType::Wsh) => format!("wsh({})", ms), - inner::Inner::Script(ref ms, inner::ScriptType::ShWsh) => format!("sh(wsh({}))", ms), + inner::Inner::Script(ref ms, inner::ScriptType::Sh) => format!("elsh({})", ms), + inner::Inner::Script(ref ms, inner::ScriptType::Wsh) => format!("elwsh({})", ms), + inner::Inner::Script(ref ms, inner::ScriptType::ShWsh) => format!("elsh(wsh({}))", ms), + inner::Inner::CovScript(ref pk, ref ms) => { + // always wsh for now + format!("elcovwsh({},{})", pk, ms) + } } } @@ -145,6 +163,7 @@ impl<'txin> Interpreter<'txin> { inner::Inner::Script(_, inner::ScriptType::Sh) => true, inner::Inner::Script(_, inner::ScriptType::Wsh) => false, inner::Inner::Script(_, inner::ScriptType::ShWsh) => false, // lol "sorta" + inner::Inner::CovScript(..) => false, } } @@ -166,16 +185,16 @@ impl<'txin> Interpreter<'txin> { /// the amount. pub fn sighash_message( &self, - unsigned_tx: &bitcoin::Transaction, + unsigned_tx: &elements::Transaction, input_idx: usize, - amount: u64, - sighash_type: bitcoin::SigHashType, + amount: confidential::Value, + sighash_type: elements::SigHashType, ) -> secp256k1::Message { + let mut sighash_cache = sighash::SigHashCache::new(unsigned_tx); let hash = if self.is_legacy() { - unsigned_tx.signature_hash(input_idx, &self.script_code, sighash_type.as_u32()) + sighash_cache.legacy_sighash(input_idx, &self.script_code, sighash_type) } else { - let mut sighash_cache = bip143::SigHashCache::new(unsigned_tx); - sighash_cache.signature_hash(input_idx, &self.script_code, amount, sighash_type) + sighash_cache.segwitv0_sighash(input_idx, &self.script_code, amount, sighash_type) }; secp256k1::Message::from_slice(&hash[..]) @@ -186,33 +205,38 @@ impl<'txin> Interpreter<'txin> { pub fn sighash_verify<'a, C: secp256k1::Verification>( &self, secp: &'a secp256k1::Secp256k1, - unsigned_tx: &'a bitcoin::Transaction, + unsigned_tx: &'a elements::Transaction, input_idx: usize, - amount: u64, - ) -> impl Fn(&bitcoin::PublicKey, BitcoinSig) -> bool + 'a { + amount: confidential::Value, + ) -> impl Fn(&bitcoin::PublicKey, ElementsSig) -> bool + 'a { // Precompute all sighash types because the borrowck doesn't like us // pulling self into the closure let sighashes = [ - self.sighash_message(unsigned_tx, input_idx, amount, bitcoin::SigHashType::All), - self.sighash_message(unsigned_tx, input_idx, amount, bitcoin::SigHashType::None), - self.sighash_message(unsigned_tx, input_idx, amount, bitcoin::SigHashType::Single), + self.sighash_message(unsigned_tx, input_idx, amount, elements::SigHashType::All), + self.sighash_message(unsigned_tx, input_idx, amount, elements::SigHashType::None), self.sighash_message( unsigned_tx, input_idx, amount, - bitcoin::SigHashType::AllPlusAnyoneCanPay, + elements::SigHashType::Single, ), self.sighash_message( unsigned_tx, input_idx, amount, - bitcoin::SigHashType::NonePlusAnyoneCanPay, + elements::SigHashType::AllPlusAnyoneCanPay, ), self.sighash_message( unsigned_tx, input_idx, amount, - bitcoin::SigHashType::SinglePlusAnyoneCanPay, + elements::SigHashType::NonePlusAnyoneCanPay, + ), + self.sighash_message( + unsigned_tx, + input_idx, + amount, + elements::SigHashType::SinglePlusAnyoneCanPay, ), ]; @@ -220,12 +244,12 @@ impl<'txin> Interpreter<'txin> { // This is an awkward way to do this lookup, but it lets us do exhaustiveness // checking in case future rust-bitcoin versions add new sighash types let sighash = match sighash_type { - bitcoin::SigHashType::All => sighashes[0], - bitcoin::SigHashType::None => sighashes[1], - bitcoin::SigHashType::Single => sighashes[2], - bitcoin::SigHashType::AllPlusAnyoneCanPay => sighashes[3], - bitcoin::SigHashType::NonePlusAnyoneCanPay => sighashes[4], - bitcoin::SigHashType::SinglePlusAnyoneCanPay => sighashes[5], + elements::SigHashType::All => sighashes[0], + elements::SigHashType::None => sighashes[1], + elements::SigHashType::Single => sighashes[2], + elements::SigHashType::AllPlusAnyoneCanPay => sighashes[3], + elements::SigHashType::NonePlusAnyoneCanPay => sighashes[4], + elements::SigHashType::SinglePlusAnyoneCanPay => sighashes[5], }; secp.verify(&sighash, &sig, &pk.key).is_ok() } @@ -283,6 +307,20 @@ pub enum SatisfiedConstraint<'intp, 'txin> { /// The value of Absolute timelock time: &'intp u32, }, + + /// Elements + /// Check Version eq + VerEq { + /// The version of transaction + n: &'intp u32, + }, + + /// Serialized outputs of this transaction start + /// this prefix + OutputsPref { + /// The version of transaction + pref: &'intp [u8], + }, } ///This is used by the interpreter to know which evaluation state a AstemElem is. @@ -311,13 +349,14 @@ struct NodeEvaluationState<'intp> { /// /// In case the script is actually dissatisfied, this may return several values /// before ultimately returning an error. -pub struct Iter<'intp, 'txin: 'intp, F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool> { +pub struct Iter<'intp, 'txin: 'intp, F: FnMut(&bitcoin::PublicKey, ElementsSig) -> bool> { verify_sig: F, public_key: Option<&'intp bitcoin::PublicKey>, state: Vec>, stack: &'intp mut Stack<'txin>, age: u32, height: u32, + cov: Option<&'intp bitcoin::PublicKey>, has_errored: bool, } @@ -325,7 +364,7 @@ pub struct Iter<'intp, 'txin: 'intp, F: FnMut(&bitcoin::PublicKey, BitcoinSig) - impl<'intp, 'txin: 'intp, F> Iterator for Iter<'intp, 'txin, F> where NoChecks: ScriptContext, - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnMut(&bitcoin::PublicKey, ElementsSig) -> bool, { type Item = Result, Error>; @@ -346,7 +385,7 @@ where impl<'intp, 'txin: 'intp, F> Iter<'intp, 'txin, F> where NoChecks: ScriptContext, - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnMut(&bitcoin::PublicKey, ElementsSig) -> bool, { /// Helper function to push a NodeEvaluationState on state stack fn push_evaluation_state( @@ -441,6 +480,22 @@ where return res; } } + Terminal::Version(ref ver) => { + debug_assert_eq!(node_state.n_evaluated, 0); + debug_assert_eq!(node_state.n_satisfied, 0); + let res = self.stack.evaluate_ver(ver); + if res.is_some() { + return res; + } + } + Terminal::OutputsPref(ref pref) => { + debug_assert_eq!(node_state.n_evaluated, 0); + debug_assert_eq!(node_state.n_satisfied, 0); + let res = self.stack.evaluate_outputs_pref(pref); + if res.is_some() { + return res; + } + } Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub) => { debug_assert_eq!(node_state.n_evaluated, 0); debug_assert_eq!(node_state.n_satisfied, 0); @@ -720,7 +775,63 @@ where } //state empty implies that either the execution has terminated or we have a - //Pk based descriptor + //Pk based descriptor or a Covenant descriptor + if let Some(pk) = self.cov { + // First verify the top of Miniscript. + // At this point, the stack must contain 13 elements + // pop the satisfied top and verify the covenant code. + if self.stack.pop() != Some(stack::Element::Satisfied) { + return Some(Err(Error::IncorrectCovenantWitness)); + } + if self.stack.len() != 12 { + return Some(Err(Error::UnexpectedStackEnd)); + } + // safe to unwrap 12 times + for i in 0..12 { + if let Err(e) = self.stack[i].try_push() { + return Some(Err(e)); + } + } + let mut ser_sig = Vec::new(); + // 1.29 errors + { + let sighash_bytes = self.stack[11].as_push(); + let sighash_u32 = util::slice_to_u32_le(sighash_bytes); + let sighash_ty = SigHashType::from_u32(sighash_u32); + let sig_vec = self.stack[0].as_push(); + ser_sig.extend(sig_vec); + ser_sig.push(sighash_ty as u8); + } + + if let Ok(sig) = verify_sersig(&mut self.verify_sig, &pk, &ser_sig) { + //Signature check successful, set cov to None to + //terminate the next() function in the subsequent call + self.cov = None; + // Do the checkSigFromStackCheck + let sighash_msg: Vec = self.stack.0[1..] + .into_iter() + .map(|x| Vec::from(x.as_push())) + .flatten() + .collect(); + let mut eng = SigHash::engine(); + eng.input(&sighash_msg); + let sighash_u256 = SigHash::from_engine(eng); + let msg = bitcoin::secp256k1::Message::from_slice(&sighash_u256[..]).unwrap(); + + // TODO: THIS SHOULD BE A SEPARATE PARAMETER TO THE FUNCTION, BUT SINCE + // IT MIGHT ELSEWHERE, CONSIDER MAKING IT A SEPARATE METHOD. RIGHT NOW, + // THIS IS CREATING A NEW CONTEXT WHICH IS EXPENSIVE + let secp = secp256k1::Secp256k1::verification_only(); + if secp.verify(&msg, &sig, &pk.key).is_err() { + return Some(Err(Error::PkEvaluationError(pk.clone().to_public_key()))); + } + self.stack.0.clear(); + self.stack.push(stack::Element::Satisfied); + return Some(Ok(SatisfiedConstraint::PublicKey { key: pk, sig })); + } else { + return Some(Err(Error::PkEvaluationError(pk.clone().to_public_key()))); + } + } if let Some(pk) = self.public_key { if let Some(stack::Element::Push(sig)) = self.stack.pop() { if let Ok(sig) = verify_sersig(&mut self.verify_sig, &pk, &sig) { @@ -754,10 +865,10 @@ fn verify_sersig<'txin, F>( sigser: &[u8], ) -> Result where - F: FnOnce(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnOnce(&bitcoin::PublicKey, ElementsSig) -> bool, { if let Some((sighash_byte, sig)) = sigser.split_last() { - let sighashtype = bitcoin::SigHashType::from_u32(*sighash_byte as u32); + let sighashtype = elements::SigHashType::from_u32(*sighash_byte as u32); let sig = secp256k1::Signature::from_der(sig)?; if verify_sig(pk, (sig, sighashtype)) { Ok(sig) @@ -775,9 +886,9 @@ mod tests { use super::*; use bitcoin; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; - use bitcoin::secp256k1::{self, Secp256k1, VerifyOnly}; + use elements::secp256k1::{self, Secp256k1, VerifyOnly}; use miniscript::context::NoChecks; - use BitcoinSig; + use ElementsSig; use Miniscript; use MiniscriptKey; use ToPublicKey; @@ -831,7 +942,7 @@ mod tests { ms: &'elem Miniscript, ) -> Iter<'elem, 'txin, F> where - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnMut(&bitcoin::PublicKey, ElementsSig) -> bool, { Iter { verify_sig: verify_fn, @@ -844,6 +955,7 @@ mod tests { }], age: 1002, height: 1002, + cov: None, has_errored: false, } }; diff --git a/src/interpreter/stack.rs b/src/interpreter/stack.rs index ff07c830..2d9a9ac5 100644 --- a/src/interpreter/stack.rs +++ b/src/interpreter/stack.rs @@ -14,14 +14,17 @@ //! Interpreter stack +use std::ops::Index; + use bitcoin; -use bitcoin::blockdata::{opcodes, script}; -use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use elements::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use elements::{self, opcodes, script}; -use {BitcoinSig, ToPublicKey}; +use {ElementsSig, ToPublicKey}; use super::{verify_sersig, Error, HashLockType, SatisfiedConstraint}; - +use miniscript::limits::{MAX_SCRIPT_ELEMENT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEM_SIZE}; +use util; /// Definition of Stack Element of the Stack used for interpretation of Miniscript. /// All stack elements with vec![] go to Dissatisfied and vec![1] are marked to Satisfied. /// Others are directly pushed as witness @@ -60,7 +63,7 @@ impl<'txin> Element<'txin> { /// /// Supports `OP_1` but no other numbers since these are not used by Miniscript pub fn from_instruction( - ins: Result, bitcoin::blockdata::script::Error>, + ins: Result, elements::script::Error>, ) -> Result { match ins { //Also covers the dissatisfied case as PushBytes0 @@ -69,12 +72,37 @@ impl<'txin> Element<'txin> { _ => Err(Error::ExpectedPush), } } + + /// Panics when the element is not a push + pub(crate) fn as_push(&self) -> &[u8] { + match self { + Element::Push(x) => x, + _ => unreachable!("Called as_push on 1/0 stack elem"), + } + } + + /// Errs when the element is not a push + pub(crate) fn try_push(&self) -> Result<&[u8], Error> { + match self { + Element::Push(x) => Ok(x), + _ => Err(Error::ExpectedPush), + } + } + + /// Convert element into slice + pub(crate) fn into_slice(self) -> &'txin [u8] { + match self { + Element::Satisfied => &[1], + Element::Dissatisfied => &[], + Element::Push(ref v) => v, + } + } } /// Stack Data structure representing the stack input to Miniscript. This Stack /// is created from the combination of ScriptSig and Witness stack. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct Stack<'txin>(Vec>); +pub struct Stack<'txin>(pub(super) Vec>); impl<'txin> From>> for Stack<'txin> { fn from(v: Vec>) -> Self { @@ -88,6 +116,14 @@ impl<'txin> Default for Stack<'txin> { } } +impl<'txin> Index for Stack<'txin> { + type Output = Element<'txin>; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + impl<'txin> Stack<'txin> { /// Whether the stack is empty pub fn is_empty(&self) -> bool { @@ -132,7 +168,7 @@ impl<'txin> Stack<'txin> { pk: &'intp bitcoin::PublicKey, ) -> Option, Error>> where - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnMut(&bitcoin::PublicKey, ElementsSig) -> bool, { if let Some(sigser) = self.pop() { match sigser { @@ -171,7 +207,7 @@ impl<'txin> Stack<'txin> { pkh: &'intp hash160::Hash, ) -> Option, Error>> where - F: FnOnce(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnOnce(&bitcoin::PublicKey, ElementsSig) -> bool, { if let Some(Element::Push(pk)) = self.pop() { let pk_hash = hash160::Hash::hash(pk); @@ -355,6 +391,81 @@ impl<'txin> Stack<'txin> { } } + /// Evaluate a ver fragment. Get the version from the global stack + /// context and check equality + pub fn evaluate_ver<'intp>( + &mut self, + n: &'intp u32, + ) -> Option, Error>> { + // Version is at index 1 + let ver = self[1]; + if let Err(e) = ver.try_push() { + return Some(Err(e)); + } + let elem = ver.as_push(); + if elem.len() == 4 { + let wit_ver = util::slice_to_u32_le(elem); + if wit_ver == *n { + self.push(Element::Satisfied); + Some(Ok(SatisfiedConstraint::VerEq { n: n })) + } else { + None + } + } else { + Some(Err(Error::CovWitnessSizeErr { + pos: 1, + expected: 4, + actual: elem.len(), + })) + } + } + + /// Evaluate a output_pref fragment. Get the hashoutputs from the global + /// stack context and check it's preimage starts with prefix. + /// The user provides the suffix as witness in 6 different elements + pub fn evaluate_outputs_pref<'intp>( + &mut self, + pref: &'intp [u8], + ) -> Option, Error>> { + // Version is at index 1 + let hash_outputs = self[9]; + if let Err(e) = hash_outputs.try_push() { + return Some(Err(e)); + } + // Maximum number of suffix elements + let max_elems = MAX_SCRIPT_ELEMENT_SIZE / MAX_STANDARD_P2WSH_STACK_ITEM_SIZE + 1; + let hash_outputs = hash_outputs.as_push(); + if hash_outputs.len() == 32 { + // We want to cat the last 6 elements(5 cats) in suffix + if self.len() < max_elems { + return Some(Err(Error::UnexpectedStackEnd)); + } + let mut outputs_builder = Vec::new(); + outputs_builder.extend(pref); + let len = self.len(); + // Add the max_elems suffix elements + for i in 0..max_elems { + outputs_builder.extend(self[len - max_elems + i].into_slice()); + } + // Pop the max_elems suffix elements + for _ in 0..max_elems { + self.pop().unwrap(); + } + if sha256d::Hash::hash(&outputs_builder).as_inner() == hash_outputs { + self.push(Element::Satisfied); + Some(Ok(SatisfiedConstraint::OutputsPref { pref: pref })) + } else { + None + } + } else { + Some(Err(Error::CovWitnessSizeErr { + pos: 9, + expected: 32, + actual: hash_outputs.len(), + })) + } + } + /// Helper function to evaluate a checkmultisig which takes the top of the /// stack as input signatures and validates it in order of pubkeys. /// For example, if the first signature is satisfied by second public key, @@ -367,7 +478,7 @@ impl<'txin> Stack<'txin> { pk: &'intp bitcoin::PublicKey, ) -> Option, Error>> where - F: FnOnce(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnOnce(&bitcoin::PublicKey, ElementsSig) -> bool, { if let Some(witness_sig) = self.pop() { if let Element::Push(sigser) = witness_sig { diff --git a/src/lib.rs b/src/lib.rs index 6de2ea34..541df832 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,29 +41,36 @@ //! //! Miniscript also admits a more human-readable encoding. //! +//! ## Elements Miniscript +//! +//! Elements Miniscript is a fork of miniscript for [elements](https://github.com/ElementsProject/elements) sidechain. +//! //! ## Output Descriptors //! //! While spending policies in Bitcoin are entirely defined by Script; there //! are multiple ways of embedding these Scripts in transaction outputs; for //! example, P2SH or Segwit v0. These different embeddings are expressed by //! *Output Descriptors*, [which are described here](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) -//! +//! Elements descriptors are extension of bitcoin Output descriptors with support +//! for blinded descriptors(WIP) //! # Examples //! //! ## Deriving an address from a descriptor //! //! ```rust //! extern crate bitcoin; -//! extern crate miniscript; +//! extern crate elements; +//! extern crate elements_miniscript as miniscript; //! //! use std::str::FromStr; //! use miniscript::{DescriptorTrait}; //! //! fn main() { +//! // Elements descriptors are prefixed by string el //! let desc = miniscript::Descriptor::< //! bitcoin::PublicKey, //! >::from_str("\ -//! sh(wsh(or_d(\ +//! elsh(wsh(or_d(\ //! c:pk_k(020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261),\ //! c:pk_k(0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352)\ //! )))\ @@ -71,8 +78,8 @@ //! //! // Derive the P2SH address //! assert_eq!( -//! desc.address(bitcoin::Network::Bitcoin).unwrap().to_string(), -//! "3CJxbQBfWAe1ZkKiGQNEYrioV73ZwvBWns" +//! desc.address(&elements::AddressParams::ELEMENTS).unwrap().to_string(), +//! "XMyBX13qCo5Lp65mymgYVdmsYR5bcznWUa" //! ); //! //! // Check whether the descriptor is safe @@ -100,11 +107,36 @@ #![deny(missing_docs)] pub extern crate bitcoin; +pub extern crate elements; #[cfg(feature = "serde")] pub extern crate serde; #[cfg(all(test, feature = "unstable"))] extern crate test; +// Miniscript imports +// It can be confusing to code when we have two miniscript libraries +// As a rule, only import the library here and pub use all the required +// items. Should help in faster code development in the long run +extern crate miniscript as bitcoin_miniscript; +pub(crate) use bitcoin_miniscript::expression::FromTree as BtcFromTree; +pub(crate) use bitcoin_miniscript::expression::Tree as BtcTree; +pub(crate) use bitcoin_miniscript::policy::semantic::Policy as BtcPolicy; +pub(crate) use bitcoin_miniscript::policy::Liftable as BtcLiftable; +pub(crate) use bitcoin_miniscript::Descriptor as BtcDescriptor; +pub(crate) use bitcoin_miniscript::DescriptorTrait as BtcDescriptorTrait; +pub(crate) use bitcoin_miniscript::Error as BtcError; +pub(crate) use bitcoin_miniscript::Miniscript as BtcMiniscript; +pub(crate) use bitcoin_miniscript::Satisfier as BtcSatisfier; +pub(crate) use bitcoin_miniscript::Segwitv0 as BtcSegwitv0; +pub(crate) use bitcoin_miniscript::Terminal as BtcTerminal; +// re-export imports +pub use bitcoin_miniscript::{DummyKey, DummyKeyHash}; +pub use bitcoin_miniscript::{ + ForEach, ForEachKey, MiniscriptKey, ToPublicKey, TranslatePk, TranslatePk1, TranslatePk2, + TranslatePk3, +}; +// End imports + #[macro_use] mod macros; @@ -113,335 +145,41 @@ pub mod expression; pub mod interpreter; pub mod miniscript; pub mod policy; -pub mod psbt; mod util; -use std::str::FromStr; -use std::{error, fmt, hash, str}; +use std::{error, fmt, str}; -use bitcoin::blockdata::{opcodes, script}; -use bitcoin::hashes::{hash160, sha256, Hash}; +// Find a better home +#[allow(deprecated)] +use bitcoin::util::contracthash; +use elements::hashes::sha256; +use elements::{opcodes, script, secp256k1, secp256k1::Secp256k1}; pub use descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait}; pub use interpreter::Interpreter; pub use miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0}; pub use miniscript::decode::Terminal; -pub use miniscript::satisfy::{BitcoinSig, Preimage32, Satisfier}; +pub use miniscript::satisfy::{ElementsSig, Preimage32, Satisfier}; pub use miniscript::Miniscript; -///Public key trait which can be converted to Hash type -pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Hash { - /// Check if the publicKey is uncompressed. The default - /// implementation returns false - fn is_uncompressed(&self) -> bool { - false - } - /// The associated Hash type with the publicKey - type Hash: Clone + Eq + Ord + fmt::Display + fmt::Debug + hash::Hash; - - /// Converts an object to PublicHash - fn to_pubkeyhash(&self) -> Self::Hash; - - /// Computes the size of a public key when serialized in a script, - /// including the length bytes - fn serialized_len(&self) -> usize { - if self.is_uncompressed() { - 66 - } else { - 34 - } - } -} - -impl MiniscriptKey for bitcoin::PublicKey { - /// `is_uncompressed` returns true only for - /// bitcoin::Publickey type if the underlying key is uncompressed. - fn is_uncompressed(&self) -> bool { - !self.compressed - } - - type Hash = hash160::Hash; - - fn to_pubkeyhash(&self) -> Self::Hash { - let mut engine = hash160::Hash::engine(); - self.write_into(&mut engine).expect("engines don't error"); - hash160::Hash::from_engine(engine) - } -} - -impl MiniscriptKey for String { - type Hash = String; - - fn to_pubkeyhash(&self) -> Self::Hash { - format!("{}", &self) - } -} - -/// Trait describing public key types which can be converted to bitcoin pubkeys -pub trait ToPublicKey: MiniscriptKey { - /// Converts an object to a public key - fn to_public_key(&self) -> bitcoin::PublicKey; - - /// Converts a hashed version of the public key to a `hash160` hash. - /// - /// This method must be consistent with `to_public_key`, in the sense - /// that calling `MiniscriptKey::to_pubkeyhash` followed by this function - /// should give the same result as calling `to_public_key` and hashing - /// the result directly. - fn hash_to_hash160(hash: &::Hash) -> hash160::Hash; -} - -impl ToPublicKey for bitcoin::PublicKey { - fn to_public_key(&self) -> bitcoin::PublicKey { - *self - } - - fn hash_to_hash160(hash: &hash160::Hash) -> hash160::Hash { - *hash - } -} - -/// Dummy key which de/serializes to the empty string; useful sometimes for testing -#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] -pub struct DummyKey; - -impl str::FromStr for DummyKey { - type Err = &'static str; - fn from_str(x: &str) -> Result { - if x.is_empty() { - Ok(DummyKey) - } else { - Err("non empty dummy key") - } - } -} - -impl MiniscriptKey for DummyKey { - type Hash = DummyKeyHash; - - fn to_pubkeyhash(&self) -> Self::Hash { - DummyKeyHash - } -} - -impl hash::Hash for DummyKey { - fn hash(&self, state: &mut H) { - "DummyKey".hash(state); - } -} - -impl fmt::Display for DummyKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("") - } -} - -impl ToPublicKey for DummyKey { - fn to_public_key(&self) -> bitcoin::PublicKey { - bitcoin::PublicKey::from_str( - "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", - ) - .unwrap() - } - - fn hash_to_hash160(_: &DummyKeyHash) -> hash160::Hash { - hash160::Hash::from_str("f54a5851e9372b87810a8e60cdd2e7cfd80b6e31").unwrap() - } -} - -/// Dummy keyhash which de/serializes to the empty string; useful sometimes for testing -#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] -pub struct DummyKeyHash; - -impl str::FromStr for DummyKeyHash { - type Err = &'static str; - fn from_str(x: &str) -> Result { - if x.is_empty() { - Ok(DummyKeyHash) - } else { - Err("non empty dummy key") - } - } -} - -impl fmt::Display for DummyKeyHash { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("") - } -} - -impl hash::Hash for DummyKeyHash { - fn hash(&self, state: &mut H) { - "DummyKeyHash".hash(state); - } -} - -/// Convert a descriptor using abstract keys to one using specific keys -/// This will panic if translatefpk returns an uncompressed key when -/// converting to a Segwit descriptor. To prevent this panic, ensure -/// translatefpk returns an error in this case instead. -pub trait TranslatePk { - /// The associated output type. This must be Self - type Output; - - /// Translate a struct from one Generic to another where the - /// translation for Pk is provided by translatefpk, and translation for - /// PkH is provided by translatefpkh - fn translate_pk( - &self, - translatefpk: Fpk, - translatefpkh: Fpkh, - ) -> Result - where - Fpk: FnMut(&P) -> Result, - Fpkh: FnMut(&P::Hash) -> Result; - - /// Calls `translate_pk` with conversion functions that cannot fail - fn translate_pk_infallible( - &self, - mut translatefpk: Fpk, - mut translatefpkh: Fpkh, - ) -> Self::Output - where - Fpk: FnMut(&P) -> Q, - Fpkh: FnMut(&P::Hash) -> Q::Hash, - { - self.translate_pk::<_, _, ()>(|pk| Ok(translatefpk(pk)), |pkh| Ok(translatefpkh(pkh))) - .expect("infallible translation function") - } -} - -/// Variant of `TranslatePk` where P and Q both have the same hash -/// type, and the hashes can be converted by just cloning them -pub trait TranslatePk1>: - TranslatePk -{ - /// Translate a struct from one generic to another where the - /// translation for Pk is provided by translatefpk - fn translate_pk1( - &self, - translatefpk: Fpk, - ) -> Result<>::Output, E> - where - Fpk: FnMut(&P) -> Result, - { - self.translate_pk(translatefpk, |h| Ok(h.clone())) - } - - /// Translate a struct from one generic to another where the - /// translation for Pk is provided by translatefpk - fn translate_pk1_infallible Q>( - &self, - translatefpk: Fpk, - ) -> >::Output { - self.translate_pk_infallible(translatefpk, P::Hash::clone) - } -} -impl, T: TranslatePk> TranslatePk1 - for T -{ -} - -/// Variant of `TranslatePk` where P's hash is P, so the hashes -/// can be converted by reusing the key-conversion function -pub trait TranslatePk2, Q: MiniscriptKey>: TranslatePk { - /// Translate a struct from one generic to another where the - /// translation for Pk is provided by translatefpk - fn translate_pk2 Result, E>( - &self, - translatefpk: Fpk, - ) -> Result<>::Output, E> { - self.translate_pk(&translatefpk, |h| { - translatefpk(h).map(|q| q.to_pubkeyhash()) - }) - } - - /// Translate a struct from one generic to another where the - /// translation for Pk is provided by translatefpk - fn translate_pk2_infallible Q>( - &self, - translatefpk: Fpk, - ) -> >::Output { - self.translate_pk_infallible(&translatefpk, |h| translatefpk(h).to_pubkeyhash()) - } -} -impl, Q: MiniscriptKey, T: TranslatePk> TranslatePk2 for T {} - -/// Variant of `TranslatePk` where Q's hash is `hash160` so we can -/// derive hashes by calling `hash_to_hash160` -pub trait TranslatePk3>: - TranslatePk -{ - /// Translate a struct from one generic to another where the - /// translation for Pk is provided by translatefpk - fn translate_pk3( - &self, - translatefpk: Fpk, - ) -> Result<>::Output, E> - where - Fpk: FnMut(&P) -> Result, - { - self.translate_pk(translatefpk, |h| Ok(P::hash_to_hash160(h))) - } - - /// Translate a struct from one generic to another where the - /// translation for Pk is provided by translatefpk - fn translate_pk3_infallible Q>( - &self, - translatefpk: Fpk, - ) -> >::Output { - self.translate_pk_infallible(translatefpk, P::hash_to_hash160) - } -} -impl< - P: MiniscriptKey + ToPublicKey, - Q: MiniscriptKey, - T: TranslatePk, - > TranslatePk3 for T +/// Tweak a MiniscriptKey to obtain the tweaked key +// Ideally, we want this in a trait, but doing so we cannot +// use it in the implementation of DescriptorTrait from +// rust-miniscript because it would require stricter bounds. +pub fn tweak_key( + pk: &Pk, + secp: &Secp256k1, + contract: &[u8], +) -> bitcoin::PublicKey +where + Pk: MiniscriptKey + ToPublicKey, { + let pk = pk.to_public_key(); + #[allow(deprecated)] + contracthash::tweak_key(secp, pk, contract) } - -/// Either a key or a keyhash -pub enum ForEach<'a, Pk: MiniscriptKey + 'a> { - /// A key - Key(&'a Pk), - /// A keyhash - Hash(&'a Pk::Hash), -} - -impl<'a, Pk: MiniscriptKey> ForEach<'a, Pk> { - /// Convenience method to avoid distinguishing between keys and hashes when these are the same type - pub fn as_key(&self) -> &'a Pk { - match *self { - ForEach::Key(ref_key) => ref_key, - ForEach::Hash(ref_key) => ref_key, - } - } -} - -/// Trait describing the ability to iterate over every key -pub trait ForEachKey { - /// Run a predicate on every key in the descriptor, returning whether - /// the predicate returned true for every key - fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, pred: F) -> bool - where - Pk: 'a, - Pk::Hash: 'a; - - /// Run a predicate on every key in the descriptor, returning whether - /// the predicate returned true for any key - fn for_any_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, mut pred: F) -> bool - where - Pk: 'a, - Pk::Hash: 'a, - { - !self.for_each_key(|key| !pred(key)) - } -} - /// Miniscript - #[derive(Debug)] pub enum Error { /// Opcode appeared which is not part of the script subset @@ -521,6 +259,10 @@ pub enum Error { ImpossibleSatisfaction, /// Bare descriptors don't have any addresses BareDescriptorAddr, + /// Upstream Miniscript Errors + BtcError(bitcoin_miniscript::Error), + /// Covenant Error + CovError(descriptor::CovError), } #[doc(hidden)] @@ -534,6 +276,13 @@ where } } +#[doc(hidden)] +impl From for Error { + fn from(e: bitcoin_miniscript::Error) -> Error { + Error::BtcError(e) + } +} + #[doc(hidden)] impl From for Error { fn from(e: policy::LiftError) -> Error { @@ -556,12 +305,19 @@ impl From for Error { } #[doc(hidden)] -impl From for Error { - fn from(e: bitcoin::secp256k1::Error) -> Error { +impl From for Error { + fn from(e: elements::secp256k1::Error) -> Error { Error::Secp(e) } } +#[doc(hidden)] +impl From for Error { + fn from(e: bitcoin::util::key::Error) -> Error { + Error::BadPubkey(e) + } +} + fn errstr(s: &str) -> Error { Error::Unexpected(s.to_owned()) } @@ -584,7 +340,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::InvalidOpcode(op) => write!(f, "invalid opcode {}", op), - Error::NonMinimalVerify(tok) => write!(f, "{} VERIFY", tok), + Error::NonMinimalVerify(ref tok) => write!(f, "{} VERIFY", tok), Error::InvalidPush(ref push) => write!(f, "invalid push {:?}", push), // TODO hexify this Error::Script(ref e) => fmt::Display::fmt(e, f), Error::CmsTooManyKeys(n) => write!(f, "checkmultisig with {} keys", n), @@ -642,6 +398,8 @@ impl fmt::Display for Error { Error::AnalysisError(ref e) => e.fmt(f), Error::ImpossibleSatisfaction => write!(f, "Impossible to satisfy Miniscript"), Error::BareDescriptorAddr => write!(f, "Bare descriptors don't have address"), + Error::BtcError(ref e) => write!(f, " Bitcoin Miniscript Error {}", e), + Error::CovError(ref e) => write!(f, "Covenant Error: {}", e), } } } @@ -692,7 +450,7 @@ fn push_opcode_size(script_size: usize) -> usize { /// Helper function used by tests #[cfg(test)] -fn hex_script(s: &str) -> bitcoin::Script { - let v: Vec = bitcoin::hashes::hex::FromHex::from_hex(s).unwrap(); - bitcoin::Script::from(v) +fn hex_script(s: &str) -> elements::Script { + let v: Vec = elements::hashes::hex::FromHex::from_hex(s).unwrap(); + elements::Script::from(v) } diff --git a/src/macros.rs b/src/macros.rs index d56a1b35..a9bdfaa6 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -10,7 +10,7 @@ macro_rules! ms_str { } /// Allows tests to create a concrete policy directly from string as -/// `policy_str!("wsh(c:or_i(pk({}),pk({})))", pk1, pk2)` +/// `policy_str!("elwsh(c:or_i(pk({}),pk({})))", pk1, pk2)` #[cfg(all(feature = "compiler", test))] macro_rules! policy_str { ($($arg:tt)*) => (::policy::Concrete::from_str(&format!($($arg)*)).unwrap()) @@ -96,3 +96,18 @@ macro_rules! serde_string_impl_pk { } }; } + +macro_rules! match_token { + // Base case + ($tokens:expr => $sub:expr,) => { $sub }; + // Recursive case + ($tokens:expr, $($first:pat $(,$rest:pat)* => $sub:expr,)*) => { + match $tokens.next() { + $( + Some($first) => match_token!($tokens $(,$rest)* => $sub,), + )* + Some(other) => return Err(Error::Unexpected(other.to_string())), + None => return Err(Error::UnexpectedStart), + } + }; +} diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 76a93c32..ac766e67 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -23,9 +23,10 @@ use std::str::FromStr; use std::sync::Arc; use std::{fmt, str}; -use bitcoin::blockdata::{opcodes, script}; -use bitcoin::hashes::hex::FromHex; -use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use elements::encode::serialize; +use elements::hashes::hex::{FromHex, ToHex}; +use elements::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use elements::{opcodes, script}; use errstr; use expression; @@ -34,6 +35,8 @@ use miniscript::ScriptContext; use script_num_size; use {Error, ForEach, ForEachKey, Miniscript, MiniscriptKey, Terminal, ToPublicKey, TranslatePk}; +use super::limits::{MAX_SCRIPT_ELEMENT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEM_SIZE}; + impl Terminal { /// Internal helper function for displaying wrapper types; returns /// a character to display before the `:` as well as a reference @@ -95,7 +98,9 @@ impl Terminal { | Terminal::Ripemd160(..) | Terminal::Hash160(..) | Terminal::True - | Terminal::False => true, + | Terminal::False + | Terminal::Version(..) + | Terminal::OutputsPref(..) => true, Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub) @@ -141,6 +146,8 @@ impl Terminal { Terminal::Hash160(x) => Terminal::Hash160(x), Terminal::True => Terminal::True, Terminal::False => Terminal::False, + Terminal::Version(n) => Terminal::Version(n), + Terminal::OutputsPref(ref pref) => Terminal::OutputsPref(pref.clone()), Terminal::Alt(ref sub) => Terminal::Alt(Arc::new( sub.real_translate_pk(translatefpk, translatefpkh)?, )), @@ -283,6 +290,8 @@ impl fmt::Debug for Terminal { Terminal::Hash160(h) => write!(f, "hash160({})", h), Terminal::True => f.write_str("1"), Terminal::False => f.write_str("0"), + Terminal::Version(k) => write!(f, "ver_eq({})", k), + Terminal::OutputsPref(ref pref) => write!(f, "outputs_pref({})", pref.to_hex()), Terminal::AndV(ref l, ref r) => write!(f, "and_v({:?},{:?})", l, r), Terminal::AndB(ref l, ref r) => write!(f, "and_b({:?},{:?})", l, r), Terminal::AndOr(ref a, ref b, ref c) => { @@ -333,6 +342,8 @@ impl fmt::Display for Terminal { Terminal::Hash160(h) => write!(f, "hash160({})", h), Terminal::True => f.write_str("1"), Terminal::False => f.write_str("0"), + Terminal::Version(n) => write!(f, "ver_eq({})", n), + Terminal::OutputsPref(ref pref) => write!(f, "outputs_pref({})", pref.to_hex()), Terminal::AndV(ref l, ref r) if r.node != Terminal::True => { write!(f, "and_v({},{})", l, r) } @@ -503,6 +514,13 @@ where }), ("1", 0) => Ok(Terminal::True), ("0", 0) => Ok(Terminal::False), + ("ver_eq", 1) => { + let n = expression::terminal(&top.args[0], expression::parse_num)?; + Ok(Terminal::Version(n)) + } + ("outputs_pref", 1) => expression::terminal(&top.args[0], |x| { + Vec::::from_hex(x).map(Terminal::OutputsPref) + }), ("and_v", 2) => { let expr = expression::binary(top, Terminal::AndV)?; if let Terminal::AndV(_, ref right) = expr { @@ -635,6 +653,63 @@ impl PushAstElem for script::Bui } } +/// Additional operations required on Script +/// for supporting Miniscript fragments that +/// have access to a global context +pub trait StackCtxOperations: Sized { + /// pick an element indexed from the bottom of + /// the stack. This cannot check whether the idx is within + /// stack limits. + /// Copies the element at index idx to the top of the stack + /// Checks item equality against the specified target + fn check_item_eq(self, idx: u32, target: &[u8]) -> Self; + + /// Since, there is a policy restriction that initial pushes must be + /// only 80 bytes, we need user to provide suffix in separate items + /// There can be atmost 7 cats, because the script element must be less + /// than 520 bytes total in order to compute an hash256 on it. + /// Even if the witness does not require 7 pushes, the user should push + /// 7 elements with possibly empty values. + /// + /// Copies the script item at position and compare the hash256 + /// with it + fn check_item_pref(self, idx: u32, pref: &[u8]) -> Self; +} + +impl StackCtxOperations for script::Builder { + fn check_item_eq(self, idx: u32, target: &[u8]) -> Self { + self.push_int((idx + 1) as i64) // +1 for depth increase + .push_opcode(opcodes::all::OP_DEPTH) + .push_opcode(opcodes::all::OP_SUB) + .push_opcode(opcodes::all::OP_PICK) + .push_slice(target) + .push_opcode(opcodes::all::OP_EQUAL) + } + + fn check_item_pref(self, idx: u32, pref: &[u8]) -> Self { + let mut builder = self; + // Initial Witness + // The nuumber of maximum witness elements in the suffix + let max_elems = MAX_SCRIPT_ELEMENT_SIZE / MAX_STANDARD_P2WSH_STACK_ITEM_SIZE + 1; + for _ in 0..(max_elems - 1) { + builder = builder.push_opcode(opcodes::all::OP_CAT); + } + builder = builder + .push_slice(pref) + .push_opcode(opcodes::all::OP_SWAP) + .push_opcode(opcodes::all::OP_CAT); + // Now the stack top is serialization of all the outputs + builder = builder.push_opcode(opcodes::all::OP_HASH256); + + builder + .push_int((idx + 1) as i64) // +1 for depth increase + .push_opcode(opcodes::all::OP_DEPTH) + .push_opcode(opcodes::all::OP_SUB) + .push_opcode(opcodes::all::OP_PICK) + .push_opcode(opcodes::all::OP_EQUAL) + } +} + impl Terminal { /// Encode the element as a fragment of Bitcoin Script. The inverse /// function, from Script to an AST element, is implemented in the @@ -684,6 +759,8 @@ impl Terminal { .push_opcode(opcodes::all::OP_EQUAL), Terminal::True => builder.push_opcode(opcodes::OP_TRUE), Terminal::False => builder.push_opcode(opcodes::OP_FALSE), + Terminal::Version(n) => builder.check_item_eq(1, &serialize(&n)), + Terminal::OutputsPref(ref pref) => builder.check_item_pref(9, pref), Terminal::Alt(ref sub) => builder .push_opcode(opcodes::all::OP_TOALTSTACK) .push_astelem(sub) @@ -780,6 +857,13 @@ impl Terminal { Terminal::Hash160(..) => 21 + 6, Terminal::True => 1, Terminal::False => 1, + Terminal::Version(_n) => 4 + 1 + 1 + 4, // opcodes + push opcodes + target size + Terminal::OutputsPref(ref pref) => { + // CAT CAT CAT CAT CAT CAT SWAP CAT /*Now we hashoutputs on stack */ + // HASH256 DEPTH <10> SUB PICK EQUAL + 8 + pref.len() + 1 /* line1 opcodes + pref.push */ + + 6 /* line 2 */ + } Terminal::Alt(ref sub) => sub.node.script_size() + 2, Terminal::Swap(ref sub) => sub.node.script_size() + 1, Terminal::Check(ref sub) => sub.node.script_size() + 1, diff --git a/src/miniscript/context.rs b/src/miniscript/context.rs index 193ac943..64d3a476 100644 --- a/src/miniscript/context.rs +++ b/src/miniscript/context.rs @@ -53,6 +53,8 @@ pub enum ScriptContextError { MaxScriptSigSizeExceeded, /// Impossible to satisfy the miniscript under the current context ImpossibleSatisfaction, + /// Covenant Prefix/ Suffix maximum allowed stack element exceeds 520 bytes + CovElementSizeExceeded, } impl fmt::Display for ScriptContextError { @@ -97,6 +99,12 @@ impl fmt::Display for ScriptContextError { "Impossible to satisfy Miniscript under the current context" ) } + ScriptContextError::CovElementSizeExceeded => { + write!( + f, + "Prefix/Suffix len in sighash covenents exceeds 520 bytes" + ) + } } } } @@ -351,6 +359,12 @@ impl ScriptContext for Segwitv0 { } Ok(()) } + Terminal::OutputsPref(ref pref) => { + if pref.len() > MAX_SCRIPT_ELEMENT_SIZE { + return Err(ScriptContextError::CovElementSizeExceeded); + } + Ok(()) + } _ => Ok(()), } } diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index 78c5c7c9..479300ae 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -17,7 +17,7 @@ //! Functionality to parse a Bitcoin Script into a `Miniscript` //! -use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use elements::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use std::marker::PhantomData; use {bitcoin, Miniscript}; @@ -87,6 +87,23 @@ pub enum Terminal { Ripemd160(ripemd160::Hash), /// `SIZE 32 EQUALVERIFY HASH160 EQUAL` Hash160(hash160::Hash), + // Elements + /// `DEPTH <12> SUB PICK EQUAL` + Version(u32), + /// Prefix is initally encoded in the script pubkey + /// User provides a suffix such that hash of (prefix || suffix) + /// is equal to hashOutputs + /// Since, there is a policy restriction that initial pushes must be + /// only 80 bytes, we need user to provide suffix in separate items + /// There can be atmost 7 cats, because the script element must be less + /// than 520 bytes total in order to compute an hash256 on it. + /// Even if the witness does not require 7 pushes, the user should push + /// 7 elements with possibly empty values. + /// + /// CAT CAT CAT CAT CAT CAT SWAP CAT /*Now we hashoutputs on stack */ + /// HASH256 + /// DEPTH <10> SUB PICK EQUALVERIFY + OutputsPref(Vec), // Wrappers /// `TOALTSTACK [E] FROMALTSTACK` Alt(Arc>), @@ -129,21 +146,6 @@ pub enum Terminal { Multi(usize, Vec), } -macro_rules! match_token { - // Base case - ($tokens:expr => $sub:expr,) => { $sub }; - // Recursive case - ($tokens:expr, $($first:pat $(,$rest:pat)* => $sub:expr,)*) => { - match $tokens.next() { - $( - Some($first) => match_token!($tokens $(,$rest)* => $sub,), - )* - Some(other) => return Err(Error::Unexpected(other.to_string())), - None => return Err(Error::UnexpectedStart), - } - }; -} - ///Vec representing terminals stack while decoding. struct TerminalStack(Vec>); @@ -280,6 +282,31 @@ pub fn parse( ))? }, ), + Tk::PickPush4(ver), Tk::Sub, Tk::Depth => match_token!( + tokens, + Tk::Num(2) => { + non_term.push(NonTerm::Verify); + term.reduce0(Terminal::Version(ver))? + }, + ), + Tk::Pick, Tk::Sub, Tk::Depth => match_token!( + tokens, + Tk::Num(10) => match_token!( + tokens, + Tk::Hash256, Tk::Cat, Tk::Swap, Tk::Push(bytes), Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat => + { + non_term.push(NonTerm::Verify); + term.reduce0(Terminal::OutputsPref(bytes))? + }, + ), + ), + Tk::Num(k) => { + non_term.push(NonTerm::Verify); + non_term.push(NonTerm::ThreshW { + k: k as usize, + n: 0 + }); + }, ), x => { tokens.un_next(x); @@ -333,6 +360,18 @@ pub fn parse( hash160::Hash::from_inner(hash) ))?, ), + Tk::PickPush4(ver), Tk::Sub, Tk::Depth => match_token!( + tokens, + Tk::Num(2) => term.reduce0(Terminal::Version(ver))?, + ), + Tk::Pick, Tk::Sub, Tk::Depth => match_token!( + tokens, + Tk::Num(10) => match_token!( + tokens, + Tk::Hash256, Tk::Cat, Tk::Swap, Tk::Push(bytes), Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat, Tk::Cat => + term.reduce0(Terminal::OutputsPref(bytes))?, + ), + ), // thresholds Tk::Num(k) => { non_term.push(NonTerm::ThreshW { diff --git a/src/miniscript/iter.rs b/src/miniscript/iter.rs index 36c4b69e..fcc542a1 100644 --- a/src/miniscript/iter.rs +++ b/src/miniscript/iter.rs @@ -435,8 +435,8 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for PkPkhIter<'a, Pk, C pub mod test { use super::{Miniscript, PkPkh}; use bitcoin; - use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; - use bitcoin::secp256k1; + use elements::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; + use elements::secp256k1; use miniscript::context::Segwitv0; pub type TestData = ( diff --git a/src/miniscript/lex.rs b/src/miniscript/lex.rs index f0f55198..596fe42e 100644 --- a/src/miniscript/lex.rs +++ b/src/miniscript/lex.rs @@ -17,27 +17,35 @@ //! Translates a script into a reversed sequence of tokens //! -use bitcoin::blockdata::{opcodes, script}; use bitcoin::PublicKey; +use elements::{opcodes, script}; use std::fmt; use super::Error; - +use util::{build_scriptint, slice_to_u32_le}; /// Atom of a tokenized version of a script -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[allow(missing_docs)] pub enum Token { BoolAnd, BoolOr, Add, + Sub, Equal, CheckSig, + CheckSigFromStack, CheckMultiSig, CheckSequenceVerify, CheckLockTimeVerify, FromAltStack, ToAltStack, + Left, + Cat, + CodeSep, + Over, + Pick, + Depth, Drop, Dup, If, @@ -57,11 +65,15 @@ pub enum Token { Hash20([u8; 20]), Hash32([u8; 32]), Pubkey(PublicKey), + Push(Vec), // Num or a + PickPush4(u32), // Pick followed by a 4 byte push + PickPush32([u8; 32]), // Pick followed by a 32 byte push + PickPush(Vec), // Pick followed by a push } impl fmt::Display for Token { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { + match self { Token::Num(n) => write!(f, "#{}", n), Token::Hash20(hash) => { for ch in &hash[..] { @@ -121,6 +133,23 @@ impl Iterator for TokenIter { pub fn lex(script: &script::Script) -> Result, Error> { let mut ret = Vec::with_capacity(script.len()); + fn process_candidate_push(ret: &mut Vec) -> Result<(), Error> { + let ret_len = ret.len(); + + if ret_len < 2 || ret[ret_len - 1] != Token::Swap { + return Ok(()); + } + let token = match &ret[ret_len - 2] { + Token::Hash20(x) => Token::Push(x.to_vec()), + Token::Hash32(x) => Token::Push(x.to_vec()), + Token::Pubkey(pk) => Token::Push(pk.to_bytes()), + Token::Num(k) => Token::Push(build_scriptint(*k as i64)), + _x => return Ok(()), // no change required + }; + ret[ret_len - 2] = token; + Ok(()) + } + for ins in script.instructions_minimal() { match ins.map_err(Error::Script)? { script::Instruction::Op(opcodes::all::OP_BOOLAND) => { @@ -139,6 +168,9 @@ pub fn lex(script: &script::Script) -> Result, Error> { script::Instruction::Op(opcodes::all::OP_CHECKSIG) => { ret.push(Token::CheckSig); } + script::Instruction::Op(opcodes::all::OP_CHECKSIGFROMSTACK) => { + ret.push(Token::CheckSigFromStack); + } script::Instruction::Op(opcodes::all::OP_CHECKSIGVERIFY) => { ret.push(Token::CheckSig); ret.push(Token::Verify); @@ -162,15 +194,37 @@ pub fn lex(script: &script::Script) -> Result, Error> { script::Instruction::Op(opcodes::all::OP_TOALTSTACK) => { ret.push(Token::ToAltStack); } + script::Instruction::Op(opcodes::all::OP_LEFT) => { + ret.push(Token::Left); + } + script::Instruction::Op(opcodes::all::OP_CAT) => { + process_candidate_push(&mut ret)?; + ret.push(Token::Cat); + } + script::Instruction::Op(opcodes::all::OP_CODESEPARATOR) => { + ret.push(Token::CodeSep); + } + script::Instruction::Op(opcodes::all::OP_OVER) => { + ret.push(Token::Over); + } + script::Instruction::Op(opcodes::all::OP_PICK) => { + ret.push(Token::Pick); + } script::Instruction::Op(opcodes::all::OP_DROP) => { ret.push(Token::Drop); } + script::Instruction::Op(opcodes::all::OP_DEPTH) => { + ret.push(Token::Depth); + } script::Instruction::Op(opcodes::all::OP_DUP) => { ret.push(Token::Dup); } script::Instruction::Op(opcodes::all::OP_ADD) => { ret.push(Token::Add); } + script::Instruction::Op(opcodes::all::OP_SUB) => { + ret.push(Token::Sub); + } script::Instruction::Op(opcodes::all::OP_IF) => { ret.push(Token::If); } @@ -199,7 +253,9 @@ pub fn lex(script: &script::Script) -> Result, Error> { match ret.last() { Some(op @ &Token::Equal) | Some(op @ &Token::CheckSig) - | Some(op @ &Token::CheckMultiSig) => return Err(Error::NonMinimalVerify(*op)), + | Some(op @ &Token::CheckMultiSig) => { + return Err(Error::NonMinimalVerify(op.clone())) + } _ => {} } ret.push(Token::Verify); @@ -217,33 +273,60 @@ pub fn lex(script: &script::Script) -> Result, Error> { ret.push(Token::Hash256); } script::Instruction::PushBytes(bytes) => { - match bytes.len() { - 20 => { - let mut x = [0; 20]; - x.copy_from_slice(bytes); - ret.push(Token::Hash20(x)) - } - 32 => { - let mut x = [0; 32]; - x.copy_from_slice(bytes); - ret.push(Token::Hash32(x)) - } - 33 | 65 => { - ret.push(Token::Pubkey( - PublicKey::from_slice(bytes).map_err(Error::BadPubkey)?, - )); + // Check for Pick Push + // Special handling of tokens for Covenants + // To determine whether some Token is actually + // 4 bytes push or a script int of 4 bytes, + // we need additional script context + if ret.last() == Some(&Token::Pick) { + ret.pop().unwrap(); + match bytes.len() { + // All other sighash elements are 32 bytes. And the script code + // is 24 bytes + 4 => ret.push(Token::PickPush4(slice_to_u32_le(bytes))), + 32 => { + let mut x = [0u8; 32]; + x.copy_from_slice(bytes); + ret.push(Token::PickPush32(x)); + } + // Other pushes should be err. This will change + // once we add script introspection + _ => return Err(Error::InvalidPush(bytes.to_owned())), } - _ => { - match script::read_scriptint(bytes) { - Ok(v) if v >= 0 => { - // check minimality of the number - if &script::Builder::new().push_int(v).into_script()[1..] != bytes { - return Err(Error::InvalidPush(bytes.to_owned())); + } else { + // Create the most specific type possible out of the + // Push. When we later encounter CAT, revisit and + // reconvert these to pushes. + // See [process_candidate_push] + match bytes.len() { + 20 => { + let mut x = [0; 20]; + x.copy_from_slice(bytes); + ret.push(Token::Hash20(x)); + } + 32 => { + let mut x = [0; 32]; + x.copy_from_slice(bytes); + ret.push(Token::Hash32(x)); + } + 33 | 65 => { + ret.push(Token::Pubkey( + PublicKey::from_slice(bytes).map_err(Error::BadPubkey)?, + )); + } + _ => { + match script::read_scriptint(bytes) { + Ok(v) if v >= 0 => { + // check minimality of the number + if &script::Builder::new().push_int(v).into_script()[1..] + != bytes + { + return Err(Error::InvalidPush(bytes.to_owned())); + } + ret.push(Token::Num(v as u32)); } - ret.push(Token::Num(v as u32)); + _ => ret.push(Token::Push(bytes.to_owned())), } - Ok(_) => return Err(Error::InvalidPush(bytes.to_owned())), - Err(e) => return Err(Error::Script(e)), } } } diff --git a/src/miniscript/limits.rs b/src/miniscript/limits.rs index 64862a92..0878f5c9 100644 --- a/src/miniscript/limits.rs +++ b/src/miniscript/limits.rs @@ -40,3 +40,6 @@ pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; /// Maximum script sig size allowed by standardness rules // https://github.com/bitcoin/bitcoin/blob/42b66a6b814bca130a9ccf0a3f747cf33d628232/src/policy/policy.cpp#L102 pub const MAX_SCRIPTSIG_SIZE: usize = 1650; +/// Maximum Initial witness size allowed +/// https://github.com/bitcoin/bitcoin/blob/283a73d7eaea2907a6f7f800f529a0d6db53d7a6/src/policy/policy.h#L42 +pub const MAX_STANDARD_P2WSH_STACK_ITEM_SIZE: usize = 80; diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 1ddc0874..c3e46c05 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -28,7 +28,7 @@ use std::marker::PhantomData; use std::{fmt, str}; use bitcoin; -use bitcoin::blockdata::script; +use elements::script; pub use self::context::{BareCtx, Legacy, Segwitv0}; @@ -431,8 +431,9 @@ mod tests { use std::marker::PhantomData; use {DummyKey, DummyKeyHash, MiniscriptKey, TranslatePk, TranslatePk1}; - use bitcoin::hashes::{hash160, sha256, Hash}; - use bitcoin::{self, secp256k1}; + use bitcoin; + use elements::hashes::{hash160, sha256, Hash}; + use elements::secp256k1; use std::str; use std::str::FromStr; use std::sync::Arc; @@ -584,6 +585,22 @@ mod tests { ms_attributes_test("c:or_i(andor(c:pk_h(fcd35ddacad9f2d5be5e464639441c6065e6955d),pk_h(9652d86bedf43ad264362e6e6eba6eb764508127),pk_h(06afd46bcdfd22ef94ac122aa11f241244a37ecc)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", true, true, true, 17, 5); } + #[test] + fn recursive_key_parsing() { + type MsStr = Miniscript; + assert!(MsStr::from_str("pk(slip77(k))").is_ok()); + assert!(MsStr::from_str("pk(musig(a))").is_ok()); + assert!(MsStr::from_str("pk(musig(a,b))").is_ok()); + assert!(MsStr::from_str("pk(musig(a,musig(b,c,d)))").is_ok()); + assert!(MsStr::from_str("pk(musig(a,musig(b,c,musig(d,e,f,musig(g,h,i)))))").is_ok()); + assert!(MsStr::from_str("pk(musig(musig(a,b),musig(c,d)))").is_ok()); + // should err on slip7 + assert!(MsStr::from_str("pk(slip7(k))").is_err()); + // should err on multi args to pk + assert!(MsStr::from_str("pk(musig(a,b),musig(c,d))").is_err()); + assert!(MsStr::from_str("musig").is_err()); + assert!(MsStr::from_str("slip77").is_err()); + } #[test] fn basic() { let pk = bitcoin::PublicKey::from_str( @@ -660,6 +677,10 @@ mod tests { &ms_str!("tv:1"), "Script(OP_PUSHNUM_1 OP_VERIFY OP_PUSHNUM_1)", ); + roundtrip( + &ms_str!("tv:thresh(1,pk(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", ), + "Script(OP_PUSHBYTES_33 02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e OP_CHECKSIG OP_PUSHNUM_1 OP_EQUALVERIFY OP_PUSHNUM_1)", + ); roundtrip(&ms_str!("0"), "Script(OP_0)"); roundtrip( &ms_str!("andor(0,1,0)"), @@ -874,6 +895,12 @@ mod tests { OP_PUSHNUM_1\ )" ); + + // Thresh bug with equal verify roundtrip + roundtrip( + &ms_str!("tv:thresh(1,pk(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", ), + "Script(OP_PUSHBYTES_33 02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e OP_CHECKSIG OP_PUSHNUM_1 OP_EQUALVERIFY OP_PUSHNUM_1)", + ); } #[test] @@ -898,4 +925,12 @@ mod tests { )) .is_err()); } + + #[test] + fn cov_script_rtt() { + roundtrip( + &ms_str!("ver_eq(4)"), + "Script(OP_PUSHNUM_2 OP_DEPTH OP_SUB OP_PICK OP_PUSHBYTES_4 04000000 OP_EQUAL)", + ); + } } diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 66d366e0..9e13c15e 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -22,12 +22,18 @@ use std::collections::HashMap; use std::sync::Arc; use std::{cmp, i64, mem}; -use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; -use bitcoin::{self, secp256k1}; +use bitcoin; +use elements::{self, secp256k1}; +use elements::{confidential, OutPoint, Script}; +use elements::{ + encode::serialize, + hashes::{hash160, ripemd160, sha256, sha256d}, +}; use {MiniscriptKey, ToPublicKey}; use miniscript::limits::{ - HEIGHT_TIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG, + HEIGHT_TIME_THRESHOLD, MAX_SCRIPT_ELEMENT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEM_SIZE, + SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG, }; use util::witness_size; use Error; @@ -36,16 +42,16 @@ use ScriptContext; use Terminal; /// Type alias for a signature/hashtype pair -pub type BitcoinSig = (secp256k1::Signature, bitcoin::SigHashType); +pub type ElementsSig = (secp256k1::Signature, elements::SigHashType); /// Type alias for 32 byte Preimage. pub type Preimage32 = [u8; 32]; -/// Helper function to create BitcoinSig from Rawsig +/// Helper function to create ElementsSig from Rawsig /// Useful for downstream when implementing Satisfier. /// Returns underlying secp if the Signature is not of correct format -pub fn bitcoinsig_from_rawsig(rawsig: &[u8]) -> Result { +pub fn bitcoinsig_from_rawsig(rawsig: &[u8]) -> Result { let (flag, sig) = rawsig.split_last().unwrap(); - let flag = bitcoin::SigHashType::from_u32(*flag as u32); + let flag = elements::SigHashType::from_u32(*flag as u32); let sig = secp256k1::Signature::from_der(sig)?; Ok((sig, flag)) } @@ -55,7 +61,7 @@ pub fn bitcoinsig_from_rawsig(rawsig: &[u8]) -> Result { /// have data for. pub trait Satisfier { /// Given a public key, look up a signature with that key - fn lookup_sig(&self, _: &Pk) -> Option { + fn lookup_sig(&self, _: &Pk) -> Option { None } @@ -68,7 +74,7 @@ pub trait Satisfier { /// Even if signatures for public key Hashes are not available, the users /// can use this map to provide pkh -> pk mapping which can be useful /// for dissatisfying pkh. - fn lookup_pkh_sig(&self, _: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + fn lookup_pkh_sig(&self, _: &Pk::Hash) -> Option<(bitcoin::PublicKey, ElementsSig)> { None } @@ -101,6 +107,62 @@ pub trait Satisfier { fn check_after(&self, _: u32) -> bool { false } + + /// Introspection Data for Covenant support + /// #1 Version + fn lookup_nversion(&self) -> Option { + None + } + + /// Item 2: hashprevouts + fn lookup_hashprevouts(&self) -> Option { + None + } + + /// Item 3: hashsequence + fn lookup_hashsequence(&self) -> Option { + None + } + + /// ELEMENTS EXTRA: Item 3b: hashsequence + fn lookup_hashissuances(&self) -> Option { + None + } + + /// Item 4: outpoint + fn lookup_outpoint(&self) -> Option { + None + } + + /// Item 5: scriptcode + fn lookup_scriptcode(&self) -> Option<&Script> { + None + } + + /// Item 6: value + fn lookup_value(&self) -> Option { + None + } + + /// Item 7: sequence + fn lookup_nsequence(&self) -> Option { + None + } + + /// Item 8: hashoutputs + fn lookup_outputs(&self) -> Option<&[elements::TxOut]> { + None + } + + /// Item 9: nlocktime + fn lookup_nlocktime(&self) -> Option { + None + } + + /// Item 10: sighash type as u32 + fn lookup_sighashu32(&self) -> Option { + None + } } // Allow use of `()` as a "no conditions available" satisfier @@ -146,17 +208,17 @@ impl Satisfier for After { } } -impl Satisfier for HashMap { - fn lookup_sig(&self, key: &Pk) -> Option { +impl Satisfier for HashMap { + fn lookup_sig(&self, key: &Pk) -> Option { self.get(key).map(|x| *x) } } -impl Satisfier for HashMap +impl Satisfier for HashMap where Pk: MiniscriptKey + ToPublicKey, { - fn lookup_sig(&self, key: &Pk) -> Option { + fn lookup_sig(&self, key: &Pk) -> Option { self.get(&key.to_pubkeyhash()).map(|x| x.1) } @@ -164,14 +226,14 @@ where self.get(pk_hash).map(|x| x.0.clone()) } - fn lookup_pkh_sig(&self, pk_hash: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + fn lookup_pkh_sig(&self, pk_hash: &Pk::Hash) -> Option<(bitcoin::PublicKey, ElementsSig)> { self.get(pk_hash) .map(|&(ref pk, sig)| (pk.to_public_key(), sig)) } } impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &'a S { - fn lookup_sig(&self, p: &Pk) -> Option { + fn lookup_sig(&self, p: &Pk) -> Option { (**self).lookup_sig(p) } @@ -179,7 +241,7 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_pkh_pk(pkh) } - fn lookup_pkh_sig(&self, pkh: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + fn lookup_pkh_sig(&self, pkh: &Pk::Hash) -> Option<(bitcoin::PublicKey, ElementsSig)> { (**self).lookup_pkh_sig(pkh) } @@ -206,10 +268,54 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' fn check_after(&self, t: u32) -> bool { (**self).check_after(t) } + + fn lookup_nversion(&self) -> Option { + (**self).lookup_nversion() + } + + fn lookup_hashprevouts(&self) -> Option { + (**self).lookup_hashprevouts() + } + + fn lookup_hashsequence(&self) -> Option { + (**self).lookup_hashsequence() + } + + fn lookup_hashissuances(&self) -> Option { + (**self).lookup_hashissuances() + } + + fn lookup_outpoint(&self) -> Option { + (**self).lookup_outpoint() + } + + fn lookup_scriptcode(&self) -> Option<&Script> { + (**self).lookup_scriptcode() + } + + fn lookup_value(&self) -> Option { + (**self).lookup_value() + } + + fn lookup_nsequence(&self) -> Option { + (**self).lookup_nsequence() + } + + fn lookup_outputs(&self) -> Option<&[elements::TxOut]> { + (**self).lookup_outputs() + } + + fn lookup_nlocktime(&self) -> Option { + (**self).lookup_nlocktime() + } + + fn lookup_sighashu32(&self) -> Option { + (**self).lookup_sighashu32() + } } impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &'a mut S { - fn lookup_sig(&self, p: &Pk) -> Option { + fn lookup_sig(&self, p: &Pk) -> Option { (**self).lookup_sig(p) } @@ -217,7 +323,7 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_pkh_pk(pkh) } - fn lookup_pkh_sig(&self, pkh: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + fn lookup_pkh_sig(&self, pkh: &Pk::Hash) -> Option<(bitcoin::PublicKey, ElementsSig)> { (**self).lookup_pkh_sig(pkh) } @@ -244,6 +350,50 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' fn check_after(&self, t: u32) -> bool { (**self).check_after(t) } + + fn lookup_nversion(&self) -> Option { + (**self).lookup_nversion() + } + + fn lookup_hashprevouts(&self) -> Option { + (**self).lookup_hashprevouts() + } + + fn lookup_hashsequence(&self) -> Option { + (**self).lookup_hashsequence() + } + + fn lookup_hashissuances(&self) -> Option { + (**self).lookup_hashissuances() + } + + fn lookup_outpoint(&self) -> Option { + (**self).lookup_outpoint() + } + + fn lookup_scriptcode(&self) -> Option<&Script> { + (**self).lookup_scriptcode() + } + + fn lookup_value(&self) -> Option { + (**self).lookup_value() + } + + fn lookup_nsequence(&self) -> Option { + (**self).lookup_nsequence() + } + + fn lookup_outputs(&self) -> Option<&[elements::TxOut]> { + (**self).lookup_outputs() + } + + fn lookup_nlocktime(&self) -> Option { + (**self).lookup_nlocktime() + } + + fn lookup_sighashu32(&self) -> Option { + (**self).lookup_sighashu32() + } } macro_rules! impl_tuple_satisfier { @@ -254,7 +404,7 @@ macro_rules! impl_tuple_satisfier { Pk: MiniscriptKey + ToPublicKey, $($ty: Satisfier< Pk>,)* { - fn lookup_sig(&self, key: &Pk) -> Option { + fn lookup_sig(&self, key: &Pk) -> Option { let &($(ref $ty,)*) = self; $( if let Some(result) = $ty.lookup_sig(key) { @@ -267,7 +417,7 @@ macro_rules! impl_tuple_satisfier { fn lookup_pkh_sig( &self, key_hash: &Pk::Hash, - ) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + ) -> Option<(bitcoin::PublicKey, ElementsSig)> { let &($(ref $ty,)*) = self; $( if let Some(result) = $ty.lookup_pkh_sig(key_hash) { @@ -349,6 +499,116 @@ macro_rules! impl_tuple_satisfier { )* false } + + fn lookup_nversion(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_nversion() { + return Some(result); + } + )* + None + } + + fn lookup_hashprevouts(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_hashprevouts() { + return Some(result); + } + )* + None + } + + fn lookup_hashsequence(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_hashsequence() { + return Some(result); + } + )* + None + } + + fn lookup_hashissuances(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_hashissuances() { + return Some(result); + } + )* + None + } + + fn lookup_outpoint(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_outpoint() { + return Some(result); + } + )* + None + } + + fn lookup_scriptcode(&self) -> Option<&Script> { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_scriptcode() { + return Some(result); + } + )* + None + } + + fn lookup_value(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_value() { + return Some(result); + } + )* + None + } + + fn lookup_nsequence(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_nsequence() { + return Some(result); + } + )* + None + } + + fn lookup_outputs(&self) -> Option<&[elements::TxOut]> { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_outputs() { + return Some(result); + } + )* + None + } + + fn lookup_nlocktime(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_nlocktime() { + return Some(result); + } + )* + None + } + + fn lookup_sighashu32(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_sighashu32() { + return Some(result); + } + )* + None + } } } } @@ -470,6 +730,109 @@ impl Witness { None => Witness::Unavailable, } } + + /// Turn a version into (part of) a satisfaction + fn ver_eq_satisfy>(sat: S, n: u32) -> Self { + match sat.lookup_nversion() { + Some(k) => { + if k == n { + Witness::empty() + } else { + Witness::Impossible + } + } + // Note the unavailable instead of impossible because we don't know + // the version + None => Witness::Unavailable, + } + } + + /// Turn a output prefix into (part of) a satisfaction + fn output_pref_satisfy>(sat: S, pref: &[u8]) -> Self { + match sat.lookup_outputs() { + Some(outs) => { + let mut ser_out = Vec::new(); + let num_wit_elems = + MAX_SCRIPT_ELEMENT_SIZE / MAX_STANDARD_P2WSH_STACK_ITEM_SIZE + 1; + let mut witness = Vec::with_capacity(num_wit_elems); + for out in outs { + ser_out.extend(serialize(out)); + } + // We need less than 520 bytes of serialized hashoutputs + // in order to compute hash256 inside script + if ser_out.len() > MAX_SCRIPT_ELEMENT_SIZE { + return Witness::Impossible; + } + if ser_out.starts_with(pref) { + let mut iter = ser_out.into_iter().skip(pref.len()).peekable(); + + while iter.peek().is_some() { + let chk_size = MAX_STANDARD_P2WSH_STACK_ITEM_SIZE; + let chunk: Vec = iter.by_ref().take(chk_size).collect(); + witness.push(chunk); + } + // Append empty elems to make for extra cats + // in the spk + while witness.len() < num_wit_elems { + witness.push(vec![]); + } + Witness::Stack(witness) + } else { + Witness::Impossible + } + } + // Note the unavailable instead of impossible because we don't know + // the hashoutputs yet + None => Witness::Unavailable, + } + } + + /// Dissatisfy ver fragment + fn ver_eq_dissatisfy>(sat: S, n: u32) -> Self { + if let Some(k) = sat.lookup_nversion() { + if k == n { + Witness::Impossible + } else { + Witness::empty() + } + } else { + Witness::empty() + } + } + + /// Turn a output prefix into (part of) a satisfaction + fn output_pref_dissatisfy>(sat: S, pref: &[u8]) -> Self { + match sat.lookup_outputs() { + Some(outs) => { + let mut ser_out = Vec::new(); + for out in outs { + ser_out.extend(serialize(out)); + } + let num_wit_elems = MAX_SCRIPT_ELEMENT_SIZE / MAX_STANDARD_P2WSH_STACK_ITEM_SIZE; + let mut witness = Vec::with_capacity(num_wit_elems); + if pref != ser_out.as_slice() { + while witness.len() < num_wit_elems { + witness.push(vec![]); + } + Witness::Stack(witness) + } else if pref.len() != MAX_SCRIPT_ELEMENT_SIZE { + // Case when prefix == ser_out and it is possible + // to add more witness + witness.push(vec![1]); + while witness.len() < num_wit_elems { + witness.push(vec![]); + } + Witness::Stack(witness) + } else { + // case when pref == ser_out and len of both is 520 + Witness::Impossible + } + } + // Note the unavailable instead of impossible because we don't know + // the hashoutputs yet + None => Witness::Unavailable, + } + } } impl Witness { @@ -811,6 +1174,14 @@ impl Satisfaction { stack: Witness::Impossible, has_sig: false, }, + Terminal::Version(n) => Satisfaction { + stack: Witness::ver_eq_satisfy(stfr, n), + has_sig: false, + }, + Terminal::OutputsPref(ref pref) => Satisfaction { + stack: Witness::output_pref_satisfy(stfr, pref), + has_sig: false, + }, Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub) @@ -997,6 +1368,14 @@ impl Satisfaction { stack: Witness::hash_dissatisfaction(), has_sig: false, }, + Terminal::Version(n) => Satisfaction { + stack: Witness::ver_eq_dissatisfy(stfr, n), + has_sig: false, + }, + Terminal::OutputsPref(ref pref) => Satisfaction { + stack: Witness::output_pref_dissatisfy(stfr, pref), + has_sig: false, + }, Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub) diff --git a/src/miniscript/types/correctness.rs b/src/miniscript/types/correctness.rs index 6cde8d64..8681eeba 100644 --- a/src/miniscript/types/correctness.rs +++ b/src/miniscript/types/correctness.rs @@ -191,6 +191,24 @@ impl Property for Correctness { } } + fn from_item_eq() -> Self { + Correctness { + base: Base::B, + input: Input::Zero, + dissatisfiable: true, + unit: true, + } + } + + fn from_item_pref(_pref: &[u8]) -> Self { + Correctness { + base: Base::B, + input: Input::Any, // 7 outputs + dissatisfiable: true, // Any 7 elements that don't cat + unit: true, + } + } + fn cast_alt(self) -> Result { Ok(Correctness { base: match self.base { diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index a3cf48f4..677f640b 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -1,7 +1,9 @@ //! Other miscellaneous type properties which are not related to //! correctness or malleability. -use miniscript::limits::{HEIGHT_TIME_THRESHOLD, SEQUENCE_LOCKTIME_TYPE_FLAG}; +use miniscript::limits::{ + HEIGHT_TIME_THRESHOLD, MAX_SCRIPT_ELEMENT_SIZE, SEQUENCE_LOCKTIME_TYPE_FLAG, +}; use super::{Error, ErrorKind, Property, ScriptContext}; use script_num_size; @@ -318,6 +320,46 @@ impl Property for ExtData { } } + fn from_item_eq() -> Self { + unreachable!() + } + + fn from_item_pref(_pref: &[u8]) -> Self { + unreachable!() + } + + fn from_ver_eq() -> Self { + ExtData { + pk_cost: 4 + 1 + 1 + 4, // 4 opcodes, 1 push, (5) 4 byte push + has_free_verify: true, + ops_count_static: 4, + ops_count_sat: Some(4), + ops_count_nsat: Some(4), + stack_elem_count_sat: Some(0), + stack_elem_count_dissat: Some(0), + max_sat_size: Some((0, 0)), + max_dissat_size: Some((0, 0)), + timelock_info: TimeLockInfo::default(), + } + } + + fn from_output_pref(pref: &[u8]) -> Self { + // Assume txouts fill out all the 520 bytes + let max_wit_sz = MAX_SCRIPT_ELEMENT_SIZE - pref.len(); + ExtData { + pk_cost: 8 + pref.len() + 1 + 6, // See script_size() in astelem.rs + has_free_verify: true, + ops_count_static: 13, + ops_count_sat: Some(13), + ops_count_nsat: Some(13), + stack_elem_count_sat: Some(7), + stack_elem_count_dissat: Some(7), + max_sat_size: Some((max_wit_sz, max_wit_sz)), + max_dissat_size: Some((0, 0)), // all empty should dissatisfy + timelock_info: TimeLockInfo::default(), + } + } + fn cast_alt(self) -> Result { Ok(ExtData { pk_cost: self.pk_cost + 2, @@ -873,6 +915,8 @@ impl Property for ExtData { Terminal::Hash256(..) => Ok(Self::from_hash256()), Terminal::Ripemd160(..) => Ok(Self::from_ripemd160()), Terminal::Hash160(..) => Ok(Self::from_hash160()), + Terminal::Version(..) => Ok(Self::from_ver_eq()), + Terminal::OutputsPref(ref pref) => Ok(Self::from_output_pref(pref)), Terminal::Alt(ref sub) => wrap_err(Self::cast_alt(sub.ext.clone())), Terminal::Swap(ref sub) => wrap_err(Self::cast_swap(sub.ext.clone())), Terminal::Check(ref sub) => wrap_err(Self::cast_check(sub.ext.clone())), diff --git a/src/miniscript/types/malleability.rs b/src/miniscript/types/malleability.rs index ebef4c11..f0cf22c9 100644 --- a/src/miniscript/types/malleability.rs +++ b/src/miniscript/types/malleability.rs @@ -138,6 +138,22 @@ impl Property for Malleability { } } + fn from_item_eq() -> Self { + Malleability { + dissat: Dissat::Unknown, + safe: false, + non_malleable: true, + } + } + + fn from_item_pref(_pref: &[u8]) -> Self { + Malleability { + dissat: Dissat::Unknown, + safe: false, + non_malleable: true, + } + } + fn cast_alt(self) -> Result { Ok(self) } diff --git a/src/miniscript/types/mod.rs b/src/miniscript/types/mod.rs index e6efe0ca..e55fbb2f 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -311,6 +311,24 @@ pub trait Property: Sized { Self::from_time(t) } + /// Type property for a general global sighash item lookup + fn from_item_eq() -> Self; + + /// Type property for the ver_eq fragment + fn from_ver_eq() -> Self { + Self::from_item_eq() + } + + /// Type property for a general global sighash item lookup + /// with prefix that must be concatted with user input + /// to obtain a specified hash value + fn from_item_pref(_pref: &[u8]) -> Self; + + /// Type property for hashoutput_pref + fn from_output_pref(pref: &[u8]) -> Self { + Self::from_item_pref(pref) + } + /// Cast using the `Alt` wrapper fn cast_alt(self) -> Result; @@ -448,6 +466,8 @@ pub trait Property: Sized { Terminal::Hash256(..) => Ok(Self::from_hash256()), Terminal::Ripemd160(..) => Ok(Self::from_ripemd160()), Terminal::Hash160(..) => Ok(Self::from_hash160()), + Terminal::Version(..) => Ok(Self::from_ver_eq()), + Terminal::OutputsPref(ref pref) => Ok(Self::from_output_pref(pref)), Terminal::Alt(ref sub) => wrap_err(Self::cast_alt(get_child(&sub.node, 0)?)), Terminal::Swap(ref sub) => wrap_err(Self::cast_swap(get_child(&sub.node, 0)?)), Terminal::Check(ref sub) => wrap_err(Self::cast_check(get_child(&sub.node, 0)?)), @@ -628,6 +648,20 @@ impl Property for Type { } } + fn from_item_eq() -> Self { + Type { + corr: Property::from_item_eq(), + mall: Property::from_item_eq(), + } + } + + fn from_item_pref(pref: &[u8]) -> Self { + Type { + corr: Property::from_item_pref(pref), + mall: Property::from_item_pref(pref), + } + } + fn cast_alt(self) -> Result { Ok(Type { corr: Property::cast_alt(self.corr)?, @@ -825,6 +859,8 @@ impl Property for Type { Terminal::Hash256(..) => Ok(Self::from_hash256()), Terminal::Ripemd160(..) => Ok(Self::from_ripemd160()), Terminal::Hash160(..) => Ok(Self::from_hash160()), + Terminal::Version(..) => Ok(Self::from_item_eq()), + Terminal::OutputsPref(ref pref) => Ok(Self::from_item_pref(pref)), Terminal::Alt(ref sub) => wrap_err(Self::cast_alt(sub.ty.clone())), Terminal::Swap(ref sub) => wrap_err(Self::cast_swap(sub.ty.clone())), Terminal::Check(ref sub) => wrap_err(Self::cast_check(sub.ty.clone())), diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index e4257f48..769bae37 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -22,6 +22,7 @@ use std::convert::From; use std::marker::PhantomData; use std::{cmp, error, f64, fmt, mem}; +use miniscript::limits::MAX_SCRIPT_ELEMENT_SIZE; use miniscript::types::{self, ErrorKind, ExtData, Property, Type}; use miniscript::ScriptContext; use policy::Concrete; @@ -206,6 +207,28 @@ impl Property for CompilerExtData { } } + fn from_item_eq() -> Self { + // both sat and dissat costs are zero + // because witness is already calculated in + // stack + CompilerExtData { + branch_prob: None, + sat_cost: 0.0, + dissat_cost: Some(0.0), + } + } + + fn from_item_pref(pref: &[u8]) -> Self { + // both sat and dissat costs are zero + // because witness is already calculated in + // stack + CompilerExtData { + branch_prob: None, + sat_cost: (MAX_SCRIPT_ELEMENT_SIZE - pref.len()) as f64, + dissat_cost: Some(0.0), + } + } + fn cast_alt(self) -> Result { Ok(CompilerExtData { branch_prob: None, @@ -1159,8 +1182,10 @@ where #[cfg(test)] mod tests { use super::*; - use bitcoin::blockdata::{opcodes, script}; - use bitcoin::{self, hashes, secp256k1, SigHashType}; + use bitcoin; + use elements::SigHashType; + use elements::{hashes, secp256k1}; + use elements::{opcodes, script}; use std::collections::HashMap; use std::str::FromStr; use std::string::String; @@ -1168,7 +1193,7 @@ mod tests { use miniscript::{satisfy, Legacy, Segwitv0}; use policy::Liftable; use script_num_size; - use BitcoinSig; + use ElementsSig; type SPolicy = Concrete; type BPolicy = Concrete; @@ -1367,10 +1392,10 @@ mod tests { let mut sigvec = sig.serialize_der().to_vec(); sigvec.push(1); // sighash all - let no_sat = HashMap::::new(); - let mut left_sat = HashMap::::new(); + let no_sat = HashMap::::new(); + let mut left_sat = HashMap::::new(); let mut right_sat = - HashMap::::new(); + HashMap::::new(); for i in 0..5 { left_sat.insert(keys[i], bitcoinsig); diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index fe102283..38d03a40 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -15,8 +15,8 @@ //! Concrete Policies //! -use bitcoin::hashes::hex::FromHex; -use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; +use elements::hashes::hex::FromHex; +use elements::hashes::{hash160, ripemd160, sha256, sha256d}; use std::collections::HashSet; use std::{error, fmt, str}; diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 6bd01bd2..9c1f237a 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -28,10 +28,14 @@ pub mod compiler; pub mod concrete; pub mod semantic; +use BtcPolicy; + use descriptor::Descriptor; use miniscript::{Miniscript, ScriptContext}; use Terminal; +use descriptor::CovError; + pub use self::concrete::Policy as Concrete; /// Semantic policies are "abstract" policies elsewhere; but we /// avoid this word because it is a reserved keyword in Rust @@ -133,6 +137,7 @@ impl Liftable for Terminal { Terminal::Hash160(h) => Semantic::Hash160(h), Terminal::True => Semantic::Trivial, Terminal::False => Semantic::Unsatisfiable, + Terminal::Version(_) | Terminal::OutputsPref(_) => return Err(CovError::CovenantLift)?, Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub) @@ -181,6 +186,7 @@ impl Liftable for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.lift(), Descriptor::Wsh(ref wsh) => wsh.lift(), Descriptor::Sh(ref sh) => sh.lift(), + Descriptor::Cov(ref _cov) => Err(Error::CovError(CovError::CovenantLift)), } } } @@ -225,6 +231,28 @@ impl Liftable for Concrete { } } +// Implement lifting from bitcoin policy to elements one +impl Liftable for BtcPolicy { + fn lift(&self) -> Result, Error> { + match *self { + BtcPolicy::Unsatisfiable => Ok(Semantic::Unsatisfiable), + BtcPolicy::Trivial => Ok(Semantic::Trivial), + BtcPolicy::KeyHash(ref pkh) => Ok(Semantic::KeyHash(pkh.clone())), + BtcPolicy::Sha256(ref h) => Ok(Semantic::Sha256(h.clone())), + BtcPolicy::Hash256(ref h) => Ok(Semantic::Hash256(h.clone())), + BtcPolicy::Ripemd160(ref h) => Ok(Semantic::Ripemd160(h.clone())), + BtcPolicy::Hash160(ref h) => Ok(Semantic::Hash160(h.clone())), + BtcPolicy::After(n) => Ok(Semantic::After(n)), + BtcPolicy::Older(n) => Ok(Semantic::Older(n)), + BtcPolicy::Threshold(k, ref subs) => { + let new_subs: Result>, _> = + subs.iter().map(|sub| Liftable::lift(sub)).collect(); + Ok(Semantic::Threshold(k, new_subs?)) + } + } + } +} + #[cfg(test)] mod tests { use super::{Concrete, Semantic}; diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index baaf1c3b..0844586b 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -17,8 +17,8 @@ use std::str::FromStr; use std::{fmt, str}; -use bitcoin::hashes::hex::FromHex; -use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; +use elements::hashes::hex::FromHex; +use elements::hashes::{hash160, ripemd160, sha256, sha256d}; use super::concrete::PolicyError; use errstr; @@ -576,7 +576,7 @@ mod tests { fn semantic_analysis() { let policy = StringPolicy::from_str("pkh()").unwrap(); assert_eq!(policy, Policy::KeyHash("".to_owned())); - assert_eq!(policy.relative_timelocks(), vec![]); + assert_eq!(policy.relative_timelocks().len(), 0); assert_eq!(policy.clone().at_age(0), policy.clone()); assert_eq!(policy.clone().at_age(10000), policy.clone()); assert_eq!(policy.n_keys(), 1); diff --git a/src/util.rs b/src/util.rs index 05b57947..969ffe8f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,8 +1,7 @@ -use bitcoin; -use bitcoin::blockdata::script; -use bitcoin::Script; +use elements::Script; +use elements::{self, script}; pub(crate) fn varint_len(n: usize) -> usize { - bitcoin::VarInt(n as u64).len() + elements::VarInt(n as u64).len() } // Helper function to calculate witness size @@ -21,3 +20,60 @@ pub(crate) fn witness_to_scriptsig(witness: &[Vec]) -> Script { } b.into_script() } + +macro_rules! define_slice_to_le { + ($name: ident, $type: ty) => { + #[inline] + pub(crate) fn $name(slice: &[u8]) -> $type { + assert_eq!(slice.len(), ::std::mem::size_of::<$type>()); + let mut res = 0; + for i in 0..::std::mem::size_of::<$type>() { + res |= (slice[i] as $type) << i * 8; + } + res + } + }; +} + +define_slice_to_le!(slice_to_u32_le, u32); + +/// Helper to encode an integer in script format +/// Copied from rust-bitcoin +pub(crate) fn build_scriptint(n: i64) -> Vec { + if n == 0 { + return vec![]; + } + + let neg = n < 0; + + let mut abs = if neg { -n } else { n } as usize; + let mut v = vec![]; + while abs > 0xFF { + v.push((abs & 0xFF) as u8); + abs >>= 8; + } + // If the number's value causes the sign bit to be set, we need an extra + // byte to get the correct value and correct sign bit + if abs & 0x80 != 0 { + v.push(abs as u8); + v.push(if neg { 0x80u8 } else { 0u8 }); + } + // Otherwise we just set the sign bit ourselves + else { + abs |= if neg { 0x80 } else { 0 }; + v.push(abs as u8); + } + v +} +/// Get the count of non-push opcodes +// Export to upstream +#[cfg(test)] +pub(crate) fn count_non_push_opcodes(script: &Script) -> Result { + let mut count = 0; + for ins in script.instructions().into_iter() { + if let script::Instruction::Op(..) = ins? { + count += 1; + } + } + Ok(count) +}