Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
000000207e3dba98460e4136659f0fccf3e59338dfe53ed5f094fb0bb94d771c48341854d875900105c87e5dd46c740cb1129c06f8f4007e868f61b25e37cffa946c718d8742805b01000000015100030200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000000009b64001976a914608c0ea8194a8ceb57f0196f44a6b48a54fc065988ac01230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201000000000000000000266a24aa21a9ed8f8a98e5623643b24167266c2648ead4a50d18b0491c6f34e11398aaee0ca6e8000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000020000000001eb04b68e9a26d116046c76e8ff47332fb71dda90ff4bef5370f25226d3bc09fc0000000000feffffff0201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000002540bd71c001976a91448633e2c0ee9495dd3f9c43732c47f4702a362c888ac01230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000000000ce4000000000000020000000101f23ceddac67cfbbc997199daa651384d0746fb2a5482b8c8629ba8df4b788f75000000006b483045022100e0feb3e2f292000d67e24b821d87c9532230dac1de428d6a0068c9f416583abf02200e76f072788dd411b2327267cd91c6b1659809598cd4fae35be475efe1e4bbad01210201e15c23c021652d07c1557b607ea0379fca0462aca840d6c33c4d4927524547feffffff030b60424a423335923c15ae387d95d4f80d944722020bfa55b9f0a0e67579e3c13c081c4f215239c77456d121eb73bd9914a9a6398fe369b4eb8f88a5f78e257fcaa303301ee46349950886ae115c9556607fcda9381c2f72368f4b5286488c62aa0b081976a9148bb6c4d5814d43fefb9e330575e326632136389c88ac0bd436b0539f5497af792d7cb281f09b73d8a5abc198b3ce6239d79e68893e5e5d0923899fd35071ba8a209d85b556d5747b6c35539c3b2f8631a27c0d477a1f45a603d1d350b8cbf900f7666da66541bf6252fc4c162141ad49c670884c93c57db6ba1976a9148c7ab6e0fca387d03643d4846f708bf39d47c1e988ac01230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000000008e800000000000000000000043010001dc65ae13f76fde4a7172e0fb380b1a5cc8dc88eaa0659e638a25eac8ae30d79bf93eb7e487eeee323e4ac8e3a2fe6523bdeba6acce32b9b085f2286174c04655fd6c0a6020000000000000000178ad016b3e5d8165423e56d8b37e3eaee96009b2f970043ccf65d61b5c3c1e1ef343e0c479bdba442717dc861c9591566010240b9d4607efb9252a5fcef05edf640e0bb6b606729246ad07baa49d0d3b52042c65a03ca737744e45b2d2d6d177c36569ae9d6eb4437305b169bbc59f85cabff3bc49a2d6d08c177cce3121a509d3c47961bd22e35c932b79d4ec5ccaf913fac04034bfebdadbc4ff3127af96344b02ee6b967bb08326cbe6a4e1c924485e64a8c0fdf70b98c99f38acaa15aa0adb2b5b7335ed5502443891bcd657310347cbd928f40f38f1dec087a2b947c9cf7d304798f77bbc4a2c843796b2d49acce91de4e88a0a9c261277df28ffc3320d7f7d64790f592ddded48a1068ef88271395fa5606389ef90856ddd6bd6710a8d27e0147983b5dde2a7efae44e83ad02a3c3da04be43d5f2c05c205f1e17b48554c2177670f46dbb6600bd2e6c75dd5ea2e1072c5f22483dcf05d8124e3f9063a5ddb179a29c23a2d15d6e89f2192f03dae5938f66fcdcff000c5a96ffd2920f23881880af72153c96a56dd80c218bb48b44a18e54a8050ff32c869c1264ee574cdb4002f86e0779c724d11dc4a768dbec1bd22054886f1fdf2e7347e4c247b829159d1375f881c6ce0a5c4da8534000e7fec3a980afb1edc99b725c29de80f260dcf144c873bf589ae1812ef6cb05f2234f9c66c23e874a0d5d0dc52f2209e015bbcf74ee449a397f6b0318c915b7e58dea5904abbe35285e90ccf548ad1f3f52f60c3b19b3cd67644d633e68aef42d8ef1782f22a8edd0620f55f29070720ca7a078ac83e87b9ebd2783ecad17dd854ef1bbd319f1a6d3a1e4931f9097422f5a3c4af037b99e06c7610ee61102c6eea763af108e9a16b93b2dc0891658d5c6a197df6aae9b306b2c895d21c79cb6cb6dd85b4018b0a9fe7468336e3907eb4adcaf930cacc97e8e951d2d6b25744a4143679bad1f31b210c9a2ed54b80d8f5d7dc1f1c985681534c1926920cd683d95dca7e8ea285f9906d2e89cd8bfa76a98e38ee4b5152522d55f79610fe8d5278fe6ed5866b5da4dcf330ea84307c34f30e1a66eb1934dafebb0074fc27c2ff73d8c0bae8416cc87bf611f81119aba9e2a911beaf3ac9507e621fc1ed1cf15dfb31408cf55e2bfdd2880db2d3489a336d6f8348347648d882f9f376331e469e809115c6cc82468f363c910673e9ded172ded90a369e1cdd135676f623e11a1531ed221177812b1ef0c65e5ca92c0df8de7fe664710f3228a226e019c99607fe1395ecd5643e1c7ad8a132bf5131737cb970a7f0dabc00029755bf71b3f47bd69ba39b3ab104c74f04239f4919dca1dfce7c9c41cba9d449073e106ebabe3c313b598ee8b11702ec46e9ee53fb9422f0326371898b8fa4c21a951684c687398e0bebd6f6fd91b829e8666b9a19a4273cfda0f34b8ecb902f7adc6539fb9a0cba6f87a63a957acfb2dfa18973f4a3063668767b2be780311513c63f1814f082176f6a953f2ffaa49ec9b39fecc2eab603be7a969bb4c1dbebf8d39fa90f802d5ea52378b5025a19b64a8c2c2dd6a6133bd8d29730bd5724b5bf50c158b238d1137082937ad91a176aaf91577868db7581b457c917e612b242ce0065ad47e11dcdc1fc6158687142249bcf312497a547b6f43e795af7d4ae8cd022e44e417987e35e83de21e39dcdf86b97bd421e6e61881a432fa2284f20be80e32459443736b875d9036468ceb881589394441e2d10aa10b6c93332951e8ba56f89fac70baf415b4511873c0f3e418ca4fe8954a28f1f7b5f590d34470119f694e2712f184882d90396c8e6aa850eaa3c2ae51990543638c46c59512167a2c5ad593532dc2142ffb6560476e4159213b9ef017ec75310d2e4624a405bb26f7192a485a94890674928c9caa4a5819ca4ddcba8fa71afc1a6baf63f039452c8fe994f8b63d58c876dfddd61a476345eaed4f66bdc0fcfc38d485c6a5b0e27d0fbc50427ff591ba38d63445c01642cfbd7d4c032f2546a6fe80bc3b598362502c552049523fe360c3bcf1cc572feb04386f97d55871dd8cea0393cdd964e724082adc98126e6f2fe1d576be4bf911e9aca70e35538175f8382bbcd614bbecc97c9607ef25da2ff08a6e5b6f76cbe9ccb0e0fdc3528e3e2c3675a5c897d295bb76524ec8a73a70b97909368f44d92f9aceaef0b03f3dafa1faa89fc663a92da3c19b4952463fac0e825e78cf046e266cfb9975af72e9d50d2c2cafee88fe2cecae2b1465fc07b280d83b66062dc9e7a372f81aec8e0bb9e97877814a5a6813c67746e35cd068d45d8664528bd00d5a306a5319e1bea7f38345da92d3a10d91476a26aed6b8441f0f72fbbad5d5e0f8ae5cabc9f4f08e6be7902b5c53632db5264afee7422c87b3237a32d5213ad0eb807b61977d9d90666cbb0c70500526b0eb762c99351796db41166b0aa2f221b5607e0d629fac4e938488245c11557381a4f8addcc49913b11d42481cf8668e37bacbad4a20509e4fe4ccbcee7aea2909a2abe59052f7f28b9340cd92f69729d615b8d3b530941c0b30506498cd4e561a9c82d915266bb7115967bc76c5593c06d094bdf4294b868afc5fa52742d3bdbd5932df599f0e1187c49f0dba8679c771a514cc9da75e03506957800bf470d4a07c4bb8918d6085499bb8ceeaba23c0b465863327e9ab8b6b8cf8b3ca530ca7b02cfadf85437b750f305e8fbc8855c95bee8595a7e9e1f0993a03adbadc68665a18936cc99b6530b4518c0754990d7bfdfdac76f88cfcbcb7b3d9a71ee10cbd3a1bdbc2e50b642c1fef56511962f845bbec6eab727b1d4add335db8d80c4c07e8356ad05adad68b012489fa5bb5d9019a667778ddf7f5edd80f1d3c4abd64397a89e554c8007809336ddc2b2e7d5219c39fdf39aad33b9350f6b18fe3b98c690b9068f36d4b7669530fd216373842fbf70fe9bbe80854b31eed4bd515d6caeb065d6c609846c9bfae1b3fce3db70b5bfb448ec69512e7f25019c789301b77a75f2a0f81c65ec29f41bf96d597a00c310e8ba4b48ac82b5a735c1e83f22394eb2fc9b35d42a35533c938f26290a5860175637982f1733c99be39c44ac4a09187406306bde2fd3d28e4e7bda73719912c338804dea03987757dac4d73def665e11da126f9414f71624a3b753797eb0472bd334094515c4f9fe57fdd8d185f22b4bf82e4b5f6b800870cce19a0c8174dc11ee9f1cb9ffe0ac6f6fff1ebf7c915c7ae20172bb70390e3759912e0e0a4e83a0a2d2318f4386314a89f6438ccb331f89377ff7947fe4b24f788aef85c1656ca87ee41c959f1b09bde09f20c2a51ac481646b28e9b0fc2ff49cfe8cf28577bf5bf6f261f54f97fcd2875da4210c6dfe685450280b68e378d9a486243cc682ed4ec747c37de1fde848e4a8f70498d22e40c462c469c884cd67330e77b694e759232313f31a1624e0e1960f23ddae47b68ff553d0de0910c8abe2e8e5fb063aa744ff77465fc731c7af79a84dcaa9b3f741a46dd3c932877d49242c6d883e14392b8c4530986605812b636a73590ef437f27e40d1af37ed1cbd68fb4e9ca5b0b41e5daee0142c1bf59c9d71f6c19b25e6148dfbb9fb142107aabe3701e36611a7e0b13ea32d3c5f8a51f63c5f34415baa15f6ca77300eb323241ffe73c5acd97fcb682c21dc8911392979e9cb81be5218acf452b5b93f6681d323b7989fdd10efe6fe9e2ac88d0d76a4cf3ee45e3b5c430100014142c1fc7e8a658eff437594a25cf34d269556d8511918f27fdc7e9d6dd73f0e4790b91f225e9d131e6abb3dbfb66549a9aa57948fbd2f183fcd951b1d2305bffd6c0a602000000000000000016f5cdf9fb6c1b5e98a36befdc2c55bd4fd8793d554b2506f51c909362495e1216ee83cd270ddb0a00785600ba23bd3363f0798e3a7a117990415adec88e61be65170bd587ab4d2ee38edb22a91e5c29afa397dd5a73465c51c6263f5fbde47fa801ce84464acc32589acaafadfe44d6558774b7085612a88f3424b6dca3c6f07217d1cbd5c41bda46a6a492a0119c1de4d25b58c94250bee3fba6b8223777535673a2f4da6af27598030f88144f408120f07ca9c98d5d9edcdf6cdc9073f118fce55e6c9d0be80b5e87992ddaa9c22053b3a00d42bdedc9768de25c0b37a5c4fb4e86710b33cebed5588d88adde607f6bca14f0279ce35126d403ffa50f288c87f528c19749ed43bd846c513fcd92c173fe76d8f2e69770439d3d075cb19b1094a42ee07ae1de197e8c136e2bc688a75a74db24adb0fbb73872dc80074f61c9cce9bd33861bdd921ee3edacab1d6e7cec325c172b6b6e82ada11687e4fc931225074dd1f20a0f9342dbce1fc3fdbf5bb6cb74ab6475e574e9f5f247a2f7e4fcfcc354d4da8c8066e574642c7fccbbb9ef0aa592ecab5366fe87eb8e14cd64aee34578aa48f68f8f4c5372df2c3fc429f5a3e39ef6c034c87f9c52b2ea35e28c7bf3be737c3817efd6569466dc859e8ff8965c5249b6f045934d3d08b0ffd388aec58df8194ac2c4fec2152942d2626595e65664b1fa33b5dae8ee796a840a56d885cbf7ae6483fad05e507ada3f075ebce0d791b626c6dfe93f8492c4dd3b34aafc33d7644c5c8e38bfd8c19194f65be88fcb4538778632e489a626896372fdd2498b16e64daa7d3c5cfac688d6f9cdf3717261b0a1f25be1bdd6be6558ddb826fa04b5f668810a291aea51a6f05ff7c34dcf81c74849a8015bad5e4e416989b10ef01de304775db725fa0b665f4330dc9c540dc29aab144837362a97d6bb0165cb3272338c2d32386cd95ee3e66d876b591a25a6907237523cf908f736d2fdc8e54ea8d9c7562697161d1f72fc4d7b775052415cd0e5ae5bdf6edfab5776b6ff75ce5e1f8f2beea6ec74252b63966cca58abd638279dc5c998a1068079f3e5dcc8a69165c304c3d8c362ccfadab05ad12208a5655ab389eb727e8ed5f86b300331a13be26e2fbabf89fbfd2b98481dd5edb52ed456a0e03a84b6f89761f91ff251412f5cfa286e35fb9f48ef0e044c4742b6e860a08767ecb80548c2f3df3b371cdb40e86dbe118f64e84faf45ecb78d73364e9e31e3412ca2a3fad0a35983370ea9e6264a222edd1fd4aca30e3c169d7ca2d07609262e786ecd019c1417a06b7dfa32a54e0897afdc6492f26611555cbff47dba3b76381f239d597a8f687669333e0b47b53d5bcc4fea1919490bad3c6f0b6a58a50aca7ddeb9745ead454e0a38d9486fb52aefe0dbb92bf7fd6c215078aba3482b11274ec8cddff92c359bbc6d20bd823ad0bbf859cfaadf8e775b3d37b3078319f46c6d2a112cf60a673fee467538c70f1687d97fbe9d9f8a0856061592a4e00b6d10e979e674dd2cd0ba8b853f733877cd508062d5f723d58d215ad69c2be6be742496aef54eb87338622eb36a9bbc5a7a602d280a45e095b1e078dab54479e783a513c722066acaae44ccc15f9560da91ed053ec05c36d82f6809766876c45c4fbeb2321d50f48f7995437d0c5fc365974a571fb0352d28cb1cdbd21d69fab576a2e68d6b881776027bcdb7f01be22b1c847d91f26e680ef6ab2c128a89b59432383d9bd661b0b01432cf8a25319426d38ac2e2114825f59b4250569c798b1094920bb31130728313ff56a6eef2e6c4b275215dce3786d0f9024952b5f572566c53597e7ef4ab1f75743e605a564054d667f48906b5481d924769ef65751e349891d725a2c1bf8b102fea4c25c874d2fc2ce1bfec4b39bea76fbf7a28855725d52b595a4fc96892c3f1f961d46310ebd5221df729c02060035c559baf0fd7efa73a2213ca29642857aeb8ebf7efdf9d2f5c84746b6fc35ab355a8dca56e7dde4831e47ca1be6b62af30cfcf807c384e56ab84ff03bbe786251e6c4b932c9217bf671046217bd0511fdc06aa69050c1480281e4843eb73d80095a2fb8e68a2c0c98c9aea637b99d87ad847a3a76d59ea308c751f9cb4a4fce2989822bd6ba2f901f09df647536dc30730ea3160dd35b8c6dcc9aa815b79ed492a8a299a298ccdf784b9b0211ca877ec1723817c98529acaa4d3727162b5740b0fc9b498dfb2212a3cbf0c63dc4f7663fafad7905643a792862b651e8497b0f0da632b897ecf9ee63f2b20b54fa5eb2f2e424dcce5a075f50b856af266655be3a815fc83ed8027508b2536976982196b160e2219ffdb5c7a56dd3e6b700860c711f4439dbf72973f4f26fe3260ec43a3446fe14444b9787d877e107be610147eec4a3574745e95a1f424aff062f84c559d13b1e6b59e8dc2221515c229f07db8eb39c515a321d8bd07b1bd6c9a79dac6d951c04415553c7a2ce1eb77495c7f89c4d5b4cffd289435b69bc53585095083cc5a1b191781342266e204e1566aca8175e2ae84a8bd711d188b666dfb65a6442776d3e23c1b5192af09ec712537f2157d0ccbc1bb3b3a1969d9705671f16bdc266e615ad2e50a8cbd666f3ee7465cc430c6cd69d30c91e717b12f7094b6f0ef89134d6c1620d28d8f238c181146448b348e4ca2e93c737210350f18fb878fb91b70ecc5689e5b6101ecfc545f6a1c903115b0c6419c91a50fb2dbe2edd362f2815f0c75070974507c34130ac9b29747ff7efbe6e37ee4c62be3ecfedfa817fdf3309163aaff677775b77f0d288c9858cfe59cb0fa18afa591e7d574eaef43c82e79d71542c4177de4e5bd724b18cfd33c68530665728a9d5ef192772094acbf3d885d5146c1634e74754e3fbcb94fa349eac8280cfd7d1f46a0813b57a83bd078b1f7cb5a60a59b59380fe04e1c600c33b33d1add69a9ff1be546f0ec5c0083979fce940b23711f382ac0d011c1103f02cb6082c18e39cf7a9c3bf4c081f905ae7b87951a7880b57e934465ccd634e5a17fd8d8866abfdfebd33b2c3d2c5be58144900c04e9c18de0c80270660e62a3c185277555f89da4c41bd33cec1359f4ed21abdb586e1d97f720a92d16014d7f1822f1836f74c97cb7f7b38e073477c6ab064fde835916c1e624de81f2ad90f6260073c5e1848582860f033630bde225821b39c2572b30c36adf8fdb8317c33df05f6413447f4985d12e9012629df09dc8f43373a6d0db4b0048453a6f1ec662472c77a30d5cf4ac7084f736d0d598c251f2aefc986052fbf12a657885d7140ad36b07c63ab86388a2be12d943747f3f29ef9f2e11e1444cc873df0ed7826eef675389a0d5a0388a8504fe89c4791ea4a572bfd406d5f01418b4f888c9a7a566e32811936bf6950bbf786b86c41c28f2045d31953fcd15f179e7bc00c72870890537921f7deff82270b0e44b88720aa738f60a85567deb7c90b0c2444467621e53e1c079436d31d3d0b34dd237fc281eb9d87175237a9a433142db4bb7f8c4cb6a34e2dc73f074045d216695ce88ef68e18564c935c9cbd902e939655c258de2ab78def8746bffd972083afce3b6881b7147262e1a44e0224689fafa1a3cb823c8da6eb7df091bec0638bf728b7b10aa95f2bce512ec8d3252938d2eb77b44ace7a2f976588032cac5af670f9e5ca25cb0721bc1baec26f9c3a9f41b02fb62997d6cb0a01314845e9d0e78139ea49f2ead8736e0000
2 changes: 2 additions & 0 deletions crates/simplicityhl-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod error;
mod fee_rate_fetcher;
mod runner;
mod scripts;
mod tx_inclusion;

#[cfg(feature = "encoding")]
pub mod encoding {
Expand Down Expand Up @@ -72,6 +73,7 @@ pub use error::EncodingError;

pub use runner::*;
pub use scripts::*;
pub use tx_inclusion::*;

pub use fee_rate_fetcher::*;

Expand Down
180 changes: 180 additions & 0 deletions crates/simplicityhl-core/src/tx_inclusion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//! Transaction inclusion verification using Merkle proofs for Liquid/Elements blocks.
//!
//! This module provides SPV (Simplified Payment Verification) functionality to prove
//! a transaction exists in a block without downloading all transactions.

use simplicityhl::elements::hashes::{Hash, HashEngine};
use simplicityhl::elements::{Block, TxMerkleNode, Txid};

/// Merkle proof: (`transaction_index`, `sibling_hashes`)
pub type MerkleProof = (usize, Vec<TxMerkleNode>);

/// Constructs a Merkle inclusion proof (Merkle branch) for a transaction TXID
/// in a block, using Bitcoin consensus Merkle tree construction rules
/// (pairwise double-SHA256 hashing with odd-hash duplication).
///
/// Liquid inherits the same Merkle tree semantics via the Elements codebase:
/// <https://developer.bitcoin.org/reference/block_chain.html>
///
/// Returns `None` if the transaction is not present in the block.
#[must_use]
pub fn merkle_branch(tx: &Txid, block: &Block) -> Option<MerkleProof> {
if block.txdata.is_empty() {
return None;
}

let tx_index = block.txdata.iter().position(|t| &t.txid() == tx)?;

Some((tx_index, build_merkle_branch(tx_index, block)))
}

/// Verifies a Merkle inclusion proof (Merkle branch) for a transaction TXID
/// against the given Merkle root using Bitcoin consensus Merkle tree rules
/// (pairwise double-SHA256 hashing with left/right ordering).
///
/// Liquid inherits the same Merkle tree semantics via the Elements codebase:
/// <https://developer.bitcoin.org/reference/block_chain.html>
///
/// Returns `true` if the proof commits the transaction to the given root.
#[must_use]
pub fn verify_tx(tx: &Txid, root: &TxMerkleNode, proof: &MerkleProof) -> bool {
root.eq(&compute_merkle_root_from_branch(tx, proof.0, &proof.1))
}

fn build_merkle_branch(tx_index: usize, block: &Block) -> Vec<TxMerkleNode> {
if block.txdata.is_empty() || block.txdata.len() == 1 {
return vec![];
}

let mut branch = vec![];
let mut layer = block
.txdata
.iter()
.map(|tx| TxMerkleNode::from_raw_hash(*tx.txid().as_raw_hash()))
.collect::<Vec<_>>();
let mut index = tx_index;

// Bottom-up traversal: pair nodes, hash parents, collect siblings along path to root
while layer.len() > 1 {
let mut next_layer = vec![];

for i in (0..layer.len()).step_by(2) {
let left = layer[i];
let right = if i + 1 < layer.len() {
layer[i + 1]
} else {
layer[i]
};

let mut eng = TxMerkleNode::engine();
eng.input(left.as_raw_hash().as_byte_array());
eng.input(right.as_raw_hash().as_byte_array());

next_layer.push(TxMerkleNode::from_engine(eng));

if index / 2 == i / 2 {
let sibling = if index.is_multiple_of(2) { right } else { left };
branch.push(sibling);
}
}

index /= 2;
layer = next_layer;
}

branch
}

fn compute_merkle_root_from_branch(
tx: &Txid,
tx_index: usize,
branch: &[TxMerkleNode],
) -> TxMerkleNode {
let mut res = TxMerkleNode::from_raw_hash(*tx.as_raw_hash());
let mut pos = tx_index;

for leaf in branch {
let mut eng = TxMerkleNode::engine();

res = if pos & 1 == 0 {
eng.input(res.as_raw_hash().as_byte_array());
eng.input(leaf.as_raw_hash().as_byte_array());
TxMerkleNode::from_engine(eng)
} else {
eng.input(leaf.as_raw_hash().as_byte_array());
eng.input(res.as_raw_hash().as_byte_array());
TxMerkleNode::from_engine(eng)
};

pos >>= 1;
}

res
}

#[cfg(test)]
mod test {

use super::*;

/// Taken from rust-elements
/// <https://github.com/ElementsProject/rust-elements/blob/master/src/internal_macros.rs>
macro_rules! hex_deserialize(
Comment on lines +120 to +122
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to file an issue, so they expose this method?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that makes sense, since it is only used for tests and would not fit the original intent (all macros in that file are private).

Copy link
Collaborator

@KyrylR KyrylR Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then, how the end user would use the merkle_branch function?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From where would he get a block (what is the deserialization flow?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From where would he get a block (what is the deserialization flow?)

There is an elements::encode::deserialize function, which is already used by the macro itself. The macro does not fit any use case other than deserializing hard-coded hex values.

($e:expr) => ({
use simplicityhl::elements::encode::deserialize;

fn hex_char(c: char) -> u8 {
match c {
'0' => 0,
'1' => 1,
'2' => 2,
'3' => 3,
'4' => 4,
'5' => 5,
'6' => 6,
'7' => 7,
'8' => 8,
'9' => 9,
'a' | 'A' => 10,
'b' | 'B' => 11,
'c' | 'C' => 12,
'd' | 'D' => 13,
'e' | 'E' => 14,
'f' | 'F' => 15,
x => panic!("Invalid character {} in hex string", x),
}
}

let mut ret = Vec::with_capacity($e.len() / 2);
let mut byte = 0;
for (ch, store) in $e.chars().zip([false, true].iter().cycle()) {
byte = (byte << 4) + hex_char(ch);
if *store {
ret.push(byte);
byte = 0;
}
}
deserialize(&ret).expect("deserialize object")
});
);

// Unfortunately, `hex_deserialize` macro aforehead returns error trying deserialize
// blocks from elements-cli regtest, so this block, taken from `elements::Block::block`, is
// the only test case I have found so far.
const BLOCK_STR: &str = include_str!("./assets/test-tx-incl-block.hex");

#[test]
fn test_merkle_branch_construction() {
let block: Block = hex_deserialize!(BLOCK_STR);

assert_eq!(block.txdata.len(), 3);

let tx = block.txdata[1].txid();
let proof = merkle_branch(&tx, &block).expect("Failed to find tx in block");

assert!(
verify_tx(&tx, &block.header.merkle_root, &proof),
"Invalid merkle proof"
);
}
}