From 50b601ea66e35300054806c1270d6c60099525b6 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 30 Apr 2023 20:56:43 +0200 Subject: [PATCH 01/21] derive: add commit_encode derivation crate template --- Cargo.lock | 355 ++++++++++++++++++++++- Cargo.toml | 10 +- commit_verify/Cargo.toml | 4 +- commit_verify/derive/Cargo.toml | 28 ++ commit_verify/derive/README.md | 55 ++++ commit_verify/derive/src/derive.rs | 213 ++++++++++++++ commit_verify/derive/src/lib.rs | 72 +++++ commit_verify/derive/src/params.rs | 255 ++++++++++++++++ commit_verify/derive/tests/base.rs | 215 ++++++++++++++ commit_verify/derive/tests/common/mod.rs | 66 +++++ commit_verify/derive/tests/dumb.rs | 176 +++++++++++ commit_verify/derive/tests/type.rs | 169 +++++++++++ 12 files changed, 1605 insertions(+), 13 deletions(-) create mode 100644 commit_verify/derive/Cargo.toml create mode 100644 commit_verify/derive/README.md create mode 100644 commit_verify/derive/src/derive.rs create mode 100644 commit_verify/derive/src/lib.rs create mode 100644 commit_verify/derive/src/params.rs create mode 100644 commit_verify/derive/tests/base.rs create mode 100644 commit_verify/derive/tests/common/mod.rs create mode 100644 commit_verify/derive/tests/dumb.rs create mode 100644 commit_verify/derive/tests/type.rs diff --git a/Cargo.lock b/Cargo.lock index 2fa7a2b3..be1b9ddc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,14 +2,23 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + [[package]] name = "amplify" -version = "4.0.0-beta.20" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da67d736b18002e654099c6b393ebb3058825513baefec514cf7725c2088ced7" +checksum = "f26966af46e0d200e8bf2b7f16230997c1c3f2d141bc27ccc091c012ed527b58" dependencies = [ "amplify_apfloat", - "amplify_derive", + "amplify_derive 3.0.0", "amplify_num", "amplify_syn", "ascii", @@ -31,6 +40,18 @@ dependencies = [ "bitflags", ] +[[package]] +name = "amplify_derive" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "580f12b79a9e10cfa8d2515128d83a53f387e290096a75904c92b8a2a4d542a6" +dependencies = [ + "amplify_syn", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "amplify_derive" version = "4.0.0-alpha.6" @@ -54,15 +75,21 @@ dependencies = [ [[package]] name = "amplify_syn" -version = "2.0.0-beta.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4ba0b995a9379a410822026a2bedbba790197dd7cb980954fb64b14476bb8d" +checksum = "29b08d74fda406d5a94abfdcdb91ba13bb06562ccf0a4581867fa924ca242b01" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + [[package]] name = "arrayref" version = "0.3.6" @@ -179,24 +206,61 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "client_side_validation" -version = "0.10.0" +version = "0.10.1" dependencies = [ "commit_verify", "serde", "single_use_seals", ] +[[package]] +name = "commit_encoding_derive" +version = "0.10.0-beta.1" +dependencies = [ + "amplify", + "amplify_syn", + "commit_verify", + "compiletest_rs", + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "commit_verify" -version = "0.10.0" +version = "0.10.1" dependencies = [ "amplify", + "commit_encoding_derive", "rand", "serde", "strict_encoding", "strict_types", ] +[[package]] +name = "compiletest_rs" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70489bbb718aea4f92e5f48f2e3b5be670c2051de30e57cb6e5377b4aa08b372" +dependencies = [ + "diff", + "filetime", + "getopts", + "lazy_static", + "libc", + "log", + "miow", + "regex", + "rustfix", + "serde", + "serde_derive", + "serde_json", + "tester", + "winapi", +] + [[package]] name = "constant_time_eq" version = "0.2.4" @@ -219,6 +283,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.6" @@ -230,6 +300,39 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "filetime" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -240,6 +343,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -272,6 +384,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "indexmap" version = "1.9.2" @@ -309,6 +430,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + [[package]] name = "mnemonic" version = "1.0.1" @@ -319,6 +455,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.17.1" @@ -385,6 +531,61 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "rustfix" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd2853d9e26988467753bd9912c3a126f642d05d229a4b53f5752ee36c56481" +dependencies = [ + "anyhow", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + [[package]] name = "ryu" version = "1.0.13" @@ -449,7 +650,7 @@ dependencies = [ name = "single_use_seals" version = "0.10.0" dependencies = [ - "amplify_derive", + "amplify_derive 4.0.0-alpha.6", "async-trait", ] @@ -519,6 +720,50 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "tester" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8bf7e0eb2dd7b4228cc1b6821fc5114cd6841ae59f652a85488c016091e5f" +dependencies = [ + "cfg-if", + "getopts", + "libc", + "num_cpus", + "term", +] + +[[package]] +name = "thiserror" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.5.11" @@ -540,6 +785,12 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "unsafe-libyaml" version = "0.2.7" @@ -611,3 +862,91 @@ name = "wasm-bindgen-shared" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index eaa06070..845ff171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,14 @@ members = [ ".", "commit_verify", - "single_use_seals" + "commit_verify/derive", + "single_use_seals", ] default-members = [ ".", "commit_verify", - "single_use_seals" + "commit_verify/derive", + "single_use_seals", ] [workspace.package] @@ -20,7 +22,7 @@ license = "Apache-2.0" [package] name = "client_side_validation" -version = "0.10.0" +version = "0.10.1" description = "Client-side validation foundation library" keywords = ["lnp-bp", "smart-contracts", "blockchain"] categories = ["cryptography"] @@ -38,7 +40,7 @@ name = "client_side_validation" path = "src/lib.rs" [dependencies] -commit_verify = { version = "0.10.0", path = "./commit_verify" } +commit_verify = { version = "0.10.1", path = "./commit_verify" } single_use_seals = { version = "0.10.0", path = "./single_use_seals" } serde_crate = { package = "serde", version = "1", features = ["derive"], optional = true } diff --git a/commit_verify/Cargo.toml b/commit_verify/Cargo.toml index 2a22a0b3..df6b6ace 100644 --- a/commit_verify/Cargo.toml +++ b/commit_verify/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "commit_verify" -version = "0.10.0" +version = "0.10.1" description = "Commit-verify API for client-side validation" keywords = ["lnp-bp", "smart-contracts", "blockchain", "commitments"] categories = ["cryptography"] @@ -11,6 +11,7 @@ edition = { workspace = true } license = { workspace = true } rust-version = { workspace = true } readme = "README.md" +exclude = ["derive"] [lib] name = "commit_verify" @@ -22,6 +23,7 @@ required-features = ["stl"] [dependencies] amplify = { version = "4.0.0-beta.20", features = ["hex", "apfloat"] } +commit_encoding_derive = { version = "0.10.0-beta.1", path = "derive" } strict_encoding = "2.0.0" strict_types = { version = "1.0.0-rc.1", optional = true } rand = { version = "0.8.5", optional = true } diff --git a/commit_verify/derive/Cargo.toml b/commit_verify/derive/Cargo.toml new file mode 100644 index 00000000..e5f08242 --- /dev/null +++ b/commit_verify/derive/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "commit_encoding_derive" +version = "0.10.0-beta.1" +description = "Commitment encoding derivation macros" +keywords = ["commitments", "proc-macro"] +categories = ["development-tools", "encoding"] +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +readme = "README.md" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +syn = { version = "1", features = ["full"] } +proc-macro2 = "1" +amplify_syn = "2.0.0" +heck = "0.4.0" + +[dev-dependencies] +commit_verify = { path = ".." } +amplify = "4.0.0" +compiletest_rs = "0.9.0" diff --git a/commit_verify/derive/README.md b/commit_verify/derive/README.md new file mode 100644 index 00000000..7f4bd576 --- /dev/null +++ b/commit_verify/derive/README.md @@ -0,0 +1,55 @@ +# Commitment encoding derivation macros + +![Build](https://github.com/LNP-BP/client_side_validation/workflows/Build/badge.svg) +![Tests](https://github.com/LNP-BP/client_side_validation/workflows/Tests/badge.svg) +![Lints](https://github.com/LNP-BP/client_side_validation/workflows/Lints/badge.svg) +[![codecov](https://codecov.io/gh/LNP-BP/client_side_validation/branch/master/graph/badge.svg)](https://codecov.io/gh/LNP-BP/client_side_validation) + +[![crates.io](https://meritbadge.herokuapp.com/strict_encoding_derive)](https://crates.io/crates/strict_encoding_derive) +[![Docs](https://docs.rs/strict_encoding_derive/badge.svg)](https://docs.rs/strict_encoding_derive) +[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) +[![Apache-2 licensed](https://img.shields.io/crates/l/strict_encoding_derive)](../LICENSE) + +Derivation macros for strict encoding. To learn more about the strict encoding +please check [`strict_encoding`] crate. + +The development of the library is supported by +[LNP/BP Standards Association](https://lnp-bp.org). + + +## Documentation + +Detailed developer & API documentation for the library can be accessed +at + + +## Usage + +To use the library, you need to reference a latest version of the +[`commit_encode_derive`] crate in`[dependencies]` section of your project +`Cargo.toml`. This crate includes derivation macros from the present library by +default. + +```toml +commit_encode_derive = "0.10" +``` + +If you are using other client-side-validation libraries, consider importing +just a single [`client_side_validation`] library which re-exports all of them, +including the current one. + +Library exports derivation macros `#[derive(CommitEncode)]`. + + +## Contributing + +Contribution guidelines can be found in [CONTRIBUTING](../../CONTRIBUTING.md) + + +## Licensing + +The libraries are distributed on the terms of Apache 2.0 opensource license. +See [LICENCE](LICENSE) file for the license details. + +[`client_side_validation`]: https://crates.io/crates/client_side_validation +[`strict_encoding`]: https://crates.io/crates/strict_encoding diff --git a/commit_verify/derive/src/derive.rs b/commit_verify/derive/src/derive.rs new file mode 100644 index 00000000..0455a2e7 --- /dev/null +++ b/commit_verify/derive/src/derive.rs @@ -0,0 +1,213 @@ +// Client-side-validation foundation libraries. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr. Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights 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 +// +// 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. + +use amplify_syn::{DeriveInner, EnumKind, Field, FieldKind, Fields, Items, NamedField, Variant}; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use syn::{Error, Index, Result}; + +use crate::params::{CommitDerive, FieldAttr, VariantAttr}; + +struct DeriveEncode<'a>(&'a CommitDerive); + +impl CommitDerive { + pub fn derive_encode(&self) -> Result { + self.data + .derive(&self.conf.strict_crate, &ident!(StrictEncode), &DeriveEncode(self)) + } +} + +impl DeriveInner for DeriveEncode<'_> { + fn derive_unit_inner(&self) -> Result { + Err(Error::new( + Span::call_site(), + "StrictEncode must not be derived on a unit types. Use just a unit type instead when \ + encoding parent structure.", + )) + } + + fn derive_struct_inner(&self, fields: &Items) -> Result { + let crate_name = &self.0.conf.strict_crate; + + let mut orig_name = Vec::with_capacity(fields.len()); + let mut field_name = Vec::with_capacity(fields.len()); + for named_field in fields { + let attr = FieldAttr::with(named_field.field.attr.clone(), FieldKind::Named)?; + if !attr.skip { + orig_name.push(&named_field.name); + field_name.push(attr.field_name(&named_field.name)); + } + } + + Ok(quote! { + fn strict_encode(&self, writer: W) -> ::std::io::Result { + use #crate_name::{TypedWrite, WriteStruct, fname}; + writer.write_struct::(|w| { + Ok(w + #( .write_field(fname!(#field_name), &self.#orig_name)? )* + .complete()) + }) + } + }) + } + + fn derive_tuple_inner(&self, fields: &Items) -> Result { + let crate_name = &self.0.conf.strict_crate; + + let no = fields.iter().enumerate().filter_map(|(index, field)| { + let attr = FieldAttr::with(field.attr.clone(), FieldKind::Unnamed).ok()?; + if attr.skip { + None + } else { + Some(Index::from(index)) + } + }); + + Ok(quote! { + fn strict_encode(&self, writer: W) -> ::std::io::Result { + use #crate_name::{TypedWrite, WriteTuple}; + writer.write_tuple::(|w| { + Ok(w + #( .write_field(&self.#no)? )* + .complete()) + }) + } + }) + } + + fn derive_enum_inner(&self, variants: &Items) -> Result { + let crate_name = &self.0.conf.strict_crate; + + let inner = if variants.enum_kind() == EnumKind::Primitive { + quote! { + writer.write_enum(*self) + } + } else { + let mut define_variants = Vec::with_capacity(variants.len()); + let mut write_variants = Vec::with_capacity(variants.len()); + for var in variants { + let attr = VariantAttr::try_from(var.attr.clone())?; + let var_name = &var.name; + let name = attr.variant_name(var_name); + match &var.fields { + Fields::Unit => { + define_variants.push(quote! { + .define_unit(vname!(#name)) + }); + write_variants.push(quote! { + Self::#var_name => writer.write_unit(vname!(#name))?, + }); + } + Fields::Unnamed(fields) if fields.is_empty() => { + define_variants.push(quote! { + .define_unit(vname!(#name)) + }); + write_variants.push(quote! { + Self::#var_name() => writer.write_unit(vname!(#name))?, + }); + } + Fields::Named(fields) if fields.is_empty() => { + define_variants.push(quote! { + .define_unit(vname!(#name)) + }); + write_variants.push(quote! { + Self::#var_name {} => writer.write_unit(vname!(#name))?, + }); + } + Fields::Unnamed(fields) => { + let mut field_ty = Vec::with_capacity(fields.len()); + let mut field_idx = Vec::with_capacity(fields.len()); + for (index, field) in fields.iter().enumerate() { + let attr = FieldAttr::with(field.attr.clone(), FieldKind::Unnamed)?; + + if !attr.skip { + let ty = &field.ty; + let index = Ident::new(&format!("_{index}"), Span::call_site()); + field_ty.push(quote! { #ty }); + field_idx.push(quote! { #index }); + } + } + define_variants.push(quote! { + .define_tuple(vname!(#name), |d| { + d #( .define_field::<#field_ty>() )* .complete() + }) + }); + write_variants.push(quote! { + Self::#var_name( #( #field_idx ),* ) => writer.write_tuple(vname!(#name), |w| { + Ok(w #( .write_field(#field_idx)? )* .complete()) + })?, + }); + } + Fields::Named(fields) => { + let mut field_ty = Vec::with_capacity(fields.len()); + let mut field_name = Vec::with_capacity(fields.len()); + let mut field_rename = Vec::with_capacity(fields.len()); + for named_field in fields { + let attr = + FieldAttr::with(named_field.field.attr.clone(), FieldKind::Named)?; + + let ty = &named_field.field.ty; + let name = &named_field.name; + let rename = attr.field_name(name); + + if !attr.skip { + field_ty.push(quote! { #ty }); + field_name.push(quote! { #name }); + field_rename.push(quote! { fname!(#rename) }); + } + } + + define_variants.push(quote! { + .define_struct(vname!(#name), |d| { + d #( .define_field::<#field_ty>(#field_rename) )* .complete() + }) + }); + write_variants.push(quote! { + Self::#var_name { #( #field_name ),* } => writer.write_struct(vname!(#name), |w| { + Ok(w #( .write_field(#field_rename, #field_name)? )* .complete()) + })?, + }); + } + } + } + + quote! { + #[allow(unused_imports)] + use #crate_name::{DefineUnion, WriteUnion, DefineTuple, DefineStruct, WriteTuple, WriteStruct, fname, vname}; + writer.write_union::(|definer| { + let writer = definer + #( #define_variants )* + .complete(); + + Ok(match self { + #( #write_variants )* + }.complete()) + }) + } + }; + + Ok(quote! { + fn strict_encode(&self, writer: W) -> ::std::io::Result { + use #crate_name::TypedWrite; + #inner + } + }) + } +} diff --git a/commit_verify/derive/src/lib.rs b/commit_verify/derive/src/lib.rs new file mode 100644 index 00000000..b751cef2 --- /dev/null +++ b/commit_verify/derive/src/lib.rs @@ -0,0 +1,72 @@ +// Client-side-validation foundation libraries. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr. Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights 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 +// +// 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. + +// Coding conventions +#![recursion_limit = "256"] + +//! Derivation macros for strict encoding. To learn more about the strict +//! encoding please check `strict_encoding` crate. +//! +//! # Derivation macros +//! +//! Library exports derivation macros `#[derive(`[`StrictEncode`]`)]`, +//! `#[derive(`[`StrictDecode`]`)]`, which can be added on top of any structure +//! you'd like to support string encoding (see Example section below). +//! +//! Encoding/decoding implemented by both of these macros may be configured at +//! type and individual field level using `#[strict_type(...)]` attributes. +//! +//! # Attribute +//! +//! [`StrictEncode`] and [`StrictDecode`] behavior can be customized with +//! `#[strict_encoding(...)]` attribute, which accepts different arguments +//! depending to which part of the data type it is applied. +//! +//! ## Attribute arguments at type declaration level +//! +//! Derivation macros accept `#[strict_encoding()]` attribute with the following +//! arguments: + +#[macro_use] +extern crate quote; +extern crate proc_macro; +#[macro_use] +extern crate syn; +#[macro_use] +extern crate amplify_syn; + +pub(crate) mod params; +mod derive; + +use proc_macro::TokenStream; +use syn::DeriveInput; + +use crate::params::CommitDerive; + +/// Derives [`CommitEncode`] implementation for the type. +#[proc_macro_derive(CommitEncode, attributes(commit_encode))] +pub fn derive_commit_encode(input: TokenStream) -> TokenStream { + let derive_input = parse_macro_input!(input as DeriveInput); + CommitDerive::try_from(derive_input) + .and_then(|engine| engine.derive_encode()) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +} diff --git a/commit_verify/derive/src/params.rs b/commit_verify/derive/src/params.rs new file mode 100644 index 00000000..de7c7fcb --- /dev/null +++ b/commit_verify/derive/src/params.rs @@ -0,0 +1,255 @@ +// Client-side-validation foundation libraries. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr. Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights 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 +// +// 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. + +use std::collections::HashMap; + +use amplify_syn::{ + ArgValueReq, AttrReq, DataType, EnumKind, FieldKind, ListReq, ParametrizedAttr, TypeClass, + ValueClass, +}; +use heck::ToLowerCamelCase; +use proc_macro2::{Ident, Span}; +use quote::ToTokens; +use syn::{DeriveInput, Error, Expr, LitInt, LitStr, Path, Result}; + +const ATTR: &str = "commit_encode"; +const ATTR_CRATE: &str = "crate"; +const ATTR_LIB: &str = "lib"; +const ATTR_RENAME: &str = "rename"; +const ATTR_WITH: &str = "with"; +const ATTR_ENCODE_WITH: &str = "encode_with"; +const ATTR_DECODE_WITH: &str = "decode_with"; +const ATTR_DUMB: &str = "dumb"; +const ATTR_TAGS: &str = "tags"; +const ATTR_TAGS_ORDER: &str = "order"; +const ATTR_TAGS_REPR: &str = "repr"; +const ATTR_TAGS_CUSTOM: &str = "custom"; +const ATTR_TAG: &str = "tag"; +const ATTR_SKIP: &str = "skip"; +const ATTR_INTO_U8: &str = "into_u8"; +const ATTR_TRY_FROM_U8: &str = "try_from_u8"; + +pub struct ContainerAttr { + pub strict_crate: Path, + pub lib: Expr, + pub rename: Option, + pub dumb: Option, + pub encode_with: Option, + pub decode_with: Option, +} + +pub struct EnumAttr { + pub tags: VariantTags, + pub try_from_u8: bool, + pub into_u8: bool, +} + +pub struct FieldAttr { + pub dumb: Option, + pub rename: Option, + pub skip: bool, +} + +pub struct VariantAttr { + pub dumb: bool, + pub rename: Option, + pub tag: Option, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum VariantTags { + Repr, + Order, + Custom, +} + +impl ContainerAttr { + fn shared_attrs() -> Vec<(&'static str, ArgValueReq)> { + vec![ + (ATTR_CRATE, ArgValueReq::optional(TypeClass::Path)), + (ATTR_LIB, ArgValueReq::required(ValueClass::Expr)), + (ATTR_RENAME, ArgValueReq::optional(ValueClass::str())), + (ATTR_DUMB, ArgValueReq::optional(ValueClass::Expr)), + (ATTR_ENCODE_WITH, ArgValueReq::optional(TypeClass::Path)), + (ATTR_DECODE_WITH, ArgValueReq::optional(TypeClass::Path)), + ] + } +} + +impl EnumAttr { + fn attr_req(map: HashMap<&str, ArgValueReq>, kind: EnumKind) -> AttrReq { + let mut req = AttrReq::with(map); + if kind == EnumKind::Primitive { + req.path_req = ListReq::any_of(vec![path!(try_from_u8), path!(into_u8)], false); + } + req + } +} + +impl TryFrom for ContainerAttr { + type Error = Error; + + fn try_from(mut params: ParametrizedAttr) -> Result { + let mut attrs = ContainerAttr::shared_attrs(); + attrs.extend([(ATTR_TAGS, ArgValueReq::optional(TypeClass::Path))]); + let map = HashMap::from_iter(attrs); + + params.check(EnumAttr::attr_req(map, EnumKind::Primitive))?; + + Ok(ContainerAttr { + strict_crate: params + .arg_value(ATTR_CRATE) + .unwrap_or_else(|_| path!(strict_encoding)), + lib: params.unwrap_arg_value(ATTR_LIB), + rename: params.arg_value(ATTR_RENAME).ok(), + dumb: params.arg_value(ATTR_DUMB).ok(), + encode_with: params + .arg_value(ATTR_ENCODE_WITH) + .or_else(|_| params.arg_value(ATTR_WITH)) + .ok(), + decode_with: params + .arg_value(ATTR_DECODE_WITH) + .or_else(|_| params.arg_value(ATTR_WITH)) + .ok(), + }) + } +} + +impl EnumAttr { + pub fn with(mut params: ParametrizedAttr, kind: EnumKind) -> Result { + let mut attrs = ContainerAttr::shared_attrs(); + attrs.extend([(ATTR_TAGS, ArgValueReq::required(TypeClass::Path))]); + let map = HashMap::from_iter(attrs); + + params.check(EnumAttr::attr_req(map, kind))?; + + let tags = match params + .arg_value(ATTR_TAGS) + .unwrap_or_else(|_| path!(custom)) + .to_token_stream() + .to_string() + .as_str() + { + ATTR_TAGS_REPR => VariantTags::Repr, + ATTR_TAGS_ORDER => VariantTags::Order, + ATTR_TAGS_CUSTOM => VariantTags::Custom, + unknown => { + return Err(Error::new( + Span::call_site(), + format!( + "invalid enum strict encoding value for `tags` attribute `{unknown}`; \ + only `repr`, `order` or `custom` are allowed" + ), + )); + } + }; + + let try_from_u8 = params.has_verbatim(ATTR_TRY_FROM_U8); + let into_u8 = params.has_verbatim(ATTR_INTO_U8); + + if tags != VariantTags::Repr && kind == EnumKind::Primitive { + return Err(Error::new( + Span::call_site(), + "primitive enum types must always use `tags = repr`", + )); + } + + Ok(EnumAttr { + tags, + try_from_u8, + into_u8, + }) + } +} + +impl FieldAttr { + pub fn with(mut params: ParametrizedAttr, kind: FieldKind) -> Result { + let mut map = + HashMap::from_iter(vec![(ATTR_DUMB, ArgValueReq::optional(ValueClass::Expr))]); + + if kind == FieldKind::Named { + map.insert(ATTR_RENAME, ArgValueReq::optional(ValueClass::str())); + } + + let mut attr_req = AttrReq::with(map); + attr_req.path_req = ListReq::maybe_one(path!(skip)); + params.check(attr_req)?; + + Ok(FieldAttr { + rename: params.arg_value(ATTR_RENAME).ok(), + dumb: params.arg_value(ATTR_DUMB).ok(), + skip: params.has_verbatim(ATTR_SKIP), + }) + } + + pub fn field_name(&self, name: &Ident) -> LitStr { + match self.rename { + None => LitStr::new(&name.to_string().to_lower_camel_case(), name.span()), + Some(ref name) => name.clone(), + } + } +} + +impl TryFrom for VariantAttr { + type Error = Error; + + fn try_from(mut params: ParametrizedAttr) -> Result { + let map = HashMap::from_iter(vec![ + (ATTR_RENAME, ArgValueReq::optional(ValueClass::str())), + (ATTR_TAG, ArgValueReq::optional(ValueClass::int())), + ]); + + let mut req = AttrReq::with(map); + req.path_req = ListReq::maybe_one(path!(dumb)); + params.check(req)?; + + Ok(VariantAttr { + rename: params.arg_value(ATTR_RENAME).ok(), + tag: params.arg_value(ATTR_TAG).ok(), + dumb: params.has_verbatim("dumb"), + }) + } +} + +impl VariantAttr { + pub fn variant_name(&self, name: &Ident) -> LitStr { + match self.rename { + None => LitStr::new(&name.to_string().to_lower_camel_case(), name.span()), + Some(ref name) => name.clone(), + } + } +} + +pub struct CommitDerive { + pub data: DataType, + pub conf: ContainerAttr, +} + +impl TryFrom for CommitDerive { + type Error = Error; + + fn try_from(input: DeriveInput) -> Result { + let params = ParametrizedAttr::with(ATTR, &input.attrs)?; + let conf = ContainerAttr::try_from(params)?; + let data = DataType::with(input, ident!(strict_type))?; + Ok(Self { data, conf }) + } +} diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs new file mode 100644 index 00000000..61b80b99 --- /dev/null +++ b/commit_verify/derive/tests/base.rs @@ -0,0 +1,215 @@ +// Client-side-validation foundation libraries. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr. Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights 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 +// +// 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. + +#[macro_use] +extern crate amplify; +#[macro_use] +extern crate strict_encoding_derive; + +mod common; + +use std::convert::Infallible; + +use strict_encoding::{ + tn, StrictDecode, StrictDumb, StrictEncode, StrictSerialize, StrictSum, VariantError, +}; + +const TEST_LIB: &str = "TestLib"; + +#[test] +fn wrapper_base() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB)] + struct ShortLen(u16); + + Ok(()) +} + +#[test] +fn tuple_base() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB)] + struct TaggedInfo(u16, u64); + + Ok(()) +} + +#[test] +fn tuple_generics() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB)] + struct Pair< + A: StrictDumb + StrictEncode + StrictDecode, + B: StrictDumb + StrictEncode + StrictDecode, + >(A, B); + + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB)] + struct WhereConstraint, B: From>(A, B) + where + A: StrictDumb + StrictEncode + StrictDecode + From, + >::Error: From, + B: StrictDumb + StrictEncode + StrictDecode; + + Ok(()) +} + +#[test] +fn struct_generics() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB)] + struct Field { + tag: u8, + value: V, + } + + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode)] + #[strict_type(lib = TEST_LIB)] + struct ComplexField<'a, V: StrictEncode + StrictDumb> + where + for<'b> V: From<&'b str>, + &'a V: Default, + { + tag: u8, + value: &'a V, + } + + Ok(()) +} + +#[test] +fn enum_ord() -> common::Result { + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] + #[repr(u8)] + enum Variants { + #[strict_type(dumb)] + One = 5, + Two = 6, + Three = 7, + } + + assert_eq!(Variants::Three as u8, 7); + assert_eq!(u8::from(Variants::Three), 7); + assert_eq!(Variants::try_from(6), Ok(Variants::Two)); + assert_eq!(Variants::try_from(3), Err(VariantError(Some(tn!("Variants")), 3))); + + Ok(()) +} + +#[test] +fn enum_repr() -> common::Result { + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] + #[repr(u16)] + enum Cls { + One = 1, + #[strict_type(dumb)] + Two, + Three, + } + + assert_eq!(u8::from(Cls::Three), 3); + assert_eq!(Cls::try_from(2), Ok(Cls::Two)); + assert_eq!(Cls::try_from(4), Err(VariantError(Some(tn!("Cls")), 4))); + + Ok(()) +} + +#[test] +fn enum_associated() -> common::Result { + #[allow(dead_code)] + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB, tags = order)] + enum Assoc { + One { + hash: [u8; 32], + ord: u8, + }, + Two(u8, u16, u32), + #[strict_type(dumb)] + Three, + Four(), + Five {}, + } + + assert_eq!(Assoc::ALL_VARIANTS, &[ + (0, "one"), + (1, "two"), + (2, "three"), + (3, "four"), + (4, "five") + ]); + + Ok(()) +} + +#[test] +fn enum_custom_tags() -> common::Result { + #[allow(dead_code)] + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB, tags = order)] + enum Assoc { + One { + hash: [u8; 32], + ord: u8, + }, + #[strict_type(tag = 2)] + Two(u8, u16, u32), + #[strict_type(dumb, tag = 3)] + Three, + #[strict_type(tag = 4)] + Four(), + #[strict_type(tag = 5)] + Five {}, + } + + impl StrictSerialize for Assoc {} + + assert_eq!(Assoc::ALL_VARIANTS, &[ + (0, "one"), + (2, "two"), + (3, "three"), + (4, "four"), + (5, "five") + ]); + + let assoc = Assoc::Two(0, 1, 2); + assert_eq!(assoc.to_strict_serialized::<256>().unwrap().as_slice(), &[2, 0, 1, 0, 2, 0, 0, 0]); + + let assoc = Assoc::One { + hash: [0u8; 32], + ord: 0, + }; + assert_eq!(assoc.to_strict_serialized::<256>().unwrap().as_slice(), &[0u8; 34]); + + Ok(()) +} diff --git a/commit_verify/derive/tests/common/mod.rs b/commit_verify/derive/tests/common/mod.rs new file mode 100644 index 00000000..ead6c76a --- /dev/null +++ b/commit_verify/derive/tests/common/mod.rs @@ -0,0 +1,66 @@ +// Client-side-validation foundation libraries. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr. Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights 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 +// +// 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. + +extern crate compiletest_rs as compiletest; + +use std::fmt::{Debug, Display, Formatter}; +use std::path::PathBuf; + +use strict_encoding::{StrictDecode, StrictEncode}; +use strict_encoding_test::DataEncodingTestFailure; + +#[allow(dead_code)] +pub fn compile_test(mode: &'static str) { + let mut config = compiletest::Config { + mode: mode.parse().expect("Invalid mode"), + src_base: PathBuf::from(format!("tests/{}", mode)), + ..default!() + }; + config.link_deps(); + config.clean_rmeta(); + compiletest::run_tests(&config); +} + +#[derive(Display)] +#[display(inner)] +pub struct Error(pub Box); + +impl Debug for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(self, f) } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(self.0.as_ref()) } +} + +impl From> for Error +where T: StrictEncode + StrictDecode + PartialEq + Debug + Clone + 'static +{ + fn from(err: DataEncodingTestFailure) -> Self { Self(Box::new(err)) } +} + +/* +impl From for Error { + fn from(err: strict_encoding::Error) -> Self { Self(Box::new(err)) } +} +*/ + +pub type Result = std::result::Result<(), Error>; diff --git a/commit_verify/derive/tests/dumb.rs b/commit_verify/derive/tests/dumb.rs new file mode 100644 index 00000000..a080c4aa --- /dev/null +++ b/commit_verify/derive/tests/dumb.rs @@ -0,0 +1,176 @@ +// Client-side-validation foundation libraries. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr. Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights 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 +// +// 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. + +// Caused by an imperfection of rust compiler in parsing proc macro args +#![allow(unused_braces)] + +#[macro_use] +extern crate amplify; + +mod common; + +use amplify::confinement::Confined; +use strict_encoding::{StrictDecode, StrictDumb, StrictEncode, StrictType}; + +const TEST_LIB: &str = "TestLib"; + +#[test] +fn struct_default() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug, Default)] + #[derive(StrictType)] + #[strict_type(lib = TEST_LIB)] + struct Field + where V: Default + { + name: u8, + value: V, + } + + assert_eq!(Field::::strict_dumb(), Field::::default()); + + Ok(()) +} + +#[test] +fn enum_default() -> common::Result { + #[allow(dead_code)] + #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] + #[derive(StrictType)] + #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] + #[repr(u8)] + enum Variants { + One, + Two, + #[default] + Three, + } + + assert_eq!(Variants::strict_dumb(), Variants::Three); + + Ok(()) +} + +#[test] +fn enum_explicit() -> common::Result { + #[allow(dead_code)] + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb)] + #[strict_type(lib = TEST_LIB)] + #[repr(u8)] + enum Variants { + One, + Two, + #[strict_type(dumb)] + Three, + } + + assert_eq!(Variants::strict_dumb(), Variants::Three); + + Ok(()) +} + +#[test] +fn dumb_wrapper_container() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb)] + #[strict_type(lib = TEST_LIB, dumb = ShortLen(u16::MAX))] + struct ShortLen(u16); + + assert_eq!(ShortLen::strict_dumb(), ShortLen(u16::MAX)); + Ok(()) +} + +#[test] +fn dumb_wrapper_field() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb)] + #[strict_type(lib = TEST_LIB)] + struct ShortLen(#[strict_type(dumb = 1)] u16); + + assert_eq!(ShortLen::strict_dumb(), ShortLen(1)); + Ok(()) +} + +#[test] +fn dumb_wrapper_precedence() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb)] + #[strict_type(lib = TEST_LIB, dumb = ShortLen(u16::MAX))] + struct ShortLen(#[strict_type(dumb = 1)] u16); + + assert_eq!(ShortLen::strict_dumb(), ShortLen(u16::MAX)); + Ok(()) +} + +#[test] +fn dumb_struct() -> common::Result { + #[allow(unused_braces)] + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType)] + #[strict_type(lib = TEST_LIB, tags = order, dumb = Dumb::new())] + struct Dumb { + field1: u8, + field2: u16, + } + + impl Dumb { + pub fn new() -> Self { + Dumb { + field1: 1, + field2: 2, + } + } + } + + assert_eq!(Dumb::strict_dumb(), Dumb { + field1: 1, + field2: 2 + }); + + Ok(()) +} + +#[test] +fn dumb_enum_associated() -> common::Result { + #[allow(unused_braces)] + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType)] + #[strict_type(lib = TEST_LIB, tags = order, dumb = { Assoc::Variant { field: 0 } })] + enum Assoc { + Variant { field: u8 }, + } + + assert_eq!(Assoc::strict_dumb(), Assoc::Variant { field: 0 }); + + Ok(()) +} + +#[test] +fn dumb_ultra_complex() -> common::Result { + #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, From)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB, dumb = NamedFields(confined_vec!(T::strict_dumb())))] + pub struct NamedFields( + Confined, 1, { u8::MAX as usize }>, + ); + + Ok(()) +} diff --git a/commit_verify/derive/tests/type.rs b/commit_verify/derive/tests/type.rs new file mode 100644 index 00000000..8a4b5d9e --- /dev/null +++ b/commit_verify/derive/tests/type.rs @@ -0,0 +1,169 @@ +// Client-side-validation foundation libraries. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr. Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights 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 +// +// 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. + +// Caused by an imperfection of rust compiler in parsing proc macro args +#![allow(unused_braces)] + +#[macro_use] +extern crate amplify; +#[macro_use] +extern crate strict_encoding_derive; + +mod common; + +use strict_encoding::{ + tn, StrictDeserialize, StrictSerialize, StrictStruct, StrictSum, StrictType, +}; + +const TEST_LIB: &str = "TestLib"; + +#[test] +fn lib_name() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug, Default)] + #[derive(StrictType)] + #[strict_type(lib = TEST_LIB)] + struct OtherName(u16); + + assert_eq!(OtherName::STRICT_LIB_NAME, TEST_LIB); + + Ok(()) +} + +#[test] +fn rename_type() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug, Default)] + #[derive(StrictType)] + #[strict_type(lib = TEST_LIB, rename = "ShortLen")] + struct OtherName(u16); + + assert_eq!(OtherName::STRICT_LIB_NAME, TEST_LIB); + assert_eq!(OtherName::strict_name().unwrap(), tn!("ShortLen")); + + Ok(()) +} + +#[test] +fn fields() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug, Default)] + #[derive(StrictType)] + #[strict_type(lib = TEST_LIB)] + struct Struct { + must_camelize: u8, + } + + assert_eq!(Struct::ALL_FIELDS, &["mustCamelize"]); + + Ok(()) +} + +#[test] +fn variants() -> common::Result { + #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] + #[derive(StrictType)] + #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] + enum Enum { + #[default] + MustCamelize, + } + + #[allow(unused_braces)] + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType)] + #[strict_type(lib = TEST_LIB, tags = order, dumb = { Assoc::MustCamelize { field: 0 } })] + enum Assoc { + MustCamelize { field: u8 }, + } + + assert_eq!(Enum::ALL_VARIANTS, &[(0, "mustCamelize")]); + assert_eq!(Assoc::ALL_VARIANTS, &[(0, "mustCamelize")]); + + Ok(()) +} + +#[test] +fn rename_field() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug, Default)] + #[derive(StrictType)] + #[strict_type(lib = TEST_LIB)] + struct Struct { + must_camelize: u8, + + #[strict_type(rename = "correctName")] + wrong_name: u8, + } + + assert_eq!(Struct::ALL_FIELDS, &["mustCamelize", "correctName"]); + + Ok(()) +} + +#[test] +fn skip_field() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug, Default)] + #[derive(StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB)] + struct Struct { + must_camelize: u8, + + #[strict_type(skip)] + wrong_name: u8, + } + impl StrictSerialize for Struct {} + impl StrictDeserialize for Struct {} + + assert_eq!(Struct::ALL_FIELDS, &["mustCamelize"]); + + let val = Struct { + must_camelize: 2, + wrong_name: 3, + }; + assert_eq!( + val.to_strict_serialized::<{ usize::MAX }>() + .unwrap() + .as_slice(), + &[2] + ); + let val = Struct { + must_camelize: 2, + wrong_name: 0, + }; + assert_eq!(Struct::from_strict_serialized(small_vec![2]).unwrap(), val); + + Ok(()) +} + +#[test] +fn rename_variant() -> common::Result { + #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] + #[derive(StrictType)] + #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] + enum Enum { + #[default] + MustCamelize, + + #[strict_type(rename = "correctName")] + WrongName, + } + + assert_eq!(Enum::ALL_VARIANTS, &[(0, "mustCamelize"), (1, "correctName")]); + + Ok(()) +} From c3be2b823019b470307857c334549392ec0c3393 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 13:23:31 +0200 Subject: [PATCH 02/21] derive: implement derivation business logic --- Cargo.lock | 1 - commit_verify/derive/Cargo.toml | 3 +- commit_verify/derive/src/derive.rs | 230 ++++++--------------- commit_verify/derive/src/lib.rs | 2 + commit_verify/derive/src/params.rs | 246 +++++++---------------- commit_verify/derive/tests/base.rs | 2 +- commit_verify/derive/tests/common/mod.rs | 12 -- commit_verify/derive/tests/dumb.rs | 176 ---------------- commit_verify/derive/tests/type.rs | 169 ---------------- 9 files changed, 140 insertions(+), 701 deletions(-) delete mode 100644 commit_verify/derive/tests/dumb.rs delete mode 100644 commit_verify/derive/tests/type.rs diff --git a/Cargo.lock b/Cargo.lock index be1b9ddc..4ab283c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,7 +221,6 @@ dependencies = [ "amplify_syn", "commit_verify", "compiletest_rs", - "heck", "proc-macro2", "quote", "syn", diff --git a/commit_verify/derive/Cargo.toml b/commit_verify/derive/Cargo.toml index e5f08242..63b31d0b 100644 --- a/commit_verify/derive/Cargo.toml +++ b/commit_verify/derive/Cargo.toml @@ -16,13 +16,12 @@ readme = "README.md" proc-macro = true [dependencies] +amplify = "4.0.0" quote = "1" syn = { version = "1", features = ["full"] } proc-macro2 = "1" amplify_syn = "2.0.0" -heck = "0.4.0" [dev-dependencies] commit_verify = { path = ".." } -amplify = "4.0.0" compiletest_rs = "0.9.0" diff --git a/commit_verify/derive/src/derive.rs b/commit_verify/derive/src/derive.rs index 0455a2e7..2f6ba372 100644 --- a/commit_verify/derive/src/derive.rs +++ b/commit_verify/derive/src/derive.rs @@ -19,195 +19,99 @@ // See the License for the specific language governing permissions and // limitations under the License. -use amplify_syn::{DeriveInner, EnumKind, Field, FieldKind, Fields, Items, NamedField, Variant}; +use amplify_syn::{DeriveInner, Field, FieldKind, Items, NamedField, Variant}; use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use quote::ToTokens; use syn::{Error, Index, Result}; -use crate::params::{CommitDerive, FieldAttr, VariantAttr}; +use crate::params::{CommitDerive, FieldAttr, StrategyAttr}; -struct DeriveEncode<'a>(&'a CommitDerive); +struct DeriveCommit<'a>(&'a CommitDerive); impl CommitDerive { pub fn derive_encode(&self) -> Result { - self.data - .derive(&self.conf.strict_crate, &ident!(StrictEncode), &DeriveEncode(self)) - } -} - -impl DeriveInner for DeriveEncode<'_> { - fn derive_unit_inner(&self) -> Result { - Err(Error::new( - Span::call_site(), - "StrictEncode must not be derived on a unit types. Use just a unit type instead when \ - encoding parent structure.", - )) + match self.conf.strategy { + StrategyAttr::CommitEncoding => self.data.derive( + &self.conf.commit_crate, + &ident!(StrictEncode), + &DeriveCommit(self), + ), + other => self.derive_strategy(other), + } } - fn derive_struct_inner(&self, fields: &Items) -> Result { - let crate_name = &self.0.conf.strict_crate; - - let mut orig_name = Vec::with_capacity(fields.len()); - let mut field_name = Vec::with_capacity(fields.len()); - for named_field in fields { - let attr = FieldAttr::with(named_field.field.attr.clone(), FieldKind::Named)?; - if !attr.skip { - orig_name.push(&named_field.name); - field_name.push(attr.field_name(&named_field.name)); - } - } + fn derive_strategy(&self, strategy: StrategyAttr) -> Result { + let (impl_generics, ty_generics, where_clause) = self.data.generics.split_for_impl(); + let trait_crate = &self.conf.commit_crate; + let ident_name = &self.data.name; + let strategy_name = strategy.to_ident(); Ok(quote! { - fn strict_encode(&self, writer: W) -> ::std::io::Result { - use #crate_name::{TypedWrite, WriteStruct, fname}; - writer.write_struct::(|w| { - Ok(w - #( .write_field(fname!(#field_name), &self.#orig_name)? )* - .complete()) - }) + #[automatically_derived] + impl #impl_generics #trait_crate::CommitStrategy for #ident_name #ty_generics #where_clause { + type Strategy = #trait_crate::strategies::#strategy_name; } }) } - fn derive_tuple_inner(&self, fields: &Items) -> Result { - let crate_name = &self.0.conf.strict_crate; + fn derive_fields<'a>( + &self, + fields: impl Iterator, &'a Field)>, + ) -> Result { + let crate_name = &self.conf.commit_crate; - let no = fields.iter().enumerate().filter_map(|(index, field)| { - let attr = FieldAttr::with(field.attr.clone(), FieldKind::Unnamed).ok()?; + let mut field_encoding = Vec::new(); + for (no, (field_name, unnamed_field)) in fields.enumerate() { + let attr = FieldAttr::with(unnamed_field.attr.clone(), FieldKind::Named)?; if attr.skip { - None - } else { - Some(Index::from(index)) + continue; } - }); + let field_name = field_name + .map(Ident::to_token_stream) + .unwrap_or_else(|| Index::from(no).to_token_stream()); + let field = if let Some(tag) = attr.merklize { + quote! { + { + use #crate_name::merkle::MerkleLeaves; + #crate_name::merkle::MerkleNode::merklize(#tag.to_be_bytes(), self.#field_name).commit_encode(e); + } + } + } else { + quote! { + self.#field_name.commit_encode(e)?; + } + }; + field_encoding.push(field) + } Ok(quote! { - fn strict_encode(&self, writer: W) -> ::std::io::Result { - use #crate_name::{TypedWrite, WriteTuple}; - writer.write_tuple::(|w| { - Ok(w - #( .write_field(&self.#no)? )* - .complete()) - }) + fn commit_encode(&self, e: &mut impl ::std::io::Write) { + use #crate_name::CommitEncode; + #( #field_encoding )* } }) } +} - fn derive_enum_inner(&self, variants: &Items) -> Result { - let crate_name = &self.0.conf.strict_crate; - - let inner = if variants.enum_kind() == EnumKind::Primitive { - quote! { - writer.write_enum(*self) - } - } else { - let mut define_variants = Vec::with_capacity(variants.len()); - let mut write_variants = Vec::with_capacity(variants.len()); - for var in variants { - let attr = VariantAttr::try_from(var.attr.clone())?; - let var_name = &var.name; - let name = attr.variant_name(var_name); - match &var.fields { - Fields::Unit => { - define_variants.push(quote! { - .define_unit(vname!(#name)) - }); - write_variants.push(quote! { - Self::#var_name => writer.write_unit(vname!(#name))?, - }); - } - Fields::Unnamed(fields) if fields.is_empty() => { - define_variants.push(quote! { - .define_unit(vname!(#name)) - }); - write_variants.push(quote! { - Self::#var_name() => writer.write_unit(vname!(#name))?, - }); - } - Fields::Named(fields) if fields.is_empty() => { - define_variants.push(quote! { - .define_unit(vname!(#name)) - }); - write_variants.push(quote! { - Self::#var_name {} => writer.write_unit(vname!(#name))?, - }); - } - Fields::Unnamed(fields) => { - let mut field_ty = Vec::with_capacity(fields.len()); - let mut field_idx = Vec::with_capacity(fields.len()); - for (index, field) in fields.iter().enumerate() { - let attr = FieldAttr::with(field.attr.clone(), FieldKind::Unnamed)?; - - if !attr.skip { - let ty = &field.ty; - let index = Ident::new(&format!("_{index}"), Span::call_site()); - field_ty.push(quote! { #ty }); - field_idx.push(quote! { #index }); - } - } - define_variants.push(quote! { - .define_tuple(vname!(#name), |d| { - d #( .define_field::<#field_ty>() )* .complete() - }) - }); - write_variants.push(quote! { - Self::#var_name( #( #field_idx ),* ) => writer.write_tuple(vname!(#name), |w| { - Ok(w #( .write_field(#field_idx)? )* .complete()) - })?, - }); - } - Fields::Named(fields) => { - let mut field_ty = Vec::with_capacity(fields.len()); - let mut field_name = Vec::with_capacity(fields.len()); - let mut field_rename = Vec::with_capacity(fields.len()); - for named_field in fields { - let attr = - FieldAttr::with(named_field.field.attr.clone(), FieldKind::Named)?; - - let ty = &named_field.field.ty; - let name = &named_field.name; - let rename = attr.field_name(name); - - if !attr.skip { - field_ty.push(quote! { #ty }); - field_name.push(quote! { #name }); - field_rename.push(quote! { fname!(#rename) }); - } - } - - define_variants.push(quote! { - .define_struct(vname!(#name), |d| { - d #( .define_field::<#field_ty>(#field_rename) )* .complete() - }) - }); - write_variants.push(quote! { - Self::#var_name { #( #field_name ),* } => writer.write_struct(vname!(#name), |w| { - Ok(w #( .write_field(#field_rename, #field_name)? )* .complete()) - })?, - }); - } - } - } +impl DeriveInner for DeriveCommit<'_> { + fn derive_unit_inner(&self) -> Result { + Err(Error::new( + Span::call_site(), + "CommitEncode must not be derived on a unit types. Use just a unit type instead when \ + encoding parent structure.", + )) + } - quote! { - #[allow(unused_imports)] - use #crate_name::{DefineUnion, WriteUnion, DefineTuple, DefineStruct, WriteTuple, WriteStruct, fname, vname}; - writer.write_union::(|definer| { - let writer = definer - #( #define_variants )* - .complete(); + fn derive_struct_inner(&self, fields: &Items) -> Result { + self.0 + .derive_fields(fields.iter().map(|f| (Some(&f.name), &f.field))) + } - Ok(match self { - #( #write_variants )* - }.complete()) - }) - } - }; + fn derive_tuple_inner(&self, fields: &Items) -> Result { + self.0.derive_fields(fields.iter().map(|f| (None, f))) + } - Ok(quote! { - fn strict_encode(&self, writer: W) -> ::std::io::Result { - use #crate_name::TypedWrite; - #inner - } - }) + fn derive_enum_inner(&self, _variants: &Items) -> Result { + Err(Error::new(Span::call_site(), "enums can't use CommitEncode strategy")) } } diff --git a/commit_verify/derive/src/lib.rs b/commit_verify/derive/src/lib.rs index b751cef2..1a6409a5 100644 --- a/commit_verify/derive/src/lib.rs +++ b/commit_verify/derive/src/lib.rs @@ -51,6 +51,8 @@ extern crate proc_macro; #[macro_use] extern crate syn; #[macro_use] +extern crate amplify; +#[macro_use] extern crate amplify_syn; pub(crate) mod params; diff --git a/commit_verify/derive/src/params.rs b/commit_verify/derive/src/params.rs index de7c7fcb..13fcf2f0 100644 --- a/commit_verify/derive/src/params.rs +++ b/commit_verify/derive/src/params.rs @@ -19,225 +19,117 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; - use amplify_syn::{ - ArgValueReq, AttrReq, DataType, EnumKind, FieldKind, ListReq, ParametrizedAttr, TypeClass, - ValueClass, + ArgValueReq, AttrReq, DataType, FieldKind, LiteralClass, ParametrizedAttr, TypeClass, }; -use heck::ToLowerCamelCase; use proc_macro2::{Ident, Span}; use quote::ToTokens; -use syn::{DeriveInput, Error, Expr, LitInt, LitStr, Path, Result}; +use syn::{DeriveInput, Error, Expr, Path, Result}; const ATTR: &str = "commit_encode"; const ATTR_CRATE: &str = "crate"; -const ATTR_LIB: &str = "lib"; -const ATTR_RENAME: &str = "rename"; -const ATTR_WITH: &str = "with"; -const ATTR_ENCODE_WITH: &str = "encode_with"; -const ATTR_DECODE_WITH: &str = "decode_with"; -const ATTR_DUMB: &str = "dumb"; -const ATTR_TAGS: &str = "tags"; -const ATTR_TAGS_ORDER: &str = "order"; -const ATTR_TAGS_REPR: &str = "repr"; -const ATTR_TAGS_CUSTOM: &str = "custom"; -const ATTR_TAG: &str = "tag"; +const ATTR_STRATEGY: &str = "strategy"; +const ATTR_STRATEGY_COMMIT: &str = "commit_encode"; +const ATTR_STRATEGY_STRICT: &str = "strict_encode"; +const ATTR_STRATEGY_CONCEAL: &str = "conceal_strict_encode"; +const ATTR_STRATEGY_TRANSPARENT: &str = "transparent"; +const ATTR_STRATEGY_INTO_U8: &str = "into_u8"; +const ATTR_MERKLIZE: &str = "merklize"; const ATTR_SKIP: &str = "skip"; -const ATTR_INTO_U8: &str = "into_u8"; -const ATTR_TRY_FROM_U8: &str = "try_from_u8"; pub struct ContainerAttr { - pub strict_crate: Path, - pub lib: Expr, - pub rename: Option, - pub dumb: Option, - pub encode_with: Option, - pub decode_with: Option, -} - -pub struct EnumAttr { - pub tags: VariantTags, - pub try_from_u8: bool, - pub into_u8: bool, -} - -pub struct FieldAttr { - pub dumb: Option, - pub rename: Option, - pub skip: bool, -} - -pub struct VariantAttr { - pub dumb: bool, - pub rename: Option, - pub tag: Option, + pub commit_crate: Path, + pub strategy: StrategyAttr, } #[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum VariantTags { - Repr, - Order, - Custom, +pub enum StrategyAttr { + CommitEncoding, + StrictEncoding, + ConcealStrictEncoding, + Transparent, + IntoU8, } -impl ContainerAttr { - fn shared_attrs() -> Vec<(&'static str, ArgValueReq)> { - vec![ - (ATTR_CRATE, ArgValueReq::optional(TypeClass::Path)), - (ATTR_LIB, ArgValueReq::required(ValueClass::Expr)), - (ATTR_RENAME, ArgValueReq::optional(ValueClass::str())), - (ATTR_DUMB, ArgValueReq::optional(ValueClass::Expr)), - (ATTR_ENCODE_WITH, ArgValueReq::optional(TypeClass::Path)), - (ATTR_DECODE_WITH, ArgValueReq::optional(TypeClass::Path)), - ] +impl TryFrom<&Path> for StrategyAttr { + type Error = Error; + + fn try_from(path: &Path) -> Result { + match path.to_token_stream().to_string().as_str() { + ATTR_STRATEGY_COMMIT => Ok(StrategyAttr::CommitEncoding), + ATTR_STRATEGY_STRICT => Ok(StrategyAttr::StrictEncoding), + ATTR_STRATEGY_CONCEAL => Ok(StrategyAttr::ConcealStrictEncoding), + ATTR_STRATEGY_TRANSPARENT => Ok(StrategyAttr::Transparent), + ATTR_STRATEGY_INTO_U8 => Ok(StrategyAttr::IntoU8), + unknown => Err(Error::new( + Span::call_site(), + format!( + "invalid commitment encoding value for `strategy` attribute `{unknown}`; only \ + `{ATTR_STRATEGY_TRANSPARENT}`, `{ATTR_STRATEGY_INTO_U8}`, \ + `{ATTR_STRATEGY_COMMIT}`, `{ATTR_STRATEGY_STRICT}` or \ + `{ATTR_STRATEGY_CONCEAL}` are allowed" + ), + )), + } } } -impl EnumAttr { - fn attr_req(map: HashMap<&str, ArgValueReq>, kind: EnumKind) -> AttrReq { - let mut req = AttrReq::with(map); - if kind == EnumKind::Primitive { - req.path_req = ListReq::any_of(vec![path!(try_from_u8), path!(into_u8)], false); +impl StrategyAttr { + pub fn to_ident(&self) -> Ident { + match self { + StrategyAttr::CommitEncoding => { + panic!("StrategyAttr::CommitEncoding must be derived manually") + } + StrategyAttr::StrictEncoding => ident!(Strict), + StrategyAttr::ConcealStrictEncoding => ident!(ConcealStrict), + StrategyAttr::Transparent => ident!(IntoInner), + StrategyAttr::IntoU8 => ident!(IntoU8), } - req } } +pub struct FieldAttr { + pub merklize: Option, + pub skip: bool, +} + impl TryFrom for ContainerAttr { type Error = Error; fn try_from(mut params: ParametrizedAttr) -> Result { - let mut attrs = ContainerAttr::shared_attrs(); - attrs.extend([(ATTR_TAGS, ArgValueReq::optional(TypeClass::Path))]); - let map = HashMap::from_iter(attrs); + let req = AttrReq::with(map![ + ATTR_CRATE => ArgValueReq::optional(TypeClass::Path), + ATTR_STRATEGY => ArgValueReq::optional(TypeClass::Path), + ]); + params.check(req)?; - params.check(EnumAttr::attr_req(map, EnumKind::Primitive))?; + let path = params + .arg_value(ATTR_STRATEGY) + .unwrap_or_else(|_| path!(commit_encoding)); Ok(ContainerAttr { - strict_crate: params + commit_crate: params .arg_value(ATTR_CRATE) - .unwrap_or_else(|_| path!(strict_encoding)), - lib: params.unwrap_arg_value(ATTR_LIB), - rename: params.arg_value(ATTR_RENAME).ok(), - dumb: params.arg_value(ATTR_DUMB).ok(), - encode_with: params - .arg_value(ATTR_ENCODE_WITH) - .or_else(|_| params.arg_value(ATTR_WITH)) - .ok(), - decode_with: params - .arg_value(ATTR_DECODE_WITH) - .or_else(|_| params.arg_value(ATTR_WITH)) - .ok(), - }) - } -} - -impl EnumAttr { - pub fn with(mut params: ParametrizedAttr, kind: EnumKind) -> Result { - let mut attrs = ContainerAttr::shared_attrs(); - attrs.extend([(ATTR_TAGS, ArgValueReq::required(TypeClass::Path))]); - let map = HashMap::from_iter(attrs); - - params.check(EnumAttr::attr_req(map, kind))?; - - let tags = match params - .arg_value(ATTR_TAGS) - .unwrap_or_else(|_| path!(custom)) - .to_token_stream() - .to_string() - .as_str() - { - ATTR_TAGS_REPR => VariantTags::Repr, - ATTR_TAGS_ORDER => VariantTags::Order, - ATTR_TAGS_CUSTOM => VariantTags::Custom, - unknown => { - return Err(Error::new( - Span::call_site(), - format!( - "invalid enum strict encoding value for `tags` attribute `{unknown}`; \ - only `repr`, `order` or `custom` are allowed" - ), - )); - } - }; - - let try_from_u8 = params.has_verbatim(ATTR_TRY_FROM_U8); - let into_u8 = params.has_verbatim(ATTR_INTO_U8); - - if tags != VariantTags::Repr && kind == EnumKind::Primitive { - return Err(Error::new( - Span::call_site(), - "primitive enum types must always use `tags = repr`", - )); - } - - Ok(EnumAttr { - tags, - try_from_u8, - into_u8, + .unwrap_or_else(|_| path!(commit_verify)), + strategy: StrategyAttr::try_from(&path)?, }) } } impl FieldAttr { - pub fn with(mut params: ParametrizedAttr, kind: FieldKind) -> Result { - let mut map = - HashMap::from_iter(vec![(ATTR_DUMB, ArgValueReq::optional(ValueClass::Expr))]); - - if kind == FieldKind::Named { - map.insert(ATTR_RENAME, ArgValueReq::optional(ValueClass::str())); - } - - let mut attr_req = AttrReq::with(map); - attr_req.path_req = ListReq::maybe_one(path!(skip)); - params.check(attr_req)?; - - Ok(FieldAttr { - rename: params.arg_value(ATTR_RENAME).ok(), - dumb: params.arg_value(ATTR_DUMB).ok(), - skip: params.has_verbatim(ATTR_SKIP), - }) - } - - pub fn field_name(&self, name: &Ident) -> LitStr { - match self.rename { - None => LitStr::new(&name.to_string().to_lower_camel_case(), name.span()), - Some(ref name) => name.clone(), - } - } -} - -impl TryFrom for VariantAttr { - type Error = Error; - - fn try_from(mut params: ParametrizedAttr) -> Result { - let map = HashMap::from_iter(vec![ - (ATTR_RENAME, ArgValueReq::optional(ValueClass::str())), - (ATTR_TAG, ArgValueReq::optional(ValueClass::int())), + pub fn with(mut params: ParametrizedAttr, _kind: FieldKind) -> Result { + let req = AttrReq::with(map![ + ATTR_MERKLIZE => ArgValueReq::optional(LiteralClass::Str), ]); - - let mut req = AttrReq::with(map); - req.path_req = ListReq::maybe_one(path!(dumb)); params.check(req)?; - Ok(VariantAttr { - rename: params.arg_value(ATTR_RENAME).ok(), - tag: params.arg_value(ATTR_TAG).ok(), - dumb: params.has_verbatim("dumb"), + Ok(FieldAttr { + skip: params.has_verbatim(ATTR_SKIP), + merklize: params.arg_value(ATTR_MERKLIZE).ok(), }) } } -impl VariantAttr { - pub fn variant_name(&self, name: &Ident) -> LitStr { - match self.rename { - None => LitStr::new(&name.to_string().to_lower_camel_case(), name.span()), - Some(ref name) => name.clone(), - } - } -} - pub struct CommitDerive { pub data: DataType, pub conf: ContainerAttr, diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs index 61b80b99..cda33069 100644 --- a/commit_verify/derive/tests/base.rs +++ b/commit_verify/derive/tests/base.rs @@ -22,7 +22,7 @@ #[macro_use] extern crate amplify; #[macro_use] -extern crate strict_encoding_derive; +extern crate commit_encoding_derive; mod common; diff --git a/commit_verify/derive/tests/common/mod.rs b/commit_verify/derive/tests/common/mod.rs index ead6c76a..76ce9bc7 100644 --- a/commit_verify/derive/tests/common/mod.rs +++ b/commit_verify/derive/tests/common/mod.rs @@ -51,16 +51,4 @@ impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(self.0.as_ref()) } } -impl From> for Error -where T: StrictEncode + StrictDecode + PartialEq + Debug + Clone + 'static -{ - fn from(err: DataEncodingTestFailure) -> Self { Self(Box::new(err)) } -} - -/* -impl From for Error { - fn from(err: strict_encoding::Error) -> Self { Self(Box::new(err)) } -} -*/ - pub type Result = std::result::Result<(), Error>; diff --git a/commit_verify/derive/tests/dumb.rs b/commit_verify/derive/tests/dumb.rs deleted file mode 100644 index a080c4aa..00000000 --- a/commit_verify/derive/tests/dumb.rs +++ /dev/null @@ -1,176 +0,0 @@ -// Client-side-validation foundation libraries. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr. Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights 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 -// -// 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. - -// Caused by an imperfection of rust compiler in parsing proc macro args -#![allow(unused_braces)] - -#[macro_use] -extern crate amplify; - -mod common; - -use amplify::confinement::Confined; -use strict_encoding::{StrictDecode, StrictDumb, StrictEncode, StrictType}; - -const TEST_LIB: &str = "TestLib"; - -#[test] -fn struct_default() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug, Default)] - #[derive(StrictType)] - #[strict_type(lib = TEST_LIB)] - struct Field - where V: Default - { - name: u8, - value: V, - } - - assert_eq!(Field::::strict_dumb(), Field::::default()); - - Ok(()) -} - -#[test] -fn enum_default() -> common::Result { - #[allow(dead_code)] - #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] - #[derive(StrictType)] - #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] - #[repr(u8)] - enum Variants { - One, - Two, - #[default] - Three, - } - - assert_eq!(Variants::strict_dumb(), Variants::Three); - - Ok(()) -} - -#[test] -fn enum_explicit() -> common::Result { - #[allow(dead_code)] - #[derive(Copy, Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb)] - #[strict_type(lib = TEST_LIB)] - #[repr(u8)] - enum Variants { - One, - Two, - #[strict_type(dumb)] - Three, - } - - assert_eq!(Variants::strict_dumb(), Variants::Three); - - Ok(()) -} - -#[test] -fn dumb_wrapper_container() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb)] - #[strict_type(lib = TEST_LIB, dumb = ShortLen(u16::MAX))] - struct ShortLen(u16); - - assert_eq!(ShortLen::strict_dumb(), ShortLen(u16::MAX)); - Ok(()) -} - -#[test] -fn dumb_wrapper_field() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb)] - #[strict_type(lib = TEST_LIB)] - struct ShortLen(#[strict_type(dumb = 1)] u16); - - assert_eq!(ShortLen::strict_dumb(), ShortLen(1)); - Ok(()) -} - -#[test] -fn dumb_wrapper_precedence() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb)] - #[strict_type(lib = TEST_LIB, dumb = ShortLen(u16::MAX))] - struct ShortLen(#[strict_type(dumb = 1)] u16); - - assert_eq!(ShortLen::strict_dumb(), ShortLen(u16::MAX)); - Ok(()) -} - -#[test] -fn dumb_struct() -> common::Result { - #[allow(unused_braces)] - #[derive(Copy, Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb, StrictType)] - #[strict_type(lib = TEST_LIB, tags = order, dumb = Dumb::new())] - struct Dumb { - field1: u8, - field2: u16, - } - - impl Dumb { - pub fn new() -> Self { - Dumb { - field1: 1, - field2: 2, - } - } - } - - assert_eq!(Dumb::strict_dumb(), Dumb { - field1: 1, - field2: 2 - }); - - Ok(()) -} - -#[test] -fn dumb_enum_associated() -> common::Result { - #[allow(unused_braces)] - #[derive(Copy, Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb, StrictType)] - #[strict_type(lib = TEST_LIB, tags = order, dumb = { Assoc::Variant { field: 0 } })] - enum Assoc { - Variant { field: u8 }, - } - - assert_eq!(Assoc::strict_dumb(), Assoc::Variant { field: 0 }); - - Ok(()) -} - -#[test] -fn dumb_ultra_complex() -> common::Result { - #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, From)] - #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] - #[strict_type(lib = TEST_LIB, dumb = NamedFields(confined_vec!(T::strict_dumb())))] - pub struct NamedFields( - Confined, 1, { u8::MAX as usize }>, - ); - - Ok(()) -} diff --git a/commit_verify/derive/tests/type.rs b/commit_verify/derive/tests/type.rs deleted file mode 100644 index 8a4b5d9e..00000000 --- a/commit_verify/derive/tests/type.rs +++ /dev/null @@ -1,169 +0,0 @@ -// Client-side-validation foundation libraries. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr. Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights 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 -// -// 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. - -// Caused by an imperfection of rust compiler in parsing proc macro args -#![allow(unused_braces)] - -#[macro_use] -extern crate amplify; -#[macro_use] -extern crate strict_encoding_derive; - -mod common; - -use strict_encoding::{ - tn, StrictDeserialize, StrictSerialize, StrictStruct, StrictSum, StrictType, -}; - -const TEST_LIB: &str = "TestLib"; - -#[test] -fn lib_name() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug, Default)] - #[derive(StrictType)] - #[strict_type(lib = TEST_LIB)] - struct OtherName(u16); - - assert_eq!(OtherName::STRICT_LIB_NAME, TEST_LIB); - - Ok(()) -} - -#[test] -fn rename_type() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug, Default)] - #[derive(StrictType)] - #[strict_type(lib = TEST_LIB, rename = "ShortLen")] - struct OtherName(u16); - - assert_eq!(OtherName::STRICT_LIB_NAME, TEST_LIB); - assert_eq!(OtherName::strict_name().unwrap(), tn!("ShortLen")); - - Ok(()) -} - -#[test] -fn fields() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug, Default)] - #[derive(StrictType)] - #[strict_type(lib = TEST_LIB)] - struct Struct { - must_camelize: u8, - } - - assert_eq!(Struct::ALL_FIELDS, &["mustCamelize"]); - - Ok(()) -} - -#[test] -fn variants() -> common::Result { - #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] - #[derive(StrictType)] - #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] - enum Enum { - #[default] - MustCamelize, - } - - #[allow(unused_braces)] - #[derive(Copy, Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb, StrictType)] - #[strict_type(lib = TEST_LIB, tags = order, dumb = { Assoc::MustCamelize { field: 0 } })] - enum Assoc { - MustCamelize { field: u8 }, - } - - assert_eq!(Enum::ALL_VARIANTS, &[(0, "mustCamelize")]); - assert_eq!(Assoc::ALL_VARIANTS, &[(0, "mustCamelize")]); - - Ok(()) -} - -#[test] -fn rename_field() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug, Default)] - #[derive(StrictType)] - #[strict_type(lib = TEST_LIB)] - struct Struct { - must_camelize: u8, - - #[strict_type(rename = "correctName")] - wrong_name: u8, - } - - assert_eq!(Struct::ALL_FIELDS, &["mustCamelize", "correctName"]); - - Ok(()) -} - -#[test] -fn skip_field() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug, Default)] - #[derive(StrictType, StrictEncode, StrictDecode)] - #[strict_type(lib = TEST_LIB)] - struct Struct { - must_camelize: u8, - - #[strict_type(skip)] - wrong_name: u8, - } - impl StrictSerialize for Struct {} - impl StrictDeserialize for Struct {} - - assert_eq!(Struct::ALL_FIELDS, &["mustCamelize"]); - - let val = Struct { - must_camelize: 2, - wrong_name: 3, - }; - assert_eq!( - val.to_strict_serialized::<{ usize::MAX }>() - .unwrap() - .as_slice(), - &[2] - ); - let val = Struct { - must_camelize: 2, - wrong_name: 0, - }; - assert_eq!(Struct::from_strict_serialized(small_vec![2]).unwrap(), val); - - Ok(()) -} - -#[test] -fn rename_variant() -> common::Result { - #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] - #[derive(StrictType)] - #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] - enum Enum { - #[default] - MustCamelize, - - #[strict_type(rename = "correctName")] - WrongName, - } - - assert_eq!(Enum::ALL_VARIANTS, &[(0, "mustCamelize"), (1, "correctName")]); - - Ok(()) -} From a98735d9293d82b309db14b82a783ef8e52cb25b Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 14:02:57 +0200 Subject: [PATCH 03/21] derive: test main cases --- Cargo.lock | 5 +- commit_verify/derive/Cargo.toml | 1 + commit_verify/derive/src/derive.rs | 4 +- commit_verify/derive/src/params.rs | 8 +- commit_verify/derive/tests/base.rs | 250 ++++++++++++++++------- commit_verify/derive/tests/common/mod.rs | 3 - 6 files changed, 184 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ab283c4..683747e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,7 @@ dependencies = [ "compiletest_rs", "proc-macro2", "quote", + "strict_encoding", "syn", ] @@ -655,9 +656,9 @@ dependencies = [ [[package]] name = "strict_encoding" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff63df75d779e9005d7790b8e5d9d46d38a6c0ea74a8850f3d3a6b0145a05d9" +checksum = "21201d9c01000d4a5e715a3f0b04eeea265a57a3557c06c5408d1a4471acb414" dependencies = [ "amplify", "half", diff --git a/commit_verify/derive/Cargo.toml b/commit_verify/derive/Cargo.toml index 63b31d0b..1e487eee 100644 --- a/commit_verify/derive/Cargo.toml +++ b/commit_verify/derive/Cargo.toml @@ -25,3 +25,4 @@ amplify_syn = "2.0.0" [dev-dependencies] commit_verify = { path = ".." } compiletest_rs = "0.9.0" +strict_encoding = "2.1.1" diff --git a/commit_verify/derive/src/derive.rs b/commit_verify/derive/src/derive.rs index 2f6ba372..e476c3d7 100644 --- a/commit_verify/derive/src/derive.rs +++ b/commit_verify/derive/src/derive.rs @@ -33,7 +33,7 @@ impl CommitDerive { match self.conf.strategy { StrategyAttr::CommitEncoding => self.data.derive( &self.conf.commit_crate, - &ident!(StrictEncode), + &ident!(CommitEncode), &DeriveCommit(self), ), other => self.derive_strategy(other), @@ -78,7 +78,7 @@ impl CommitDerive { } } else { quote! { - self.#field_name.commit_encode(e)?; + self.#field_name.commit_encode(e); } }; field_encoding.push(field) diff --git a/commit_verify/derive/src/params.rs b/commit_verify/derive/src/params.rs index 13fcf2f0..39ea08ba 100644 --- a/commit_verify/derive/src/params.rs +++ b/commit_verify/derive/src/params.rs @@ -29,9 +29,9 @@ use syn::{DeriveInput, Error, Expr, Path, Result}; const ATTR: &str = "commit_encode"; const ATTR_CRATE: &str = "crate"; const ATTR_STRATEGY: &str = "strategy"; -const ATTR_STRATEGY_COMMIT: &str = "commit_encode"; -const ATTR_STRATEGY_STRICT: &str = "strict_encode"; -const ATTR_STRATEGY_CONCEAL: &str = "conceal_strict_encode"; +const ATTR_STRATEGY_COMMIT: &str = "propagate"; +const ATTR_STRATEGY_STRICT: &str = "strict"; +const ATTR_STRATEGY_CONCEAL: &str = "conceal_strict"; const ATTR_STRATEGY_TRANSPARENT: &str = "transparent"; const ATTR_STRATEGY_INTO_U8: &str = "into_u8"; const ATTR_MERKLIZE: &str = "merklize"; @@ -105,7 +105,7 @@ impl TryFrom for ContainerAttr { let path = params .arg_value(ATTR_STRATEGY) - .unwrap_or_else(|_| path!(commit_encoding)); + .unwrap_or_else(|_| path!(propagate)); Ok(ContainerAttr { commit_crate: params diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs index cda33069..b58f9afd 100644 --- a/commit_verify/derive/tests/base.rs +++ b/commit_verify/derive/tests/base.rs @@ -23,55 +23,179 @@ extern crate amplify; #[macro_use] extern crate commit_encoding_derive; +#[macro_use] +extern crate strict_encoding; mod common; use std::convert::Infallible; -use strict_encoding::{ - tn, StrictDecode, StrictDumb, StrictEncode, StrictSerialize, StrictSum, VariantError, -}; +use commit_verify::CommitEncode; +use strict_encoding::{StrictDecode, StrictDumb, StrictEncode}; const TEST_LIB: &str = "TestLib"; +fn verify_commit(t: T, c: impl AsRef<[u8]>) { + let mut e = Vec::::new(); + t.commit_encode(&mut e); + assert_eq!(e.as_slice(), c.as_ref(), "invalid commitment"); +} + #[test] -fn wrapper_base() -> common::Result { - #[derive(Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] - #[strict_type(lib = TEST_LIB)] +fn strategy_transparent() -> common::Result { + #[derive(Wrapper, Clone, PartialEq, Eq, Debug, From)] + #[derive(CommitEncode)] + #[commit_encode(strategy = transparent)] struct ShortLen(u16); + verify_commit(ShortLen(0), [0, 0]); + verify_commit(ShortLen(0xFFde), [0xde, 0xFF]); + + Ok(()) +} + +#[test] +fn strategy_into_u8() -> common::Result { + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(CommitEncode)] + #[commit_encode(strategy = into_u8)] + #[repr(u8)] + enum Prim { + A, + B, + C, + } + impl Into for Prim { + fn into(self) -> u8 { self as u8 } + } + + verify_commit(Prim::A, [0]); + verify_commit(Prim::B, [1]); + verify_commit(Prim::C, [2]); + Ok(()) } #[test] -fn tuple_base() -> common::Result { +fn strategy_default_tuple() -> common::Result { #[derive(Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] - #[strict_type(lib = TEST_LIB)] + #[derive(CommitEncode)] struct TaggedInfo(u16, u64); + verify_commit(TaggedInfo(0xdead, 0xbeefcafebaddafec), [ + 0xad, 0xde, 0xec, 0xaf, 0xdd, 0xba, 0xfe, 0xca, 0xef, 0xbe, + ]); + Ok(()) } #[test] -fn tuple_generics() -> common::Result { +fn strategy_commit_tuple() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(CommitEncode)] + #[commit_encode(strategy = propagate)] + struct TaggedInfo(u16, u64); + + verify_commit(TaggedInfo(0xdead, 0xbeefcafebaddafec), [ + 0xad, 0xde, 0xec, 0xaf, 0xdd, 0xba, 0xfe, 0xca, 0xef, 0xbe, + ]); + + Ok(()) +} + +#[test] +fn strategy_strict_tuple() -> common::Result { #[derive(Clone, PartialEq, Eq, Debug)] #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = TEST_LIB)] - struct Pair< - A: StrictDumb + StrictEncode + StrictDecode, - B: StrictDumb + StrictEncode + StrictDecode, - >(A, B); + #[derive(CommitEncode)] + #[commit_encode(strategy = strict)] + struct TaggedInfo(u16, u64); + + verify_commit(TaggedInfo(0xdead, 0xbeefcafebaddafec), [ + 0xad, 0xde, 0xec, 0xaf, 0xdd, 0xba, 0xfe, 0xca, 0xef, 0xbe, + ]); + + Ok(()) +} + +#[test] +fn strategy_default_struct() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(CommitEncode)] + struct TaggedInfo { + a: u16, + b: u64, + } + + verify_commit( + TaggedInfo { + a: 0xdead, + b: 0xbeefcafebaddafec, + }, + [0xad, 0xde, 0xec, 0xaf, 0xdd, 0xba, 0xfe, 0xca, 0xef, 0xbe], + ); + + Ok(()) +} + +#[test] +fn strategy_commit_struct() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(CommitEncode)] + #[commit_encode(strategy = propagate)] + struct TaggedInfo { + a: u16, + b: u64, + } + + verify_commit( + TaggedInfo { + a: 0xdead, + b: 0xbeefcafebaddafec, + }, + [0xad, 0xde, 0xec, 0xaf, 0xdd, 0xba, 0xfe, 0xca, 0xef, 0xbe], + ); + + Ok(()) +} +#[test] +fn strategy_strict_struct() -> common::Result { #[derive(Clone, PartialEq, Eq, Debug)] #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = TEST_LIB)] + #[derive(CommitEncode)] + #[commit_encode(strategy = strict)] + struct TaggedInfo { + a: u16, + b: u64, + } + + verify_commit( + TaggedInfo { + a: 0xdead, + b: 0xbeefcafebaddafec, + }, + [0xad, 0xde, 0xec, 0xaf, 0xdd, 0xba, 0xfe, 0xca, 0xef, 0xbe], + ); + + Ok(()) +} + +#[test] +fn tuple_generics() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(CommitEncode)] + struct Pair(A, B); + + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(CommitEncode)] struct WhereConstraint, B: From>(A, B) where - A: StrictDumb + StrictEncode + StrictDecode + From, + A: CommitEncode + From, >::Error: From, - B: StrictDumb + StrictEncode + StrictDecode; + B: CommitEncode + Default; Ok(()) } @@ -79,17 +203,15 @@ fn tuple_generics() -> common::Result { #[test] fn struct_generics() -> common::Result { #[derive(Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] - #[strict_type(lib = TEST_LIB)] - struct Field { + #[derive(CommitEncode)] + struct Field { tag: u8, value: V, } #[derive(Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb, StrictType, StrictEncode)] - #[strict_type(lib = TEST_LIB)] - struct ComplexField<'a, V: StrictEncode + StrictDumb> + #[derive(CommitEncode)] + struct ComplexField<'a, V: CommitEncode> where for<'b> V: From<&'b str>, &'a V: Default, @@ -101,32 +223,13 @@ fn struct_generics() -> common::Result { Ok(()) } -#[test] -fn enum_ord() -> common::Result { - #[derive(Copy, Clone, PartialEq, Eq, Debug)] - #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] - #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] - #[repr(u8)] - enum Variants { - #[strict_type(dumb)] - One = 5, - Two = 6, - Three = 7, - } - - assert_eq!(Variants::Three as u8, 7); - assert_eq!(u8::from(Variants::Three), 7); - assert_eq!(Variants::try_from(6), Ok(Variants::Two)); - assert_eq!(Variants::try_from(3), Err(VariantError(Some(tn!("Variants")), 3))); - - Ok(()) -} - #[test] fn enum_repr() -> common::Result { #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = TEST_LIB, tags = repr, into_u8, try_from_u8)] + #[derive(CommitEncode)] + #[commit_encode(strategy = into_u8)] #[repr(u16)] enum Cls { One = 1, @@ -135,9 +238,9 @@ fn enum_repr() -> common::Result { Three, } - assert_eq!(u8::from(Cls::Three), 3); - assert_eq!(Cls::try_from(2), Ok(Cls::Two)); - assert_eq!(Cls::try_from(4), Err(VariantError(Some(tn!("Cls")), 4))); + verify_commit(Cls::One, [1]); + verify_commit(Cls::Two, [2]); + verify_commit(Cls::Three, [3]); Ok(()) } @@ -148,6 +251,8 @@ fn enum_associated() -> common::Result { #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = TEST_LIB, tags = order)] + #[derive(CommitEncode)] + #[commit_encode(strategy = strict)] enum Assoc { One { hash: [u8; 32], @@ -160,13 +265,15 @@ fn enum_associated() -> common::Result { Five {}, } - assert_eq!(Assoc::ALL_VARIANTS, &[ - (0, "one"), - (1, "two"), - (2, "three"), - (3, "four"), - (4, "five") - ]); + let mut res = vec![0; 33]; + res.extend([1]); + verify_commit( + Assoc::One { + hash: default!(), + ord: 1, + }, + res, + ); Ok(()) } @@ -177,11 +284,11 @@ fn enum_custom_tags() -> common::Result { #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = TEST_LIB, tags = order)] + #[derive(CommitEncode)] + #[commit_encode(strategy = strict)] enum Assoc { - One { - hash: [u8; 32], - ord: u8, - }, + #[strict_type(tag = 8)] + One { hash: [u8; 32], ord: u8 }, #[strict_type(tag = 2)] Two(u8, u16, u32), #[strict_type(dumb, tag = 3)] @@ -192,24 +299,15 @@ fn enum_custom_tags() -> common::Result { Five {}, } - impl StrictSerialize for Assoc {} - - assert_eq!(Assoc::ALL_VARIANTS, &[ - (0, "one"), - (2, "two"), - (3, "three"), - (4, "four"), - (5, "five") - ]); - - let assoc = Assoc::Two(0, 1, 2); - assert_eq!(assoc.to_strict_serialized::<256>().unwrap().as_slice(), &[2, 0, 1, 0, 2, 0, 0, 0]); - - let assoc = Assoc::One { - hash: [0u8; 32], - ord: 0, - }; - assert_eq!(assoc.to_strict_serialized::<256>().unwrap().as_slice(), &[0u8; 34]); + let mut res = vec![8; 33]; + res.extend([1]); + verify_commit( + Assoc::One { + hash: [8; 32], + ord: 1, + }, + res, + ); Ok(()) } diff --git a/commit_verify/derive/tests/common/mod.rs b/commit_verify/derive/tests/common/mod.rs index 76ce9bc7..97b52f48 100644 --- a/commit_verify/derive/tests/common/mod.rs +++ b/commit_verify/derive/tests/common/mod.rs @@ -24,9 +24,6 @@ extern crate compiletest_rs as compiletest; use std::fmt::{Debug, Display, Formatter}; use std::path::PathBuf; -use strict_encoding::{StrictDecode, StrictEncode}; -use strict_encoding_test::DataEncodingTestFailure; - #[allow(dead_code)] pub fn compile_test(mode: &'static str) { let mut config = compiletest::Config { From b42ce89b39a59318f599aa570df10da80ed3cee4 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 14:21:23 +0200 Subject: [PATCH 04/21] derive: test skipping and concealing --- commit_verify/derive/src/derive.rs | 6 +++- commit_verify/derive/src/params.rs | 7 +++-- commit_verify/derive/tests/base.rs | 49 +++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/commit_verify/derive/src/derive.rs b/commit_verify/derive/src/derive.rs index e476c3d7..67e71259 100644 --- a/commit_verify/derive/src/derive.rs +++ b/commit_verify/derive/src/derive.rs @@ -62,7 +62,11 @@ impl CommitDerive { let mut field_encoding = Vec::new(); for (no, (field_name, unnamed_field)) in fields.enumerate() { - let attr = FieldAttr::with(unnamed_field.attr.clone(), FieldKind::Named)?; + let kind = match field_name { + Some(_) => FieldKind::Named, + None => FieldKind::Unnamed, + }; + let attr = FieldAttr::with(unnamed_field.attr.clone(), kind)?; if attr.skip { continue; } diff --git a/commit_verify/derive/src/params.rs b/commit_verify/derive/src/params.rs index 39ea08ba..42e3fc94 100644 --- a/commit_verify/derive/src/params.rs +++ b/commit_verify/derive/src/params.rs @@ -20,7 +20,7 @@ // limitations under the License. use amplify_syn::{ - ArgValueReq, AttrReq, DataType, FieldKind, LiteralClass, ParametrizedAttr, TypeClass, + ArgValueReq, AttrReq, DataType, FieldKind, ListReq, LiteralClass, ParametrizedAttr, TypeClass, }; use proc_macro2::{Ident, Span}; use quote::ToTokens; @@ -118,9 +118,10 @@ impl TryFrom for ContainerAttr { impl FieldAttr { pub fn with(mut params: ParametrizedAttr, _kind: FieldKind) -> Result { - let req = AttrReq::with(map![ + let mut req = AttrReq::with(map![ ATTR_MERKLIZE => ArgValueReq::optional(LiteralClass::Str), ]); + req.path_req = ListReq::maybe_one(path!(skip)); params.check(req)?; Ok(FieldAttr { @@ -141,7 +142,7 @@ impl TryFrom for CommitDerive { fn try_from(input: DeriveInput) -> Result { let params = ParametrizedAttr::with(ATTR, &input.attrs)?; let conf = ContainerAttr::try_from(params)?; - let data = DataType::with(input, ident!(strict_type))?; + let data = DataType::with(input, ident!(commit_encode))?; Ok(Self { data, conf }) } } diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs index b58f9afd..9f779a23 100644 --- a/commit_verify/derive/tests/base.rs +++ b/commit_verify/derive/tests/base.rs @@ -19,6 +19,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(unused_braces)] + #[macro_use] extern crate amplify; #[macro_use] @@ -30,7 +32,7 @@ mod common; use std::convert::Infallible; -use commit_verify::CommitEncode; +use commit_verify::{CommitEncode, Conceal}; use strict_encoding::{StrictDecode, StrictDumb, StrictEncode}; const TEST_LIB: &str = "TestLib"; @@ -311,3 +313,48 @@ fn enum_custom_tags() -> common::Result { Ok(()) } + +#[test] +fn conceal() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB, tags = order, dumb = { Self::Concealed(0) })] + #[derive(CommitEncode)] + #[commit_encode(strategy = conceal_strict)] + enum Data { + Revealed(u128), + Concealed(u8), + } + + impl Conceal for Data { + type Concealed = Self; + fn conceal(&self) -> Self { Self::Concealed(0xde) } + } + + verify_commit(Data::Revealed(0xcafe1234), [1, 0xde]); + + Ok(()) +} + +#[test] +fn skip() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(CommitEncode)] + struct Data { + data: u8, + #[commit_encode(skip)] + bulletproof: Vec, + } + + verify_commit( + Data { + data: 0xfe, + bulletproof: vec![0xde, 0xad], + }, + [0xfe], + ); + + Ok(()) +} + +// TODO: Test merklize From 2bf731284a447644128497fa9ed5dfd77082a478 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 14:43:24 +0200 Subject: [PATCH 05/21] commit: add AsRef strategy --- commit_verify/src/encode.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/commit_verify/src/encode.rs b/commit_verify/src/encode.rs index 618e87f6..17f0f3e8 100644 --- a/commit_verify/src/encode.rs +++ b/commit_verify/src/encode.rs @@ -116,6 +116,8 @@ pub mod strategies { use super::*; use crate::merkle::{MerkleLeaves, MerkleNode}; + pub enum AsRef {} + /// Commits to the value by converting it into `u8` type. Useful for enum /// types. /// @@ -169,6 +171,12 @@ pub mod strategies { } } + impl<'a, T> CommitEncode for Holder<&'a T, AsRef> + where T: CommitEncode + { + fn commit_encode(&self, e: &mut impl io::Write) { self.as_type().commit_encode(e); } + } + impl<'a, T> CommitEncode for Holder<&'a T, IntoInner> where T: Wrapper, @@ -314,9 +322,9 @@ pub mod strategies { } impl CommitStrategy for &T - where T: CommitStrategy + where T: CommitEncode { - type Strategy = T::Strategy; + type Strategy = AsRef; } } From 1a9c77b711f653a97624af21fd0ea47ef169d14f Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 14:47:19 +0200 Subject: [PATCH 06/21] commit: refactor to inherit strategy for Box and Option types --- commit_verify/src/encode.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/commit_verify/src/encode.rs b/commit_verify/src/encode.rs index 17f0f3e8..c7ff6b4d 100644 --- a/commit_verify/src/encode.rs +++ b/commit_verify/src/encode.rs @@ -308,14 +308,14 @@ pub mod strategies { } impl CommitStrategy for Box - where T: StrictEncode + where T: CommitStrategy { - type Strategy = Strict; + type Strategy = T::Strategy; } impl CommitStrategy for Option - where T: StrictEncode + where T: CommitStrategy { - type Strategy = Strict; + type Strategy = T::Strategy; } impl CommitStrategy for Confined, MIN, MAX> { type Strategy = Strict; From aea4c5eb966db1aca4d100a3cd31ba9ec3666478 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 14:48:50 +0200 Subject: [PATCH 07/21] derive: fix merklize implementation --- commit_verify/derive/src/derive.rs | 2 +- commit_verify/derive/src/params.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commit_verify/derive/src/derive.rs b/commit_verify/derive/src/derive.rs index 67e71259..a7e71c12 100644 --- a/commit_verify/derive/src/derive.rs +++ b/commit_verify/derive/src/derive.rs @@ -77,7 +77,7 @@ impl CommitDerive { quote! { { use #crate_name::merkle::MerkleLeaves; - #crate_name::merkle::MerkleNode::merklize(#tag.to_be_bytes(), self.#field_name).commit_encode(e); + #crate_name::merkle::MerkleNode::merklize(#tag.to_be_bytes(), &self.#field_name).commit_encode(e); } } } else { diff --git a/commit_verify/derive/src/params.rs b/commit_verify/derive/src/params.rs index 42e3fc94..5e9a2448 100644 --- a/commit_verify/derive/src/params.rs +++ b/commit_verify/derive/src/params.rs @@ -119,7 +119,7 @@ impl TryFrom for ContainerAttr { impl FieldAttr { pub fn with(mut params: ParametrizedAttr, _kind: FieldKind) -> Result { let mut req = AttrReq::with(map![ - ATTR_MERKLIZE => ArgValueReq::optional(LiteralClass::Str), + ATTR_MERKLIZE => ArgValueReq::optional(LiteralClass::Int), ]); req.path_req = ListReq::maybe_one(path!(skip)); params.check(req)?; From 22c6b45acec9d339956a88fe9e7bb955cdddb7fc Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 14:49:25 +0200 Subject: [PATCH 08/21] derive: test merklize strategy --- commit_verify/derive/tests/base.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs index 9f779a23..b41fa956 100644 --- a/commit_verify/derive/tests/base.rs +++ b/commit_verify/derive/tests/base.rs @@ -32,6 +32,7 @@ mod common; use std::convert::Infallible; +use amplify::confinement::TinyVec; use commit_verify::{CommitEncode, Conceal}; use strict_encoding::{StrictDecode, StrictDumb, StrictEncode}; @@ -357,4 +358,21 @@ fn skip() -> common::Result { Ok(()) } -// TODO: Test merklize +#[test] +fn merklize() -> common::Result { + #[derive(Clone, PartialEq, Eq, Debug)] + #[derive(CommitEncode)] + struct Tree { + #[commit_encode(merklize = 0u128)] + leaves: TinyVec, + } + + verify_commit( + Tree { + leaves: tiny_vec!(0, 1, 2, 3), + }, + [0], + ); + + Ok(()) +} From a5595f8705493f077ce7f427e919b4ecead746cd Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 17:17:57 +0200 Subject: [PATCH 09/21] commit: remove MerkleIter type --- commit_verify/src/merkle.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/commit_verify/src/merkle.rs b/commit_verify/src/merkle.rs index b071cfd2..192430d8 100644 --- a/commit_verify/src/merkle.rs +++ b/commit_verify/src/merkle.rs @@ -150,7 +150,7 @@ impl MerkleNode { fn _merklize<'leaf, Leaf: CommitEncode + 'leaf>( tag: [u8; 16], - mut iter: impl MerkleIter, + mut iter: impl ExactSizeIterator, depth: u4, offset: u16, ) -> Self { @@ -185,14 +185,10 @@ impl MerkleNode { } } -pub trait MerkleIter: ExactSizeIterator {} - -impl MerkleIter for I where I: ExactSizeIterator {} - pub trait MerkleLeaves { type Leaf: CommitEncode; - type LeafIter: MerkleIter; + type LeafIter: ExactSizeIterator; fn merkle_leaves(&self) -> Self::LeafIter; } From 0bcd04a6d0ee64765fc7335b96a7e11b63156baf Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 17:22:26 +0200 Subject: [PATCH 10/21] commit: remove cloning in merkle iter --- commit_verify/src/merkle.rs | 13 +++++++------ commit_verify/src/mpc/mod.rs | 2 +- commit_verify/src/mpc/tree.rs | 16 ++++++++-------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/commit_verify/src/merkle.rs b/commit_verify/src/merkle.rs index 192430d8..ebc2c572 100644 --- a/commit_verify/src/merkle.rs +++ b/commit_verify/src/merkle.rs @@ -188,18 +188,19 @@ impl MerkleNode { pub trait MerkleLeaves { type Leaf: CommitEncode; - type LeafIter: ExactSizeIterator; + type LeafIter<'leaf>: ExactSizeIterator + where Self: 'leaf; - fn merkle_leaves(&self) -> Self::LeafIter; + fn merkle_leaves(&self) -> Self::LeafIter<'_>; } impl<'a, T, const MIN: usize> MerkleLeaves for &'a Confined, MIN, { u16::MAX as usize }> where &'a T: CommitEncode { type Leaf = &'a T; - type LeafIter = std::slice::Iter<'a, T>; + type LeafIter<'leaf> = std::slice::Iter<'a, T> where Self: 'leaf, 'a: 'leaf; - fn merkle_leaves(&self) -> Self::LeafIter { self.iter() } + fn merkle_leaves(&self) -> Self::LeafIter<'_> { self.iter() } } impl<'a, T: Ord, const MIN: usize> MerkleLeaves @@ -207,9 +208,9 @@ impl<'a, T: Ord, const MIN: usize> MerkleLeaves where &'a T: CommitEncode { type Leaf = &'a T; - type LeafIter = std::collections::btree_set::Iter<'a, T>; + type LeafIter<'leaf> = std::collections::btree_set::Iter<'a, T> where Self: 'leaf, 'a: 'leaf; - fn merkle_leaves(&self) -> Self::LeafIter { self.iter() } + fn merkle_leaves(&self) -> Self::LeafIter<'_> { self.iter() } } /* diff --git a/commit_verify/src/mpc/mod.rs b/commit_verify/src/mpc/mod.rs index d8d28f84..5890eedb 100644 --- a/commit_verify/src/mpc/mod.rs +++ b/commit_verify/src/mpc/mod.rs @@ -31,7 +31,7 @@ pub use atoms::{Commitment, Leaf, Message, MessageMap, MultiSource, ProtocolId}; pub use block::{LeafNotKnown, MerkleBlock, MerkleProof, UnrelatedProof}; #[cfg(feature = "rand")] pub use tree::Error; -pub use tree::{IntoIter, MerkleTree}; +pub use tree::{Iter, MerkleTree}; const LNPBP4_TAG: [u8; 16] = *b"urn:lnpbp:lnpbp4"; diff --git a/commit_verify/src/mpc/tree.rs b/commit_verify/src/mpc/tree.rs index a7d19c12..9b2e37a4 100644 --- a/commit_verify/src/mpc/tree.rs +++ b/commit_verify/src/mpc/tree.rs @@ -60,14 +60,14 @@ impl CommitmentId for MerkleTree { type Id = Commitment; } -pub struct IntoIter { +pub struct Iter<'src> { width: u16, pos: u16, - map: OrderedMap, + map: &'src OrderedMap, entropy: u64, } -impl Iterator for IntoIter { +impl<'src> Iterator for Iter<'src> { type Item = Leaf; fn next(&mut self) -> Option { @@ -91,18 +91,18 @@ impl Iterator for IntoIter { } } -impl ExactSizeIterator for IntoIter {} +impl<'src> ExactSizeIterator for Iter<'src> {} impl MerkleLeaves for MerkleTree { type Leaf = Leaf; - type LeafIter = IntoIter; + type LeafIter<'src> = Iter<'src>; - fn merkle_leaves(&self) -> Self::LeafIter { - IntoIter { + fn merkle_leaves(&self) -> Self::LeafIter<'_> { + Iter { entropy: self.entropy, width: self.width(), pos: 0, - map: self.as_ordered_map().clone(), // TODO: Remove clone + map: self.as_ordered_map(), } } } From 8d0fba58b8a32bbe148cfb6504b4c263d860548f Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 17:55:38 +0200 Subject: [PATCH 11/21] commit: refactor merklization iterators --- commit_verify/src/merkle.rs | 29 ++++++++------- commit_verify/src/mpc/block.rs | 6 ++-- commit_verify/src/mpc/mod.rs | 6 ++-- commit_verify/src/mpc/tree.rs | 66 +++++++--------------------------- 4 files changed, 34 insertions(+), 73 deletions(-) diff --git a/commit_verify/src/merkle.rs b/commit_verify/src/merkle.rs index ebc2c572..c789e33c 100644 --- a/commit_verify/src/merkle.rs +++ b/commit_verify/src/merkle.rs @@ -141,16 +141,16 @@ impl CommitStrategy for MerkleNode { impl MerkleNode { /// Merklization procedure that uses tagged hashes with depth commitments - /// according to [LNPBP-81] standard of client-side-validation merklization + /// according to [LNPBP-81] standard of client-side-validation merklization. /// /// [LNPBP-81]: https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0081.md pub fn merklize(tag: [u8; 16], nodes: &impl MerkleLeaves) -> Self { Self::_merklize(tag, nodes.merkle_leaves(), u4::ZERO, 0) } - fn _merklize<'leaf, Leaf: CommitEncode + 'leaf>( + pub fn _merklize<'leaf, Leaf: CommitEncode + 'leaf>( tag: [u8; 16], - mut iter: impl ExactSizeIterator, + mut iter: impl ExactSizeIterator, depth: u4, offset: u16, ) -> Self { @@ -160,9 +160,9 @@ impl MerkleNode { if len <= 2 { match (iter.next(), iter.next()) { (None, None) => MerkleNode::void(tag, depth, width), - (Some(branch), None) => MerkleNode::single(tag, depth, width, &branch), + (Some(branch), None) => MerkleNode::single(tag, depth, width, branch), (Some(branch1), Some(branch2)) => { - MerkleNode::couple(tag, depth, width, &branch1, &branch2) + MerkleNode::couple(tag, depth, width, branch1, branch2) } (None, Some(_)) => unreachable!(), } @@ -188,27 +188,26 @@ impl MerkleNode { pub trait MerkleLeaves { type Leaf: CommitEncode; - type LeafIter<'leaf>: ExactSizeIterator + type LeafIter<'leaf>: ExactSizeIterator where Self: 'leaf; fn merkle_leaves(&self) -> Self::LeafIter<'_>; } -impl<'a, T, const MIN: usize> MerkleLeaves for &'a Confined, MIN, { u16::MAX as usize }> -where &'a T: CommitEncode +impl MerkleLeaves for Confined, MIN, { u16::MAX as usize }> +where T: CommitEncode { - type Leaf = &'a T; - type LeafIter<'leaf> = std::slice::Iter<'a, T> where Self: 'leaf, 'a: 'leaf; + type Leaf = T; + type LeafIter<'leaf> = std::slice::Iter<'leaf, T> where Self: 'leaf; fn merkle_leaves(&self) -> Self::LeafIter<'_> { self.iter() } } -impl<'a, T: Ord, const MIN: usize> MerkleLeaves - for &'a Confined, MIN, { u16::MAX as usize }> -where &'a T: CommitEncode +impl MerkleLeaves for Confined, MIN, { u16::MAX as usize }> +where T: CommitEncode { - type Leaf = &'a T; - type LeafIter<'leaf> = std::collections::btree_set::Iter<'a, T> where Self: 'leaf, 'a: 'leaf; + type Leaf = T; + type LeafIter<'leaf> = std::collections::btree_set::Iter<'leaf, T> where Self: 'leaf; fn merkle_leaves(&self) -> Self::LeafIter<'_> { self.iter() } } diff --git a/commit_verify/src/mpc/block.rs b/commit_verify/src/mpc/block.rs index fe0dd3d0..860f6ef5 100644 --- a/commit_verify/src/mpc/block.rs +++ b/commit_verify/src/mpc/block.rs @@ -32,7 +32,9 @@ use crate::id::CommitmentId; use crate::merkle::MerkleNode; use crate::mpc::atoms::Leaf; use crate::mpc::tree::protocol_id_pos; -use crate::mpc::{Commitment, MerkleTree, Message, MessageMap, Proof, ProtocolId, LNPBP4_TAG}; +use crate::mpc::{ + Commitment, MerkleTree, Message, MessageMap, Proof, ProtocolId, MERKLE_LNPBP4_TAG, +}; use crate::{strategies, CommitStrategy, Conceal, LIB_NAME_COMMIT_VERIFY}; /// commitment under protocol id {_0} is absent from the known part of a given @@ -76,7 +78,7 @@ impl TreeNode { fn with(hash1: MerkleNode, hash2: MerkleNode, depth: u4, width: u16) -> TreeNode { TreeNode::ConcealedNode { depth, - hash: MerkleNode::branches(LNPBP4_TAG, depth, width, hash1, hash2), + hash: MerkleNode::branches(MERKLE_LNPBP4_TAG.to_be_bytes(), depth, width, hash1, hash2), } } diff --git a/commit_verify/src/mpc/mod.rs b/commit_verify/src/mpc/mod.rs index 5890eedb..eb9bb179 100644 --- a/commit_verify/src/mpc/mod.rs +++ b/commit_verify/src/mpc/mod.rs @@ -31,13 +31,13 @@ pub use atoms::{Commitment, Leaf, Message, MessageMap, MultiSource, ProtocolId}; pub use block::{LeafNotKnown, MerkleBlock, MerkleProof, UnrelatedProof}; #[cfg(feature = "rand")] pub use tree::Error; -pub use tree::{Iter, MerkleTree}; +pub use tree::MerkleTree; -const LNPBP4_TAG: [u8; 16] = *b"urn:lnpbp:lnpbp4"; +pub const MERKLE_LNPBP4_TAG: u128 = u128::from_le_bytes(*b"urn:lnpbp:lnpbp4"); /// Marker trait for variates of LNPBP-4 commitment proofs, which differ by the /// amount of concealed information. pub trait Proof: - strict_encoding::StrictEncode + strict_encoding::StrictDecode + Clone + Eq + std::fmt::Debug + strict_encoding::StrictEncode + strict_encoding::StrictDecode + Eq + std::fmt::Debug { } diff --git a/commit_verify/src/mpc/tree.rs b/commit_verify/src/mpc/tree.rs index 9b2e37a4..dcb3dca6 100644 --- a/commit_verify/src/mpc/tree.rs +++ b/commit_verify/src/mpc/tree.rs @@ -19,15 +19,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use amplify::confinement::SmallOrdMap; +use amplify::confinement::{SmallOrdMap, SmallVec}; use amplify::num::{u256, u4}; use amplify::Wrapper; #[cfg(feature = "rand")] pub use self::commit::Error; -use crate::merkle::{MerkleLeaves, MerkleNode}; +use crate::merkle::MerkleNode; use crate::mpc::atoms::Leaf; -use crate::mpc::{Commitment, Message, MessageMap, Proof, ProtocolId, LNPBP4_TAG}; +use crate::mpc::{Commitment, Message, MessageMap, Proof, ProtocolId, MERKLE_LNPBP4_TAG}; use crate::{strategies, CommitStrategy, CommitmentId, Conceal, LIB_NAME_COMMIT_VERIFY}; type OrderedMap = SmallOrdMap; @@ -60,55 +60,17 @@ impl CommitmentId for MerkleTree { type Id = Commitment; } -pub struct Iter<'src> { - width: u16, - pos: u16, - map: &'src OrderedMap, - entropy: u64, -} - -impl<'src> Iterator for Iter<'src> { - type Item = Leaf; - - fn next(&mut self) -> Option { - if self.pos == self.width { - return None; - } - self.pos += 1; - - let leaf = self - .map - .get(&self.pos) - .map(|(protocol, msg)| Leaf::inhabited(*protocol, *msg)) - .unwrap_or_else(|| Leaf::entropy(self.entropy, self.pos)); - - Some(leaf) - } - - fn size_hint(&self) -> (usize, Option) { - let remains = self.map.len() - self.pos as usize; - (remains, Some(remains)) - } -} - -impl<'src> ExactSizeIterator for Iter<'src> {} - -impl MerkleLeaves for MerkleTree { - type Leaf = Leaf; - type LeafIter<'src> = Iter<'src>; - - fn merkle_leaves(&self) -> Self::LeafIter<'_> { - Iter { - entropy: self.entropy, - width: self.width(), - pos: 0, - map: self.as_ordered_map(), - } - } -} - impl MerkleTree { - pub fn root(&self) -> MerkleNode { MerkleNode::merklize(LNPBP4_TAG, self) } + pub fn root(&self) -> MerkleNode { + let iter = (0..self.width()).into_iter().map(|pos| { + self.map + .get(&pos) + .map(|(protocol, msg)| Leaf::inhabited(*protocol, *msg)) + .unwrap_or_else(|| Leaf::entropy(self.entropy, pos)) + }); + let leaves = SmallVec::try_from_iter(iter).expect("u16-bound size"); + MerkleNode::merklize(MERKLE_LNPBP4_TAG.to_be_bytes(), &leaves) + } } impl Conceal for MerkleTree { @@ -201,6 +163,4 @@ impl MerkleTree { pub fn depth(&self) -> u4 { self.depth } pub fn entropy(&self) -> u64 { self.entropy } - - fn as_ordered_map(&self) -> &OrderedMap { &self.map } } From 053b791faef1453f71d3826236871c12c74a4ab3 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 18:06:01 +0200 Subject: [PATCH 12/21] derive: fix mekrlization strategy test --- commit_verify/derive/src/params.rs | 4 ++-- commit_verify/derive/tests/base.rs | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/commit_verify/derive/src/params.rs b/commit_verify/derive/src/params.rs index 5e9a2448..8866cb88 100644 --- a/commit_verify/derive/src/params.rs +++ b/commit_verify/derive/src/params.rs @@ -20,7 +20,7 @@ // limitations under the License. use amplify_syn::{ - ArgValueReq, AttrReq, DataType, FieldKind, ListReq, LiteralClass, ParametrizedAttr, TypeClass, + ArgValueReq, AttrReq, DataType, FieldKind, ListReq, ParametrizedAttr, TypeClass, ValueClass, }; use proc_macro2::{Ident, Span}; use quote::ToTokens; @@ -119,7 +119,7 @@ impl TryFrom for ContainerAttr { impl FieldAttr { pub fn with(mut params: ParametrizedAttr, _kind: FieldKind) -> Result { let mut req = AttrReq::with(map![ - ATTR_MERKLIZE => ArgValueReq::optional(LiteralClass::Int), + ATTR_MERKLIZE => ArgValueReq::optional(ValueClass::Expr), ]); req.path_req = ListReq::maybe_one(path!(skip)); params.check(req)?; diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs index b41fa956..83053ac5 100644 --- a/commit_verify/derive/tests/base.rs +++ b/commit_verify/derive/tests/base.rs @@ -32,7 +32,10 @@ mod common; use std::convert::Infallible; -use amplify::confinement::TinyVec; +use amplify::confinement::SmallVec; +use amplify::Wrapper; +use commit_verify::merkle::MerkleNode; +use commit_verify::mpc::MERKLE_LNPBP4_TAG; use commit_verify::{CommitEncode, Conceal}; use strict_encoding::{StrictDecode, StrictDumb, StrictEncode}; @@ -363,15 +366,16 @@ fn merklize() -> common::Result { #[derive(Clone, PartialEq, Eq, Debug)] #[derive(CommitEncode)] struct Tree { - #[commit_encode(merklize = 0u128)] - leaves: TinyVec, + #[commit_encode(merklize = MERKLE_LNPBP4_TAG)] + leaves: SmallVec, } + let test_vec = small_vec!(0, 1, 2, 3); verify_commit( Tree { - leaves: tiny_vec!(0, 1, 2, 3), + leaves: test_vec.clone(), }, - [0], + MerkleNode::merklize(MERKLE_LNPBP4_TAG.to_be_bytes(), &test_vec).as_slice(), ); Ok(()) From a685146928d766070e08589bd1e008a4a75d5614 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 18:11:05 +0200 Subject: [PATCH 13/21] derive: add feature and re-export macro --- .github/workflows/build.yml | 1 + Cargo.toml | 5 +++-- commit_verify/Cargo.toml | 7 ++++--- commit_verify/derive/tests/base.rs | 2 -- commit_verify/src/lib.rs | 3 +++ 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77c19a9c..0d1a71a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,7 @@ jobs: - serde - async - stl + - derive - all steps: - uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index 845ff171..cea2e185 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,15 +40,16 @@ name = "client_side_validation" path = "src/lib.rs" [dependencies] -commit_verify = { version = "0.10.1", path = "./commit_verify" } +commit_verify = { version = "0.10.1", path = "./commit_verify", default-features = false } single_use_seals = { version = "0.10.0", path = "./single_use_seals" } serde_crate = { package = "serde", version = "1", features = ["derive"], optional = true } [features] -default = [] +default = ["derive"] all = ["serde", "rand", "async"] async = ["single_use_seals/async"] rand = ["commit_verify/rand"] +derive = ["commit_verify/derive"] serde = ["serde_crate", "commit_verify/serde"] [package.metadata.docs.rs] diff --git a/commit_verify/Cargo.toml b/commit_verify/Cargo.toml index df6b6ace..211d8ef1 100644 --- a/commit_verify/Cargo.toml +++ b/commit_verify/Cargo.toml @@ -23,7 +23,7 @@ required-features = ["stl"] [dependencies] amplify = { version = "4.0.0-beta.20", features = ["hex", "apfloat"] } -commit_encoding_derive = { version = "0.10.0-beta.1", path = "derive" } +commit_encoding_derive = { version = "0.10.0-beta.1", path = "derive", optional = true } strict_encoding = "2.0.0" strict_types = { version = "1.0.0-rc.1", optional = true } rand = { version = "0.8.5", optional = true } @@ -33,10 +33,11 @@ serde_crate = { version = "1.0", package = "serde", optional = true } rand = "0.8.5" [features] -default = [] -all = ["rand", "serde", "stl"] +default = ["derive"] +all = ["rand", "serde", "stl", "derive"] serde = ["serde_crate", "amplify/serde"] stl = ["strict_types", "strict_types/base64"] +derive = ["commit_encoding_derive"] [package.metadata.docs.rs] features = [ "all" ] diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs index 83053ac5..e66f67e3 100644 --- a/commit_verify/derive/tests/base.rs +++ b/commit_verify/derive/tests/base.rs @@ -24,8 +24,6 @@ #[macro_use] extern crate amplify; #[macro_use] -extern crate commit_encoding_derive; -#[macro_use] extern crate strict_encoding; mod common; diff --git a/commit_verify/src/lib.rs b/commit_verify/src/lib.rs index ad3f0569..cce8fe1b 100644 --- a/commit_verify/src/lib.rs +++ b/commit_verify/src/lib.rs @@ -39,6 +39,9 @@ extern crate strict_encoding; #[macro_use] extern crate serde_crate as serde; +#[cfg(feature = "derive")] +pub use commit_encoding_derive::CommitEncode; + pub(self) mod commit; mod conceal; mod convolve; From 9d997deb21b5a4b9b5439edfe895766fd2077c3d Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 19:04:12 +0200 Subject: [PATCH 14/21] commit: refactor MerkleLeaves trait to work with owned types --- commit_verify/src/lib.rs | 1 + commit_verify/src/merkle.rs | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/commit_verify/src/lib.rs b/commit_verify/src/lib.rs index cce8fe1b..a4144fac 100644 --- a/commit_verify/src/lib.rs +++ b/commit_verify/src/lib.rs @@ -38,6 +38,7 @@ extern crate strict_encoding; #[cfg(feature = "serde")] #[macro_use] extern crate serde_crate as serde; +extern crate core; #[cfg(feature = "derive")] pub use commit_encoding_derive::CommitEncode; diff --git a/commit_verify/src/merkle.rs b/commit_verify/src/merkle.rs index c789e33c..b5c52e5a 100644 --- a/commit_verify/src/merkle.rs +++ b/commit_verify/src/merkle.rs @@ -19,7 +19,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::BTreeSet; +use core::{iter, slice}; +use std::collections::{btree_set, BTreeSet}; use std::io::Write; use amplify::confinement::Confined; @@ -148,9 +149,9 @@ impl MerkleNode { Self::_merklize(tag, nodes.merkle_leaves(), u4::ZERO, 0) } - pub fn _merklize<'leaf, Leaf: CommitEncode + 'leaf>( + pub fn _merklize( tag: [u8; 16], - mut iter: impl ExactSizeIterator, + mut iter: impl ExactSizeIterator, depth: u4, offset: u16, ) -> Self { @@ -160,9 +161,9 @@ impl MerkleNode { if len <= 2 { match (iter.next(), iter.next()) { (None, None) => MerkleNode::void(tag, depth, width), - (Some(branch), None) => MerkleNode::single(tag, depth, width, branch), + (Some(branch), None) => MerkleNode::single(tag, depth, width, &branch), (Some(branch1), Some(branch2)) => { - MerkleNode::couple(tag, depth, width, branch1, branch2) + MerkleNode::couple(tag, depth, width, &branch1, &branch2) } (None, Some(_)) => unreachable!(), } @@ -187,29 +188,28 @@ impl MerkleNode { pub trait MerkleLeaves { type Leaf: CommitEncode; - - type LeafIter<'leaf>: ExactSizeIterator - where Self: 'leaf; + type LeafIter<'tmp>: ExactSizeIterator + where Self: 'tmp; fn merkle_leaves(&self) -> Self::LeafIter<'_>; } impl MerkleLeaves for Confined, MIN, { u16::MAX as usize }> -where T: CommitEncode +where T: CommitEncode + Copy { type Leaf = T; - type LeafIter<'leaf> = std::slice::Iter<'leaf, T> where Self: 'leaf; + type LeafIter<'tmp> = iter::Copied> where Self: 'tmp; - fn merkle_leaves(&self) -> Self::LeafIter<'_> { self.iter() } + fn merkle_leaves(&self) -> Self::LeafIter<'_> { self.iter().copied() } } impl MerkleLeaves for Confined, MIN, { u16::MAX as usize }> -where T: CommitEncode +where T: CommitEncode + Copy { type Leaf = T; - type LeafIter<'leaf> = std::collections::btree_set::Iter<'leaf, T> where Self: 'leaf; + type LeafIter<'tmp> = iter::Copied> where Self: 'tmp; - fn merkle_leaves(&self) -> Self::LeafIter<'_> { self.iter() } + fn merkle_leaves(&self) -> Self::LeafIter<'_> { self.iter().copied() } } /* From f4a8af261104b8fafffec3921bcc9dffdd6527b0 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 19:23:19 +0200 Subject: [PATCH 15/21] commit: hide AsRef strategy --- commit_verify/src/encode.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/commit_verify/src/encode.rs b/commit_verify/src/encode.rs index c7ff6b4d..859d5e77 100644 --- a/commit_verify/src/encode.rs +++ b/commit_verify/src/encode.rs @@ -116,6 +116,8 @@ pub mod strategies { use super::*; use crate::merkle::{MerkleLeaves, MerkleNode}; + /// Used only internally for blank implementation on reference types. + #[doc(hidden)] pub enum AsRef {} /// Commits to the value by converting it into `u8` type. Useful for enum From d16ef22b57e2e01a4ef3bbcb05a1a180282b4edc Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 19:23:40 +0200 Subject: [PATCH 16/21] derive: add ability to conceal with transparent strategy --- commit_verify/derive/src/derive.rs | 15 +++++++++++++-- commit_verify/derive/src/params.rs | 18 ++++++++++++------ commit_verify/derive/tests/base.rs | 2 +- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/commit_verify/derive/src/derive.rs b/commit_verify/derive/src/derive.rs index a7e71c12..7198fce9 100644 --- a/commit_verify/derive/src/derive.rs +++ b/commit_verify/derive/src/derive.rs @@ -60,6 +60,16 @@ impl CommitDerive { ) -> Result { let crate_name = &self.conf.commit_crate; + let conceal_code = if self.conf.conceal { + quote! { + let me = self.conceal(); + } + } else { + quote! { + let me = &self; + } + }; + let mut field_encoding = Vec::new(); for (no, (field_name, unnamed_field)) in fields.enumerate() { let kind = match field_name { @@ -77,12 +87,12 @@ impl CommitDerive { quote! { { use #crate_name::merkle::MerkleLeaves; - #crate_name::merkle::MerkleNode::merklize(#tag.to_be_bytes(), &self.#field_name).commit_encode(e); + #crate_name::merkle::MerkleNode::merklize(#tag.to_be_bytes(), &me.#field_name).commit_encode(e); } } } else { quote! { - self.#field_name.commit_encode(e); + me.#field_name.commit_encode(e); } }; field_encoding.push(field) @@ -91,6 +101,7 @@ impl CommitDerive { Ok(quote! { fn commit_encode(&self, e: &mut impl ::std::io::Write) { use #crate_name::CommitEncode; + #conceal_code #( #field_encoding )* } }) diff --git a/commit_verify/derive/src/params.rs b/commit_verify/derive/src/params.rs index 8866cb88..f6fe975d 100644 --- a/commit_verify/derive/src/params.rs +++ b/commit_verify/derive/src/params.rs @@ -28,10 +28,10 @@ use syn::{DeriveInput, Error, Expr, Path, Result}; const ATTR: &str = "commit_encode"; const ATTR_CRATE: &str = "crate"; +const ATTR_CONCEAL: &str = "conceal"; const ATTR_STRATEGY: &str = "strategy"; const ATTR_STRATEGY_COMMIT: &str = "propagate"; const ATTR_STRATEGY_STRICT: &str = "strict"; -const ATTR_STRATEGY_CONCEAL: &str = "conceal_strict"; const ATTR_STRATEGY_TRANSPARENT: &str = "transparent"; const ATTR_STRATEGY_INTO_U8: &str = "into_u8"; const ATTR_MERKLIZE: &str = "merklize"; @@ -40,6 +40,7 @@ const ATTR_SKIP: &str = "skip"; pub struct ContainerAttr { pub commit_crate: Path, pub strategy: StrategyAttr, + pub conceal: bool, } #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -58,7 +59,6 @@ impl TryFrom<&Path> for StrategyAttr { match path.to_token_stream().to_string().as_str() { ATTR_STRATEGY_COMMIT => Ok(StrategyAttr::CommitEncoding), ATTR_STRATEGY_STRICT => Ok(StrategyAttr::StrictEncoding), - ATTR_STRATEGY_CONCEAL => Ok(StrategyAttr::ConcealStrictEncoding), ATTR_STRATEGY_TRANSPARENT => Ok(StrategyAttr::Transparent), ATTR_STRATEGY_INTO_U8 => Ok(StrategyAttr::IntoU8), unknown => Err(Error::new( @@ -66,8 +66,7 @@ impl TryFrom<&Path> for StrategyAttr { format!( "invalid commitment encoding value for `strategy` attribute `{unknown}`; only \ `{ATTR_STRATEGY_TRANSPARENT}`, `{ATTR_STRATEGY_INTO_U8}`, \ - `{ATTR_STRATEGY_COMMIT}`, `{ATTR_STRATEGY_STRICT}` or \ - `{ATTR_STRATEGY_CONCEAL}` are allowed" + `{ATTR_STRATEGY_COMMIT}`, or `{ATTR_STRATEGY_STRICT}` are allowed" ), )), } @@ -97,21 +96,28 @@ impl TryFrom for ContainerAttr { type Error = Error; fn try_from(mut params: ParametrizedAttr) -> Result { - let req = AttrReq::with(map![ + let mut req = AttrReq::with(map![ ATTR_CRATE => ArgValueReq::optional(TypeClass::Path), ATTR_STRATEGY => ArgValueReq::optional(TypeClass::Path), ]); + req.path_req = ListReq::maybe_one(path!(conceal)); params.check(req)?; let path = params .arg_value(ATTR_STRATEGY) .unwrap_or_else(|_| path!(propagate)); + let mut strategy = StrategyAttr::try_from(&path)?; + let conceal = params.has_verbatim(ATTR_CONCEAL); + if conceal && strategy == StrategyAttr::StrictEncoding { + strategy = StrategyAttr::ConcealStrictEncoding + } Ok(ContainerAttr { commit_crate: params .arg_value(ATTR_CRATE) .unwrap_or_else(|_| path!(commit_verify)), - strategy: StrategyAttr::try_from(&path)?, + strategy, + conceal, }) } } diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs index e66f67e3..296cd4f5 100644 --- a/commit_verify/derive/tests/base.rs +++ b/commit_verify/derive/tests/base.rs @@ -322,7 +322,7 @@ fn conceal() -> common::Result { #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = TEST_LIB, tags = order, dumb = { Self::Concealed(0) })] #[derive(CommitEncode)] - #[commit_encode(strategy = conceal_strict)] + #[commit_encode(conceal, strategy = strict)] enum Data { Revealed(u128), Concealed(u8), From b7ac9d949621d3b6d6f34e4ea3ca43196a7053cb Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 20:08:48 +0200 Subject: [PATCH 17/21] derive: support enum derivation with propagate strategy --- commit_verify/derive/src/derive.rs | 117 ++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/commit_verify/derive/src/derive.rs b/commit_verify/derive/src/derive.rs index 7198fce9..ba4d0229 100644 --- a/commit_verify/derive/src/derive.rs +++ b/commit_verify/derive/src/derive.rs @@ -19,7 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use amplify_syn::{DeriveInner, Field, FieldKind, Items, NamedField, Variant}; +use amplify_syn::{DeriveInner, EnumKind, Field, FieldKind, Fields, Items, NamedField, Variant}; use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::ToTokens; use syn::{Error, Index, Result}; @@ -126,7 +126,118 @@ impl DeriveInner for DeriveCommit<'_> { self.0.derive_fields(fields.iter().map(|f| (None, f))) } - fn derive_enum_inner(&self, _variants: &Items) -> Result { - Err(Error::new(Span::call_site(), "enums can't use CommitEncode strategy")) + fn derive_enum_inner(&self, variants: &Items) -> Result { + let crate_name = &self.0.conf.commit_crate; + + if variants.enum_kind() == EnumKind::Primitive { + return Err(Error::new( + Span::call_site(), + "primitive enums can't use `propagate` strategy", + )); + } + + let conceal_code = if self.0.conf.conceal { + quote! { + let me = self.conceal(); + } + } else { + quote! { + let me = &self; + } + }; + + let mut write_variants = Vec::with_capacity(variants.len()); + for var in variants { + let var_name = &var.name; + match &var.fields { + Fields::Unit => { + write_variants.push(quote! { + Self::#var_name() => {}, + }); + } + Fields::Unnamed(fields) if fields.is_empty() => { + write_variants.push(quote! { + Self::#var_name() => {}, + }); + } + Fields::Named(fields) if fields.is_empty() => { + write_variants.push(quote! { + Self::#var_name {} => {}, + }); + } + Fields::Unnamed(fields) => { + let mut field_idx = Vec::with_capacity(fields.len()); + let mut field_fragments = Vec::with_capacity(fields.len()); + for (no, field) in fields.iter().enumerate() { + let index = Index::from(no); + let attr = FieldAttr::with(field.attr.clone(), FieldKind::Unnamed)?; + if attr.skip { + continue; + } + + field_idx.push(index.clone()); + if let Some(tag) = attr.merklize { + field_fragments.push(quote! { + MerkleNode::merklize(#tag.to_be_bytes(), &me.#index).commit_encode(e); + }) + } else { + field_fragments.push(quote! { + me.#index.commit_encode(e); + }) + } + } + write_variants.push(quote! { + Self::#var_name( #( #field_idx ),* ) => { + #( #field_fragments )* + }, + }); + } + Fields::Named(fields) => { + let mut field_name = Vec::with_capacity(fields.len()); + let mut field_fragments = Vec::with_capacity(fields.len()); + for named_field in fields { + let attr = + FieldAttr::with(named_field.field.attr.clone(), FieldKind::Named)?; + let name = &named_field.name; + if attr.skip { + continue; + } + + field_name.push(name.clone()); + if let Some(tag) = attr.merklize { + field_fragments.push(quote! { + MerkleNode::merklize(#tag.to_be_bytes(), &me.#name).commit_encode(e); + }) + } else { + field_fragments.push(quote! { + me.#name.commit_encode(e); + }) + } + } + + write_variants.push(quote! { + Self::#var_name { #( #field_name ),* } => { + #( #field_fragments )* + }, + }); + } + } + } + + Ok(quote! { + #[allow(unused_imports)] + fn commit_encode(&self, e: &mut impl ::std::io::Write) { + use #crate_name::CommitEncode; + use #crate_name::merkle::{MerkleLeaves, MerkleNode}; + use ::strict_encoding::StrictSum; + + #conceal_code + me.variant_ord.commit_encode(e); + + match self { + #( #write_variants )* + } + } + }) } } From 2b9b98ff1f0fed9bcab3f95bb4842fedf05f9468 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 20:19:45 +0200 Subject: [PATCH 18/21] derive: test enum derivation and skipping variant fields --- commit_verify/derive/src/derive.rs | 24 +++++++++--------- commit_verify/derive/tests/base.rs | 39 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/commit_verify/derive/src/derive.rs b/commit_verify/derive/src/derive.rs index ba4d0229..d92532b4 100644 --- a/commit_verify/derive/src/derive.rs +++ b/commit_verify/derive/src/derive.rs @@ -66,7 +66,7 @@ impl CommitDerive { } } else { quote! { - let me = &self; + let me = self; } }; @@ -142,7 +142,7 @@ impl DeriveInner for DeriveCommit<'_> { } } else { quote! { - let me = &self; + let me = self; } }; @@ -152,7 +152,7 @@ impl DeriveInner for DeriveCommit<'_> { match &var.fields { Fields::Unit => { write_variants.push(quote! { - Self::#var_name() => {}, + Self::#var_name => {}, }); } Fields::Unnamed(fields) if fields.is_empty() => { @@ -169,20 +169,20 @@ impl DeriveInner for DeriveCommit<'_> { let mut field_idx = Vec::with_capacity(fields.len()); let mut field_fragments = Vec::with_capacity(fields.len()); for (no, field) in fields.iter().enumerate() { - let index = Index::from(no); + let index = Ident::new(&format!("_{no}"), Span::call_site()); let attr = FieldAttr::with(field.attr.clone(), FieldKind::Unnamed)?; + field_idx.push(index.clone()); if attr.skip { continue; } - field_idx.push(index.clone()); if let Some(tag) = attr.merklize { field_fragments.push(quote! { - MerkleNode::merklize(#tag.to_be_bytes(), &me.#index).commit_encode(e); + MerkleNode::merklize(#tag.to_be_bytes(), #index).commit_encode(e); }) } else { field_fragments.push(quote! { - me.#index.commit_encode(e); + #index.commit_encode(e); }) } } @@ -199,18 +199,18 @@ impl DeriveInner for DeriveCommit<'_> { let attr = FieldAttr::with(named_field.field.attr.clone(), FieldKind::Named)?; let name = &named_field.name; + field_name.push(name.clone()); if attr.skip { continue; } - field_name.push(name.clone()); if let Some(tag) = attr.merklize { field_fragments.push(quote! { - MerkleNode::merklize(#tag.to_be_bytes(), &me.#name).commit_encode(e); + MerkleNode::merklize(#tag.to_be_bytes(), #name).commit_encode(e); }) } else { field_fragments.push(quote! { - me.#name.commit_encode(e); + #name.commit_encode(e); }) } } @@ -225,14 +225,14 @@ impl DeriveInner for DeriveCommit<'_> { } Ok(quote! { - #[allow(unused_imports)] + #[allow(unused_imports, unused_variables)] fn commit_encode(&self, e: &mut impl ::std::io::Write) { use #crate_name::CommitEncode; use #crate_name::merkle::{MerkleLeaves, MerkleNode}; use ::strict_encoding::StrictSum; #conceal_code - me.variant_ord.commit_encode(e); + me.variant_ord().commit_encode(e); match self { #( #write_variants )* diff --git a/commit_verify/derive/tests/base.rs b/commit_verify/derive/tests/base.rs index 296cd4f5..0d8bd04f 100644 --- a/commit_verify/derive/tests/base.rs +++ b/commit_verify/derive/tests/base.rs @@ -316,6 +316,45 @@ fn enum_custom_tags() -> common::Result { Ok(()) } +#[test] +fn enum_propagate() -> common::Result { + #[allow(dead_code)] + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] + #[strict_type(lib = TEST_LIB, tags = order)] + #[derive(CommitEncode)] + enum Assoc { + #[strict_type(tag = 8)] + One { hash: [u8; 32], ord: u8 }, + #[strict_type(tag = 2)] + Two(u8, u16, #[commit_encode(skip)] u32), + #[strict_type(dumb, tag = 3)] + Three, + #[strict_type(tag = 4)] + Four(), + #[strict_type(tag = 5)] + Five {}, + Six { + a: u8, + #[commit_encode(skip)] + b: u16, + }, + } + + let mut res = vec![8; 33]; + res.extend([1]); + verify_commit( + Assoc::One { + hash: [8; 32], + ord: 1, + }, + res, + ); + verify_commit(Assoc::Two(0xfe, 0xdead, 0xbeefcafe), [2, 0xfe, 0xad, 0xde]); + + Ok(()) +} + #[test] fn conceal() -> common::Result { #[derive(Clone, PartialEq, Eq, Debug)] From 0b4ab5d5624e40fa57025a79b2e1bf1c7062f8ea Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 21:12:28 +0200 Subject: [PATCH 19/21] mpc: add CommitEncode constraint to Proof --- commit_verify/src/mpc/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/commit_verify/src/mpc/mod.rs b/commit_verify/src/mpc/mod.rs index eb9bb179..ef6cb4fe 100644 --- a/commit_verify/src/mpc/mod.rs +++ b/commit_verify/src/mpc/mod.rs @@ -38,6 +38,10 @@ pub const MERKLE_LNPBP4_TAG: u128 = u128::from_le_bytes(*b"urn:lnpbp:lnpbp4"); /// Marker trait for variates of LNPBP-4 commitment proofs, which differ by the /// amount of concealed information. pub trait Proof: - strict_encoding::StrictEncode + strict_encoding::StrictDecode + Eq + std::fmt::Debug + strict_encoding::StrictEncode + + strict_encoding::StrictDecode + + crate::CommitEncode + + Eq + + std::fmt::Debug { } From 39a5ab9bfdc7824a52f9238385031243d3257ac9 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 1 May 2023 21:22:46 +0200 Subject: [PATCH 20/21] commit: use new CommitEncode derivation macros --- commit_verify/src/lib.rs | 2 ++ commit_verify/src/merkle.rs | 6 ++---- commit_verify/src/mpc/atoms.rs | 6 ++---- commit_verify/src/mpc/block.rs | 10 +++++----- commit_verify/src/mpc/tree.rs | 8 +++----- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/commit_verify/src/lib.rs b/commit_verify/src/lib.rs index a4144fac..afe109c0 100644 --- a/commit_verify/src/lib.rs +++ b/commit_verify/src/lib.rs @@ -35,6 +35,8 @@ extern crate amplify; #[macro_use] extern crate strict_encoding; +#[macro_use] +extern crate commit_encoding_derive; #[cfg(feature = "serde")] #[macro_use] extern crate serde_crate as serde; diff --git a/commit_verify/src/merkle.rs b/commit_verify/src/merkle.rs index b5c52e5a..8533766d 100644 --- a/commit_verify/src/merkle.rs +++ b/commit_verify/src/merkle.rs @@ -64,6 +64,8 @@ impl CommitStrategy for NodeBranching { #[wrapper(Deref, BorrowSlice, Display, FromStr, Hex, Index, RangeOps)] #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_COMMIT_VERIFY, dumb = MerkleNode(default!()))] +#[derive(CommitEncode)] +#[commit_encode(crate = crate, strategy = strict)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -136,10 +138,6 @@ impl MerkleNode { } } -impl CommitStrategy for MerkleNode { - type Strategy = strategies::Strict; -} - impl MerkleNode { /// Merklization procedure that uses tagged hashes with depth commitments /// according to [LNPBP-81] standard of client-side-validation merklization. diff --git a/commit_verify/src/mpc/atoms.rs b/commit_verify/src/mpc/atoms.rs index 2d80bd80..f8eb50d5 100644 --- a/commit_verify/src/mpc/atoms.rs +++ b/commit_verify/src/mpc/atoms.rs @@ -66,6 +66,8 @@ impl ProtocolId { #[wrapper(Deref, BorrowSlice, Display, FromStr, Hex, Index, RangeOps)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = crate::LIB_NAME_COMMIT_VERIFY)] +#[derive(CommitEncode)] +#[commit_encode(crate = crate, strategy = strict)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -77,10 +79,6 @@ pub struct Message( Bytes32, ); -impl CommitStrategy for Message { - type Strategy = strategies::Strict; -} - impl Message { pub fn from_slice(slice: &[u8]) -> Option { Bytes32::from_slice(slice).map(Self) } } diff --git a/commit_verify/src/mpc/block.rs b/commit_verify/src/mpc/block.rs index 860f6ef5..cd5e1030 100644 --- a/commit_verify/src/mpc/block.rs +++ b/commit_verify/src/mpc/block.rs @@ -35,7 +35,7 @@ use crate::mpc::tree::protocol_id_pos; use crate::mpc::{ Commitment, MerkleTree, Message, MessageMap, Proof, ProtocolId, MERKLE_LNPBP4_TAG, }; -use crate::{strategies, CommitStrategy, Conceal, LIB_NAME_COMMIT_VERIFY}; +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. @@ -108,6 +108,8 @@ impl TreeNode { #[derive(Getters, Clone, PartialEq, Eq, Hash, Debug, Default)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_COMMIT_VERIFY)] +#[derive(CommitEncode)] +#[commit_encode(crate = crate, conceal, strategy = strict)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] pub struct MerkleBlock { /// Tree depth (up to 16). @@ -476,10 +478,6 @@ impl Conceal for MerkleBlock { } } -impl CommitStrategy for MerkleBlock { - type Strategy = strategies::ConcealStrict; -} - impl CommitmentId for MerkleBlock { const TAG: [u8; 32] = *b"urn:lnpbp:lnpbp0004:tree:v01#23A"; type Id = Commitment; @@ -489,6 +487,8 @@ impl CommitmentId for MerkleBlock { #[derive(Getters, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_COMMIT_VERIFY)] +#[derive(CommitEncode)] +#[commit_encode(crate = crate, strategy = strict)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] pub struct MerkleProof { /// Position of the leaf in the tree. diff --git a/commit_verify/src/mpc/tree.rs b/commit_verify/src/mpc/tree.rs index dcb3dca6..f9c28aa7 100644 --- a/commit_verify/src/mpc/tree.rs +++ b/commit_verify/src/mpc/tree.rs @@ -28,7 +28,7 @@ pub use self::commit::Error; use crate::merkle::MerkleNode; use crate::mpc::atoms::Leaf; use crate::mpc::{Commitment, Message, MessageMap, Proof, ProtocolId, MERKLE_LNPBP4_TAG}; -use crate::{strategies, CommitStrategy, CommitmentId, Conceal, LIB_NAME_COMMIT_VERIFY}; +use crate::{CommitmentId, Conceal, LIB_NAME_COMMIT_VERIFY}; type OrderedMap = SmallOrdMap; @@ -36,6 +36,8 @@ type OrderedMap = SmallOrdMap; #[derive(Clone, PartialEq, Eq, Hash, Debug)] #[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_COMMIT_VERIFY)] +#[derive(CommitEncode)] +#[commit_encode(crate = crate, conceal, strategy = strict)] pub struct MerkleTree { /// Tree depth (up to 16). pub(super) depth: u4, @@ -51,10 +53,6 @@ pub struct MerkleTree { impl Proof for MerkleTree {} -impl CommitStrategy for MerkleTree { - type Strategy = strategies::ConcealStrict; -} - impl CommitmentId for MerkleTree { const TAG: [u8; 32] = *b"urn:lnpbp:lnpbp0004:tree:v01#23A"; type Id = Commitment; From 291e24db87ca0030e9bf94cc892f4f8cce62c498 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 2 May 2023 10:44:39 +0200 Subject: [PATCH 21/21] ci: fix no-default-features builds --- commit_verify/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commit_verify/Cargo.toml b/commit_verify/Cargo.toml index 211d8ef1..0551bc8a 100644 --- a/commit_verify/Cargo.toml +++ b/commit_verify/Cargo.toml @@ -23,7 +23,7 @@ required-features = ["stl"] [dependencies] amplify = { version = "4.0.0-beta.20", features = ["hex", "apfloat"] } -commit_encoding_derive = { version = "0.10.0-beta.1", path = "derive", optional = true } +commit_encoding_derive = { version = "0.10.0-beta.1", path = "derive" } strict_encoding = "2.0.0" strict_types = { version = "1.0.0-rc.1", optional = true } rand = { version = "0.8.5", optional = true } @@ -37,7 +37,7 @@ default = ["derive"] all = ["rand", "serde", "stl", "derive"] serde = ["serde_crate", "amplify/serde"] stl = ["strict_types", "strict_types/base64"] -derive = ["commit_encoding_derive"] +derive = [] [package.metadata.docs.rs] features = [ "all" ]