Repository: LNP-BP/client_side_validation (language: rust)

Issue:
Modify LNPBP4 commitment arithmetics as per implemented `MultimsgCommitment`
https://github.com/LNP-BP/rust-lnpbp/blob/95e66c5297fa0222b9a29c2e9b4fe82edeae2e49/src/seals/lnpbp4.rs#L99-L100


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 86978f63..5b7023bf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -152,7 +152,7 @@ version = "0.5.0-rc.1"
 dependencies = [
  "amplify",
  "bitcoin_hashes",
- "rand 0.8.3",
+ "rand 0.7.3",
  "serde",
  "serde_with",
  "strict_encoding 1.2.3",
@@ -283,18 +283,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
 dependencies = [
  "cfg-if",
  "libc",
- "wasi 0.9.0+wasi-snapshot-preview1",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.10.2+wasi-snapshot-preview1",
+ "wasi",
 ]
 
 [[package]]
@@ -432,23 +421,11 @@ version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
 dependencies = [
- "getrandom 0.1.16",
+ "getrandom",
  "libc",
- "rand_chacha 0.2.2",
+ "rand_chacha",
  "rand_core 0.5.1",
- "rand_hc 0.2.0",
-]
-
-[[package]]
-name = "rand"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
-dependencies = [
- "libc",
- "rand_chacha 0.3.0",
- "rand_core 0.6.2",
- "rand_hc 0.3.0",
+ "rand_hc",
 ]
 
 [[package]]
@@ -461,16 +438,6 @@ dependencies = [
  "rand_core 0.5.1",
 ]
 
-[[package]]
-name = "rand_chacha"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.6.2",
-]
-
 [[package]]
 name = "rand_core"
 version = "0.3.1"
@@ -492,16 +459,7 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
 dependencies = [
- "getrandom 0.1.16",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
-dependencies = [
- "getrandom 0.2.2",
+ "getrandom",
 ]
 
 [[package]]
@@ -513,15 +471,6 @@ dependencies = [
  "rand_core 0.5.1",
 ]
 
-[[package]]
-name = "rand_hc"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
-dependencies = [
- "rand_core 0.6.2",
-]
-
 [[package]]
 name = "rustc-serialize"
 version = "0.3.24"
@@ -799,12 +748,6 @@ version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
 
-[[package]]
-name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
-
 [[package]]
 name = "winapi"
 version = "0.3.9"
diff --git a/Cargo.toml b/Cargo.toml
index af86ab77..199b2e1d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ name = "client_side_validation"
 version = "0.5.0-rc.1"
 license = "Apache-2.0"
 authors = ["Dr. Maxim Orlovsky <orlovsky@pandoracore.com>"]
-description = "Client-side validation library"
+description = "Client-side validation foundation library"
 repository = "https://github.com/LNP-BP/client_side_validation"
 homepage = "https://github.com/LNP-BP"
 keywords = ["lnp-bp", "cryptocurrency", "smart-contracts", "client-side-validation"]
diff --git a/README.md b/README.md
index c1ef8e72..5891e22e 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Client-side-validation library
+# Client-side-validation Foundation Libraries
 
 ![Build](https://github.com/LNP-BP/client_side_validation/workflows/Build/badge.svg)
 ![Tests](https://github.com/LNP-BP/client_side_validation/workflows/Tests/badge.svg)
@@ -10,8 +10,10 @@
 [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
 [![Apache-2 licensed](https://img.shields.io/crates/l/client_side_validation)](./LICENSE)
 
-This is an implementation defining standard of client-side-validation, i.e. its
-Core library.
+This is an implementation defining standard of client-side-validation 
+representing a set of its Foundation libraries. The standards covered by the
+libraries include [LNPBP-4], [LNPBP-7], [LNPBP-8], [LNPBP-9], [LNPBP-42], and 
+[LNPBP-81] (see [Components](#components) section below for more details).
 
 Client-side-validation is a paradigm for distributed computing, based on top of
 proof-of-publication/commitment medium layer, which may be a bitcoin blockchain
@@ -44,6 +46,7 @@ functionality. These are:
 - Commit-verify scheme and its client-side-validation specific implementations
   * consensus commitments ([LNPBP-9] standard)
   * multi-commitments ([LNPBP-4] standard)
+  * merklization for client-side-validation ([LNPBP-81] standard)
 - Single-use-seals ([LNPBP-8] standard)
 
 
@@ -128,3 +131,4 @@ See [LICENCE](LICENSE) file for the license details.
 [LNPBP-8]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0008.md
 [LNPBP-9]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0009.md
 [LNPBP-42]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0042.md
+[LNPBP-81]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0081.md
diff --git a/commit_verify/Cargo.toml b/commit_verify/Cargo.toml
index c8a20a84..e36c9327 100644
--- a/commit_verify/Cargo.toml
+++ b/commit_verify/Cargo.toml
@@ -19,10 +19,13 @@ path = "src/lib.rs"
 amplify = "3.5.3"
 bitcoin_hashes = "0.9.6"
 strict_encoding = "1.2.3"
-rand = { version = "0.8", optional = true }
+rand = { version = "0.7", optional = true } # We need older version since secp256k1 depends on it
 serde_crate = { version = "1", package = "serde", optional = true }
 serde_with = { version = "1.8", optional = true }
 
+[dev-dependencies]
+rand = "0.7"
+
 [features]
 default = []
 all = ["rand", "serde"]
diff --git a/commit_verify/src/api.rs b/commit_verify/src/api.rs
index aa4266ec..4dc4f62c 100644
--- a/commit_verify/src/api.rs
+++ b/commit_verify/src/api.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
@@ -18,17 +18,17 @@
 /// Trait for commit-verify scheme. A message for the commitment may be any
 /// structure that can be represented as a byte array (i.e. implements
 /// `AsRef<[u8]>`).
-pub trait CommitVerify<MSG>
+pub trait CommitVerify<M>
 where
     Self: Eq + Sized,
 {
     /// Creates a commitment to a byte representation of a given message
-    fn commit(msg: &MSG) -> Self;
+    fn commit(msg: &M) -> Self;
 
     /// Verifies commitment against the message; default implementation just
     /// repeats the commitment to the message and check it against the `self`.
     #[inline]
-    fn verify(&self, msg: &MSG) -> bool {
+    fn verify(&self, msg: &M) -> bool {
         Self::commit(msg) == *self
     }
 }
@@ -36,7 +36,7 @@ where
 /// Trait for a failable version of commit-verify scheme. A message for the
 /// commitment may be any structure that can be represented as a byte array
 /// (i.e. implements `AsRef<[u8]>`).
-pub trait TryCommitVerify<MSG>
+pub trait TryCommitVerify<M>
 where
     Self: Eq + Sized,
 {
@@ -44,13 +44,13 @@ where
     type Error: std::error::Error;
 
     /// Tries to create commitment to a byte representation of a given message
-    fn try_commit(msg: &MSG) -> Result<Self, Self::Error>;
+    fn try_commit(msg: &M) -> Result<Self, Self::Error>;
 
     /// Tries to verify commitment against the message; default implementation
     /// just repeats the commitment to the message and check it against the
     /// `self`.
     #[inline]
-    fn try_verify(&self, msg: &MSG) -> Result<bool, Self::Error> {
+    fn try_verify(&self, msg: &M) -> Result<bool, Self::Error> {
         Ok(Self::try_commit(msg)? == *self)
     }
 }
@@ -72,7 +72,7 @@ where
 ///
 /// This trait is heavily used in **deterministic bitcoin commitments**
 /// [crate::dbc] module implementations
-pub trait EmbedCommitVerify<MSG>
+pub trait EmbedCommitVerify<M>
 where
     Self: Sized + Eq,
 {
@@ -87,7 +87,7 @@ where
     /// required to reconstruct the original container
     fn embed_commit(
         container: &mut Self::Container,
-        msg: &MSG,
+        msg: &M,
     ) -> Result<Self, Self::Error>;
 
     /// Verifies commitment against the message; default implementation just
@@ -107,7 +107,7 @@ where
     fn verify(
         &self,
         container: &Self::Container,
-        msg: &MSG,
+        msg: &M,
     ) -> Result<bool, Self::Error> {
         let mut container = container.clone();
         Ok(match Self::embed_commit(&mut container, msg) {
diff --git a/commit_verify/src/commit_encode.rs b/commit_verify/src/commit_encode.rs
index 62cf4c72..3d369eb2 100644
--- a/commit_verify/src/commit_encode.rs
+++ b/commit_verify/src/commit_encode.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
@@ -281,159 +281,3 @@ pub trait ConsensusCommit: Sized + CommitEncode {
         commitment.verify(&encoder.into_inner())
     }
 }
-
-#[cfg(test)]
-mod test {
-    use super::*;
-    use crate::merkle::{
-        ConsensusMerkleCommit, MerkleNode, MerkleSource, ToMerkleSource,
-    };
-    use amplify::{bmap, s};
-    use bitcoin_hashes::hex::ToHex;
-    use bitcoin_hashes::{sha256d, Hash};
-    use std::collections::BTreeMap;
-    use strict_encoding::StrictEncode;
-
-    #[test]
-    fn collections() {
-        // First, we define a data type
-        #[derive(
-            Clone,
-            PartialEq,
-            Eq,
-            PartialOrd,
-            Ord,
-            Hash,
-            Debug,
-            StrictEncode,
-            StrictDecode,
-        )]
-        struct Item(pub String);
-        // Next, we say that it should be concealed using some function
-        // (double SHA256 hash in this case)
-        impl CommitConceal for Item {
-            type ConcealedCommitment = sha256d::Hash;
-            fn commit_conceal(&self) -> Self::ConcealedCommitment {
-                sha256d::Hash::hash(self.0.as_bytes())
-            }
-        }
-        // Next, we need to specify how the concealed data should be
-        // commit-encoded: this time we strict-serialize the hash
-        impl Strategy for sha256d::Hash {
-            type Strategy = strategies::UsingStrict;
-        }
-        // Now, we define commitment encoding for our concealable type: it
-        // should conceal the data
-        impl Strategy for Item {
-            type Strategy = strategies::UsingConceal;
-        }
-        // Now, we need to say that consensus commit procedure should produce
-        // a final commitment from commit-encoded data (equal to the
-        // strict encoding of the conceal result) using `CommitVerify` type.
-        // Here, we use another round of hashing, producing merkle node hash
-        // from the concealed data.
-        impl ConsensusCommit for Item {
-            type Commitment = MerkleNode;
-        }
-        // Next, we need to provide merkle node tags for each type of the tree
-        impl ConsensusMerkleCommit for Item {
-            const MERKLE_NODE_TAG: &'static str = "item";
-        }
-        impl ConsensusMerkleCommit for (usize, Item) {
-            const MERKLE_NODE_TAG: &'static str = "usize->item";
-        }
-
-        impl ToMerkleSource for BTreeMap<usize, Item> {
-            type Leaf = (usize, Item);
-            fn to_merkle_source(&self) -> MerkleSource<Self::Leaf> {
-                self.iter().map(|(k, v)| (*k, v.clone())).collect()
-            }
-        }
-
-        let item = Item(s!("Some text"));
-        assert_eq!(&b"\x09\x00Some text"[..], item.strict_serialize().unwrap());
-        assert_eq!(
-            "6680bbec0d05d3eaac9c8b658c40f28d2f0cb0f245c7b1cabf5a61c35bd03d8e",
-            item.commit_serialize().to_hex()
-        );
-        assert_eq!(
-            "df08dc157bbd5676d5aeb1b437fa0cded8d3e21699adee2fcbbadef131a9e895",
-            item.consensus_commit().to_hex()
-        );
-        assert_ne!(item.commit_serialize(), item.strict_serialize().unwrap());
-        assert_eq!(
-            MerkleNode::hash(&item.commit_serialize()),
-            item.consensus_commit()
-        );
-
-        let original = bmap! {
-            0usize => Item(s!("My first case")),
-            1usize => Item(s!("My second case with a very long string")),
-            3usize => Item(s!("My third case to make the Merkle tree two layered"))
-        };
-        let collection = original.to_merkle_source();
-        assert_eq!(
-            &b"\x03\x00\
-             \x00\x00\
-             \x0d\x00\
-             My first case\
-             \x01\x00\
-             \x26\x00\
-             My second case with a very long string\
-             \x03\x00\
-             \x31\x00\
-             My third case to make the Merkle tree two layered"[..],
-            original.strict_serialize().unwrap()
-        );
-        assert_eq!(
-            "1d183cc7f24017fb33c46ba0d47b85987357183ff1c59d0e32a21781f73ea5b7",
-            collection.commit_serialize().to_hex()
-        );
-        assert_eq!(
-            "b7a53ef78117a2320e9dc5f13f18577398857bd4a06bc433fb1740f2c73c181d",
-            collection.consensus_commit().to_hex()
-        );
-        assert_ne!(
-            collection.commit_serialize(),
-            original.strict_serialize().unwrap()
-        );
-        assert_eq!(
-            MerkleNode::from_slice(&collection.commit_serialize()).unwrap(),
-            collection.consensus_commit()
-        );
-
-        let original = vec![
-            Item(s!("My first case")),
-            Item(s!("My second case with a very long string")),
-            Item(s!("My third case to make the Merkle tree two layered")),
-        ];
-        let vec: MerkleSource<Item> = original.clone().into();
-        assert_eq!(
-            &b"\x03\x00\
-             \x0d\x00\
-             My first case\
-             \x26\x00\
-             My second case with a very long string\
-             \x31\x00\
-             My third case to make the Merkle tree two layered"[..],
-            original.strict_serialize().unwrap()
-        );
-        assert_eq!(
-            "be4c548b5fb14b2c86475ff6edc5d186f5980f53191d7df44faa22a97e7be49b",
-            vec.commit_serialize().to_hex()
-        );
-        assert_eq!(
-            "9be47b7ea922aa4ff47d1d19530f98f586d1c5edf65f47862c4bb15f8b544cbe",
-            vec.consensus_commit().to_hex()
-        );
-        assert_ne!(
-            vec.commit_serialize(),
-            original.strict_serialize().unwrap()
-        );
-        assert_eq!(
-            MerkleNode::from_slice(&vec.commit_serialize()).unwrap(),
-            vec.consensus_commit()
-        );
-        assert_ne!(vec.consensus_commit(), collection.consensus_commit());
-    }
-}
diff --git a/commit_verify/src/digests.rs b/commit_verify/src/digests.rs
index 6ab4be42..131ca877 100644
--- a/commit_verify/src/digests.rs
+++ b/commit_verify/src/digests.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/commit_verify/src/lib.rs b/commit_verify/src/lib.rs
index badf575e..f81fd19b 100644
--- a/commit_verify/src/lib.rs
+++ b/commit_verify/src/lib.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/commit_verify/src/merkle.rs b/commit_verify/src/merkle.rs
index bc1d01da..8c35a1e1 100644
--- a/commit_verify/src/merkle.rs
+++ b/commit_verify/src/merkle.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
@@ -20,7 +20,7 @@
 use std::io;
 use std::iter::FromIterator;
 
-use bitcoin_hashes::{sha256, sha256d, Hash, HashEngine};
+use bitcoin_hashes::{sha256, Hash, HashEngine};
 
 use crate::{commit_encode, CommitEncode, CommitVerify, ConsensusCommit};
 
@@ -30,16 +30,17 @@ use crate::{commit_encode, CommitEncode, CommitVerify, ConsensusCommit};
 pub trait ConsensusMerkleCommit:
     ConsensusCommit<Commitment = MerkleNode>
 {
-    /// The tag which will be used in the merklization process (see
+    /// The tag prefix which will be used in the merklization process (see
     /// [`merklize`])
-    const MERKLE_NODE_TAG: &'static str;
+    const MERKLE_NODE_PREFIX: &'static str;
 }
 
 hash_newtype!(
     MerkleNode,
-    sha256d::Hash,
+    sha256::Hash,
     32,
-    doc = "A hash of a arbitrary Merkle tree branch or root"
+    doc = "A hash type for LNPBP-81 Merkle tree leaves, branches and root",
+    false // We do not reverse displaying MerkleNodes in hexadecimal
 );
 
 impl strict_encoding::Strategy for MerkleNode {
@@ -78,28 +79,45 @@ where
 }
 
 /// Merklization procedure that uses tagged hashes with depth commitments
-pub fn merklize<N>(prefix: &str, data: &[N]) -> (MerkleNode, u8)
+/// according to [LNPBP-81] standard of client-side-validation merklization
+///
+/// [LNPBP-81]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0081.md
+pub fn merklize<I>(prefix: &str, data: I) -> (MerkleNode, u8)
 where
-    N: AsRef<[u8]>,
+    I: IntoIterator<Item = MerkleNode>,
+    <I as IntoIterator>::IntoIter: ExactSizeIterator<Item = MerkleNode>,
 {
     let mut tag_engine = sha256::Hash::engine();
     tag_engine.input(prefix.as_bytes());
     tag_engine.input(":merkle:".as_bytes());
-    merklize_inner(&tag_engine, data, 0, false, None)
+
+    let iter = data.into_iter();
+    let width = iter.len();
+
+    // Tagging merkle tree root
+    let (root, height) = merklize_inner(&tag_engine, iter, 0, false, None);
+    tag_engine.input("root:height=".as_bytes());
+    tag_engine.input(&height.to_string().into_bytes());
+    tag_engine.input(":width=".as_bytes());
+    tag_engine.input(&width.to_string().into_bytes());
+    let tag_hash = sha256::Hash::hash(&sha256::Hash::from_engine(tag_engine));
+    let mut engine = MerkleNode::engine();
+    engine.input(&tag_hash[..]);
+    engine.input(&tag_hash[..]);
+    root.commit_encode(&mut engine);
+    let tagged_root = MerkleNode::from_engine(engine);
+
+    (tagged_root, height)
 }
 
-fn merklize_inner<N>(
+fn merklize_inner(
     engine_proto: &sha256::HashEngine,
-    data: &[N],
+    mut iter: impl ExactSizeIterator<Item = MerkleNode>,
     depth: u8,
     extend: bool,
     empty_node: Option<MerkleNode>,
-) -> (MerkleNode, u8)
-where
-    N: AsRef<[u8]>,
-{
-    let len = data.len();
-    let mut iter = data.iter();
+) -> (MerkleNode, u8) {
+    let len = iter.len();
     let ext_len = len + if extend { 1 } else { 0 };
     let empty_node = empty_node.unwrap_or_else(|| MerkleNode::hash(&[0xFF]));
 
@@ -150,14 +168,16 @@ where
 
         let (node1, height1) = merklize_inner(
             engine_proto,
-            &data[..div],
+            // Normally we should use `iter.by_ref().take(div)`, but currently
+            // rust compilers is unable to parse recursion with generic types
+            iter.by_ref().take(div).collect::<Vec<_>>().into_iter(),
             depth + 1,
             false,
             Some(empty_node),
         );
         let (node2, height2) = merklize_inner(
             engine_proto,
-            &data[div..],
+            iter,
             depth + 1,
             len % 2 == 0,
             Some(empty_node),
@@ -178,7 +198,9 @@ where
     }
 }
 
-/// The source data for the merklization process
+/// The source data for the [LNPBP-81] merklization process.
+///
+/// [LNPBP-81]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0081.md
 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
 pub struct MerkleSource<T>(
     /// Array of the data which will be merklized
@@ -209,12 +231,8 @@ where
     L: ConsensusMerkleCommit,
 {
     fn commit_encode<E: io::Write>(&self, e: E) -> usize {
-        let leafs = self
-            .0
-            .iter()
-            .map(L::consensus_commit)
-            .collect::<Vec<MerkleNode>>();
-        merklize(L::MERKLE_NODE_TAG, &leafs).0.commit_encode(e)
+        let leafs = self.0.iter().map(L::consensus_commit);
+        merklize(L::MERKLE_NODE_PREFIX, leafs).0.commit_encode(e)
     }
 }
 
@@ -237,7 +255,15 @@ where
 }
 
 /// Converts given piece of client-side-validated data into a structure which
-/// can be used in merklization process
+/// can be used in merklization process.
+///
+/// This dedicated structure is required since with
+/// `impl From<_> for MerkleSource` we would not be able to specify a concrete
+/// tagged hash, which we require in [LNPBP-81] merklization and which we
+/// provide here via [`ToMerkleSource::Leaf`]` associated type holding
+/// [`ConsensusMerkleCommit::MERKLE_NODE_PREFIX`] prefix value.
+///
+/// [LNPBP-81]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0081.md
 pub trait ToMerkleSource {
     /// Defining type of the commitment produced during merlization process
     type Leaf: ConsensusMerkleCommit;
@@ -245,3 +271,158 @@ pub trait ToMerkleSource {
     /// Performs transformation of the data type into a merkilzable data
     fn to_merkle_source(&self) -> MerkleSource<Self::Leaf>;
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::commit_encode::{strategies, Strategy};
+    use crate::CommitConceal;
+    use amplify::{bmap, s};
+    use bitcoin_hashes::hex::ToHex;
+    use bitcoin_hashes::{sha256d, Hash};
+    use std::collections::BTreeMap;
+    use strict_encoding::StrictEncode;
+
+    #[test]
+    fn collections() {
+        // First, we define a data type
+        #[derive(
+            Clone,
+            PartialEq,
+            Eq,
+            PartialOrd,
+            Ord,
+            Hash,
+            Debug,
+            StrictEncode,
+            StrictDecode,
+        )]
+        struct Item(pub String);
+        // Next, we say that it should be concealed using some function
+        // (double SHA256 hash in this case)
+        impl CommitConceal for Item {
+            type ConcealedCommitment = sha256d::Hash;
+            fn commit_conceal(&self) -> Self::ConcealedCommitment {
+                sha256d::Hash::hash(self.0.as_bytes())
+            }
+        }
+        // Next, we need to specify how the concealed data should be
+        // commit-encoded: this time we strict-serialize the hash
+        impl Strategy for sha256d::Hash {
+            type Strategy = strategies::UsingStrict;
+        }
+        // Now, we define commitment encoding for our concealable type: it
+        // should conceal the data
+        impl Strategy for Item {
+            type Strategy = strategies::UsingConceal;
+        }
+        // Now, we need to say that consensus commit procedure should produce
+        // a final commitment from commit-encoded data (equal to the
+        // strict encoding of the conceal result) using `CommitVerify` type.
+        // Here, we use another round of hashing, producing merkle node hash
+        // from the concealed data.
+        impl ConsensusCommit for Item {
+            type Commitment = MerkleNode;
+        }
+        // Next, we need to provide merkle node tags for each type of the tree
+        impl ConsensusMerkleCommit for Item {
+            const MERKLE_NODE_PREFIX: &'static str = "item";
+        }
+        impl ConsensusMerkleCommit for (usize, Item) {
+            const MERKLE_NODE_PREFIX: &'static str = "usize->item";
+        }
+
+        impl ToMerkleSource for BTreeMap<usize, Item> {
+            type Leaf = (usize, Item);
+            fn to_merkle_source(&self) -> MerkleSource<Self::Leaf> {
+                self.iter().map(|(k, v)| (*k, v.clone())).collect()
+            }
+        }
+
+        let item = Item(s!("Some text"));
+        assert_eq!(&b"\x09\x00Some text"[..], item.strict_serialize().unwrap());
+        assert_eq!(
+            "6680bbec0d05d3eaac9c8b658c40f28d2f0cb0f245c7b1cabf5a61c35bd03d8e",
+            item.commit_serialize().to_hex()
+        );
+        assert_eq!(
+            "3e4b2dcf9bca33400028c8947565c1ff421f6d561e9ec48f88f0c9a24ebc8c30",
+            item.consensus_commit().to_hex()
+        );
+        assert_ne!(item.commit_serialize(), item.strict_serialize().unwrap());
+        assert_eq!(
+            MerkleNode::hash(&item.commit_serialize()),
+            item.consensus_commit()
+        );
+
+        let original = bmap! {
+            0usize => Item(s!("My first case")),
+            1usize => Item(s!("My second case with a very long string")),
+            3usize => Item(s!("My third case to make the Merkle tree two layered"))
+        };
+        let collection = original.to_merkle_source();
+        assert_eq!(
+            &b"\x03\x00\
+             \x00\x00\
+             \x0d\x00\
+             My first case\
+             \x01\x00\
+             \x26\x00\
+             My second case with a very long string\
+             \x03\x00\
+             \x31\x00\
+             My third case to make the Merkle tree two layered"[..],
+            original.strict_serialize().unwrap()
+        );
+        assert_eq!(
+            "3b970fa581bcdf4987e6455e2e5a1ea575bcb3f5b37c25600f600cc8d44e5598",
+            collection.commit_serialize().to_hex()
+        );
+        assert_eq!(
+            "3b970fa581bcdf4987e6455e2e5a1ea575bcb3f5b37c25600f600cc8d44e5598",
+            collection.consensus_commit().to_hex()
+        );
+        assert_ne!(
+            collection.commit_serialize(),
+            original.strict_serialize().unwrap()
+        );
+        assert_eq!(
+            MerkleNode::from_slice(&collection.commit_serialize()).unwrap(),
+            collection.consensus_commit()
+        );
+
+        let original = vec![
+            Item(s!("My first case")),
+            Item(s!("My second case with a very long string")),
+            Item(s!("My third case to make the Merkle tree two layered")),
+        ];
+        let vec: MerkleSource<Item> = original.clone().into();
+        assert_eq!(
+            &b"\x03\x00\
+             \x0d\x00\
+             My first case\
+             \x26\x00\
+             My second case with a very long string\
+             \x31\x00\
+             My third case to make the Merkle tree two layered"[..],
+            original.strict_serialize().unwrap()
+        );
+        assert_eq!(
+            "4ee97b1318fe417cac30790335033ece29ea4b6183ebf51d083d5a2e89ac33da",
+            vec.commit_serialize().to_hex()
+        );
+        assert_eq!(
+            "4ee97b1318fe417cac30790335033ece29ea4b6183ebf51d083d5a2e89ac33da",
+            vec.consensus_commit().to_hex()
+        );
+        assert_ne!(
+            vec.commit_serialize(),
+            original.strict_serialize().unwrap()
+        );
+        assert_eq!(
+            MerkleNode::from_slice(&vec.commit_serialize()).unwrap(),
+            vec.consensus_commit()
+        );
+        assert_ne!(vec.consensus_commit(), collection.consensus_commit());
+    }
+}
diff --git a/commit_verify/src/multi_commit.rs b/commit_verify/src/multi_commit.rs
index bd04ac0e..308980e2 100644
--- a/commit_verify/src/multi_commit.rs
+++ b/commit_verify/src/multi_commit.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
@@ -14,42 +14,73 @@
 
 //! Multi-message commitments: implementation of [LNPBP-4] standard.
 //!
-//! LNPBP-4 defines a commit-verify scheme for committing to a multiple messages
-//! under distinct protocols with ability to partially reveal set of the
-//! commitments and still be able to prove the commitment for each message
+//! [LNPBP-4] defines a commit-verify scheme for committing to a multiple
+//! messages under distinct protocols with ability to partially reveal set of
+//! the commitments and still be able to prove the commitment for each message
 //! without exposing the exact number of other messages and their respective
 //! protocol identifiers.
 //!
-//! [LNPBP]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0004.md
+//! [LNPBP-4]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0004.md
 
 use std::collections::BTreeMap;
+use std::io;
 
-use bitcoin_hashes::sha256;
+use bitcoin_hashes::{sha256, sha256t};
+use strict_encoding::StrictEncode;
 
-use crate::Slice32;
 #[cfg(feature = "rand")]
 use crate::TryCommitVerify;
+use crate::{
+    commit_encode, CommitEncode, CommitVerify, ConsensusCommit, Slice32,
+    TaggedHash,
+};
 
-/// Source data for creation of multi-message commitments according to LNPBP-4
-/// procedure
+/// Source data for creation of multi-message commitments according to [LNPBP-4]
+/// procedure.
+///
+/// [LNPBP-4]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0004.md
 pub type ProtocolId = Slice32;
 
 /// Original message participating in multi-message commitment.
 ///
 /// The message must be represented by a SHA256 tagged hash. Since each message
-/// may have a different tag, we can't use `sha256t` type directly and use its
-/// `sha256::Hash` equivalent.
+/// may have a different tag, we can't use [`sha256t`] type directly and use its
+/// [`sha256::Hash`] equivalent.
 pub type Message = sha256::Hash;
 
-/// Type alias for structured source multi-message data for commitment creation
-pub type MessageMap = BTreeMap<ProtocolId, Message>;
+/// Structured source multi-message data for commitment creation
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+pub struct MultiSource {
+    /// Minimal length of the created LNPBP-4 commitment buffer
+    pub min_length: u16,
+    /// Map of the messages by their respective protocol ids
+    pub messages: BTreeMap<ProtocolId, Message>,
+}
+
+impl Default for MultiSource {
+    fn default() -> Self {
+        MultiSource {
+            min_length: 3,
+            messages: Default::default(),
+        }
+    }
+}
 
-/// LNPBP-4 commitment procedure is limited to 2^16 messages
+/// Errors generated during multi-message commitment process by
+/// [`MultiCommitBlock::try_commit`]
 #[derive(
     Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Error, Debug, Display,
 )]
 #[display(doc_comments)]
-pub struct TooManyMessagesError;
+pub enum Error {
+    /// Number of messages ({0}) for LNPBP-4 commitment which exceeds the
+    /// protocol limit of 2^16
+    TooManyMessages(usize),
+
+    /// The provided number of messages can't fit LNPBP-4 commitment size
+    /// limits for a given set of protocol ids.
+    CantFitInMaxSlots,
+}
 
 #[cfg_attr(
     feature = "serde",
@@ -57,7 +88,7 @@ pub struct TooManyMessagesError;
     serde(crate = "serde_crate")
 )]
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)]
-#[display("{commitment}")]
+#[display("{message}")]
 #[derive(StrictEncode, StrictDecode)]
 /// Single item within a multi-message commitment, consisting of optional
 /// protocol information (if known) and the actual single message commitment
@@ -67,15 +98,15 @@ pub struct MultiCommitItem {
     pub protocol: Option<ProtocolId>,
 
     /// Message commitment (LNPBP-4 tagged hash of the message)
-    pub commitment: Message,
+    pub message: Message,
 }
 
 impl MultiCommitItem {
-    /// Constructs multi-message commitment item
-    pub fn new(protocol: Option<ProtocolId>, commitment: Message) -> Self {
+    /// Constructs multi-message commitment item for a given protocol
+    pub fn new(protocol: ProtocolId, message: Message) -> Self {
         Self {
-            protocol,
-            commitment,
+            protocol: Some(protocol),
+            message,
         }
     }
 }
@@ -97,9 +128,11 @@ impl MultiCommitItem {
     StrictEncode,
     StrictDecode,
 )]
-/// Multi-message commitment data according to LNPBP-4 specification.
+/// Multi-message commitment data according to [LNPBP-4] specification.
 ///
-/// To create commitment use [`TryCommitVerify::try_commit`] method
+/// To create commitment use [`TryCommitVerify::try_commit`] method.
+///
+/// [LNPBP-4]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0004.md
 pub struct MultiCommitBlock {
     /// Array of commitment items (see [`MultiCommitItem`])
     pub commitments: Vec<MultiCommitItem>,
@@ -110,66 +143,80 @@ pub struct MultiCommitBlock {
     pub entropy: Option<u64>,
 }
 
+// When we commit to `MultiCommitBlock` we do not use `entropy`, since we
+// already committed to its value inside `MultiCommitItem` using the entropy
+impl CommitEncode for MultiCommitBlock {
+    fn commit_encode<E: io::Write>(&self, e: E) -> usize {
+        self.commitments
+            .strict_encode(e)
+            .expect("CommitEncode of Vec<MultiCommitItem> has failed")
+    }
+}
+
 #[cfg(feature = "rand")]
-impl TryCommitVerify<MessageMap> for MultiCommitBlock {
-    type Error = TooManyMessagesError;
+const MIDSTATE_ENTROPY: [u8; 32] = [
+    0xF4, 0x0D, 0x86, 0x94, 0x9F, 0xFF, 0xAD, 0xEE, 0x19, 0xEA, 0x50, 0x20,
+    0x60, 0xAB, 0x6B, 0xAD, 0x11, 0x61, 0xB2, 0x35, 0x83, 0xD3, 0x78, 0x18,
+    0x52, 0x0D, 0xD4, 0xD1, 0xD8, 0x88, 0x1E, 0x61,
+];
 
-    fn try_commit(
-        multi_msg: &MessageMap,
-    ) -> Result<Self, TooManyMessagesError> {
+#[cfg(feature = "rand")]
+impl TryCommitVerify<MultiSource> for MultiCommitBlock {
+    type Error = Error;
+
+    fn try_commit(source: &MultiSource) -> Result<Self, Error> {
         use amplify::num::u256;
         use bitcoin_hashes::{Hash, HashEngine};
         use rand::{thread_rng, Rng};
 
-        const SORT_LIMIT: usize = 2 << 16;
-
-        let mut n = multi_msg.len();
+        let m = source.messages.len();
+        if m > u16::MAX as usize {
+            return Err(Error::TooManyMessages(m));
+        }
+        let mut n = m;
         // We use some minimum number of items, to increase privacy
-        n = n.max(3);
+        n = n.max(source.min_length as usize);
+
         let ordered = loop {
+            if n > u16::MAX as usize {
+                return Err(Error::CantFitInMaxSlots);
+            }
+
             let mut ordered = BTreeMap::<usize, (ProtocolId, Message)>::new();
-            // TODO #6: Modify arithmetics in LNPBP-4 spec
-            //       <https://github.com/LNP-BP/LNPBPs/issues/19>
-            if multi_msg.iter().all(|(protocol, digest)| {
-                let rem = u256::from_be_bytes(**protocol)
-                    % u256::from_u64(n as u64)
-                        .expect("Bitcoin U256 struct is broken");
+            if source.messages.iter().all(|(protocol, message)| {
+                let rem = u256::from_le_bytes(**protocol)
+                    % u256::from_u64(n as u64).expect("u256 type is broken");
                 ordered
-                    .insert(rem.low_u64() as usize, (*protocol, *digest))
+                    .insert(rem.low_u64() as usize, (*protocol, *message))
                     .is_none()
             }) {
                 break ordered;
             }
             n += 1;
-            if n > SORT_LIMIT {
-                // Memory allocation limit exceeded while trying to sort
-                // multi-message commitment
-                return Err(TooManyMessagesError);
-            }
         };
+        let n = n as u16;
 
         let entropy = {
             let mut rng = thread_rng();
             rng.gen::<u64>()
         };
+        let midstate = sha256::Midstate::from_inner(MIDSTATE_ENTROPY);
+        let mut engine = sha256::HashEngine::from_midstate(midstate, 64);
+        engine.input(&entropy.to_le_bytes());
 
-        let mut commitments = Vec::<_>::with_capacity(n);
+        let mut commitments = Vec::<_>::with_capacity(n as usize);
         for i in 0..n {
-            match ordered.get(&i) {
+            match ordered.get(&(i as usize)) {
                 None => {
-                    let mut engine = Message::engine();
-                    for _ in 0..4 {
-                        engine.input(&entropy.to_le_bytes());
-                        engine.input(&i.to_le_bytes());
-                    }
-                    commitments.push(MultiCommitItem::new(
-                        None,
-                        Message::from_engine(engine),
-                    ))
+                    let mut subengine = engine.clone();
+                    subengine.input(&i.to_le_bytes());
+                    commitments.push(MultiCommitItem {
+                        protocol: None,
+                        message: Message::from_engine(subengine),
+                    })
                 }
-                Some((contract_id, commitment)) => commitments.push(
-                    MultiCommitItem::new(Some(*contract_id), *commitment),
-                ),
+                Some((contract_id, commitment)) => commitments
+                    .push(MultiCommitItem::new(*contract_id, *commitment)),
             }
         }
 
@@ -179,3 +226,162 @@ impl TryCommitVerify<MessageMap> for MultiCommitBlock {
         })
     }
 }
+
+static MIDSTATE_LNPBP4: [u8; 32] = [
+    0x23, 0x4B, 0x4D, 0xBA, 0x22, 0x2A, 0x64, 0x1C, 0x7F, 0x74, 0xD5, 0xC9,
+    0x80, 0x17, 0x36, 0x1A, 0x90, 0x76, 0x4F, 0xB3, 0xC2, 0xB1, 0xA1, 0x6F,
+    0xDE, 0x28, 0x66, 0x89, 0xF1, 0xCC, 0x99, 0x3F,
+];
+
+/// Tag used for [`MultiCommitment`] hash type
+pub struct Lnpbp4Tag;
+
+impl sha256t::Tag for Lnpbp4Tag {
+    #[inline]
+    fn engine() -> sha256::HashEngine {
+        let midstate = sha256::Midstate::from_inner(MIDSTATE_LNPBP4);
+        sha256::HashEngine::from_midstate(midstate, 64)
+    }
+}
+
+/// Final [LNPBP-4] commitment value.
+///
+/// Represents tagged hash (with [`Lnpbp4Tag`]) of the sequentially serialized
+/// [`MultiCommitBlock::commitments`].
+///
+/// [LNPBP-4]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0004.md
+#[cfg_attr(
+    feature = "serde",
+    derive(Serialize, Deserialize),
+    serde(crate = "serde_crate", transparent)
+)]
+#[derive(
+    Wrapper, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, From,
+)]
+#[wrapper(
+    Debug, Display, LowerHex, Index, IndexRange, IndexFrom, IndexTo, IndexFull
+)]
+pub struct MultiCommitment(sha256t::Hash<Lnpbp4Tag>);
+
+impl<M> CommitVerify<M> for MultiCommitment
+where
+    M: AsRef<[u8]>,
+{
+    #[inline]
+    fn commit(msg: &M) -> MultiCommitment {
+        MultiCommitment::hash(msg)
+    }
+}
+
+#[cfg(feature = "rand")]
+impl TryCommitVerify<MultiSource> for MultiCommitment {
+    type Error = Error;
+
+    fn try_commit(msg: &MultiSource) -> Result<Self, Self::Error> {
+        Ok(MultiCommitBlock::try_commit(msg)?.consensus_commit())
+    }
+}
+
+impl strict_encoding::Strategy for MultiCommitment {
+    type Strategy = strict_encoding::strategies::Wrapped;
+}
+
+impl commit_encode::Strategy for MultiCommitment {
+    type Strategy = commit_encode::strategies::UsingStrict;
+}
+
+impl ConsensusCommit for MultiCommitBlock {
+    type Commitment = MultiCommitment;
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use amplify::Wrapper;
+    use bitcoin_hashes::{Hash, HashEngine};
+
+    fn entropy_tagged_engine() -> sha256::HashEngine {
+        let tag_hash = sha256::Hash::hash("LNPBP4:entropy".as_bytes());
+        let mut engine = Message::engine();
+        engine.input(&tag_hash[..]);
+        engine.input(&tag_hash[..]);
+        engine
+    }
+
+    #[test]
+    fn test_lnpbp4_tags() {
+        let midstate = sha256::Midstate::from_inner(MIDSTATE_ENTROPY);
+        assert_eq!(midstate, entropy_tagged_engine().midstate());
+
+        let midstate = sha256::Midstate::from_inner(MIDSTATE_LNPBP4);
+        let tag_hash = sha256::Hash::hash("LNPBP4".as_bytes());
+        let mut engine = Message::engine();
+        engine.input(&tag_hash[..]);
+        engine.input(&tag_hash[..]);
+        assert_eq!(midstate, engine.midstate());
+    }
+
+    #[test]
+    #[cfg(feature = "rand")]
+    fn test_commit() {
+        let mut protocol = ProtocolId::from_inner([0u8; 32]);
+        let message = Message::hash("First message".as_bytes());
+
+        for index in 0u8..3 {
+            let mut source = MultiSource::default();
+            protocol[0] = index;
+            source.messages.insert(protocol, message);
+            let commitment = MultiCommitBlock::try_commit(&source).unwrap();
+
+            let slot = commitment.commitments[index as usize];
+            assert_eq!(slot.protocol, Some(protocol));
+            assert_eq!(slot.message, message);
+
+            for others in 0u8..3 {
+                if index == others {
+                    continue;
+                }
+                let slot = commitment.commitments[others as usize];
+                assert_eq!(slot.protocol, None);
+                assert_ne!(slot.message, message);
+
+                let mut engine = entropy_tagged_engine();
+                engine.input(&commitment.entropy.unwrap().to_le_bytes());
+                engine.input(&[others, 0u8]);
+                assert_eq!(slot.message, Message::from_engine(engine));
+            }
+
+            let lnpbp4 = commitment.consensus_commit();
+            let crafted = MultiCommitment::hash(
+                commitment.commitments.strict_serialize().unwrap(),
+            );
+            assert_eq!(lnpbp4, crafted);
+            assert!(commitment.consensus_verify(&lnpbp4));
+        }
+
+        for index in 1u8..3 {
+            let mut source = MultiSource::default();
+            protocol[31] = index; // Checking endianness
+            source.messages.insert(protocol, message);
+            let commitment = MultiCommitBlock::try_commit(&source).unwrap();
+
+            let slot = commitment.commitments[index as usize];
+            assert_eq!(slot.protocol, None);
+            assert_ne!(slot.message, message);
+        }
+    }
+
+    #[test]
+    #[cfg(feature = "rand")]
+    fn test_extension() {
+        let source = MultiSource {
+            min_length: 1,
+            messages: bmap! {
+                ProtocolId::from_inner([0u8; 32]) => Message::hash("First message".as_bytes()),
+                ProtocolId::from_inner([1u8; 32]) => Message::hash("Second message".as_bytes())
+            },
+        };
+
+        MultiCommitment::try_commit(&source).unwrap();
+    }
+}
diff --git a/commit_verify/src/slice32.rs b/commit_verify/src/slice32.rs
index 1ad8c35d..34a7678c 100644
--- a/commit_verify/src/slice32.rs
+++ b/commit_verify/src/slice32.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
@@ -22,7 +22,10 @@ use bitcoin_hashes::{sha256, Hash};
 use strict_encoding::{StrictDecode, StrictEncode};
 
 /// Wrapper type for all slice-based 256-bit types implementing many important
-/// traits, so types based on it can simply derive their implementations
+/// traits, so types based on it can simply derive their implementations.
+///
+/// Type keeps data in little-endian byte order and displays them in the same
+/// order (like bitcoin SHA256 single hash type).
 #[cfg_attr(
     feature = "serde",
     derive(Serialize, Deserialize),
diff --git a/commit_verify/src/tagged_hash.rs b/commit_verify/src/tagged_hash.rs
index 59621a45..c2fce505 100644
--- a/commit_verify/src/tagged_hash.rs
+++ b/commit_verify/src/tagged_hash.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/license_header b/license_header
index d30dd9b1..4ff84bf9 100644
--- a/license_header
+++ b/license_header
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/single_use_seals/src/lib.rs b/single_use_seals/src/lib.rs
index 7be70c16..a66829ba 100644
--- a/single_use_seals/src/lib.rs
+++ b/single_use_seals/src/lib.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/src/lib.rs b/src/lib.rs
index a291d0e0..8592e204 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/src/trust_resolvers.rs b/src/trust_resolvers.rs
index 8dbbb744..71895527 100644
--- a/src/trust_resolvers.rs
+++ b/src/trust_resolvers.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/derive/src/decode.rs b/strict_encoding/derive/src/decode.rs
index 8316d26a..02ef0edb 100644
--- a/strict_encoding/derive/src/decode.rs
+++ b/strict_encoding/derive/src/decode.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/derive/src/encode.rs b/strict_encoding/derive/src/encode.rs
index c464d340..cd45118b 100644
--- a/strict_encoding/derive/src/encode.rs
+++ b/strict_encoding/derive/src/encode.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/derive/src/lib.rs b/strict_encoding/derive/src/lib.rs
index e27f5a41..7a61b0ba 100644
--- a/strict_encoding/derive/src/lib.rs
+++ b/strict_encoding/derive/src/lib.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/derive/src/param.rs b/strict_encoding/derive/src/param.rs
index 61ca5bb0..de69742d 100644
--- a/strict_encoding/derive/src/param.rs
+++ b/strict_encoding/derive/src/param.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/amplify_types.rs b/strict_encoding/src/amplify_types.rs
index 27c32811..639d7ea5 100644
--- a/strict_encoding/src/amplify_types.rs
+++ b/strict_encoding/src/amplify_types.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
@@ -75,3 +75,57 @@ impl StrictDecode for u1024 {
         Ok(u1024::from_le_bytes(bytes))
     }
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::test_helpers::test_encoding_roundtrip;
+
+    #[test]
+    fn test_large_uints() {
+        test_encoding_roundtrip(
+            &u256::from_u64(0x_dead_cafe_4bad_beef).unwrap(),
+            [
+                0xef, 0xbe, 0xad, 0x4b, 0xfe, 0xca, 0xad, 0xde, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00,
+            ],
+        )
+        .unwrap();
+
+        test_encoding_roundtrip(
+            &u512::from_u64(0x_dead_cafe_4bad_beef).unwrap(),
+            [
+                0xef, 0xbe, 0xad, 0x4b, 0xfe, 0xca, 0xad, 0xde, 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, 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,
+            ],
+        )
+        .unwrap();
+
+        test_encoding_roundtrip(
+            &u1024::from_u64(0x_dead_cafe_4bad_beef).unwrap(),
+            [
+                0xef, 0xbe, 0xad, 0x4b, 0xfe, 0xca, 0xad, 0xde, 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, 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,
+                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, 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,
+            ],
+        )
+        .unwrap();
+    }
+}
diff --git a/strict_encoding/src/bitcoin.rs b/strict_encoding/src/bitcoin.rs
index f2ade74f..c70e2b89 100644
--- a/strict_encoding/src/bitcoin.rs
+++ b/strict_encoding/src/bitcoin.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/collections.rs b/strict_encoding/src/collections.rs
index 038d3c73..97e57f39 100644
--- a/strict_encoding/src/collections.rs
+++ b/strict_encoding/src/collections.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/crypto.rs b/strict_encoding/src/crypto.rs
index 3438b654..8167b937 100644
--- a/strict_encoding/src/crypto.rs
+++ b/strict_encoding/src/crypto.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/lib.rs b/strict_encoding/src/lib.rs
index 735d0037..8013d926 100644
--- a/strict_encoding/src/lib.rs
+++ b/strict_encoding/src/lib.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/macros.rs b/strict_encoding/src/macros.rs
index 39749953..e60b6919 100644
--- a/strict_encoding/src/macros.rs
+++ b/strict_encoding/src/macros.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/miniscript.rs b/strict_encoding/src/miniscript.rs
index 44c54fd3..f4333a5e 100644
--- a/strict_encoding/src/miniscript.rs
+++ b/strict_encoding/src/miniscript.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
@@ -27,6 +27,17 @@ use miniscript::{
 
 use crate::{Error, StrictDecode, StrictEncode};
 
+/// Maximum level of nested miniscript and miniscript concrete policy levels
+/// supported by strict encoding process.
+///
+/// This is required in order to prevent attacks in client-side-validated data
+/// when limitations on the maximum number of items is bypassed due to the
+/// nested nature of miniscript AST. While this may is controlled by miniscript
+/// implementation and bitcoin consensus script length rules, this control does
+/// not takes place during deserialization (plus miniscript may be used outside
+/// of bitcoin transactions), and thus we need this redundant control.
+pub const MINISCRIPT_DEPTH_LIMIT: u8 = 64;
+
 const MS_FALSE: u8 = 0;
 const MS_TRUE: u8 = 1;
 
@@ -58,38 +69,31 @@ const MS_VERIFY: u8 = 0x18;
 const MS_NON_ZERO: u8 = 0x19;
 const MS_ZERO_NE: u8 = 0x1a;
 
-/// We need this shit because of rust compiler limitations.
+/// We need this because of rust compiler limitations.
 ///
-/// The two macros below, as well as custom private functions `decode_policy`,
-/// `encode_miniscript` and `decode_miniscript` were introduced because rust
-/// compiler dies on recursion overflow each time when a generic type calls
-/// itself in a recursive mode passing one of its arguments by a mutable
-/// reference to itself. Thus, we had to split the implementation logic in such
-/// a way that we do not pass a mutable reference to a variable, and just re-use
-/// the reference instead. This contradicts rust API guidelines, but in fact it
-/// is a rust compiler who contradicts them, we just do not have other choice.
-macro_rules! strict_encode_ms {
-    ($encoder:ident; $tag:ident, $depth:expr, $($ms:expr),+) => { {
-        let mut len = 0usize;
-        $encoder.write_all(&$tag.strict_serialize()?)?;
-        len += 1;
-        $( len += encode_miniscript($ms, $encoder, $depth)?; )+
-        len
-    } };
+/// The macros below were introduced because rust compiler dies on recursion
+/// overflow each time when a generic type calls itself in a recursive mode
+/// passing one of its arguments by a mutable reference to itself. Thus, we had
+/// to split the implementation logic in such a way that we do not pass a
+/// mutable reference to a variable, and just re-use the reference instead. This
+/// contradicts rust API guidelines, but in fact it is a rust compiler who
+/// contradicts them, we just do not have other choice.
+macro_rules! strict_encode_tuple {
+    ( $encoder:ident; $tag:ident, $item:ident ) => {{
+        $encoder.write_all(&[$tag])?;
+        1 + $item.strict_encode($encoder)?
+    }};
 }
 
-macro_rules! strict_encode_seq {
-    ( $encoder:ident; $($item:expr),+ ) => {
-        {
-            let mut len = 0usize;
-            $(
-                let data = $item.strict_serialize()?;
-                $encoder.write_all(&data)?;
-                len += data.len();
-            )+
-            len
+macro_rules! strict_encode_usize {
+    ( $encoder:ident; $int:expr ) => { {
+        let count = $int; // Evaluating expression to reduce number of function calls
+        if count > u16::MAX as usize {
+            return Err(Error::ExceedMaxItems(count));
         }
-    };
+        $encoder.write_all(&(count as u16).to_le_bytes())?;
+        2 // We know that we write exactly two bytes
+    } };
 }
 
 impl<Pk> StrictEncode for Policy<Pk>
@@ -98,298 +102,404 @@ where
     <Pk as MiniscriptKey>::Hash: StrictEncode,
 {
     fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
-        Ok(match self {
-            Policy::Unsatisfiable => MS_FALSE.strict_encode(e)?,
-            Policy::Trivial => MS_TRUE.strict_encode(e)?,
-            Policy::Key(pk) => strict_encode_seq!(e; MS_KEY, pk),
-            Policy::After(tl) => strict_encode_seq!(e; MS_AFTER, tl),
-            Policy::Older(tl) => strict_encode_seq!(e; MS_OLDER, tl),
-            Policy::Sha256(hash) => strict_encode_seq!(e; MS_SHA256, hash),
-            Policy::Hash256(hash) => strict_encode_seq!(e; MS_HASH256, hash),
-            Policy::Ripemd160(hash) => {
-                strict_encode_seq!(e; MS_RIPEMD160, hash)
-            }
-            Policy::Hash160(hash) => strict_encode_seq!(e; MS_HASH160, hash),
-            Policy::And(ast) => strict_encode_seq!(e; MS_AND_B, ast),
-            Policy::Or(ast) => strict_encode_seq!(e; MS_OR_B, ast),
-            Policy::Threshold(thresh, vec) => {
-                strict_encode_seq!(e; MS_THRESH, thresh, vec)
+        // We need this because of the need to control maximum number of nested
+        // miniscript code
+        fn encode_policy_inner<Pk>(
+            policy: &Policy<Pk>,
+            e: &mut impl io::Write,
+            mut depth: u8,
+        ) -> Result<usize, Error>
+        where
+            Pk: MiniscriptKey + StrictEncode,
+            <Pk as MiniscriptKey>::Hash: StrictEncode,
+        {
+            if depth > MINISCRIPT_DEPTH_LIMIT {
+                return Err(Error::ExceedMaxItems(
+                    MINISCRIPT_DEPTH_LIMIT as usize,
+                ));
             }
-        })
+            depth += 1;
+
+            Ok(match policy {
+                Policy::Unsatisfiable => MS_FALSE.strict_encode(e)?,
+                Policy::Trivial => MS_TRUE.strict_encode(e)?,
+                Policy::Key(pk) => strict_encode_tuple!(e; MS_KEY, pk),
+                Policy::After(tl) => strict_encode_tuple!(e; MS_AFTER, tl),
+                Policy::Older(tl) => strict_encode_tuple!(e; MS_OLDER, tl),
+                Policy::Sha256(hash) => {
+                    strict_encode_tuple!(e; MS_SHA256, hash)
+                }
+                Policy::Hash256(hash) => {
+                    strict_encode_tuple!(e; MS_HASH256, hash)
+                }
+                Policy::Ripemd160(hash) => {
+                    strict_encode_tuple!(e; MS_RIPEMD160, hash)
+                }
+                Policy::Hash160(hash) => {
+                    strict_encode_tuple!(e; MS_HASH160, hash)
+                }
+                Policy::And(vec) => {
+                    let mut len = 1usize;
+                    e.write_all(&[MS_AND_B])?;
+                    len += strict_encode_usize!(e; vec.len());
+                    for p in vec {
+                        len += encode_policy_inner(p, e, depth)?;
+                    }
+                    len
+                }
+                Policy::Or(vec) => {
+                    let mut len = 1usize;
+                    e.write_all(&[MS_OR_B])?;
+                    len += strict_encode_usize!(e; vec.len());
+                    for (x, p) in vec {
+                        len += strict_encode_usize!(e; *x);
+                        len += encode_policy_inner(p, e, depth)?;
+                    }
+                    len
+                }
+                Policy::Threshold(thresh, vec) => {
+                    let mut len = 1usize;
+                    e.write_all(&[MS_THRESH])?;
+                    len += strict_encode_usize!(e; *thresh);
+                    len += strict_encode_usize!(e; vec.len());
+                    for p in vec {
+                        len += encode_policy_inner(p, e, depth)?;
+                    }
+                    len
+                }
+            })
+        }
+
+        encode_policy_inner(self, &mut e, 1)
     }
 }
 
-fn decode_policy<Pk>(d: &mut impl io::Read) -> Result<Policy<Pk>, Error>
+impl<Pk> StrictDecode for Policy<Pk>
 where
     Pk: MiniscriptKey + StrictDecode,
     <Pk as MiniscriptKey>::Hash: StrictDecode,
 {
-    let byte = d.read_u8()?;
-    Ok(match byte {
-        MS_TRUE => Policy::Trivial,
-        MS_FALSE => Policy::Unsatisfiable,
-        MS_KEY => Policy::Key(Pk::strict_decode(d)?),
-        MS_AFTER => Policy::After(u32::strict_decode(d)?),
-        MS_OLDER => Policy::Older(u32::strict_decode(d)?),
-        MS_SHA256 => Policy::Sha256(sha256::Hash::strict_decode(d)?),
-        MS_HASH256 => Policy::Hash256(sha256d::Hash::strict_decode(d)?),
-        MS_RIPEMD160 => Policy::Ripemd160(ripemd160::Hash::strict_decode(d)?),
-        MS_HASH160 => Policy::Hash160(hash160::Hash::strict_decode(d)?),
-        MS_AND_B => {
-            let len = d.read_u16()?;
-            let mut vec = Vec::with_capacity(len as usize);
-            for _ in 0..len {
-                vec.push(decode_policy(d)?);
-            }
-            Policy::And(vec)
-        }
-        MS_OR_B => {
-            let len = d.read_u16()?;
-            let mut vec = Vec::with_capacity(len as usize);
-            for _ in 0..len {
-                vec.push((d.read_u16()? as usize, decode_policy(d)?));
-            }
-            Policy::Or(vec)
-        }
-        MS_THRESH => {
-            let thresh = d.read_u16()? as usize;
-            let len = d.read_u16()?;
-            let mut vec = Vec::with_capacity(len as usize);
-            for _ in 0..len {
-                vec.push(decode_policy(d)?);
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        // We need this first because of the need to control maximum number of
+        // nested miniscript code, and than because of the rust compiler
+        // limitations in working with recursive generic functions
+        fn decode_policy_inner<Pk>(
+            d: &mut impl io::Read,
+            mut depth: u8,
+        ) -> Result<Policy<Pk>, Error>
+        where
+            Pk: MiniscriptKey + StrictDecode,
+            <Pk as MiniscriptKey>::Hash: StrictDecode,
+        {
+            if depth > MINISCRIPT_DEPTH_LIMIT {
+                return Err(Error::ExceedMaxItems(
+                    MINISCRIPT_DEPTH_LIMIT as usize,
+                ));
             }
-            Policy::Threshold(thresh, vec)
-        }
+            depth += 1;
+
+            let byte = d.read_u8()?;
+            Ok(match byte {
+                MS_TRUE => Policy::Trivial,
+                MS_FALSE => Policy::Unsatisfiable,
+                MS_KEY => Policy::Key(Pk::strict_decode(d)?),
+                MS_AFTER => Policy::After(u32::strict_decode(d)?),
+                MS_OLDER => Policy::Older(u32::strict_decode(d)?),
+                MS_SHA256 => Policy::Sha256(sha256::Hash::strict_decode(d)?),
+                MS_HASH256 => Policy::Hash256(sha256d::Hash::strict_decode(d)?),
+                MS_RIPEMD160 => Policy::Ripemd160(ripemd160::Hash::strict_decode(d)?),
+                MS_HASH160 => Policy::Hash160(hash160::Hash::strict_decode(d)?),
+                MS_AND_B => {
+                    let len = d.read_u16()?;
+                    let mut vec = Vec::with_capacity(len as usize);
+                    for _ in 0..len {
+                        vec.push(decode_policy_inner(d, depth)?);
+                    }
+                    Policy::And(vec)
+                }
+                MS_OR_B => {
+                    let len = d.read_u16()?;
+                    let mut vec = Vec::with_capacity(len as usize);
+                    for _ in 0..len {
+                        vec.push((d.read_u16()? as usize, decode_policy_inner(d, depth)?));
+                    }
+                    Policy::Or(vec)
+                }
+                MS_THRESH => {
+                    let thresh = d.read_u16()? as usize;
+                    let len = d.read_u16()?;
+                    let mut vec = Vec::with_capacity(len as usize);
+                    for _ in 0..len {
+                        vec.push(decode_policy_inner(d, depth)?);
+                    }
+                    Policy::Threshold(thresh, vec)
+                }
 
-        MS_KEY_HASH | MS_ALT | MS_SWAP | MS_CHECK | MS_DUP_IF | MS_VERIFY
-        | MS_NON_ZERO | MS_ZERO_NE | MS_AND_V | MS_AND_OR | MS_OR_D
-        | MS_OR_C | MS_OR_I | MS_MULTI => {
-            return Err(Error::DataIntegrityError(format!(
-                "byte {:#04X} is a valid miniscript instruction, but does  \
+                MS_KEY_HASH | MS_ALT | MS_SWAP | MS_CHECK | MS_DUP_IF | MS_VERIFY
+                | MS_NON_ZERO | MS_ZERO_NE | MS_AND_V | MS_AND_OR | MS_OR_D
+                | MS_OR_C | MS_OR_I | MS_MULTI => {
+                    return Err(Error::DataIntegrityError(format!(
+                        "byte {:#04X} is a valid miniscript instruction, but does  \
                      not belong to a set of concrete policy instructions. Try \
                      to decode data using different miniscript type",
-                byte
-            )))
-        }
+                        byte
+                    )))
+                }
 
-        wrong => {
-            return Err(Error::DataIntegrityError(format!(
-                "byte {:#04X} does not correspond to any of miniscript \
+                wrong => {
+                    return Err(Error::DataIntegrityError(format!(
+                        "byte {:#04X} does not correspond to any of miniscript \
                      concrete policy instructions",
-                wrong
-            )))
+                        wrong
+                    )))
+                }
+            })
         }
-    })
-}
 
-impl<Pk> StrictDecode for Policy<Pk>
-where
-    Pk: MiniscriptKey + StrictDecode,
-    <Pk as MiniscriptKey>::Hash: StrictDecode,
-{
-    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
-        decode_policy(&mut d)
+        decode_policy_inner(&mut d, 1)
     }
 }
 
-// We need this shit because of rust compiler generic limitations
-fn encode_miniscript<Pk, Ctx>(
-    ms: &Miniscript<Pk, Ctx>,
-    e: &mut impl io::Write,
-    mut depth: u8,
-) -> Result<usize, Error>
+impl<Pk, Ctx> StrictEncode for Miniscript<Pk, Ctx>
 where
-    Pk: MiniscriptKey,
     Pk: MiniscriptKey + StrictEncode,
     <Pk as MiniscriptKey>::Hash: StrictEncode,
     Ctx: miniscript::ScriptContext,
 {
-    if depth > 64 {
-        return Err(Error::ExceedMaxItems(65));
-    }
-    depth += 1;
-    Ok(match &ms.node {
-        Terminal::False => MS_FALSE.strict_encode(e)?,
-        Terminal::True => MS_TRUE.strict_encode(e)?,
-        Terminal::PkK(pk) => strict_encode_seq!(e; MS_KEY, pk),
-        Terminal::PkH(hash) => strict_encode_seq!(e; MS_KEY_HASH, hash),
-        Terminal::After(tl) => strict_encode_seq!(e; MS_AFTER, tl),
-        Terminal::Older(tl) => strict_encode_seq!(e; MS_OLDER, tl),
-        Terminal::Sha256(hash) => strict_encode_seq!(e; MS_SHA256, hash),
-        Terminal::Hash256(hash) => strict_encode_seq!(e; MS_HASH256, hash),
-        Terminal::Ripemd160(hash) => {
-            strict_encode_seq!(e; MS_RIPEMD160, hash)
-        }
-        Terminal::Hash160(hash) => strict_encode_seq!(e; MS_HASH160, hash),
-
-        Terminal::Alt(ms) => strict_encode_ms!(e; MS_ALT, depth, ms),
-        Terminal::Swap(ms) => strict_encode_ms!(e; MS_SWAP, depth, ms),
-        Terminal::Check(ms) => strict_encode_ms!(e; MS_CHECK, depth, ms),
-        Terminal::DupIf(ms) => strict_encode_ms!(e; MS_DUP_IF, depth, ms),
-        Terminal::Verify(ms) => strict_encode_ms!(e; MS_VERIFY, depth, ms),
-        Terminal::NonZero(ms) => strict_encode_ms!(e; MS_NON_ZERO, depth, ms),
-        Terminal::ZeroNotEqual(ms) => {
-            strict_encode_ms!(e; MS_ZERO_NE, depth, ms)
+    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
+        // We need this macro and inner function because of the need to control
+        // maximum number of nested miniscript code, and because of the rust
+        // compiler limitations in working with recursive generic functions
+
+        macro_rules! strict_encode_ms {
+            ($encoder:ident; $tag:ident, $depth:expr, $($ms:expr),+) => { {
+                let mut len = 1usize;
+                $encoder.write_all(&[$tag])?;
+                $( len += encode_miniscript_inner($ms, $encoder, $depth)?; )+
+                len
+            } };
         }
 
-        Terminal::AndV(ms1, ms2) => {
-            strict_encode_ms!(e; MS_AND_V, depth, ms1, ms2)
-        }
-        Terminal::AndB(ms1, ms2) => {
-            strict_encode_ms!(e; MS_AND_B, depth, ms1, ms2)
-        }
-        Terminal::AndOr(ms1, ms2, ms3) => {
-            strict_encode_ms!(e; MS_AND_OR, depth, ms1, ms2, ms3)
-        }
-        Terminal::OrB(ms1, ms2) => {
-            strict_encode_ms!(e; MS_OR_B, depth, ms1, ms2)
-        }
-        Terminal::OrD(ms1, ms2) => {
-            strict_encode_ms!(e; MS_OR_D, depth, ms1, ms2)
-        }
-        Terminal::OrC(ms1, ms2) => {
-            strict_encode_ms!(e; MS_OR_C, depth, ms1, ms2)
-        }
-        Terminal::OrI(ms1, ms2) => {
-            strict_encode_ms!(e; MS_OR_I, depth, ms1, ms2)
-        }
-        Terminal::Multi(thresh, vec) => {
-            strict_encode_seq!(e; MS_MULTI, thresh, vec)
-        }
-        Terminal::Thresh(thresh, vec) => {
-            let mut len = 0usize;
-            len += strict_encode_seq!(e; MS_THRESH, thresh, vec.len());
-            for ms in vec {
-                len += encode_miniscript(ms, e, depth)?;
+        fn encode_miniscript_inner<Pk, Ctx>(
+            ms: &Miniscript<Pk, Ctx>,
+            mut e: &mut impl io::Write,
+            mut depth: u8,
+        ) -> Result<usize, Error>
+        where
+            Pk: MiniscriptKey + StrictEncode,
+            <Pk as MiniscriptKey>::Hash: StrictEncode,
+            Ctx: miniscript::ScriptContext,
+        {
+            if depth > MINISCRIPT_DEPTH_LIMIT {
+                return Err(Error::ExceedMaxItems(
+                    MINISCRIPT_DEPTH_LIMIT as usize,
+                ));
             }
-            len
+            depth += 1;
+
+            Ok(match &ms.node {
+                Terminal::False => MS_FALSE.strict_encode(e)?,
+                Terminal::True => MS_TRUE.strict_encode(e)?,
+                Terminal::PkK(pk) => strict_encode_tuple!(e; MS_KEY, pk),
+                Terminal::PkH(hash) => {
+                    strict_encode_tuple!(e; MS_KEY_HASH, hash)
+                }
+                Terminal::After(tl) => strict_encode_tuple!(e; MS_AFTER, tl),
+                Terminal::Older(tl) => strict_encode_tuple!(e; MS_OLDER, tl),
+                Terminal::Sha256(hash) => {
+                    strict_encode_tuple!(e; MS_SHA256, hash)
+                }
+                Terminal::Hash256(hash) => {
+                    strict_encode_tuple!(e; MS_HASH256, hash)
+                }
+                Terminal::Ripemd160(hash) => {
+                    strict_encode_tuple!(e; MS_RIPEMD160, hash)
+                }
+                Terminal::Hash160(hash) => {
+                    strict_encode_tuple!(e; MS_HASH160, hash)
+                }
+
+                Terminal::Alt(ms) => strict_encode_ms!(e; MS_ALT, depth, ms),
+                Terminal::Swap(ms) => strict_encode_ms!(e; MS_SWAP, depth, ms),
+                Terminal::Check(ms) => {
+                    strict_encode_ms!(e; MS_CHECK, depth, ms)
+                }
+                Terminal::DupIf(ms) => {
+                    strict_encode_ms!(e; MS_DUP_IF, depth, ms)
+                }
+                Terminal::Verify(ms) => {
+                    strict_encode_ms!(e; MS_VERIFY, depth, ms)
+                }
+                Terminal::NonZero(ms) => {
+                    strict_encode_ms!(e; MS_NON_ZERO, depth, ms)
+                }
+                Terminal::ZeroNotEqual(ms) => {
+                    strict_encode_ms!(e; MS_ZERO_NE, depth, ms)
+                }
+
+                Terminal::AndV(ms1, ms2) => {
+                    strict_encode_ms!(e; MS_AND_V, depth, ms1, ms2)
+                }
+                Terminal::AndB(ms1, ms2) => {
+                    strict_encode_ms!(e; MS_AND_B, depth, ms1, ms2)
+                }
+                Terminal::AndOr(ms1, ms2, ms3) => {
+                    strict_encode_ms!(e; MS_AND_OR, depth, ms1, ms2, ms3)
+                }
+                Terminal::OrB(ms1, ms2) => {
+                    strict_encode_ms!(e; MS_OR_B, depth, ms1, ms2)
+                }
+                Terminal::OrD(ms1, ms2) => {
+                    strict_encode_ms!(e; MS_OR_D, depth, ms1, ms2)
+                }
+                Terminal::OrC(ms1, ms2) => {
+                    strict_encode_ms!(e; MS_OR_C, depth, ms1, ms2)
+                }
+                Terminal::OrI(ms1, ms2) => {
+                    strict_encode_ms!(e; MS_OR_I, depth, ms1, ms2)
+                }
+                Terminal::Multi(thresh, vec) => {
+                    let mut len = 1usize;
+                    e.write_all(&[MS_MULTI])?;
+                    len += strict_encode_usize!(e; *thresh);
+                    len += strict_encode_usize!(e; vec.len());
+                    for pk in vec {
+                        len += pk.strict_encode(&mut e)?;
+                    }
+                    len
+                }
+                Terminal::Thresh(thresh, vec) => {
+                    let mut len = 1usize;
+                    e.write_all(&[MS_THRESH])?;
+                    len += strict_encode_usize!(e; *thresh);
+                    len += strict_encode_usize!(e; vec.len());
+                    for ms in vec {
+                        len += encode_miniscript_inner(ms, e, depth)?;
+                    }
+                    len
+                }
+            })
         }
-    })
-}
 
-impl<Pk, Ctx> StrictEncode for Miniscript<Pk, Ctx>
-where
-    Pk: MiniscriptKey,
-    Pk: MiniscriptKey + StrictEncode,
-    <Pk as MiniscriptKey>::Hash: StrictEncode,
-    Ctx: miniscript::ScriptContext,
-{
-    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
-        encode_miniscript(self, &mut e, 1)
+        encode_miniscript_inner(self, &mut e, 1)
     }
 }
 
-fn decode_miniscript<Pk, Ctx>(
-    d: &mut impl io::Read,
-    mut depth: u8,
-) -> Result<Miniscript<Pk, Ctx>, Error>
+impl<Pk, Ctx> StrictDecode for Miniscript<Pk, Ctx>
 where
-    Pk: MiniscriptKey,
     Pk: MiniscriptKey + StrictDecode,
     <Pk as MiniscriptKey>::Hash: StrictDecode,
     Ctx: miniscript::ScriptContext,
 {
-    if depth > 64 {
-        return Err(Error::ExceedMaxItems(65));
-    }
-    depth += 1;
-    let term = match d.read_u8()? {
-        MS_TRUE => Terminal::True,
-        MS_FALSE => Terminal::False,
-        MS_KEY => Terminal::PkK(Pk::strict_decode(d)?),
-        MS_KEY_HASH => Terminal::PkH(Pk::Hash::strict_decode(d)?),
-
-        MS_AFTER => Terminal::After(u32::strict_decode(d)?),
-        MS_OLDER => Terminal::Older(u32::strict_decode(d)?),
-        MS_SHA256 => Terminal::Sha256(sha256::Hash::strict_decode(d)?),
-        MS_HASH256 => {
-            Terminal::Hash256(sha256d::Hash::strict_decode(d)?)
-        }
-        MS_RIPEMD160 => {
-            Terminal::Ripemd160(ripemd160::Hash::strict_decode(d)?)
-        }
-        MS_HASH160 => {
-            Terminal::Hash160(hash160::Hash::strict_decode(d)?)
-        }
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        // We need this first because of the need to control maximum number of
+        // nested miniscript code, and than because of the rust compiler
+        // limitations in working with recursive generic functions
+        fn decode_miniscript_inner<Pk, Ctx>(
+            d: &mut impl io::Read,
+            mut depth: u8,
+        ) -> Result<Miniscript<Pk, Ctx>, Error>
+        where
+            Pk: MiniscriptKey + StrictDecode,
+            <Pk as MiniscriptKey>::Hash: StrictDecode,
+            Ctx: miniscript::ScriptContext,
+        {
+            if depth > MINISCRIPT_DEPTH_LIMIT {
+                return Err(Error::ExceedMaxItems(
+                    MINISCRIPT_DEPTH_LIMIT as usize,
+                ));
+            }
+            depth += 1;
+            let term = match d.read_u8()? {
+                MS_TRUE => Terminal::True,
+                MS_FALSE => Terminal::False,
+                MS_KEY => Terminal::PkK(Pk::strict_decode(d)?),
+                MS_KEY_HASH => Terminal::PkH(Pk::Hash::strict_decode(d)?),
+
+                MS_AFTER => Terminal::After(u32::strict_decode(d)?),
+                MS_OLDER => Terminal::Older(u32::strict_decode(d)?),
+                MS_SHA256 => Terminal::Sha256(sha256::Hash::strict_decode(d)?),
+                MS_HASH256 => {
+                    Terminal::Hash256(sha256d::Hash::strict_decode(d)?)
+                }
+                MS_RIPEMD160 => {
+                    Terminal::Ripemd160(ripemd160::Hash::strict_decode(d)?)
+                }
+                MS_HASH160 => {
+                    Terminal::Hash160(hash160::Hash::strict_decode(d)?)
+                }
 
-        MS_ALT => Terminal::Alt(decode_miniscript(d, depth)?.into()),
-        MS_SWAP => Terminal::Swap(decode_miniscript(d, depth)?.into()),
-        MS_CHECK => Terminal::Check(decode_miniscript(d, depth)?.into()),
-        MS_DUP_IF => Terminal::DupIf(decode_miniscript(d, depth)?.into()),
-        MS_VERIFY => Terminal::Verify(decode_miniscript(d, depth)?.into()),
-        MS_NON_ZERO => {
-            Terminal::NonZero(decode_miniscript(d, depth)?.into())
-        }
-        MS_ZERO_NE => {
-            Terminal::ZeroNotEqual(decode_miniscript(d, depth)?.into())
-        }
+                MS_ALT => Terminal::Alt(decode_miniscript_inner(d, depth)?.into()),
+                MS_SWAP => Terminal::Swap(decode_miniscript_inner(d, depth)?.into()),
+                MS_CHECK => Terminal::Check(decode_miniscript_inner(d, depth)?.into()),
+                MS_DUP_IF => Terminal::DupIf(decode_miniscript_inner(d, depth)?.into()),
+                MS_VERIFY => Terminal::Verify(decode_miniscript_inner(d, depth)?.into()),
+                MS_NON_ZERO => {
+                    Terminal::NonZero(decode_miniscript_inner(d, depth)?.into())
+                }
+                MS_ZERO_NE => {
+                    Terminal::ZeroNotEqual(decode_miniscript_inner(d, depth)?.into())
+                }
 
-        MS_AND_V => Terminal::AndV(
-            decode_miniscript(d, depth)?.into(),
-            decode_miniscript(d, depth)?.into(),
-        ),
-        MS_AND_B => Terminal::AndB(
-            decode_miniscript(d, depth)?.into(),
-            decode_miniscript(d, depth)?.into(),
-        ),
-        MS_AND_OR => Terminal::AndOr(
-            decode_miniscript(d, depth)?.into(),
-            decode_miniscript(d, depth)?.into(),
-            decode_miniscript(d, depth)?.into(),
-        ),
-        MS_OR_B => Terminal::OrB(
-            decode_miniscript(d, depth)?.into(),
-            decode_miniscript(d, depth)?.into(),
-        ),
-        MS_OR_D => Terminal::OrD(
-            decode_miniscript(d, depth)?.into(),
-            decode_miniscript(d, depth)?.into(),
-        ),
-        MS_OR_C => Terminal::OrC(
-            decode_miniscript(d, depth)?.into(),
-            decode_miniscript(d, depth)?.into(),
-        ),
-        MS_OR_I => Terminal::OrI(
-            decode_miniscript(d, depth)?.into(),
-            decode_miniscript(d, depth)?.into(),
-        ),
-        MS_MULTI => Terminal::Multi(
-            d.read_u16()? as usize,
-            Vec::strict_decode(d)?,
-        ),
-        MS_THRESH => {
-            let thresh = d.read_u16()? as usize;
-            let len = d.read_u16()?;
-            let mut vec = Vec::with_capacity(len as usize);
-            for _ in 0..len {
-                vec.push(decode_miniscript(d, depth)?.into());
-            }
-            Terminal::Thresh(thresh, vec)
-        },
-
-        wrong => {
-            return Err(Error::DataIntegrityError(format!(
-                "byte {:#04X} does not correspond to any of miniscript instructions",
-                wrong
-            )))
+                MS_AND_V => Terminal::AndV(
+                    decode_miniscript_inner(d, depth)?.into(),
+                    decode_miniscript_inner(d, depth)?.into(),
+                ),
+                MS_AND_B => Terminal::AndB(
+                    decode_miniscript_inner(d, depth)?.into(),
+                    decode_miniscript_inner(d, depth)?.into(),
+                ),
+                MS_AND_OR => Terminal::AndOr(
+                    decode_miniscript_inner(d, depth)?.into(),
+                    decode_miniscript_inner(d, depth)?.into(),
+                    decode_miniscript_inner(d, depth)?.into(),
+                ),
+                MS_OR_B => Terminal::OrB(
+                    decode_miniscript_inner(d, depth)?.into(),
+                    decode_miniscript_inner(d, depth)?.into(),
+                ),
+                MS_OR_D => Terminal::OrD(
+                    decode_miniscript_inner(d, depth)?.into(),
+                    decode_miniscript_inner(d, depth)?.into(),
+                ),
+                MS_OR_C => Terminal::OrC(
+                    decode_miniscript_inner(d, depth)?.into(),
+                    decode_miniscript_inner(d, depth)?.into(),
+                ),
+                MS_OR_I => Terminal::OrI(
+                    decode_miniscript_inner(d, depth)?.into(),
+                    decode_miniscript_inner(d, depth)?.into(),
+                ),
+                MS_MULTI => Terminal::Multi(
+                    d.read_u16()? as usize,
+                    Vec::strict_decode(d)?,
+                ),
+                MS_THRESH => {
+                    let thresh = d.read_u16()? as usize;
+                    let len = d.read_u16()?;
+                    let mut vec = Vec::with_capacity(len as usize);
+                    for _ in 0..len {
+                        vec.push(decode_miniscript_inner(d, depth)?.into());
+                    }
+                    Terminal::Thresh(thresh, vec)
+                },
+
+                wrong => {
+                    return Err(Error::DataIntegrityError(format!(
+                        "byte {:#04X} does not correspond to any of miniscript instructions",
+                        wrong
+                    )))
+                }
+            };
+            Miniscript::from_ast(term).map_err(|err| {
+                Error::DataIntegrityError(format!(
+                    "miniscript does not pass check: {}",
+                    err
+                ))
+            })
         }
-    };
-    Miniscript::from_ast(term).map_err(|err| {
-        Error::DataIntegrityError(format!(
-            "miniscript does not pass check: {}",
-            err
-        ))
-    })
-}
 
-impl<Pk, Ctx> StrictDecode for Miniscript<Pk, Ctx>
-where
-    Pk: MiniscriptKey + StrictDecode,
-    <Pk as MiniscriptKey>::Hash: StrictDecode,
-    Ctx: miniscript::ScriptContext,
-{
-    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
-        decode_miniscript(&mut d, 1)
+        decode_miniscript_inner(&mut d, 1)
     }
 }
 
diff --git a/strict_encoding/src/net.rs b/strict_encoding/src/net.rs
index 2420e74d..9e4534ee 100644
--- a/strict_encoding/src/net.rs
+++ b/strict_encoding/src/net.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/pointers.rs b/strict_encoding/src/pointers.rs
index 483a7ae7..d43b5f8e 100644
--- a/strict_encoding/src/pointers.rs
+++ b/strict_encoding/src/pointers.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/primitives.rs b/strict_encoding/src/primitives.rs
index 36e6dc90..04ad0631 100644
--- a/strict_encoding/src/primitives.rs
+++ b/strict_encoding/src/primitives.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/strategies.rs b/strict_encoding/src/strategies.rs
index 8a71ab85..4775ea25 100644
--- a/strict_encoding/src/strategies.rs
+++ b/strict_encoding/src/strategies.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>


Test patch:
diff --git a/strict_encoding/derive/examples/test.rs b/strict_encoding/derive/examples/test.rs
index 21c4634c..e05f8017 100644
--- a/strict_encoding/derive/examples/test.rs
+++ b/strict_encoding/derive/examples/test.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
diff --git a/strict_encoding/src/test_helpers.rs b/strict_encoding/src/test_helpers.rs
index f18f175e..c9627ebd 100644
--- a/strict_encoding/src/test_helpers.rs
+++ b/strict_encoding/src/test_helpers.rs
@@ -1,5 +1,5 @@
-// LNP/BP client-side-validation library implementing respective LNPBP
-// specifications & standards (LNPBP-7, 8, 9, 42)
+// LNP/BP client-side-validation foundation libraries implementing LNPBP
+// specifications & standards (LNPBP-4, 7, 8, 9, 42, 81)
 //
 // Written in 2019-2021 by
 //     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>