diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c35cae99..1449ecda 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,12 @@ on: branches: - master tags: - - 'v[0-9]+\.*' + - 'v[0-9]+.*' pull_request: branches: - master - develop - - 'v[0-9]+.[0-9]+' + - 'v[0-9]+.?*' env: CARGO_TERM_COLOR: always @@ -33,7 +33,7 @@ jobs: strategy: fail-fast: false matrix: - feature: [ derive, rand, stl, serde ] + feature: [ derive, rand, stl, vesper, serde ] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 3711d7ad..9ba863f5 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -5,12 +5,12 @@ on: branches: - master tags: - - 'v[0-9]+\.*' + - 'v[0-9]+.*' pull_request: branches: - master - develop - - 'v[0-9]+.[0-9]+' + - 'v[0-9]+.?*' env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fb630fb0..91f36b69 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,7 +5,7 @@ on: branches: - master - develop - - 'v[0-9]+.[0-9]+' + - 'v[0-9]+.?*' env: CARGO_TERM_COLOR: always @@ -38,3 +38,8 @@ jobs: components: rust-docs - name: Formatting run: cargo +nightly doc --workspace --all-features + typos: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: crate-ci/typos@master diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 503ed120..e7e1eb1b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,12 +5,12 @@ on: branches: - master tags: - - 'v[0-9]+\.*' + - 'v[0-9]+.*' pull_request: branches: - master - develop - - 'v[0-9]+.[0-9]+' + - 'v[0-9]+.?*' env: CARGO_TERM_COLOR: always @@ -36,4 +36,4 @@ jobs: - name: Add wasm32 target run: rustup target add wasm32-unknown-unknown - name: Test in headless Chrome - run: wasm-pack test --headless --chrome + run: RUSTFLAGS='--cfg getrandom_backend="wasm_js"' wasm-pack test --headless --chrome diff --git a/Cargo.lock b/Cargo.lock index 3c53ed64..141361bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,14 +4,16 @@ version = 3 [[package]] name = "amplify" -version = "4.8.1" +version = "4.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9d7cb29f1d4c6ec8650abbee35948b8bdefb7f0750a26445ff593eb9bf7fcf" +checksum = "3f7fb4ac7c881e54a8e7015e399b6112a2a5bc958b6c89ac510840ff20273b31" dependencies = [ "amplify_derive", "amplify_num", "amplify_syn", "ascii", + "getrandom 0.2.16", + "getrandom 0.3.3", "serde", "stringly_conversions", "wasm-bindgen", @@ -99,6 +101,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + [[package]] name = "block-buffer" version = "0.10.4" @@ -116,9 +124,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "cc" -version = "1.2.22" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "shlex", ] @@ -157,7 +165,8 @@ version = "0.12.0-beta.6" dependencies = [ "amplify", "commit_encoding_derive", - "getrandom", + "getrandom 0.2.16", + "getrandom 0.3.3", "rand", "ripemd", "serde", @@ -223,7 +232,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", ] @@ -326,22 +349,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" -version = "0.8.5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ - "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -349,11 +377,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.3", ] [[package]] @@ -437,11 +465,13 @@ dependencies = [ [[package]] name = "strict_encoding" -version = "2.8.2" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8553c0321466c11aa1e33f082c9190194f380efd8824bf5ce4fa56b64b875be9" +checksum = "5b8d9721d221d797767f8d5ad72b3a34dcdb5b9d646fe76248ee0f5e6fa6ca89" dependencies = [ "amplify", + "getrandom 0.2.16", + "getrandom 0.3.3", "strict_encoding_derive", "wasm-bindgen", ] @@ -461,13 +491,15 @@ dependencies = [ [[package]] name = "strict_types" -version = "2.8.3" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07dd1bdf4bfce0a1ff3eec041e7d4d20b06d22ca2aaf71338726dd9609e57a5e" +checksum = "493821521630a023d79a210033fe1f5135ae3d3f8fccd8dbf1a98b7dfe56b144" dependencies = [ "amplify", "ascii-armor", "baid64", + "getrandom 0.2.16", + "getrandom 0.3.3", "indexmap", "sha2", "strict_encoding", @@ -571,6 +603,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -758,6 +799,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "zerocopy" version = "0.8.25" diff --git a/Cargo.toml b/Cargo.toml index 1eeba168..1ba173f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,9 @@ edition = "2021" license = "Apache-2.0" [workspace.dependencies] -amplify = "~4.8.0" -strict_encoding = "~2.8.2" -strict_types = "~2.8.3" +amplify = "~4.9.0" +strict_encoding = "~2.9.1" +strict_types = "~2.9.0" serde = { version = "1", features = ["derive"] } [package] @@ -53,11 +53,15 @@ serde = { workspace = true, optional = true } [features] default = ["derive"] -all = ["serde", "rand", "stl"] -stl = ["commit_verify/stl"] -rand = ["commit_verify/rand"] +all = ["serde", "rand", "stl", "vesper"] derive = ["commit_verify/derive"] +rand = ["commit_verify/rand"] +stl = ["commit_verify/stl"] +vesper = ["commit_verify/vesper"] serde = ["dep:serde", "commit_verify/serde"] [package.metadata.docs.rs] features = ["all"] + +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/LICENSE b/LICENSE index 5987cb5c..d9a10c0d 100644 --- a/LICENSE +++ b/LICENSE @@ -174,28 +174,3 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2019-2024 LNP/BP Standards Association, Switzerland - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000..0c6e78b7 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,13 @@ +[files] +# exclude the directory "stl/" +extend-exclude = ["stl/"] + +[default.extend-words] +# Don't correct the name "Jon Atack". +Atack = "Atack" + +[default] +extend-ignore-re = [ + # Don't correct URIs + "([a-z]+:)*[$!0-9A-Za-z-]+", +] diff --git a/codecov.yml b/codecov.yml index 453306aa..3d2619df 100644 --- a/codecov.yml +++ b/codecov.yml @@ -4,7 +4,7 @@ codecov: coverage: precision: 1 round: nearest - range: "0...95" + range: "0...80" status: project: default: diff --git a/commit_verify/Cargo.toml b/commit_verify/Cargo.toml index 9536fbe4..70b27a72 100644 --- a/commit_verify/Cargo.toml +++ b/commit_verify/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" [[bin]] name = "commit-stl" -required-features = ["stl"] +required-features = ["stl", "vesper"] [dependencies] amplify = { workspace = true } @@ -29,27 +29,32 @@ vesper-lang = "0.2.1" commit_encoding_derive = { version = "0.12.0-beta.4", path = "derive" } sha2 = "0.10.8" ripemd = "0.1.3" -rand = { version = "0.8.5", optional = true } +rand = { version = "0.9.1", optional = true } serde = { version = "1.0", package = "serde", optional = true } [dev-dependencies] -rand = "0.8.5" +rand = "0.9.1" [features] default = ["derive"] -all = ["rand", "serde", "stl", "derive"] +all = ["rand", "derive", "stl", "vesper", "serde"] +derive = [] rand = ["dep:rand"] -serde = ["dep:serde", "amplify/serde"] stl = ["strict_types/armor"] -derive = [] +vesper = [] +serde = ["dep:serde", "amplify/serde"] [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" -rand = { version = "0.8.5", optional = true } -getrandom = { version = "0.2", features = ["js"] } +rand = { version = "0.9.1", optional = true } +getrandom = { version = "0.3", features = ["wasm_js"] } +getrandom2 = { package = "getrandom", version = "0.2", features = ["js"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3" [package.metadata.docs.rs] features = ["all"] + +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/commit_verify/LICENSE b/commit_verify/LICENSE index a6a3b8bc..d9a10c0d 100644 --- a/commit_verify/LICENSE +++ b/commit_verify/LICENSE @@ -174,28 +174,3 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2019-2022 LNP/BP Standards Association, Switzerland - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/commit_verify/derive/src/derive.rs b/commit_verify/derive/src/derive.rs index 797cd720..3e9d5be3 100644 --- a/commit_verify/derive/src/derive.rs +++ b/commit_verify/derive/src/derive.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. use proc_macro2::TokenStream as TokenStream2; use syn::Result; diff --git a/commit_verify/derive/src/lib.rs b/commit_verify/derive/src/lib.rs index 433b6149..90721651 100644 --- a/commit_verify/derive/src/lib.rs +++ b/commit_verify/derive/src/lib.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. // Coding conventions #![recursion_limit = "256"] diff --git a/commit_verify/derive/src/params.rs b/commit_verify/derive/src/params.rs index a1da3f64..a14b93a9 100644 --- a/commit_verify/derive/src/params.rs +++ b/commit_verify/derive/src/params.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. use amplify_syn::{ArgValueReq, AttrReq, DataType, ParametrizedAttr, TypeClass}; use proc_macro2::Span; diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs index ec9db444..3d57bf85 100644 --- a/commit_verify/derive/tests/base.rs +++ b/commit_verify/derive/tests/base.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. #![allow(unused_braces)] diff --git a/commit_verify/derive/tests/common/mod.rs b/commit_verify/derive/tests/common/mod.rs index a6bfda1c..0fa5adcd 100644 --- a/commit_verify/derive/tests/common/mod.rs +++ b/commit_verify/derive/tests/common/mod.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. use std::fmt::{Debug, Display, Formatter}; diff --git a/commit_verify/src/bin/commit-stl.rs b/commit_verify/src/bin/commit-stl.rs index 22e9737d..1b038343 100644 --- a/commit_verify/src/bin/commit-stl.rs +++ b/commit_verify/src/bin/commit-stl.rs @@ -12,6 +12,8 @@ // You should have received a copy of the Apache 2.0 License along with this // software. If not, see . +#![cfg_attr(coverage_nightly, feature(coverage_attribute), coverage(off))] + use std::fs; use std::io::Write; diff --git a/commit_verify/src/commit.rs b/commit_verify/src/commit.rs index e0aee7c6..0ced01cb 100644 --- a/commit_verify/src/commit.rs +++ b/commit_verify/src/commit.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. //! Base commit-verify scheme interface. @@ -80,6 +84,8 @@ where Self: Eq + Sized /// Helpers for writing test functions working with commit-verify scheme #[cfg(test)] pub(crate) mod test_helpers { + #![cfg_attr(coverage_nightly, coverage(off))] + use core::fmt::Debug; use core::hash::Hash; use std::collections::HashSet; diff --git a/commit_verify/src/conceal.rs b/commit_verify/src/conceal.rs index 0748d4e0..fb5a0eac 100644 --- a/commit_verify/src/conceal.rs +++ b/commit_verify/src/conceal.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. /// Trait that should perform conversion of a given client-side-validated data /// type into a concealed (private) form, for instance hiding some of the data diff --git a/commit_verify/src/convolve.rs b/commit_verify/src/convolve.rs index 2f722180..475261a1 100644 --- a/commit_verify/src/convolve.rs +++ b/commit_verify/src/convolve.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. //! Convolved commitments (convolve-commit-verify scheme). diff --git a/commit_verify/src/digest.rs b/commit_verify/src/digest.rs index 99b13e17..f7f77da9 100644 --- a/commit_verify/src/digest.rs +++ b/commit_verify/src/digest.rs @@ -2,30 +2,58 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. use amplify::num::u24; pub use ripemd::Ripemd160; pub use sha2::{Digest, Sha256}; +/// A generic cryptographic digest trait. pub trait DigestExt: Digest { + /// Initialize a diges with a given tag. fn from_tag(tag: impl AsRef<[u8]>) -> Self; + + /// Digest raw byte slice. fn input_raw(&mut self, data: &[u8]); + + /// Digest raw byte slice returning self. + fn with_raw(mut self, data: &[u8]) -> Self + where Self: Sized { + self.input_raw(data); + self + } + + /// Digest bytes, adding the data length to the digest (preventing length + /// extension attack). + /// + /// Returns self. + fn with_len(mut self, data: &[u8]) -> Self + where Self: Sized { + self.input_with_len::(data); + self + } + + /// Digest bytes, adding the data length to the digest (preventing length + /// extension attack). fn input_with_len(&mut self, data: &[u8]) { let len = data.len(); match MAX { @@ -37,6 +65,8 @@ pub trait DigestExt: Digest { } self.input_raw(data); } + + /// Compute the final cryptographic digest. fn finish(self) -> [u8; BYTE_LEN]; } diff --git a/commit_verify/src/embed.rs b/commit_verify/src/embed.rs index 3009e717..6c94e3a3 100644 --- a/commit_verify/src/embed.rs +++ b/commit_verify/src/embed.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. //! Embedded commitments (commit-embed-verify scheme). @@ -46,8 +50,8 @@ pub enum EmbedVerifyError { } /// Trait for equivalence verification. Implemented for all types implementing -/// `Eq`. For non-`Eq` types this trait provides way to implement custom -/// equivalence verification used during commitment verification procedure. +/// `Eq`. For non-`Eq` types this trait provides a way to implement custom +/// equivalence verification used during the commitment verification procedure. pub trait VerifyEq { /// Verifies commit-equivalence of two instances of the same type. fn verify_eq(&self, other: &Self) -> bool; @@ -80,27 +84,27 @@ where } /// Trait for *embed-commit-verify scheme*, where some data structure (named -/// *container*) may commit to existing *message* (producing *commitment* data -/// structure and a *proof*) in such way that the original message can't be -/// restored from the commitment, however the fact of the commitment may be +/// *container*) may commit to an existing *message* (producing *commitment* +/// data structure and a *proof*) in such a way that the original message can't +/// be restored from the commitment, however, the fact of the commitment may be /// deterministically *verified* when the message and the proof are *revealed*. /// -/// To use *embed-commit-verify scheme* one needs to implement this trait for -/// a data structure acting as a container for a specific commitment under -/// certain protocol, specified as generic parameter. The container type must -/// specify as associated types proof and commitment types. +/// To use the *embed-commit-verify scheme*, one needs to implement this trait +/// for a data structure acting as a container for a specific commitment under +/// a certain protocol, specified as a generic parameter. The container type +/// must specify as associated types proof and commitment types. /// -/// Operations with *embed-commit-verify scheme* may be represented in form of -/// `EmbedCommit: (Container, Message) -> (Container', Proof)` (see -/// [`Self::embed_commit`] and `Verify: (Container', Message, Proof) -> bool` +/// Operations with *embed-commit-verify scheme* may be represented in the form +/// of `EmbedCommit: (Container, Message) -> (Container', Proof)` (see +/// [`Self::embed_commit`]) and `Verify: (Container', Message, Proof) -> bool` /// (see [`Self::verify`]). /// /// This trait is heavily used in **deterministic bitcoin commitments**. /// /// # Protocol definition /// -/// Generic parameter `Protocol` provides context & configuration for commitment -/// scheme protocol used for this container type. +/// Generic parameter `Protocol` provides context and configuration for the +/// commitment scheme protocol used for this container type. /// /// Introduction of this generic allows to: /// - implement trait for foreign data types; @@ -136,7 +140,7 @@ where /// Error type that may be reported during [`Self::embed_commit`] procedure. /// It may also be returned from [`Self::verify`] (wrapped into /// [`EmbedVerifyError`] in case the proof data are invalid and the - /// commitment can't be re-created. + /// commitment can't be re-created). type CommitError: std::error::Error; /// Creates a commitment to a message and embeds it into the provided @@ -148,7 +152,7 @@ where /// Verifies commitment with commitment proof against the message. /// - /// Default implementation reconstructs original container with the + /// Default implementation reconstructs the original container with the /// [`EmbedCommitProof::restore_original_container`] method and repeats /// [`Self::embed_commit`] procedure checking that the resulting proof and /// commitment matches the provided `self` and `proof`. @@ -177,20 +181,24 @@ where Ok(()) } - /// Phantom method used to add `Protocol` generic parameter to the trait. + /// Phantom method used to add a `Protocol` generic parameter to the trait. /// /// # Panics /// /// Always panics when called. #[doc(hidden)] fn _phantom(_: Protocol) { - unimplemented!("EmbedCommitVerify::_phantom is a marker method which must not be used") + unimplemented!("EmbedCommitVerify::_phantom is a marker method that must not be used") } } -/// Helpers for writing test functions working with embed-commit-verify scheme. +/// Helpers for writing test functions working with the embed-commit-verify +/// scheme. #[cfg(test)] pub(crate) mod test_helpers { + #![allow(missing_docs)] + #![cfg_attr(coverage_nightly, coverage(off))] + use core::fmt::Debug; use core::hash::Hash; use std::collections::HashSet; @@ -298,6 +306,8 @@ pub(crate) mod test_helpers { #[cfg(test)] mod test { + #![cfg_attr(coverage_nightly, coverage(off))] + use core::fmt::Debug; use amplify::confinement::{SmallBlob, SmallVec, U32}; diff --git a/commit_verify/src/id.rs b/commit_verify/src/id.rs index b8435fd7..24b31077 100644 --- a/commit_verify/src/id.rs +++ b/commit_verify/src/id.rs @@ -2,24 +2,29 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. use std::collections::{BTreeMap, BTreeSet}; +#[cfg(feature = "vesper")] use std::fmt::{self, Display, Formatter}; use std::hash::Hash; @@ -33,22 +38,49 @@ use crate::{Conceal, DigestExt, MerkleHash, MerkleLeaves, LIB_NAME_COMMIT_VERIFY const COMMIT_MAX_LEN: usize = U64MAX; +/// Type of the collection participating in a commitment id creation. #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub enum CommitColType { + /// A vector-type collection (always correspond to a confined variant of + /// [`Vec`]). List, + /// A set of unique sorted elements (always correspond to a confined variant + /// of [`BTreeSet`]). Set, - Map { key: TypeFqn }, + /// A map of unique sorted keys to values (always correspond to a confined + /// variant of [`BTreeMap`]). + Map { + /// A fully qualified strict type name for the keys. + key: TypeFqn, + }, } +/// Step of the commitment id creation. #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub enum CommitStep { + /// Serialization with [`CommitEngine::commit_to_serialized`]. Serialized(TypeFqn), + + /// Serialization with either + /// - [`CommitEngine::commit_to_linear_list`], + /// - [`CommitEngine::commit_to_linear_set`], + /// - [`CommitEngine::commit_to_linear_map`]. + /// + /// A specific type of serialization depends on the first field + /// ([`CommitColType`]). Collection(CommitColType, Sizing, TypeFqn), + + /// Serialization with [`CommitEngine::commit_to_hash`]. Hashed(TypeFqn), + + /// Serialization with [`CommitEngine::commit_to_merkle`]. Merklized(TypeFqn), + + /// Serialization with [`CommitEngine::commit_to_concealed`]. Concealed(TypeFqn), } +/// A helper engine used in computing commitment ids. #[derive(Clone, Debug)] pub struct CommitEngine { finished: bool, @@ -64,6 +96,10 @@ fn commitment_fqn() -> TypeFqn { } impl CommitEngine { + /// Initialize the engine using a type-specific tag string. + /// + /// The tag should be in a form of a valid URN, ending with a fragment + /// specifying the date of the tag, or other form of versioning. pub fn new(tag: &'static str) -> Self { Self { finished: false, @@ -79,18 +115,19 @@ impl CommitEngine { debug_assert!(ok); } + /// Add a commitment to a strict-encoded value. pub fn commit_to_serialized(&mut self, value: &T) { let fqn = commitment_fqn::(); debug_assert!( Some(&fqn.name) != MerkleHash::strict_name().as_ref() || fqn.lib.as_str() != MerkleHash::STRICT_LIB_NAME, - "do not use commit_to_serialized for merklized collections, use commit_to_merkle \ + "do not use `commit_to_serialized` for merklized collections, use `commit_to_merkle` \ instead" ); debug_assert!( Some(&fqn.name) != StrictHash::strict_name().as_ref() || fqn.lib.as_str() != StrictHash::STRICT_LIB_NAME, - "do not use commit_to_serialized for StrictHash types, use commit_to_hash instead" + "do not use `commit_to_serialized` for StrictHash types, use `commit_to_hash` instead" ); self.layout .push(CommitStep::Serialized(fqn)) @@ -99,6 +136,7 @@ impl CommitEngine { self.inner_commit_to::<_, COMMIT_MAX_LEN>(&value); } + /// Add a commitment to a strict-encoded optional value. pub fn commit_to_option(&mut self, value: &Option) { let fqn = commitment_fqn::(); self.layout @@ -108,6 +146,7 @@ impl CommitEngine { self.inner_commit_to::<_, COMMIT_MAX_LEN>(&value); } + /// Add a commitment to a value which supports [`StrictHash`]ing. pub fn commit_to_hash + StrictType>( &mut self, value: &T, @@ -120,6 +159,9 @@ impl CommitEngine { self.inner_commit_to::<_, 32>(&value.commit_id()); } + /// Add a commitment to a merklized collection. + /// + /// The collection must implement [`MerkleLeaves`] trait. pub fn commit_to_merkle(&mut self, value: &T) where T::Leaf: StrictType { let fqn = commitment_fqn::(); @@ -131,6 +173,11 @@ impl CommitEngine { self.inner_commit_to::<_, 32>(&root); } + /// Add a commitment to a type which supports [`Conceal`] procedure (hiding + /// some of its data). + /// + /// First, the conceal procedure is called for the `value`, and then the + /// resulting data are serialized using strict encoding. pub fn commit_to_concealed(&mut self, value: &T) where T: Conceal + StrictType, @@ -145,6 +192,13 @@ impl CommitEngine { self.inner_commit_to::<_, COMMIT_MAX_LEN>(&concealed); } + /// Add a commitment to a vector collection. + /// + /// Does not use merklization and encodes each element as strict encoding + /// binary data right in to the hasher. + /// + /// Additionally to all elements, commits to the length of the collection + /// and minimal and maximal dimensions of the confinement. pub fn commit_to_linear_list( &mut self, collection: &Confined, MIN, MAX>, @@ -160,6 +214,13 @@ impl CommitEngine { self.inner_commit_to::<_, COMMIT_MAX_LEN>(&collection); } + /// Add a commitment to a set collection. + /// + /// Does not use merklization and encodes each element as strict encoding + /// binary data right in to the hasher. + /// + /// Additionally to all elements, commits to the length of the collection + /// and minimal and maximal dimensions of the confinement. pub fn commit_to_linear_set( &mut self, collection: &Confined, MIN, MAX>, @@ -175,6 +236,13 @@ impl CommitEngine { self.inner_commit_to::<_, COMMIT_MAX_LEN>(&collection); } + /// Add a commitment to a mapped collection. + /// + /// Does not use merklization and encodes each element as strict encoding + /// binary data right in to the hasher. + /// + /// Additionally to all keys and values, commits to the length of the + /// collection and minimal and maximal dimensions of the confinement. pub fn commit_to_linear_map( &mut self, collection: &Confined, MIN, MAX>, @@ -195,20 +263,34 @@ impl CommitEngine { self.inner_commit_to::<_, COMMIT_MAX_LEN>(&collection); } + /// Get a reference for the underlying sequence of commit steps. pub fn as_layout(&mut self) -> &[CommitStep] { self.finished = true; self.layout.as_ref() } + /// Convert into the underlying sequence of commit steps. pub fn into_layout(self) -> TinyVec { self.layout } + /// Mark the procedure as completed, preventing any further data from being + /// added. pub fn set_finished(&mut self) { self.finished = true; } + /// Complete the commitment returning the resulting hash. pub fn finish(self) -> Sha256 { self.hasher } + /// Complete the commitment returning the resulting hash and the description + /// of all commitment steps performed during the procedure. pub fn finish_layout(self) -> (Sha256, TinyVec) { (self.hasher, self.layout) } } +/// A trait for types supporting commit-encode procedure. +/// +/// The procedure is used to generate a cryptographic deterministic commitment +/// to data encoded in a binary form. +/// +/// Later the commitment can be used to produce [`CommitmentId`] (which does a +/// tagged hash of the commitment). pub trait CommitEncode { /// Type of the resulting commitment. type CommitmentId: CommitmentId; @@ -218,6 +300,14 @@ pub trait CommitEncode { fn commit_encode(&self, e: &mut CommitEngine); } +/// The description of the commitment layout used in production of +/// [`CommitmentId`] (or other users of [`CommitEncode`]). +/// +/// The layout description is useful in producing provably correct documentation +/// of the commitment process for a specific type. For instance, this library +/// uses it to generate a description of commitments in [Vesper] language. +/// +/// [Vesper]: https://vesper-lang.org #[derive(Getters, Clone, Eq, PartialEq, Hash, Debug)] pub struct CommitLayout { idty: TypeFqn, @@ -226,17 +316,25 @@ pub struct CommitLayout { fields: TinyVec, } +#[cfg(feature = "vesper")] impl Display for CommitLayout { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.to_vesper().display(), f) } } +/// A definition of a resulting commitment type, which represent a unique +/// identifier of the underlying data. pub trait CommitmentId: Copy + Ord + From + StrictType { + /// A tag string used in initializing SHA256 hasher. const TAG: &'static str; } +/// A trait adding blanked implementation generating [`CommitmentLayout`] for +/// any type implementing [`CommitEncode`]. pub trait CommitmentLayout: CommitEncode { + /// Generate a descriptive commitment layout, which includes a description + /// of each encoded field and the used hashing strategies. fn commitment_layout() -> CommitLayout; } @@ -286,6 +384,9 @@ impl CommitId for T { fn commit_id(&self) -> Self::CommitmentId { self.commit().finish().into() } } +/// A commitment to the strict encoded-representation of any data. +/// +/// It is created using tagged hash with [`StrictHash::TAG`] value. #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Deref, BorrowSlice, Display, FromStr, Hex, Index, RangeOps)] #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] @@ -304,3 +405,191 @@ impl CommitmentId for StrictHash { impl From for StrictHash { fn from(hash: Sha256) -> Self { hash.finish().into() } } + +#[cfg(test)] +mod tests { + #![cfg_attr(coverage_nightly, coverage(off))] + use super::*; + + #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)] + #[derive(StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = "Test")] + struct DumbConceal(u8); + + impl Conceal for DumbConceal { + type Concealed = DumbHash; + fn conceal(&self) -> Self::Concealed { DumbHash(0xFF - self.0) } + } + + #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)] + #[derive(StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = "Test")] + #[derive(CommitEncode)] + #[commit_encode(crate = self, strategy = strict, id = StrictHash)] + struct DumbHash(u8); + + #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)] + #[derive(StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = "Test")] + #[derive(CommitEncode)] + #[commit_encode(crate = self, strategy = strict, id = MerkleHash)] + struct DumbMerkle(u8); + + #[test] + fn commit_engine_strict() { + let val = 123u64; + let mut engine = CommitEngine::new("test"); + engine.commit_to_serialized(&val); + engine.set_finished(); + let (id, layout) = engine.finish_layout(); + assert_eq!(layout, tiny_vec![CommitStep::Serialized(TypeFqn::from("_.U64"))]); + assert_eq!( + id.finish(), + Sha256::from_tag("test") + .with_raw(&val.to_le_bytes()) + .finish() + ); + } + + #[test] + fn commit_engine_option() { + let val = Some(128u64); + let mut engine = CommitEngine::new("test"); + engine.commit_to_option(&val); + engine.set_finished(); + let (id, layout) = engine.finish_layout(); + assert_eq!(layout, tiny_vec![CommitStep::Serialized(TypeFqn::from("_.U64"))]); + assert_eq!( + id.finish(), + Sha256::from_tag("test") + .with_raw(b"\x01\x80\x00\x00\x00\x00\x00\x00\x00") + .finish() + ); + } + + #[test] + fn commit_engine_conceal() { + let val = DumbConceal(123); + let mut engine = CommitEngine::new("test"); + engine.commit_to_concealed(&val); + engine.set_finished(); + let (id, layout) = engine.finish_layout(); + assert_eq!(layout, tiny_vec![CommitStep::Concealed(TypeFqn::from("Test.DumbConceal"))]); + assert_eq!( + id.finish(), + Sha256::from_tag("test") + .with_raw(&(0xFF - val.0).to_le_bytes()) + .finish() + ); + } + + #[test] + fn commit_engine_hash() { + let val = DumbHash(10); + let mut engine = CommitEngine::new("test"); + engine.commit_to_hash(&val); + engine.set_finished(); + let (id, layout) = engine.finish_layout(); + assert_eq!(layout, tiny_vec![CommitStep::Hashed(TypeFqn::from("Test.DumbHash"))]); + assert_eq!( + id.finish(), + Sha256::from_tag("test") + .with_raw(val.commit_id().as_slice()) + .finish() + ); + } + + #[test] + fn commit_engine_merkle() { + let val = [DumbMerkle(1), DumbMerkle(2), DumbMerkle(3), DumbMerkle(4)]; + let mut engine = CommitEngine::new("test"); + engine.commit_to_merkle(&val); + engine.set_finished(); + let (id, layout) = engine.finish_layout(); + assert_eq!(layout, tiny_vec![CommitStep::Merklized(TypeFqn::from("Test.DumbMerkle"))]); + assert_eq!( + id.finish(), + Sha256::from_tag("test") + .with_raw(MerkleHash::merklize(&val).as_slice()) + .finish() + ); + } + + #[test] + fn commit_engine_list() { + let val = tiny_vec![0, 1, 2u8]; + let mut engine = CommitEngine::new("test"); + engine.commit_to_linear_list(&val); + engine.set_finished(); + let (id, layout) = engine.finish_layout(); + assert_eq!(layout, tiny_vec![CommitStep::Collection( + CommitColType::List, + Sizing::new(0, 0xFF), + TypeFqn::from("_.U8") + )]); + assert_eq!( + id.finish(), + Sha256::from_tag("test") + .with_len::<0xFF>(b"\x00\x01\x02") + .finish() + ); + } + + #[test] + fn commit_engine_set() { + let val = tiny_bset![0, 1, 2u8]; + let mut engine = CommitEngine::new("test"); + engine.commit_to_linear_set(&val); + engine.set_finished(); + let (id, layout) = engine.finish_layout(); + assert_eq!(layout, tiny_vec![CommitStep::Collection( + CommitColType::Set, + Sizing::new(0, 0xFF), + TypeFqn::from("_.U8") + )]); + assert_eq!( + id.finish(), + Sha256::from_tag("test") + .with_len::<0xFF>(b"\x00\x01\x02") + .finish() + ); + } + + #[test] + fn commit_engine_map() { + let val = tiny_bmap! {0 => tn!("A"), 1 => tn!("B"), 2u8 => tn!("C")}; + let mut engine = CommitEngine::new("test"); + engine.commit_to_linear_map(&val); + engine.set_finished(); + let (id, layout) = engine.finish_layout(); + assert_eq!(layout, tiny_vec![CommitStep::Collection( + CommitColType::Map { + key: TypeFqn::from("_.U8") + }, + Sizing::new(0, 0xFF), + TypeFqn::from("StrictTypes.TypeName") + )]); + assert_eq!( + id.finish(), + Sha256::from_tag("test") + .with_raw(b"\x03\x00\x01A\x01\x01B\x02\x01C") + .finish() + ); + } + + #[test] + #[should_panic] + fn commit_engine_reject_hash() { + let val = StrictHash::strict_dumb(); + let mut engine = CommitEngine::new("test"); + engine.commit_to_serialized(&val); + } + + #[test] + #[should_panic] + fn commit_engine_reject_merkle() { + let val = MerkleHash::strict_dumb(); + let mut engine = CommitEngine::new("test"); + engine.commit_to_serialized(&val); + } +} diff --git a/commit_verify/src/lib.rs b/commit_verify/src/lib.rs index 59381866..b11faf49 100644 --- a/commit_verify/src/lib.rs +++ b/commit_verify/src/lib.rs @@ -2,34 +2,45 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. // Coding conventions #![deny( - non_upper_case_globals, - non_camel_case_types, - non_snake_case, + unsafe_code, + dead_code, + missing_docs, + unused_variables, unused_mut, unused_imports, - dead_code, - // TODO: uncomment missing_docs + non_upper_case_globals, + non_camel_case_types, + non_snake_case )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(coverage_nightly, feature(coverage_attribute))] + +//! Standard cryptographic commitment library, created and supported by the +//! LNP/BP Labs. +// TODO: Extend description and readme #[macro_use] extern crate amplify; @@ -55,6 +66,7 @@ pub mod stl; pub mod merkle; pub mod mpc; mod digest; +#[cfg(feature = "vesper")] pub mod vesper; pub use commit::{CommitVerify, TryCommitVerify, VerifyError}; @@ -68,6 +80,7 @@ pub use id::{ }; pub use merkle::{MerkleBuoy, MerkleHash, MerkleLeaves, MerkleNode, NodeBranching}; +/// Name of the CommitVerify strict type library. pub const LIB_NAME_COMMIT_VERIFY: &str = "CommitVerify"; /// Marker trait for specific commitment protocols. @@ -105,6 +118,11 @@ impl From<[u8; LEN]> for ReservedBytes ReservedBytes { + /// Constant constructor. + pub const fn new() -> Self { Self([VAL; LEN]) } +} + mod _reserved { use strict_encoding::{DecodeError, ReadTuple, StrictDecode, TypedRead}; @@ -133,46 +151,38 @@ mod _reserved { #[cfg(feature = "serde")] mod _serde { - use std::fmt; - - use serde::de::Visitor; - use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + use amplify::hex::{FromHex, ToHex}; + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; use super::*; impl Serialize for ReservedBytes { fn serialize(&self, serializer: S) -> Result where S: Serializer { - // Doing nothing - serializer.serialize_unit() + if serializer.is_human_readable() { + serializer.serialize_str(&self.0.to_hex()) + } else { + serializer.serialize_bytes(self.0.as_ref()) + } } } impl<'de, const LEN: usize, const VAL: u8> Deserialize<'de> for ReservedBytes { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { - #[derive(Default)] - pub struct UntaggedUnitVisitor; - - impl Visitor<'_> for UntaggedUnitVisitor { - type Value = (); - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "reserved unit") - } - - fn visit_none(self) -> Result<(), E> - where E: de::Error { - Ok(()) - } - - fn visit_unit(self) -> Result<(), E> - where E: de::Error { - Ok(()) - } + let v = if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + Vec::::from_hex(&s) + .map_err(|_| D::Error::custom("invalid reserved value"))? + } else { + Vec::::deserialize(deserializer)? + }; + + if v != [VAL; LEN] { + return Err(Error::custom("invalid reserved value")); } - deserializer.deserialize_unit(UntaggedUnitVisitor)?; Ok(default!()) } } @@ -182,6 +192,8 @@ mod _reserved { /// Helpers for writing test functions working with commit schemes #[cfg(test)] pub mod test_helpers { + #![cfg_attr(coverage_nightly, coverage(off))] + use amplify::confinement::SmallVec; use amplify::hex::FromHex; diff --git a/commit_verify/src/merkle.rs b/commit_verify/src/merkle.rs index adcb426b..ddba1eed 100644 --- a/commit_verify/src/merkle.rs +++ b/commit_verify/src/merkle.rs @@ -2,22 +2,28 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +//! Generic cryptographic merklization procedures. use std::collections::BTreeSet; use std::ops::SubAssign; @@ -37,9 +43,9 @@ use crate::{CommitId, CommitmentId, LIB_NAME_COMMIT_VERIFY}; #[strict_type(lib = LIB_NAME_COMMIT_VERIFY, tags = repr, into_u8, try_from_u8)] #[repr(u8)] pub enum NodeBranching { - /// Void node: virtual node with no leafs. + /// Void node: virtual node with no leaves. /// - /// Used when the total width of the three is not a power two. + /// Used when the total width of the three is not power two. #[strict_type(dumb)] Void = 0x00, @@ -50,28 +56,41 @@ pub enum NodeBranching { Branch = 0x02, } +/// A node in the merkle tree used during the merklization process. +/// +/// The data are hashed using [`CommitEncode`] procedure, ensuring that each +/// node commits to its types, leaves, depth, and witdth of the tree at the node +/// depth. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_COMMIT_VERIFY)] #[derive(CommitEncode)] #[commit_encode(crate = crate, strategy = strict, id = MerkleHash)] pub struct MerkleNode { + /// A type of the node, based on its leaves. pub branching: NodeBranching, + /// A depth of the node, with the root being a zero depth. pub depth: u8, + /// The width of the merkle tree at the depth of the node. pub width: u256, + /// First leaf of the node. pub node1: MerkleHash, + /// Second leaf of the node. pub node2: MerkleHash, } impl MerkleNode { + /// Construct a non-existing (virtual) merkle node. pub fn void(depth: impl Into, width: impl Into) -> Self { Self::with(NodeBranching::Void, depth, width, VIRTUAL_LEAF, VIRTUAL_LEAF) } + /// Construct a merkle node with just a single leaf. pub fn single(depth: impl Into, width: impl Into, node: MerkleHash) -> Self { Self::with(NodeBranching::Single, depth, width, node, VIRTUAL_LEAF) } + /// Construct a merkle node with two leaves. pub fn branches( depth: impl Into, width: impl Into, @@ -124,14 +143,17 @@ impl From for MerkleHash { const VIRTUAL_LEAF: MerkleHash = MerkleHash(Bytes32::from_array([0xFF; 32])); impl MerkleHash { + /// Construct a non-existing (virtual) merkle node. pub fn void(depth: impl Into, width: impl Into) -> Self { MerkleNode::void(depth, width).commit_id() } + /// Construct a merkle node with just a single leaf. pub fn single(depth: impl Into, width: impl Into, node: MerkleHash) -> Self { MerkleNode::single(depth, width, node).commit_id() } + /// Construct a merkle node with two leaves. pub fn branches( depth: impl Into, width: impl Into, @@ -195,11 +217,25 @@ impl MerkleHash { } } +/// Trait, which can be implemented for any collection type such that its +/// elements can be easily merklized with [`MerkleHash::merklize`]. pub trait MerkleLeaves { + /// The type of the collection element. + /// + /// It must be able to produce a commitment in the form of a [`MerkleHash`]. type Leaf: CommitId; + + /// Get an iterator over all collection elements that have to be merklized. fn merkle_leaves(&self) -> impl ExactSizeIterator; } +impl MerkleLeaves for [T; LEN] +where T: CommitId +{ + type Leaf = T; + fn merkle_leaves(&self) -> impl ExactSizeIterator { self.iter() } +} + impl MerkleLeaves for Confined, MIN, { u8::MAX as usize }> where T: CommitId { @@ -243,6 +279,9 @@ where T: CommitId } /// Helper struct to track depth when working with Merkle blocks. +/// +/// Generic argument `D` is for a concrete arithmetic type matching the maximum +/// depth of the merkle tree. #[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct MerkleBuoy + Default> { buoy: D, @@ -250,6 +289,7 @@ pub struct MerkleBuoy + Default> { } impl + Default> MerkleBuoy { + /// Construct a new merkle buoy. pub fn new(top: D) -> Self { Self { buoy: top, @@ -268,10 +308,10 @@ impl + Default> MerkleBuoy { /// Add new item to the buoy. /// - /// Returns whether the buoy have surfaced in a result. + /// Returns whether the buoy has surfaced in a result. /// - /// The buoy surfaces each time the contents it has is reduced to two depth - /// of the same level. + /// The buoy surfaces each time the content it has is reduced to two + /// depths of the same level. pub fn push(&mut self, depth: D) -> bool { if depth == D::default() { return false; diff --git a/commit_verify/src/mpc/atoms.rs b/commit_verify/src/mpc/atoms.rs index 028daa2d..952252cf 100644 --- a/commit_verify/src/mpc/atoms.rs +++ b/commit_verify/src/mpc/atoms.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. use amplify::confinement::MediumOrdMap; use amplify::num::u5; @@ -28,8 +32,16 @@ use strict_encoding::StrictDumb; use crate::merkle::MerkleHash; use crate::{CommitmentId, DigestExt}; +/// The constant defining the minimum depth of the MPC Merkle tree. pub const MPC_MINIMAL_DEPTH: u5 = u5::with(3); +/// A specific cryptographic digest algorithm used in constructing MPC Merkle +/// trees. +/// +/// # Future use +/// +/// For supporting zk compression of the proves in the future it is planned to +/// add more zk-friendly digest algorithms. #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Default)] #[display(lowercase)] #[derive(StrictType, StrictEncode, StrictDecode)] @@ -37,6 +49,7 @@ pub const MPC_MINIMAL_DEPTH: u5 = u5::with(3); #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))] #[repr(u8)] pub enum Method { + /// A tagged SHA256 digest. #[default] Sha256t = 0, } @@ -60,6 +73,8 @@ pub struct ProtocolId( ); impl ProtocolId { + /// Construct a protocol id from a slice, returning an error if the slice + /// length differs from 32 bytes. pub fn copy_from_slice(slice: &[u8]) -> Result { Bytes32::copy_from_slice(slice).map(Self) } @@ -80,31 +95,46 @@ pub struct Message( ); impl Message { + /// Construct a message from a slice, returning an error if the slice + /// length differs from 32 bytes. pub fn copy_from_slice(slice: &[u8]) -> Result { Bytes32::copy_from_slice(slice).map(Self) } } +/// A leaf of the MPC Merkle tree. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, From)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = crate::LIB_NAME_COMMIT_VERIFY, tags = custom)] #[derive(CommitEncode)] #[commit_encode(crate = crate, strategy = strict, id = MerkleHash)] pub enum Leaf { + /// Leaf, inhabited with a commitment under a protocol. // We use this constant since we'd like to be distinct from NodeBranching values #[strict_type(tag = 0x10)] Inhabited { + /// The protocol under which the message is created. protocol: ProtocolId, + /// The commitment under the protocol. message: Message, }, + + /// Lead, uninhabited by any protocol. // We use this constant since we'd like to be distinct from NodeBranching values #[strict_type(tag = 0x11)] - Entropy { entropy: u64, pos: u32 }, + Entropy { + /// Entropic value. + entropy: u64, + /// A position of the leaf + pos: u32, + }, } impl Leaf { + /// Construct a [`Leaf::Entropy`] variant. pub fn entropy(entropy: u64, pos: u32) -> Self { Self::Entropy { entropy, pos } } + /// Construct a [`Leaf::Inhabited`] variant. pub fn inhabited(protocol: ProtocolId, message: Message) -> Self { Self::Inhabited { protocol, message } } @@ -136,6 +166,8 @@ impl CommitmentId for Commitment { } impl Commitment { + /// Construct a commitment from a slice, returning an error if the slice + /// length differs from 32 bytes. pub fn copy_from_slice(slice: &[u8]) -> Result { Bytes32::copy_from_slice(slice).map(Self) } @@ -148,11 +180,16 @@ impl From for Commitment { /// Structured source multi-message data for commitment creation #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct MultiSource { + /// A cryptographic digest algorithm used for the construction. pub method: Method, /// Minimal depth of the created LNPBP-4 commitment tree pub min_depth: u5, /// Map of the messages by their respective protocol ids pub messages: MessageMap, + /// An optional entropy used in creating MPC Merkle tree. + /// + /// If not provided, a system random generator is used. + /// If the generator is not available, it will cause panic. pub static_entropy: Option, } @@ -170,6 +207,8 @@ impl Default for MultiSource { impl MultiSource { #[inline] + /// Create a default [`MultiSource`] instance, initializing the + /// [`MultiSource::static_entropy`] with the provided value. pub fn with_static_entropy(static_entropy: u64) -> Self { MultiSource { static_entropy: Some(static_entropy), diff --git a/commit_verify/src/mpc/block.rs b/commit_verify/src/mpc/block.rs index 4ab117e1..844b86cd 100644 --- a/commit_verify/src/mpc/block.rs +++ b/commit_verify/src/mpc/block.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. #![allow(unused_braces)] @@ -35,36 +39,57 @@ use crate::mpc::tree::protocol_id_pos; use crate::mpc::{Commitment, MerkleTree, Message, MessageMap, Method, Proof, ProtocolId}; use crate::{Conceal, LIB_NAME_COMMIT_VERIFY}; -/// commitment under protocol id {0} is absent from the known part of a given -/// LNPBP-4 Merkle block. +/// Error indicating that a given [`ProtocolId`] does not participate in a +/// specific MPC Merkle tree. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)] -#[display(doc_comments)] -pub struct LeafNotKnown(ProtocolId); - -/// the provided merkle proof protocol id {protocol_id} position {actual} -/// doesn't match the expected position {expected} within the tree of width -/// {width}. +#[display( + "commitment under protocol id {0} is absent from the known part of a given LNPBP-4 Merkle \ + block" +)] +pub struct LeafNotKnown(pub ProtocolId); + +/// Error constructing MPC Merkle tree due to invalid [`MerkleProof`] data. +/// +/// # Returned by +/// +/// - [`MerkleBlock::with`] +/// - [`MerkleProof::convolve`] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)] -#[display(doc_comments)] +#[display( + "the provided merkle proof protocol id {protocol_id} position {actual} doesn't match the \ + expected position {expected} within the tree of width {width}." +)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))] pub struct InvalidProof { - protocol_id: ProtocolId, - expected: u32, - actual: u32, - width: u32, + /// The protocol id which position is mismatched. + pub protocol_id: ProtocolId, + /// The position in which the protocol should appear. + pub expected: u32, + /// The position in which the protocol appears in the proof. + pub actual: u32, + /// The width of the MPC Merkle tree. + pub width: u32, } +/// Errors happening during the [`MerkleBlock::merge_reveal_path`] procedure. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error, From)] -#[display(doc_comments)] pub enum MergeError { + /// Invalid [`MerkleProof`] data. + /// + /// See [`InvalidProof`] inner error type for the details. #[from] #[display(inner)] InvalidProof(InvalidProof), - /// attempt to merge two unrelated LNPBP-4 blocks with different Merkle - /// roots (base {base_root}, merged-in {merged_root}). + /// Attempt to merge two unrelated [`MerkleBlock`]s. + #[display( + "attempt to merge two unrelated LNPBP-4 blocks with different Merkle roots (base \ + {base_root}, merged-in {merged_root})." + )] UnrelatedBlocks { + /// The merkle root of the first merged MPC block. base_root: Commitment, + /// The merkle root of the second merged MPC block. merged_root: Commitment, }, } @@ -125,6 +150,8 @@ impl TreeNode { } } +/// A fully concealed MPC Merkle tree, consisting just of the Merkle root and +/// information about its original depth and used cofactor. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_COMMIT_VERIFY)] @@ -425,7 +452,7 @@ impl MerkleBlock { } /// Merges information from the given `proof` to the merkle block, revealing - /// path related to te `commitment` to the message under the given + /// the path related to te `commitment` to the message under the given /// `protocol_id`. pub fn merge_reveal_path( &mut self, @@ -583,8 +610,8 @@ Changed commitment id: {}", }) } - /// Constructs merkle proof for the inclusion of a commitment under given - /// `protocol_id` for the current Merkle block. + /// Constructs merkle proof for the inclusion of a commitment under the + /// given `protocol_id` for the current Merkle block. pub fn to_merkle_proof(&self, protocol_id: ProtocolId) -> Result { self.clone().into_merkle_proof(protocol_id) } @@ -601,6 +628,8 @@ Changed commitment id: {}", /// `2 ^ depth - cofactor`. pub fn factored_width(&self) -> u32 { self.width_limit() - self.cofactor as u32 } + /// Get an iterator over the known protocol ids present in the MPC Merkle + /// block. pub fn known_protocol_ids(&self) -> impl Iterator + '_ { self.cross_section.iter().filter_map(|item| match item { TreeNode::ConcealedNode { .. } => None, @@ -625,6 +654,8 @@ Changed commitment id: {}", .expect("same collection size") } + /// Convert this MPC Merkle block into an iterator over items and proofs of + /// their inclusion. pub fn into_known_proofs(self) -> impl Iterator { self.known_protocol_ids() .collect::>() @@ -734,6 +765,8 @@ impl MerkleProof { #[cfg(test)] mod test { + #![cfg_attr(coverage_nightly, coverage(off))] + use super::*; use crate::mpc::tree::test_helpers::{ make_det_messages, make_random_messages, make_random_tree, diff --git a/commit_verify/src/mpc/mod.rs b/commit_verify/src/mpc/mod.rs index 3044e0bd..388e231f 100644 --- a/commit_verify/src/mpc/mod.rs +++ b/commit_verify/src/mpc/mod.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. //! Multi-protocol commitments according to [LNPBP-4] standard. //! diff --git a/commit_verify/src/mpc/test.rs b/commit_verify/src/mpc/test.rs index 7dc466b1..5c296aee 100644 --- a/commit_verify/src/mpc/test.rs +++ b/commit_verify/src/mpc/test.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod test { + #![cfg_attr(coverage_nightly, coverage(off))] + use std::str::FromStr; use super::*; diff --git a/commit_verify/src/mpc/tree.rs b/commit_verify/src/mpc/tree.rs index 9ea0a719..8dbda1a2 100644 --- a/commit_verify/src/mpc/tree.rs +++ b/commit_verify/src/mpc/tree.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. use amplify::confinement::{LargeVec, MediumOrdMap}; use amplify::num::{u256, u5}; @@ -69,6 +73,7 @@ impl Proof for MerkleTree { } impl MerkleTree { + /// Compute the root of the Merkle tree. pub fn root(&self) -> MerkleHash { let iter = (0..self.width_limit()).map(|pos| { self.map @@ -124,7 +129,7 @@ mod commit { fn try_commit(source: &MultiSource) -> Result { #[cfg(feature = "rand")] - use rand::{thread_rng, RngCore}; + use rand::{rng, RngCore}; let msg_count = source.messages.len(); @@ -136,9 +141,7 @@ mod commit { } #[cfg(feature = "rand")] - let entropy = source - .static_entropy - .unwrap_or_else(|| thread_rng().next_u64()); + let entropy = source.static_entropy.unwrap_or_else(|| rng().next_u64()); #[cfg(not(feature = "rand"))] let entropy = source.static_entropy.expect( "use must use `rand` feature for crate commit_verify if you do not provide static \ @@ -193,20 +196,25 @@ impl MerkleTree { protocol_id_pos(protocol_id, self.cofactor, self.depth) } - /// Computes the maximum possible width of the merkle tree, equal to `2 ^ - /// depth`. + /// Computes the maximum possible width of the MPC Merkle tree, equal to `2 + /// ^ depth`. pub fn width_limit(&self) -> u32 { 2u32.pow(self.depth.to_u8() as u32) } - /// Computes the factored width of the merkle tree, equal to `2 ^ depth - - /// cofactor`. + /// Computes the factored width of the MPC Merkle tree, equal to `2 ^ depth + /// - cofactor`. pub fn factored_width(&self) -> u32 { self.width_limit() - self.cofactor as u32 } + /// Get the depth of the MPC Merkle tree. pub fn depth(&self) -> u5 { self.depth } + /// Get the cofactor of the MPC Merkle tree. pub fn cofactor(&self) -> u16 { self.cofactor } + /// Get the value of the entropy used in the MPC Merkle tree construct. pub fn entropy(&self) -> u64 { self.entropy } + /// Convert this MPC Merkle tree into an iterator over items and proofs of + /// their inclusion. pub fn into_proofs(self) -> impl Iterator { let block = MerkleBlock::from(self); block.into_known_proofs() @@ -215,6 +223,8 @@ impl MerkleTree { #[cfg(test)] pub(crate) mod test_helpers { + #![cfg_attr(coverage_nightly, coverage(off))] + use std::collections::BTreeMap; use amplify::confinement::Confined; @@ -265,6 +275,8 @@ pub(crate) mod test_helpers { #[cfg(test)] mod test { + #![cfg_attr(coverage_nightly, coverage(off))] + use std::collections::BTreeSet; use amplify::num::u5; diff --git a/commit_verify/src/stl.rs b/commit_verify/src/stl.rs index f7d3c482..beb290bf 100644 --- a/commit_verify/src/stl.rs +++ b/commit_verify/src/stl.rs @@ -2,27 +2,34 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +//! CommitVerify strict type library. use strict_types::{CompileError, LibBuilder, TypeLib}; use crate::{mpc, MerkleHash, MerkleNode, ReservedBytes, StrictHash, LIB_NAME_COMMIT_VERIFY}; +/// Strict type library ID for the CommitVerify types. pub const LIB_ID_COMMIT_VERIFY: &str = "stl:wH1wmGy2-0vBNWxL-MK~_eQb-Ayskv~e-oFmDrzI-O_IW_P0#biology-news-adam"; @@ -76,12 +83,15 @@ fn _commit_verify_stl() -> Result { .compile() } +/// Compiles CommitVerify strict type library. pub fn commit_verify_stl() -> TypeLib { _commit_verify_stl().expect("invalid strict type CommitVerify library") } #[cfg(test)] mod test { + #![cfg_attr(coverage_nightly, coverage(off))] + use super::*; #[test] diff --git a/commit_verify/src/vesper.rs b/commit_verify/src/vesper.rs index af7dc08c..f7b2aab2 100644 --- a/commit_verify/src/vesper.rs +++ b/commit_verify/src/vesper.rs @@ -19,6 +19,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(missing_docs)] + use amplify::confinement::{Confined, SmallVec, TinyVec}; use strict_encoding::Ident; use strict_types::layout::vesper::LenRange; diff --git a/scripts/typelib.sh b/scripts/typelib.sh index 2a16acc8..8c684813 100755 --- a/scripts/typelib.sh +++ b/scripts/typelib.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -cargo run --features stl --package commit_verify --bin commit-stl -- --stl -cargo run --features stl --package commit_verify --bin commit-stl -- --sty -cargo run --features stl --package commit_verify --bin commit-stl -- --sta +cargo run --features stl,vesper --package commit_verify --bin commit-stl -- --stl +cargo run --features stl,vesper --package commit_verify --bin commit-stl -- --sty +cargo run --features stl,vesper --package commit_verify --bin commit-stl -- --sta diff --git a/single_use_seals/Cargo.toml b/single_use_seals/Cargo.toml index bd6be1b5..acb759a0 100644 --- a/single_use_seals/Cargo.toml +++ b/single_use_seals/Cargo.toml @@ -26,3 +26,6 @@ all = ["strict_encoding", "serde"] [package.metadata.docs.rs] features = ["all"] + +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/single_use_seals/LICENSE b/single_use_seals/LICENSE index a6a3b8bc..d9a10c0d 100644 --- a/single_use_seals/LICENSE +++ b/single_use_seals/LICENSE @@ -174,28 +174,3 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2019-2022 LNP/BP Standards Association, Switzerland - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/single_use_seals/src/lib.rs b/single_use_seals/src/lib.rs index d1126ce0..0cb8da3e 100644 --- a/single_use_seals/src/lib.rs +++ b/single_use_seals/src/lib.rs @@ -2,31 +2,48 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// Coding conventions +#![deny( + unsafe_code, + dead_code, + missing_docs, + unused_variables, + unused_mut, + unused_imports, + non_upper_case_globals, + non_camel_case_types, + non_snake_case +)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(coverage_nightly, feature(coverage_attribute))] #![cfg_attr(not(feature = "strict_encoding"), no_std)] //! # Single-use-seals //! //! Set of traits that allow to implement Peter's Todd **single-use seal** //! paradigm. Information in this file partially contains extracts from Peter's -//! works listed in "Further reading" section. +//! works listed in the "Further reading" section. //! //! ## Single-use-seal definition //! @@ -36,7 +53,7 @@ //! abstract mechanism to prevent double-spends. //! //! A single-use-seal implementation supports two fundamental operations: -//! * `Close(l,m) → w` — Close seal l over message m, producing a witness `w`. +//! * `Close(l,m) → w` — Close seal l over a message m, producing a witness `w`. //! * `Verify(l,w,m) → bool` — Verify that the seal l was closed over message //! `m`. //! @@ -48,51 +65,42 @@ //! //! Practical single-use-seal implementations will also obviously require some //! way of generating new single-use-seals: -//! * `Gen(p)→l` — Generate a new seal basing on some seal definition data `p`. +//! * `Gen(p)→l` — Generate a new seal based on some seal definition data `p`. //! //! ## Terminology //! //! **Single-use-seal**: a commitment to commit to some (potentially unknown) -//! message. The first commitment (i.e. single-use-seal) must be a -//! well-defined (i.e. fully specified and unequally identifiable +//! message. The first commitment (i.e., single-use-seal) must be a +//! well-defined (i.e., fully specified and unequally identifiable //! in some space, like in time/place or within a given formal informational //! system). -//! **Closing of a single-use-seal over message**: a fulfilment of the first +//! **Closing of a single-use-seal over message**: fulfilment of the first //! commitment: creation of the actual commitment to some message in a form //! unequally defined by the seal. -//! **Witness**: data produced with closing of a single use seal which are +//! **Witness**: data produced with closing of a single use seal which is //! required and sufficient for an independent party to verify that the seal -//! was indeed closed over a given message (i.e. the commitment to the message -//! had being created according to the seal definition). +//! was indeed closed over a given message (i.e.б the commitment to the +//! message had been created according to the seal definition). //! -//! NB: It's important to note, that while its possible to deterministically -//! define was a given seal closed it yet may be not possible to find out -//! if the seal is open; i.e. seal status may be either "closed over message" +//! NB: It is important to note that while it is possible to deterministically +//! define was a given seal closed, it yet may be not possible to find out +//! if the seal is open; i.e., seal status may be either "closed over message" //! or "unknown". Some specific implementations of single-use-seals may define -//! procedure to deterministically prove that a given seal is not closed (i.e. -//! opened), however this is not a part of the specification, and we should -//! not rely on the existence of such possibility in all cases. +//! a procedure to deterministically prove that a given seal is not closed +//! (i.e., opened), however, this is not a part of the specification, and we +//! should not rely on the existence of such a possibility in all cases. //! //! ## Trait structure //! -//! The module defines trait [`SealProtocol`] that can be used for -//! implementation of single-use-seals with methods for seal close and -//! verification. A type implementing this trait operates only with messages -//! (which is represented by any type that implements `AsRef<[u8]>`,i.e. can be -//! represented as a sequence of bytes) and witnesses (which is represented by -//! an associated type [`SealProtocol::Witness`]). At the same time, -//! [`SealProtocol`] can't define seals by itself. -//! -//! Seal protocol operates with a *seal medium *: a proof of publication medium -//! on which the seals are defined. -//! -//! The module provides two options of implementing such medium: synchronous -//! [`SealProtocol`] and asynchronous `SealProtocolAsync`. +//! The main trait is [`SingleUseSeal`], which should be implemented for a +//! single-use seal data type. It references component types for seal witness +//! [`SealWitness`], which are a _published witness_ [`PublishedWitness`] and a +//! _client-side witness_ [`ClientSideWitness`]. //! //! ## Sample implementation //! -//! Examples of implementations can be found in `bp::seals` module of `bp-core` -//! crate. +//! Examples of implementations can be found in the [`bp::seals`] module of +//! `bp-core` crate. //! //! ## Further reading //! @@ -102,6 +110,8 @@ //! * Peter Todd. Scalable Semi-Trustless Asset Transfer via Single-Use-Seals //! and Proof-of-Publication. 1. Single-Use-Seal Definition. //! +//! +//! [`bp::seals`]: https://github.com/BP-WG/bp-core/tree/master/seals #[cfg(feature = "strict_encoding")] #[macro_use] @@ -135,59 +145,88 @@ trait StrictDecode {} #[cfg(not(feature = "strict_encoding"))] impl StrictDecode for T {} -/// Trait for proof-of-publication medium on which the seals are defined, -/// closed, verified and which can be used for convenience operations related to -/// seals: -/// * finding out the seal status -/// * publishing witness information -/// * get some identifier on the exact place of the witness publication -/// * check validity of the witness publication identifier -/// -/// All these operations are medium-specific; for the same single-use-seal type -/// they may differ when are applied to different proof of publication mediums. -/// -/// To read more on proof-of-publication please check -/// +/// Strict type library name for single-use seals. +pub const LIB_NAME_SEALS: &str = "SingleUseSeals"; + +/// Trait for the types implementing single-use seal protocol, composing all +/// their components (seal definition, message, and seal closing withness) +/// together, and implementing the logic of the protocol-specific verification +/// of the seal closing over the message (see [`Self::is_included`]). pub trait SingleUseSeal: Clone + Debug + Display + StrictDumb + StrictEncode + StrictDecode { /// Message type that is supported by the current single-use-seal. type Message: Copy + Eq; + /// A type for the published part of the seal closing witness. type PubWitness: PublishedWitness + StrictDumb + StrictEncode + StrictDecode; + /// A type for the client-side part of the seal closing witness. type CliWitness: ClientSideWitness + StrictDumb + StrictEncode + StrictDecode; + /// Check that the seal was closing over a message is a part of the witness. + /// + /// Some public or client-side witnesses must be checked to include specific + /// seal closing information. This method ensures that this is the case. + /// + /// NB: This method does not perform the seal closing verification; for this + /// purpose use [`SealWitness::verify_seal_closing`] and + /// [`SealWitness::verify_seals_closing`]. fn is_included(&self, message: Self::Message, witness: &SealWitness) -> bool; } +/// A client-side part of the seal closing witness [`SealWitness`]. +/// +/// A client-side witness is always specific to a particular [`SingleUseSeal`] +/// protocol, hence it specifies single-use seal implementation as an associated +/// type [`Self::Seal`]. pub trait ClientSideWitness: Eq { /// Client-side witness is specific to just one type of single-use seals, /// provided as an associated type. type Seal: SingleUseSeal; + /// Proof which is passed from the client-side witness to the public-side /// witness during single-use seal validation. type Proof; + + /// Error type returned by the [`Self::convolve_commit`] operation. type Error: Clone + Error; + /// Procedure that convolves the message with the client-side data kept in + /// the client-side part of the seal closing witness. This produces + /// [`Self::Proof`], which is lately verified by + /// [`SealWitness::verify_seal_closing`] and + /// [`SealWitness::verify_seals_closing`] against the published part of the + /// witness. fn convolve_commit( &self, msg: ::Message, ) -> Result; + /// Merge two compatible client-side witnesses together, or error in case of + /// their incompatibility. + /// + /// Client-side witnesses may be split into different client-specific + /// versions, for instance, by concealing some of the data which should be + /// private and not known to the other users. + /// This procedure allows combining information from multiple sources back. fn merge(&mut self, other: Self) -> Result<(), impl Error> where Self: Sized; } -#[derive(Copy, Clone, Debug, Default)] -pub struct NoWitness(PhantomData); +/// Some single-use seal protocols may not distinguish client-side seal closing +/// witness and have just the published one. To use [`SealWitness`] type in such +/// protocols, the [`SingleUseSeal`] must set its [`SingleUseSeal::CliWitness`] +/// to [`NoClientWitness`] type. +#[derive(Copy, Clone, Debug)] +pub struct NoClientWitness(PhantomData); -impl PartialEq for NoWitness { +impl PartialEq for NoClientWitness { fn eq(&self, _: &Self) -> bool { true } } -impl Eq for NoWitness {} +impl Eq for NoClientWitness {} -impl ClientSideWitness for NoWitness { +impl ClientSideWitness for NoClientWitness { type Seal = Seal; type Proof = Seal::Message; type Error = Infallible; @@ -200,28 +239,78 @@ impl ClientSideWitness for NoWitness { } } -/// Public witness can be used by multiple types of single-use seals, hence it -/// has the seal type as a generic parameter. +impl NoClientWitness { + /// Constructs the object. + pub fn new() -> Self { Self(PhantomData) } +} + +impl Default for NoClientWitness { + fn default() -> Self { Self::new() } +} + +#[cfg(feature = "strict_encoding")] +mod _strict_encoding_impls { + use strict_encoding::{ + DecodeError, StrictProduct, StrictTuple, StrictType, TypedRead, TypedWrite, + }; + + use super::*; + + impl StrictType for NoClientWitness { + const STRICT_LIB_NAME: &'static str = LIB_NAME_SEALS; + } + impl StrictProduct for NoClientWitness {} + impl StrictTuple for NoClientWitness { + const FIELD_COUNT: u8 = 0; + } + impl StrictEncode for NoClientWitness { + fn strict_encode(&self, writer: W) -> std::io::Result { Ok(writer) } + } + impl StrictDecode for NoClientWitness { + fn strict_decode(_reader: &mut impl TypedRead) -> Result { + Ok(NoClientWitness::new()) + } + } +} + +/// A published part of the seal closing witness [`SealWitness`]. +/// +/// Published witness may be used by multiple implementations of single-use +/// seals ([`SingleUseSeal`]), hence it binds the specific seal type as a +/// generic parameter. pub trait PublishedWitness { + /// A unique id for the published part of the single-use seal closing + /// witness. + /// /// Publication id that may be used for referencing publication of - /// witness data in the medium. By default, set `()`, so [`SingleUseSeal`] - /// may not implement publication id and related functions. + /// witness data in the medium. type PubId: Copy + Ord + Debug + Display; + + /// Error type returned by [`Self::verify_commitment`]. type Error: Clone + Error; + /// Get the unique id of this witness publication. fn pub_id(&self) -> Self::PubId; + + /// Verify that the public witness commits to the message using a proof + /// [`ClientSideWitness::Proof`], which is prepared by the client-side part + /// of the seal closing witness and include the information about the + /// message. fn verify_commitment( &self, proof: ::Proof, ) -> Result<(), Self::Error>; } -/// Seal closing witness. +/// Seal closing witness, consisting of published and client-side parts. +/// +/// The seal closing witness commits to the specific [`SingleUseSeal`] protocol +/// implementation via its `Seal` generic parameter. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] #[cfg_attr( feature = "strict_encoding", derive(StrictType, StrictDumb, StrictEncode, StrictDecode), - strict_type(lib = "SingleUseSeals") + strict_type(lib = LIB_NAME_SEALS) )] #[cfg_attr( feature = "serde", @@ -232,7 +321,9 @@ pub trait PublishedWitness { pub struct SealWitness where Seal: SingleUseSeal { + /// The published part of the single-use seal closing witness. pub published: Seal::PubWitness, + /// The client-side part of the single-use seal closing witness. pub client: Seal::CliWitness, #[cfg_attr(feature = "serde", serde(skip))] #[cfg_attr(feature = "strict_encoding", strict_type(skip))] @@ -242,6 +333,8 @@ where Seal: SingleUseSeal impl SealWitness where Seal: SingleUseSeal { + /// Construct seal closing withness out of published and client-side + /// components. pub fn new(published: Seal::PubWitness, client: Seal::CliWitness) -> Self { Self { published, @@ -250,6 +343,13 @@ where Seal: SingleUseSeal } } + /// Verify that a single `seal` is correctly closed over the `message` using + /// the current seal closing witness. + /// + /// This is the implementation of the single-use seals `verigy` procedure. + /// + /// If you have multiple seals closed over the same message, consider + /// calling [`Self::verify_seals_closing`]. pub fn verify_seal_closing( &self, seal: impl Borrow, @@ -258,6 +358,13 @@ where Seal: SingleUseSeal self.verify_seals_closing([seal], message) } + /// Verify that all the seals from a set of `seals` are correctly closed + /// over the single `message` using the current seal closing witness. + /// + /// This is the implementation of the single-use seals `verigy` procedure. + /// + /// If you have just a single seal, consider calling + /// [`Self::verify_seal_closing`]. pub fn verify_seals_closing( &self, seals: impl IntoIterator>, @@ -270,7 +377,7 @@ where Seal: SingleUseSeal .then_some(()) .ok_or(SealError::NotIncluded(seal.borrow().clone(), self.published.pub_id()))?; } - // ensure that published witness contains the commitment to the + // ensure that the published witness contains the commitment to the // f(message), where `f` is defined in the client-side witness let f_msg = self .client @@ -282,10 +389,18 @@ where Seal: SingleUseSeal } } +/// Errors indicating cases of failed single-use seal verification with +/// [`SealWitness::verify_seal_closing`] and +/// [`SealWitness::verify_seals_closing`] procedures. #[derive(Clone)] pub enum SealError { + /// The single-use seal was not included in the public witness. NotIncluded(Seal, >::PubId), + /// The provided proof of the seal closing is not valid for the published + /// part of the seal closing witness. Published(>::Error), + /// The client part of the single-use seal doesn't match the provided seal + /// definition or is unrelated to the message. Client(::Error), } @@ -307,7 +422,7 @@ impl Display for SealError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { SealError::NotIncluded(seal, pub_id) => { - write!(f, "seal {seal} is not included in the witness {pub_id}") + write!(f, "seal {seal} is not included in the public witness {pub_id}") } SealError::Published(err) => Display::fmt(err, f), SealError::Client(err) => Display::fmt(err, f), @@ -328,3 +443,195 @@ where } } } + +#[cfg(test)] +mod tests { + #![cfg_attr(coverage_nightly, coverage(off))] + + use super::*; + + const LIB_NAME: &str = "SingleUseSealTests"; + + type DumbMessage = [u8; 16]; + + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] + #[derive(StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = LIB_NAME)] + struct DumbSeal(u8); + + impl Display for DumbSeal { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("DumbSeal") } + } + + impl SingleUseSeal for DumbSeal { + type Message = DumbMessage; + type PubWitness = DumbPubWitness; + type CliWitness = NoClientWitness; + + fn is_included(&self, _message: Self::Message, witness: &SealWitness) -> bool { + witness.published.1 == *self + } + } + + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)] + #[derive(StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = LIB_NAME)] + struct DumbPubWitness(DumbMessage, Seal); + + impl PublishedWitness for DumbPubWitness { + type PubId = u8; + type Error = Invalid; + + fn pub_id(&self) -> Self::PubId { self.1 .0 } + + fn verify_commitment(&self, proof: DumbMessage) -> Result<(), Self::Error> { + (self.0 == proof).then_some(()).ok_or(Invalid) + } + } + + impl PublishedWitness for DumbPubWitness { + type PubId = u8; + type Error = Invalid; + + fn pub_id(&self) -> Self::PubId { self.1 .0 } + + fn verify_commitment(&self, proof: DumbMessage) -> Result<(), Self::Error> { + (self.0 == proof).then_some(()).ok_or(Invalid) + } + } + + impl PublishedWitness for DumbPubWitness { + type PubId = u8; + type Error = Invalid; + + fn pub_id(&self) -> Self::PubId { self.1 .0 } + + fn verify_commitment(&self, proof: DumbMessage) -> Result<(), Self::Error> { + (self.0 == proof).then_some(()).ok_or(Invalid) + } + } + + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] + #[derive(StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = LIB_NAME)] + struct FailingSeal(u8); + + impl Display for FailingSeal { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("DumbSeal") } + } + + impl SingleUseSeal for FailingSeal { + type Message = DumbMessage; + type PubWitness = DumbPubWitness; + type CliWitness = FailingCliWitness; + + fn is_included(&self, _message: Self::Message, _witness: &SealWitness) -> bool { + true + } + } + + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)] + #[derive(StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = LIB_NAME)] + struct FailingCliWitness(); + + impl ClientSideWitness for FailingCliWitness { + type Seal = FailingSeal; + type Proof = DumbMessage; + type Error = Invalid; + + fn convolve_commit( + &self, + _msg: ::Message, + ) -> Result { + Err(Invalid) + } + + fn merge(&mut self, _other: Self) -> Result<(), impl Error> + where Self: Sized { + Result::<_, Infallible>::Ok(()) + } + } + + #[derive(Copy, Clone, Debug)] + struct Invalid; + impl Display for Invalid { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("invalid seal") } + } + impl Error for Invalid {} + + #[test] + fn verify_no_client_witness() { + let mut a = NoClientWitness::default(); + let b = NoClientWitness::::new(); + assert_eq!(a, b); + a.merge(b).unwrap(); + } + + #[test] + fn verify_success() { + let seal = DumbSeal(0xCA); + let message = [ + 0xDEu8, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xFE, 0xED, 0xBE, 0xD0, 0xBA, 0xD1, + 0xDA, 0xFE, + ]; + let witness = SealWitness::new(DumbPubWitness(message, seal), NoClientWitness::default()); + witness + .verify_seal_closing(seal, message) + // We need this to test Display impl for the SealError + .inspect_err(|e| eprintln!("{e}")) + .unwrap() + } + + #[test] + fn verify_not_included() { + let seal = DumbSeal(0xCA); + let fake_seal = DumbSeal(0x00); + let message = [ + 0xDEu8, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xFE, 0xED, 0xBE, 0xD0, 0xBA, 0xD1, + 0xDA, 0xFE, + ]; + let witness = + SealWitness::new(DumbPubWitness(message, fake_seal), NoClientWitness::default()); + let res = witness.verify_seal_closing(seal, message).unwrap_err(); + // We need this to test cover Debug and Display impl for SealError + println!("{res}"); + println!("{res:?}"); + assert!(matches!( + res, + SealError::NotIncluded(s, 0x00) if s == seal + )); + } + + #[test] + fn verify_fake_message() { + let seal = DumbSeal(0xCA); + let message = [ + 0xDEu8, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xFE, 0xED, 0xBE, 0xD0, 0xBA, 0xD1, + 0xDA, 0xFE, + ]; + let mut fake_message = message; + fake_message[0] = 0x00; + let witness = SealWitness::new(DumbPubWitness(message, seal), NoClientWitness::default()); + let res = witness.verify_seal_closing(seal, fake_message).unwrap_err(); + // We need this to test cover Debug and Display impl for SealError + println!("{res}"); + println!("{res:?}"); + assert!(matches!(res, SealError::Published(Invalid))); + } + + #[test] + fn verify_failing_client_witness() { + let seal = FailingSeal(0xCA); + let message = [ + 0xDEu8, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xFE, 0xED, 0xBE, 0xD0, 0xBA, 0xD1, + 0xDA, 0xFE, + ]; + let witness = SealWitness::new(DumbPubWitness(message, seal), FailingCliWitness::default()); + let res = witness.verify_seal_closing(seal, message).unwrap_err(); + // We need this to test cover Debug and Display impl for SealError + println!("{res}"); + println!("{res:?}"); + assert!(matches!(res, SealError::Client(Invalid))); + } +} diff --git a/src/api.rs b/src/api.rs index 7b462d5c..f2421acb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,22 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. use std::fmt::{self, Debug, Display, Formatter}; use std::hash::Hash; diff --git a/src/lib.rs b/src/lib.rs index e95a4b94..0c0091aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,34 +2,41 @@ // // SPDX-License-Identifier: Apache-2.0 // -// Written in 2019-2024 by -// Dr. Maxim Orlovsky +// Designed in 2019-2025 by Dr Maxim Orlovsky +// Written in 2024-2025 by Dr Maxim Orlovsky // -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland. +// Copyright (C) 2024-2025 LNP/BP Laboratories, +// Institute for Distributed and Cognitive Systems +// (InDCS), Switzerland. Copyright (C) 2019-2025 Dr Maxim Orlovsky. +// All rights under the above copyrights are reserved. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. // Coding conventions #![deny( - non_upper_case_globals, - non_camel_case_types, - non_snake_case, + unsafe_code, + dead_code, + missing_docs, + unused_variables, unused_mut, unused_imports, - dead_code, - missing_docs + non_upper_case_globals, + non_camel_case_types, + non_snake_case )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(coverage_nightly, feature(coverage_attribute))] //! The LNP/BP client-side-validation foundation libraries implementing LNPBP //! specifications & standards (LNPBP-4, 7, 8, 9, 81). @@ -42,9 +49,9 @@ //! * Strict binary data serialization used by client-side validation //! //! The goal of this module is to maximally reduce the probability of errors and -//! mistakes within particular implementations of this paradigms by -//! standardizing typical workflow processes in a form of interfaces that -//! will be nearly impossible to use in a wrong way. +//! mistakes within particular implementations of this paradigm by +//! standardizing typical workflow processes in the form of interfaces that +//! will be nearly impossible to use in the wrong way. /// Re-export of `commit_verify` crate. pub extern crate commit_verify as commit;