diff --git a/.github/workflows/cron-daily-fuzz.yml b/.github/workflows/cron-daily-fuzz.yml index 6154bf60..7f3af38e 100644 --- a/.github/workflows/cron-daily-fuzz.yml +++ b/.github/workflows/cron-daily-fuzz.yml @@ -1,4 +1,7 @@ -# Automatically generated by fuzz/generate-files.sh +###### +## DO NOT EDIT THIS FILE DIRECTLY. It is generated by generate-files.sh. +## Edit that script instead and re-run it. +###### name: Fuzz on: schedule: diff --git a/.gitignore b/.gitignore index 2e47091b..42dea11a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ Cargo.lock *~ #fuzz -fuzz/hfuzz_target -fuzz/hfuzz_workspace +fuzz/corpus +fuzz/artifacts +fuzz/*.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 179c2d2d..ff4f1a45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +# 0.27.0 - 2026-07-28 + +This release updates several dependencies. It attempts to minimize API breakage beyond updating +the dependencies. It should be considered a "transitional release" as we modernize the crate's API +over the next several releases. In particular, this release introduces a new encoding paradigm but +does not remove the old one, so depending on this may help users transition. + +Dependency updates: + +* **Bump MRSV to 1.74.0** + [#269](https://github.com/ElementsProject/rust-elements/pull/269) + [#265](https://github.com/ElementsProject/rust-elements/pull/265) +* Update `bitcoin_hashes` dependency to newly-stable 1.0; improve array/slice APIs +* Replace custom hex parsing with newly-stable `hex-conservative` 1.0 + [#261](https://github.com/ElementsProject/rust-elements/pull/261) + [#272](https://github.com/ElementsProject/rust-elements/pull/272) +* Introduce newly-stable `bitcoin-consensus-encoding` 1.0 crate and implement its `Encode` and + `Decode` traits on `Transaction` and subtypes. + [#288](https://github.com/ElementsProject/rust-elements/pull/288) + + +* Remove `TxOut::MAX_MONEY` whose use is likely incorrect on a multi-asset chain + [#261](https://github.com/ElementsProject/rust-elements/pull/261) +* pset: fix pegin/issuance flag handling in extract_tx + [#278](https://github.com/ElementsProject/rust-elements/pull/278) + [#279](https://github.com/ElementsProject/rust-elements/pull/279) + [#282](https://github.com/ElementsProject/rust-elements/pull/282) +* Implement calculation of AssetId and genesis blocks + [#276](https://github.com/ElementsProject/rust-elements/pull/276) +* Improve type safety for objects related to confidential transactions and asset issuance + [#286](https://github.com/ElementsProject/rust-elements/pull/286) + [#289](https://github.com/ElementsProject/rust-elements/pull/289) + +# 0.26.2 - 2026-06-15 + +* Fix multiple panics related to slicing and blinding data validation # 0.26.1 - 2025-08-28 diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 5a99fb08..1211fd24 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -22,12 +22,11 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "base58ck" -version = "0.1.0" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +checksum = "365c0acd5b2e8dd0111a46c4faea83fb3cfb6e39a49a7c73a06e090db7b2eff0" dependencies = [ - "bitcoin-internals 0.3.0", - "bitcoin_hashes", + "bitcoin_hashes 0.14.101", ] [[package]] @@ -59,43 +58,48 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.6" +version = "0.32.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8929a18b8e33ea6b3c09297b687baaa71fb1b97353243a3f1029fad5c59c5b" +checksum = "bb0ce8bd5baaa0d303a19915a6d93afed161f528654e42da2a7a97d05c59499a" dependencies = [ "base58ck", "base64 0.21.7", "bech32", - "bitcoin-internals 0.3.0", + "bitcoin-consensus-encoding", "bitcoin-io", "bitcoin-units", - "bitcoin_hashes", - "hex-conservative 0.2.1", + "bitcoin_hashes 0.14.101", + "hex-conservative 0.2.2", "hex_lit", "secp256k1", "serde", ] [[package]] -name = "bitcoin-internals" -version = "0.3.0" +name = "bitcoin-consensus-encoding" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9" dependencies = [ + "bitcoin-internals", + "hex-conservative 1.1.0", "serde", ] [[package]] name = "bitcoin-internals" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" [[package]] name = "bitcoin-io" -version = "0.1.3" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] [[package]] name = "bitcoin-private" @@ -105,22 +109,34 @@ checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" [[package]] name = "bitcoin-units" -version = "0.1.2" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +checksum = "9cb95693f371d089a4b5b6fc41c6f3ea6e01ee8c15388335dfac8ea685173b51" dependencies = [ - "bitcoin-internals 0.3.0", + "bitcoin-consensus-encoding", "serde", ] [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative 0.2.1", + "hex-conservative 0.2.2", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67800fcf7f3ca52f7796d4466948a424326b225950c79d1e76d0458760bb25d" +dependencies = [ + "bitcoin-consensus-encoding", + "bitcoin-internals", + "hex-conservative 1.1.0", "serde", ] @@ -204,12 +220,14 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elements" -version = "0.26.1" +version = "0.27.0" dependencies = [ "bech32", "bincode", "bitcoin", - "bitcoin-internals 0.5.0", + "bitcoin-consensus-encoding", + "bitcoin-internals", + "bitcoin_hashes 1.1.0", "getrandom 0.2.16", "hex-conservative 1.1.0", "rand", @@ -291,9 +309,9 @@ dependencies = [ [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -490,7 +508,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.14.101", "rand", "secp256k1-sys", "serde", diff --git a/Cargo.toml b/Cargo.toml index a1d5f4af..8abc3df0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "elements" -version = "0.26.1" +version = "0.27.0" authors = ["Andrew Poelstra "] description = "Library with support for de/serialization, parsing and executing on data structures and network messages related to Elements" license = "CC0-1.0" @@ -11,8 +11,8 @@ edition = "2018" rust-version = "1.74.0" [workspace.metadata.rbmt.toolchains] -nightly = "nightly-2026-05-14" -stable = "1.96.0" +nightly = "nightly-2026-07-23" +stable = "1.97.1" [features] default = ["json-contract"] @@ -22,14 +22,17 @@ json-contract = ["serde_json"] "dep:serde", "bitcoin/serde", "bitcoin/serde", + "hashes/serde", "secp256k1-zkp/serde", ] base64 = ["bitcoin/base64"] [dependencies] bech32 = "0.11.0" -bitcoin = "0.32.2" -internals = { package = "bitcoin-internals", version = "0.5" } +bitcoin = { version = "0.32.102", default-features = false, features = [ "encoding", "std" ] } +encoding = { package = "bitcoin-consensus-encoding", version = "1.1.0", default-features = false, features = [ "hex", "std" ] } +hashes = { package = "bitcoin_hashes", version = "1.1", features = [ "hex" ] } +internals = { package = "bitcoin-internals", version = "0.6" } secp256k1-zkp = { version = "0.11.0", features = ["global-context", "hashes"] } # Used for ContractHash::from_json_contract. @@ -71,6 +74,7 @@ match_bool = "allow" # Adds extra indentation and LOC. match_same_arms = "allow" # Collapses things that are conceptually unrelated to each other. must_use_candidate = "allow" # Useful for audit but many false positives. similar_names = "allow" # Too many (subjectively) false positives. +struct_field_names = "allow" # Dumb # Exhaustive list of pedantic clippy lints assigning_clones = "warn" bool_to_int_with_if = "warn" @@ -97,7 +101,6 @@ explicit_iter_loop = "warn" filter_map_next = "warn" flat_map_option = "warn" fn_params_excessive_bools = "warn" -from_iter_instead_of_collect = "warn" if_not_else = "warn" ignored_unit_patterns = "warn" implicit_clone = "warn" @@ -166,7 +169,6 @@ stable_sort_primitive = "warn" str_split_at_newline = "warn" string_add_assign = "warn" struct_excessive_bools = "warn" -struct_field_names = "warn" too_many_lines = "allow" # FIXME 14 triggers for this lint; probably most should be fixed transmute_ptr_to_ptr = "warn" trivially_copy_pass_by_ref = "warn" @@ -189,7 +191,7 @@ zero_sized_map_values = "warn" [package.metadata.rbmt.lint] allowed_duplicates = [ - "bitcoin-internals", + "bitcoin_hashes", "hex-conservative", ] diff --git a/clippy.toml b/clippy.toml index 5f304987..c4931941 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,2 @@ avoid-breaking-exported-api = true +large-error-threshold = 192 # default 128 complains about two public keys in RangeProofMessageError diff --git a/elementsd-tests/Cargo.toml b/elementsd-tests/Cargo.toml index 5912f723..d08aa099 100644 --- a/elementsd-tests/Cargo.toml +++ b/elementsd-tests/Cargo.toml @@ -16,6 +16,7 @@ rand = "0.8" [package.metadata.rbmt.lint] # FIXME the bulk of these are because elementsd/bitcoind is much older than rust-bitcoin. allowed_duplicates = [ + "bitcoin_hashes", "bitcoin-internals", "hex-conservative", "base64", diff --git a/elementsd-tests/src/pset.rs b/elementsd-tests/src/pset.rs index 7ce1d95b..7b907278 100644 --- a/elementsd-tests/src/pset.rs +++ b/elementsd-tests/src/pset.rs @@ -8,7 +8,6 @@ use crate::{setup, Call}; use bitcoin::{self, Address, Amount}; use elements::encode::serialize; -use elements::hashes::Hash; use elements::hex::DisplayHex as _; use elements::pset::PartiallySignedTransaction; use elements::{AssetId, ContractHash}; diff --git a/elementsd-tests/src/taproot.rs b/elementsd-tests/src/taproot.rs index 8dcc6b13..364c7230 100644 --- a/elementsd-tests/src/taproot.rs +++ b/elementsd-tests/src/taproot.rs @@ -10,7 +10,6 @@ use bitcoin::Amount; use elements::hex; use elements::confidential::{AssetBlindingFactor, ValueBlindingFactor}; use elements::encode::{deserialize, serialize_hex}; -use elements::hashes::Hash; use elements::script::Builder; use elements::secp256k1_zkp; use elements::sighash::{self, SighashCache}; @@ -215,7 +214,7 @@ fn taproot_spend_test( ); let tweak = secp256k1_zkp::Scalar::from_be_bytes(tweak.to_byte_array()).expect("hash value greater than curve order"); let sig = secp.sign_schnorr( - &secp256k1_zkp::Message::from_digest_slice(&sighash_msg[..]).unwrap(), + &secp256k1_zkp::Message::from_digest(sighash_msg.to_byte_array()), &output_keypair.add_xonly_tweak(secp, &tweak).unwrap(), ); @@ -224,7 +223,7 @@ fn taproot_spend_test( hash_ty: sighash_ty, }; - tx.input[0].witness.script_witness = vec![schnorr_sig.to_vec()]; + tx.input[0].witness.script_witness.push(schnorr_sig.to_vec()); } else { // script spend // try spending using leaf1 @@ -239,7 +238,7 @@ fn taproot_spend_test( .unwrap(); let sig = secp.sign_schnorr( - &secp256k1_zkp::Message::from_digest_slice(&sighash_msg[..]).unwrap(), + &secp256k1_zkp::Message::from_digest(sighash_msg.to_byte_array()), &test_data.leaf1_keypair, ); @@ -251,11 +250,9 @@ fn taproot_spend_test( hash_ty: sighash_ty, }; - tx.input[0].witness.script_witness = vec![ - schnorr_sig.to_vec(), // witness - script_ver.0.into_bytes(), // leaf script - ctrl_block.serialize(), // control block - ]; + tx.input[0].witness.script_witness.push(schnorr_sig.to_vec()); // witness + tx.input[0].witness.script_witness.push(script_ver.0.into_bytes()); // leaf script + tx.input[0].witness.script_witness.push(ctrl_block.serialize()); // control block } let tx_hex = serialize_hex(&tx); diff --git a/examples/raw_blind.rs b/examples/raw_blind.rs index c45be283..18a0e974 100644 --- a/examples/raw_blind.rs +++ b/examples/raw_blind.rs @@ -305,15 +305,13 @@ fn main() { // Finalize(TODO in miniscript) pset.inputs_mut()[0].partial_sigs.clear(); - pset.inputs_mut()[0].final_script_witness = Some(vec![ - inp0_sig, - inp0_pk.to_bytes(), - ]); + let wit = pset.inputs_mut()[0].final_script_witness.insert(elements::Witness::new()); + wit.push(inp0_sig); + wit.push(inp0_pk.to_bytes()); pset.inputs_mut()[1].partial_sigs.clear(); - pset.inputs_mut()[1].final_script_witness = Some(vec![ - inp1_sig, - inp1_pk.to_bytes(), - ]); + let wit = pset.inputs_mut()[1].final_script_witness.insert(elements::Witness::new()); + wit.push(inp1_sig); + wit.push(inp1_pk.to_bytes()); assert_eq!(pset, deser_pset(&tests["finalized"])); // Extracted tx diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 899e9a24..5bbfee40 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -1,3 +1,7 @@ +###### +## DO NOT EDIT THIS FILE DIRECTLY. It is generated by generate-files.sh. +## Edit that script instead and re-run it. +###### [package] name = "elements-fuzz" edition = "2021" @@ -22,12 +26,20 @@ use_self = "warn" [package.metadata.rbmt.lint] allowed_duplicates = [ + "bitcoin_hashes", "bitcoin-internals", "hex-conservative", "getrandom", "wasi", ] +[[bin]] +name = "decode_equiv_transaction" +path = "fuzz_targets/decode_equiv_transaction.rs" +test = false +doc = false +bench = false + [[bin]] name = "deserialize_block" path = "fuzz_targets/deserialize_block.rs" diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 00000000..001f1ef0 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,161 @@ +# Fuzzing + +`rust-bitcoin` has fuzzing harnesses setup for use with +`cargo-fuzz`. + +To run the fuzz-tests as in CI -- briefly fuzzing every target -- simply +run + +```bash +./fuzz.sh +``` + +in this directory. + +By default, `fuzz.sh` runs each target for 100 seconds. Pass +`-max_total_time` to run for longer or shorter: + +```bash +./fuzz.sh -max_total_time=300 +``` + +## Fuzzing with weak cryptography + +You may wish to replace the hashing and signing code with broken crypto, +which will be faster and enable the fuzzer to do otherwise impossible +things such as forging signatures or finding preimages to hashes. + +Doing so may result in spurious bug reports since the broken crypto does +not respect the encoding or algebraic invariants upheld by the real crypto. We +would like to improve this, but it's a nontrivial problem -- though not +beyond the abilities of a motivated student with a few months of time. +Please let us know if you are interested in taking this on! + +Meanwhile, to use the broken crypto, simply compile (and run the fuzzing +scripts) with + +```bash +RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz" +``` + +which will replace the hashing library with broken hashes, and the +`secp256k1` library with broken cryptography. + +Needless to say, NEVER COMPILE REAL CODE WITH THESE FLAGS because if a +fuzzer can break your crypto, so can anybody. + +## Long-term fuzzing + +To see the full list of targets, the most straightforward way is to run + +```bash +cargo fuzz list +``` + +To run each of them for an hour, run + +```bash +./cycle.sh +``` +This script uses the `chrt` utility to try to reduce the priority of the +jobs. If you would like to run for longer, the most straightforward way +is to edit `cycle.sh` before starting. To run the fuzz-tests in parallel, +you will need to implement a custom harness. + +To run a single fuzztest indefinitely, run + +```bash +cargo +nightly fuzz run "" +``` + +## Adding fuzz tests + +All fuzz tests can be found in the `fuzz_target/` directory. Adding a new +one is as simple as copying an existing one and editing the `do_test` +function to do what you want. + +If your test clearly belongs to a specific crate, please put it in that +crate's directory. Otherwise, you can put it directly in `fuzz_target/`. + +If you need to add dependencies, edit the file `generate-files.sh` to add +it to the generated `Cargo.toml`. + +Once you've added a fuzztest, regenerate the `Cargo.toml` and CI job by +running + +```bash +./generate-files.sh +``` + +Then to test your fuzztest, run + +```bash +./fuzz.sh +``` + +If it is working, you will see a rapid stream of data for many seconds +(you can hit Ctrl+C to stop it early) that looks something like this: +```text +INFO: Running with entropic power schedule (0xFF, 100). +INFO: Seed: 2953319389 +INFO: Loaded 1 modules (9121 inline 8-bit counters): 9121 [0x104132ea0, 0x104135241), +INFO: Loaded 1 PC tables (9121 PCs): 9121 [0x104135248,0x104158c58), +INFO: 0 files found in /some/path/to/rust-bitcoin/fuzz/corpus/units_arbitrary_weight +INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes +INFO: A corpus is not provided, starting from an empty corpus +#2 INITED cov: 42 ft: 42 corp: 1/1b exec/s: 0 rss: 36Mb +#411 NEW cov: 43 ft: 43 corp: 2/9b lim: 8 exec/s: 0 rss: 37Mb L: 8/8 MS: 4 ChangeBinInt-ShuffleBytes-ShuffleBytes-InsertRepeatedBytes- +#1329 NEW cov: 43 ft: 44 corp: 3/26b lim: 17 exec/s: 0 rss: 37Mb L: 17/17 MS: 3 InsertRepeatedBytes-CMP-CopyPart- DE: "\001\000\000\000"- +#1357 REDUCE cov: 43 ft: 44 corp: 3/25b lim: 17 exec/s: 0 rss: 37Mb L: 16/16 MS: 3 CopyPart-CMP-EraseBytes- DE: "\000\000\000\000\000\000\000\000"- +... +``` +If you don't see this, you should quickly see an error. + +## Reproducing Failures + +If a fuzztest fails, it will exit with a summary which looks something like +```text +... +thread '' (3001874) panicked at units/src/weight.rs:103:25: +attempt to multiply with overflow +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +==66478== ERROR: libFuzzer: deadly signal + #0 0x0001049fd3c4 in __sanitizer_print_stack_trace+0x28 (librustc-nightly_rt.asan.dylib:arm64+0x5d3c4) + #1 0x000104078b90 in fuzzer::PrintStackTrace()+0x30 (units_arbitrary_weight:arm64+0x100070b90) + #2 0x00010406d074 in fuzzer::Fuzzer::CrashCallback()+0x54 (units_arbitrary_weight:arm64+0x100065074) + #3 0x000180d26740 in _sigtramp+0x34 (libsystem_platform.dylib:arm64+0x3740) + ... +``` +This will tell you where the test failed and is followed by information about how to reproduce the crash. +It will look something like this: + +```text +... +NOTE: libFuzzer has rudimentary signal handlers. + Combine libFuzzer with AddressSanitizer or similar for better crash reports. +SUMMARY: libFuzzer: deadly signal +MS: 2 ChangeByte-CopyPart-; base unit: 25058c6b0d02cd1d71a030ad61c46b7396ddcdb9 +0x5e,0x5e,0x5e,0x5e,0x5e,0x44,0x0,0x0,0x0,0x0,0x0,0x5d,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x5e,0xa,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0xa5,0x1,0x1,0x1, +^^^^^D\000\000\000\000\000]\001\000\000\000\000\000\000^\012\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\245\001\001\001 +artifact_prefix='/some/path/to/rust-bitcoin/fuzz/artifacts/units_arbitrary_weight/'; Test unit written to /some/path/to/rust-bitcoin/fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822 +Base64: Xl5eXl5EAAAAAABdAQAAAAAAAF4KAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBpQEBAQ== +──────────────────────────────────────────────────────────────────────────────── + +Failing input: + + fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822 + +Output of `std::fmt::Debug`: + + [94, 94, 94, 94, 94, 68, 0, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 0, 0, 94, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 165, 1, 1, 1] + +Reproduce with: + + cargo fuzz run units_arbitrary_weight fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822 + +Minimize test case with: + + cargo fuzz tmin units_arbitrary_weight fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822 + +──────────────────────────────────────────────────────────────────────────────── +``` diff --git a/fuzz/cycle.sh b/fuzz/cycle.sh new file mode 100755 index 00000000..84c1cb6a --- /dev/null +++ b/fuzz/cycle.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Continuously cycle over fuzz targets running each for 1 hour. +# It uses chrt SCHED_IDLE so that other process takes priority. +# +# For cargo-fuzz usage see https://github.com/rust-fuzz/cargo-fuzz?tab=readme-ov-file#usage + +set -euo pipefail + +REPO_DIR=$(git rev-parse --show-toplevel) +# can't find the file because of the ENV var +# shellcheck source=/dev/null +source "$REPO_DIR/fuzz/fuzz-util.sh" + +while : +do + for targetFile in $(listTargetFiles); do + targetName=$(targetFileToName "$targetFile") + echo "Fuzzing target $targetName ($targetFile)" + + # fuzz for one hour + chrt -i 0 cargo +nightly fuzz run "$targetName" -- -max_total_time=3600 + cargo +nightly fuzz cmin "$targetName" + done +done + diff --git a/fuzz/fuzz.sh b/fuzz/fuzz.sh new file mode 100755 index 00000000..3a4aa602 --- /dev/null +++ b/fuzz/fuzz.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# This script is used to briefly fuzz every target when no target is provided. Otherwise, it will briefly fuzz the +# provided target + +set -euox pipefail + +REPO_DIR=$(git rev-parse --show-toplevel) + +# can't find the file because of the ENV var +# shellcheck source=/dev/null +source "$REPO_DIR/fuzz/fuzz-util.sh" + +target= +max_total_time=100 + +for arg in "$@"; do + case "$arg" in + -max_total_time=*) + max_total_time="${arg#-max_total_time=}" + ;; + -*) + echo "Unknown option: $arg" + exit 2 + ;; + *) + if [ -n "$target" ]; then + echo "Unexpected argument: $arg" + exit 2 + fi + target="$arg" + ;; + esac +done + +case "$max_total_time" in + ''|*[!0-9]*) + echo "-max_total_time must be a non-negative integer number of seconds" + exit 2 + ;; +esac + +# Check that input files are correct Windows file names +checkWindowsFiles + +if [ -z "$target" ]; then + targetFiles="$(listTargetFiles)" +else + targetFiles=fuzz_targets/"$target".rs +fi + +cargo --version +rustc --version + +# Testing +cargo install --force --locked --version 0.12.0 cargo-fuzz +for targetFile in $targetFiles; do + targetName=$(targetFileToName "$targetFile") + echo "Fuzzing target $targetName ($targetFile) for $max_total_time seconds" + # cargo-fuzz will check for the corpus at fuzz/corpus/ + cargo +nightly fuzz run "$targetName" -- -max_total_time="$max_total_time" + checkReport "$targetName" +done diff --git a/fuzz/fuzz_targets/decode_equiv_transaction.rs b/fuzz/fuzz_targets/decode_equiv_transaction.rs new file mode 100644 index 00000000..38281db5 --- /dev/null +++ b/fuzz/fuzz_targets/decode_equiv_transaction.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Fuzz test for equivalent decoding between the legacy and consensus-encoding decoding schemes. + +#![cfg_attr(fuzzing, no_main)] +#![cfg_attr(not(fuzzing), allow(unused))] +use libfuzzer_sys::fuzz_target; + +use elements::bitcoin::hex::DisplayHex as _; + +type Target = elements::Transaction; + +#[cfg(not(fuzzing))] +fn main() {} + +struct DisplayError<'e>(&'e dyn std::error::Error); + +impl core::fmt::Display for DisplayError<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.0)?; + let mut e = self.0; + while let Some(source) = e.source() { + writeln!(f, "caused by {}", source)?; + e = source; + } + Ok(()) + } +} + +fn do_test(data: &[u8]) { + let old: Result = elements::encode::deserialize(data); + let new: Result = elements::encoding::decode_from_slice(data); + + match (old, new) { + (Err(_), Err(_)) => {}, + (Err(e), Ok(new)) => { + let e = DisplayError(&e); + panic!("Decodable trait failed with {e}; Decode parsed {:?}", new); + } + (Ok(old), Err(e)) => { + let e = DisplayError(&e); + panic!("Decode trait failed with {e}; Decodable parsed {:?}", old); + } + (Ok(old), Ok(new)) => { + assert_eq!( + old, new, + "Decodable (left) did not match Decode (right)", + ); + + let reser_old = elements::encode::serialize(&old); + let reser_new = elements::encoding::encode_to_vec(&new); + assert_eq!( + reser_old, reser_new, + "Encodable (left) did not match Encode (right)\nOld hex: {}\nNew hex: {}\nTransaction: {:?}", + reser_old.as_hex(), + reser_new.as_hex(), + old, + ); + } + } +} + +fuzz_target!(|data: &[u8]| { + do_test(data); +}); + +#[cfg(test)] +mod tests { + #[test] + fn duplicate_crash() { + let v = elements::hex::decode_to_vec("abcd").unwrap(); + super::do_test(&v); + } +} diff --git a/fuzz/generate-files.sh b/fuzz/generate-files.sh index 9eb98e0e..dc19fb28 100755 --- a/fuzz/generate-files.sh +++ b/fuzz/generate-files.sh @@ -10,6 +10,10 @@ source "$REPO_DIR/fuzz/fuzz-util.sh" # 1. Generate fuzz/Cargo.toml cat > "$REPO_DIR/fuzz/Cargo.toml" < "$REPO_DIR/.github/workflows/cron-daily-fuzz.yml" < Address { let ws = script::Builder::new() .push_int(0) - .push_slice(&WScriptHash::hash(&script[..])[..]) + .push_slice(WScriptHash::hash_script(script).as_ref()) .into_script(); Address { params, - payload: Payload::ScriptHash(ScriptHash::hash(&ws[..])), + payload: Payload::ScriptHash(ScriptHash::hash_script(&ws)), blinding_pubkey: blinder, } } @@ -386,9 +386,9 @@ impl Address { ) -> Option
{ Some(Address { payload: if script.is_p2pkh() { - Payload::PubkeyHash(Hash::from_slice(&script.as_bytes()[3..23]).unwrap()) + Payload::PubkeyHash(PubkeyHash::from_byte_array(script.as_bytes()[3..23].try_into().unwrap())) } else if script.is_p2sh() { - Payload::ScriptHash(Hash::from_slice(&script.as_bytes()[2..22]).unwrap()) + Payload::ScriptHash(ScriptHash::from_byte_array(script.as_bytes()[2..22].try_into().unwrap())) } else if script.is_v0_p2wpkh() { Payload::WitnessProgram { version: Fe32::Q, @@ -418,12 +418,12 @@ impl Address { Payload::PubkeyHash(ref hash) => script::Builder::new() .push_opcode(opcodes::all::OP_DUP) .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&hash[..]) + .push_slice(hash.as_ref()) .push_opcode(opcodes::all::OP_EQUALVERIFY) .push_opcode(opcodes::all::OP_CHECKSIG), Payload::ScriptHash(ref hash) => script::Builder::new() .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&hash[..]) + .push_slice(hash.as_byte_array()) .push_opcode(opcodes::all::OP_EQUAL), Payload::WitnessProgram { version: witver, @@ -566,12 +566,12 @@ impl fmt::Display for Address { prefixed[0] = self.params.blinded_prefix; prefixed[1] = self.params.p2pkh_prefix; prefixed[2..35].copy_from_slice(&blinder.serialize()); - prefixed[35..].copy_from_slice(&hash[..]); + prefixed[35..].copy_from_slice(hash.as_ref()); base58::encode_check_to_fmt(fmt, &prefixed[..]) } else { let mut prefixed = [0; 21]; prefixed[0] = self.params.p2pkh_prefix; - prefixed[1..].copy_from_slice(&hash[..]); + prefixed[1..].copy_from_slice(hash.as_ref()); base58::encode_check_to_fmt(fmt, &prefixed[..]) } } @@ -581,12 +581,12 @@ impl fmt::Display for Address { prefixed[0] = self.params.blinded_prefix; prefixed[1] = self.params.p2sh_prefix; prefixed[2..35].copy_from_slice(&blinder.serialize()); - prefixed[35..].copy_from_slice(&hash[..]); + prefixed[35..].copy_from_slice(hash.as_byte_array()); base58::encode_check_to_fmt(fmt, &prefixed[..]) } else { let mut prefixed = [0; 21]; prefixed[0] = self.params.p2sh_prefix; - prefixed[1..].copy_from_slice(&hash[..]); + prefixed[1..].copy_from_slice(hash.as_byte_array()); base58::encode_check_to_fmt(fmt, &prefixed[..]) } } @@ -937,7 +937,7 @@ mod test { "93c7378d96518a75448821c4f7c8f4bae7ce60f804d03d1f0628dd5dd0f5de51", ) .unwrap(); - let tap_node_hash = TapNodeHash::all_zeros(); + let tap_node_hash = TapNodeHash::from_byte_array([0; 32]); let mut expected = IntoIterator::into_iter([ "2dszRCFv8Ub4ytKo1Q1vXXGgSx7mekNDwSJ", diff --git a/src/blech32/decode.rs b/src/blech32/decode.rs index 2dcea3e6..8a504bbb 100644 --- a/src/blech32/decode.rs +++ b/src/blech32/decode.rs @@ -257,7 +257,7 @@ impl<'s> CheckedHrpstring<'s> { let padding_len = fe_iter.len() * 5 % 8; if padding_len > 4 { - return Err(PaddingError::TooMuch)?; + return Err(PaddingError::TooMuch); } let last_fe = fe_iter.last().expect("checked above"); diff --git a/src/blind.rs b/src/blind.rs index 7954ddcc..65ae360e 100644 --- a/src/blind.rs +++ b/src/blind.rs @@ -15,7 +15,6 @@ //! # Transactions Blinding //! -use internals::array::ArrayExt as _; use internals::slice::SliceExt; use std::{self, collections::BTreeMap, fmt}; @@ -24,9 +23,9 @@ use secp256k1_zkp::{ rand::{CryptoRng, RngCore}, PedersenCommitment, SecretKey, Tag, Tweak, Verification, ZERO_TWEAK, }; -use secp256k1_zkp::{Generator, RangeProof, Secp256k1, Signing, SurjectionProof}; +use secp256k1_zkp::{Generator, Secp256k1, Signing}; -use crate::{AddressParams, Script, TxIn}; +use crate::{AddressParams, RangeProof, Script, TxIn, SurjectionProof}; use crate::{ confidential::{Asset, AssetBlindingFactor, Nonce, Value, ValueBlindingFactor}, @@ -201,26 +200,144 @@ impl From for ConfidentialTxOutError { ConfidentialTxOutError::Upstream(from) } } -/// The Rangeproof message -#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct RangeProofMessage { - /// The asset id - pub asset: AssetId, - /// The asset blinding factor - pub bf: AssetBlindingFactor, -} -impl RangeProofMessage { - /// Converts the message to bytes - pub fn to_bytes(&self) -> [u8; 64] { - let mut message = [0u8; 64]; +mod range_proof_message { + use core::fmt; + use internals::array::ArrayExt; + use secp256k1_zkp::{Generator, Signing, Secp256k1}; + use super::{Asset, AssetId, AssetBlindingFactor}; + + /// The Rangeproof message + #[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] + pub struct RangeProofMessage { + /// The asset id + asset_id: AssetId, + /// The asset blinding factor + asset_bf: AssetBlindingFactor, + } + + impl RangeProofMessage { + /// Constructs a [`RangeProofMessage`] from an asset ID and blinding factor. + pub fn new(asset_id: AssetId, asset_bf: AssetBlindingFactor) -> Self { + Self { asset_id, asset_bf } + } + + /// The asset ID embedded in the rangeproof message. + pub fn asset_id(&self) -> &AssetId { + &self.asset_id + } + + /// The asset blinding factor embedded in the rangeproof message. + pub fn blinding_factor(&self) -> &AssetBlindingFactor { + &self.asset_bf + } + + /// Computes the commmitment of the rangeproof message. + pub fn commitment(&self, secp: &Secp256k1) -> Generator { + Generator::new_blinded(secp, self.asset_id.into_tag(), self.asset_bf.into_inner()) + } + + /// Parses a message from bytes + pub fn from_byte_array( + secp: &Secp256k1, + inner: [u8; 64], + expected_asset: &Asset, + ) -> Result { + let (asset_id, asset_bf) = inner.split_array::<32, 32>(); + let ret = Self { + asset_id: AssetId::from_byte_array(*asset_id), + asset_bf: AssetBlindingFactor::from_byte_array(*asset_bf) + .map_err(RangeProofMessageError::BlindingFactorOutOfRange)?, + }; + + match expected_asset { + Asset::Null => return Err(RangeProofMessageError::NullExpectedAsset), + Asset::Explicit(asset_id) => { + if ret.asset_id != *asset_id { + return Err(RangeProofMessageError::ExplicitAssetMismatch { + in_txout: *asset_id, + in_message: ret.asset_id, + }) + } + if ret.asset_bf != AssetBlindingFactor::zero() { + return Err(RangeProofMessageError::ExplicitAssetNonzeroBf { + blinding_factor: ret.asset_bf, + }); + } + } + Asset::Confidential(commitment) => { + let ret_commitment = ret.commitment(secp); + if ret_commitment != *commitment { + return Err(RangeProofMessageError::ConfidentialAssetMismatch { + in_txout: *commitment, + in_message: ret_commitment, + }) + } + } + } + + Ok(ret) + } + + /// Converts the message to bytes + pub fn to_byte_array(&self) -> [u8; 64] { + let mut message = [0u8; 64]; + message[..32].copy_from_slice(self.asset_id.into_tag().as_ref()); + message[32..].copy_from_slice(self.asset_bf.into_inner().as_ref()); + message + } + } + + #[non_exhaustive] + #[derive(PartialEq, Eq, Clone, Debug)] + pub enum RangeProofMessageError { + NullExpectedAsset, + ExplicitAssetMismatch { + in_txout: AssetId, + in_message: AssetId, + }, + ExplicitAssetNonzeroBf { + blinding_factor: AssetBlindingFactor, + }, + ConfidentialAssetMismatch { + in_txout: Generator, + in_message: Generator, + }, + BlindingFactorOutOfRange(secp256k1_zkp::Error), + } - message[..32].copy_from_slice(self.asset.into_tag().as_ref()); - message[32..].copy_from_slice(self.bf.into_inner().as_ref()); + impl fmt::Display for RangeProofMessageError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::NullExpectedAsset => f.write_str("rangeproof associated with null asset"), + Self::ExplicitAssetMismatch { in_txout, in_message } => { + write!(f, "txout had explicit asset ID {in_txout}, but rangeproof encoded asset ID {in_message}") + } + Self::ExplicitAssetNonzeroBf { blinding_factor } => { + write!(f, "txout had explicit asset ID, but rangeproof encoded a nonzero asset blinding factor {blinding_factor}") + } + Self::ConfidentialAssetMismatch { in_txout, in_message } => { + write!(f, "txout had asset commitment {in_txout}, but rangeproof encoded asset commitment {in_message}") + } + Self::BlindingFactorOutOfRange(ref e) => e.fmt(f), + } + } + } - message + impl std::error::Error for RangeProofMessageError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::NullExpectedAsset => None, + Self::ExplicitAssetMismatch { .. } => None, + Self::ExplicitAssetNonzeroBf { .. } => None, + Self::ConfidentialAssetMismatch { .. } => None, + Self::BlindingFactorOutOfRange(ref e) => Some(e), + } + + } } } +pub use self::range_proof_message::{RangeProofMessage, RangeProofMessageError}; /// Information about Transaction Input Asset #[cfg_attr( @@ -380,13 +497,7 @@ impl Asset { }) .collect::, _>>()?; - let surjection_proof = SurjectionProof::new( - secp, - rng, - asset.into_tag(), - asset_bf.into_inner(), - inputs.as_ref(), - )?; + let surjection_proof = SurjectionProof::new(secp, rng, asset, asset_bf, inputs)?; Ok((out_asset, surjection_proof)) } @@ -432,8 +543,7 @@ impl Value { let value = self .explicit() .ok_or(ConfidentialTxOutError::ExpectedExplicitValue)?; - let out_asset_commitment = - Generator::new_blinded(secp, msg.asset.into_tag(), msg.bf.into_inner()); + let out_asset_commitment = msg.commitment(secp); let value_commitment = Value::new_confidential(secp, value, out_asset_commitment, vbf); let rangeproof = RangeProof::new( @@ -442,7 +552,7 @@ impl Value { value_commitment.commitment().expect("confidential value"), value, vbf.into_inner(), - &msg.to_bytes(), + &msg.to_byte_array(), spk.as_bytes(), shared_secret, TxOut::RANGEPROOF_EXP_SHIFT, @@ -536,12 +646,12 @@ impl TxOut { let (out_asset, surjection_proof) = exp_asset.blind(rng, secp, out_secrets.asset_bf, spent_utxo_secrets)?; - let msg = RangeProofMessage { - asset: out_secrets.asset, - bf: out_secrets.asset_bf, - }; + let msg = RangeProofMessage::new( + out_secrets.asset, + out_secrets.asset_bf, + ); let exp_value = Value::Explicit(out_secrets.value); - let (out_value, nonce, range_proof) = exp_value.blind( + let (out_value, nonce, rangeproof) = exp_value.blind( secp, out_secrets.value_bf, receiver_blinding_pk, @@ -556,8 +666,8 @@ impl TxOut { nonce, script_pubkey: spk, witness: TxOutWitness { - surjection_proof: Some(Box::new(surjection_proof)), - rangeproof: Some(Box::new(range_proof)), + surjection_proof, + rangeproof, }, }; Ok(txout) @@ -735,7 +845,7 @@ impl TxOut { /// Unblinds a transaction output, if it is confidential. /// /// It returns the secret elements of the value and asset Pedersen commitments. - pub fn unblind( + pub fn unblind( &self, secp: &Secp256k1, blinding_key: SecretKey, @@ -760,34 +870,22 @@ impl TxOut { shared_secret, self.script_pubkey.as_bytes(), additional_generator, - )?; + ).map_err(UnblindError::Rewind)?; - // Use `MissingRangeproof` error because it's available so does not require - // API breaks. In a later PR we should extend that enum and add #[non_exhaustive] - // to it. The maybe-better `MalformedAssetId` error requires we start with a - // std `FromSliceError` which we don't have. + let value = opening.value; + let value_bf = ValueBlindingFactor(opening.blinding_factor); let asset_and_bf = SliceExt::split_first_chunk::<64>(opening.message.as_ref()) .ok_or(UnblindError::MissingRangeproof)? .0; - let (asset_id, asset_bf) = asset_and_bf.split_array(); - - let asset_id = AssetId::from_byte_array(*asset_id); - let asset_bf = AssetBlindingFactor::from_byte_array(*asset_bf)?; - if let Asset::Confidential(own_asset) = self.asset { - let secp = Secp256k1::signing_only(); // needed to avoid API break - let asset = Generator::new_blinded(&secp, asset_id.into_tag(), asset_bf.into_inner()); - if asset != own_asset { - // See above about use of MissingRangeproof. - return Err(UnblindError::MissingRangeproof); - } - } - - let value = opening.value; - let value_bf = ValueBlindingFactor(opening.blinding_factor); + let message = RangeProofMessage::from_byte_array( + secp, + *asset_and_bf, + &self.asset, + ).map_err(UnblindError::RangeProofMessage)?; Ok(TxOutSecrets { - asset: asset_id, - asset_bf, + asset: *message.asset_id(), + asset_bf: *message.blinding_factor(), value, value_bf, }) @@ -796,6 +894,7 @@ impl TxOut { /// Errors encountered when unblinding `TxOut`s. #[derive(Debug)] +#[non_exhaustive] pub enum UnblindError { /// The `TxOut` is not fully confidential. NotConfidential, @@ -803,18 +902,18 @@ pub enum UnblindError { MissingNonce, /// Transaction output does not have a rangeproof. MissingRangeproof, - /// Malformed asset ID. - MalformedAssetId(core::array::TryFromSliceError), + /// Malformed rangeproof message. + RangeProofMessage(RangeProofMessageError), /// Error originated in `secp256k1_zkp`. - Upstream(secp256k1_zkp::Error), + Rewind(secp256k1_zkp::Error), } impl fmt::Display for UnblindError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { UnblindError::MissingNonce => write!(f, "missing nonce in txout"), - UnblindError::MalformedAssetId(_) => write!(f, "malformed asset id"), - UnblindError::Upstream(e) => write!(f, "{}", e), + UnblindError::RangeProofMessage(_) => f.write_str("failed to parse message embedded in rangeproof"), + UnblindError::Rewind(_) => f.write_str("failed to rewind rangeproof"), UnblindError::NotConfidential => write!(f, "cannot unblind non-confidential txout"), UnblindError::MissingRangeproof => write!(f, "missing rangeproof in txout"), } @@ -825,20 +924,14 @@ impl std::error::Error for UnblindError { fn cause(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { UnblindError::MissingNonce => None, - UnblindError::MalformedAssetId(e) => Some(e), - UnblindError::Upstream(e) => Some(e), + UnblindError::RangeProofMessage(e) => Some(e), + UnblindError::Rewind(e) => Some(e), UnblindError::NotConfidential => None, UnblindError::MissingRangeproof => None, } } } -impl From for UnblindError { - fn from(from: secp256k1_zkp::Error) -> Self { - UnblindError::Upstream(from) - } -} - impl TxIn { /// Blind issuances for this [`TxIn`]. Asset amount and token amount must be /// set in [`AssetIssuance`](crate::AssetIssuance) field for this input @@ -871,17 +964,17 @@ impl TxIn { Value::Explicit(v) => Value::Explicit(v), }; let spk = Script::new(); - let msg = RangeProofMessage { + let msg = RangeProofMessage::new( asset, - bf: AssetBlindingFactor::zero(), - }; + AssetBlindingFactor::zero(), + ); let (comm, prf) = v.blind_with_shared_secret(secp, bf, blind_sk, &spk, &msg)?; if i == 0 { self.asset_issuance.amount = comm; - self.witness.amount_rangeproof = Some(Box::new(prf)); + self.witness.amount_rangeproof = prf; } else { self.asset_issuance.inflation_keys = comm; - self.witness.inflation_keys_rangeproof = Some(Box::new(prf)); + self.witness.inflation_keys_rangeproof = prf; } } Ok(()) @@ -1266,122 +1359,6 @@ impl From for BlindError { } } -/// A trait to create and verify explicit rangeproofs -pub trait BlindValueProofs: Sized { - /// Outputs a `[RangeProof]` that blinded value - /// corresponfs to unblinded explicit value - fn blind_value_proof( - rng: &mut R, - secp: &Secp256k1, - explicit_val: u64, - value_commit: PedersenCommitment, - asset_gen: Generator, - vbf: ValueBlindingFactor, - ) -> Result; - - /// Verify that the Rangeproof proves that commitment - /// is actually bound to the explicit value - fn blind_value_proof_verify( - &self, - secp: &Secp256k1, - explicit_val: u64, - asset_gen: Generator, - value_commit: PedersenCommitment, - ) -> bool; -} - -impl BlindValueProofs for RangeProof { - /// Outputs a [`RangeProof`] that blinded `value_commit` - /// corresponds to explicit value - fn blind_value_proof( - rng: &mut R, - secp: &Secp256k1, - explicit_val: u64, - value_commit: PedersenCommitment, - asset_gen: Generator, - vbf: ValueBlindingFactor, - ) -> Result { - RangeProof::new( - secp, - explicit_val, // min_value - value_commit, // value_commit - explicit_val, // value - vbf.into_inner(), // blinding factor - &[], // message - &[], // add commitment - SecretKey::new(rng), // nonce - -1, // exp - 0, // min bits - asset_gen, // additional gen - ) - } - - /// Verify that the Rangeproof proves that commitment - /// is actually bound to the explicit value - fn blind_value_proof_verify( - &self, - secp: &Secp256k1, - explicit_val: u64, - asset_gen: Generator, - value_commit: PedersenCommitment, - ) -> bool { - let r = self.verify(secp, value_commit, &[], asset_gen); - match r { - Ok(e) => e.start == explicit_val && e.end - 1 == explicit_val, - Err(..) => false, - } - } -} - -/// A trait to create and verify explicit surjection proofs -pub trait BlindAssetProofs: Sized { - /// Outputs a `[SurjectionProof]` that blinded asset - /// corresponfs to unblinded explicit asset - fn blind_asset_proof( - rng: &mut R, - secp: &Secp256k1, - asset: AssetId, - abf: AssetBlindingFactor, - ) -> Result; - - /// Verify that the Surjection proves that asset commitment - /// is actually bound to the explicit asset - fn blind_asset_proof_verify( - &self, - secp: &Secp256k1, - asset: AssetId, - asset_commit: Generator, - ) -> bool; -} - -impl BlindAssetProofs for SurjectionProof { - fn blind_asset_proof( - rng: &mut R, - secp: &Secp256k1, - asset: AssetId, - abf: AssetBlindingFactor, - ) -> Result { - let gen = Generator::new_unblinded(secp, asset.into_tag()); - SurjectionProof::new( - secp, - rng, - asset.into_tag(), - abf.into_inner(), - &[(gen, asset.into_tag(), ZERO_TWEAK)], - ) - } - - fn blind_asset_proof_verify( - &self, - secp: &Secp256k1, - asset: AssetId, - asset_commit: Generator, - ) -> bool { - let gen = Generator::new_unblinded(secp, asset.into_tag()); - self.verify(secp, asset_commit, &[gen]) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/block.rs b/src/block.rs index 0f9b1ba5..c2153b33 100644 --- a/src/block.rs +++ b/src/block.rs @@ -21,7 +21,7 @@ use std::io; #[cfg(feature = "serde")] use std::fmt; use crate::dynafed; -use crate::hashes::Hash; +use crate::hashes::{HashEngine as _, sha256d}; use crate::Transaction; use crate::encode::{self, serialize, Decodable, Encodable, VarInt}; use crate::{BlockHash, Script, TxMerkleNode}; @@ -235,7 +235,7 @@ impl BlockHeader { }; // Everything except the signblock witness goes into the hash - let mut enc = BlockHash::engine(); + let mut enc = sha256d::Hash::engine(); version.consensus_encode(&mut enc).unwrap(); self.prev_blockhash.consensus_encode(&mut enc).unwrap(); self.merkle_root.consensus_encode(&mut enc).unwrap(); @@ -250,7 +250,7 @@ impl BlockHeader { proposed.consensus_encode(&mut enc).unwrap(); }, } - BlockHash::from_engine(enc) + BlockHash(enc.finalize()) } /// Returns true if this is a block with dynamic federations enabled. @@ -360,7 +360,6 @@ pub struct Block { /// Complete list of transaction in the block pub txdata: Vec, } -serde_struct_impl!(Block, header, txdata); impl_consensus_encoding!(Block, header, txdata); impl Block { diff --git a/src/confidential.rs b/src/confidential.rs deleted file mode 100644 index ad3066b0..00000000 --- a/src/confidential.rs +++ /dev/null @@ -1,1458 +0,0 @@ -// Rust Elements Library -// Written in 2018 by -// Andrew Poelstra -// -// To the extent possible under law, the author(s) have dedicated all -// copyright and related and neighboring rights to this software to -// the public domain worldwide. This software is distributed without -// any warranty. -// -// You should have received a copy of the CC0 Public Domain Dedication -// along with this software. -// If not, see . -// - -//! # Confidential Commitments -//! -//! Structures representing Pedersen commitments of various types -//! - -use crate::hashes::{sha256d, Hash}; -use secp256k1_zkp::{self, CommitmentSecrets, Generator, PedersenCommitment, - PublicKey, Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK, - compute_adaptive_blinding_factor, - rand::{CryptoRng, Rng, RngCore} -}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use std::{fmt, io, ops::{AddAssign, Neg}, str}; - -use crate::encode::{self, Decodable, Encodable}; -use crate::issuance::AssetId; - -/// A CT commitment to an amount -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum Value { - /// No value - #[default] - Null, - /// Value is explicitly encoded - Explicit(u64), - /// Value is committed - Confidential(PedersenCommitment), -} - -impl Value { - /// Create value commitment. - pub fn new_confidential( - secp: &Secp256k1, - value: u64, - asset: Generator, - bf: ValueBlindingFactor, - ) -> Self { - Value::Confidential(PedersenCommitment::new(secp, value, bf.0, asset)) - } - - /// Create value commitment from assetID, asset blinding factor, - /// value and value blinding factor - pub fn new_confidential_from_assetid( - secp: &Secp256k1, - value: u64, - asset: AssetId, - v_bf: ValueBlindingFactor, - a_bf: AssetBlindingFactor, - ) -> Self { - let generator = Generator::new_blinded(secp, asset.into_tag(), a_bf.0); - let comm = PedersenCommitment::new(secp, value, v_bf.0, generator); - - Value::Confidential(comm) - } - - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { - match *self { - Value::Null => 1, - Value::Explicit(..) => 9, - Value::Confidential(..) => 33, - } - } - - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Value::Confidential(PedersenCommitment::from_slice(bytes)?)) - } - - /// Check if the object is null. - pub fn is_null(&self) -> bool { - matches!(*self, Value::Null) - } - - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - matches!(*self, Value::Explicit(_)) - } - - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - matches!(*self, Value::Confidential(_)) - } - - /// Returns the explicit inner value. - /// Returns [None] if [`Value::is_explicit`] returns false. - pub fn explicit(&self) -> Option { - match *self { - Value::Explicit(i) => Some(i), - _ => None, - } - } - - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [`Value::is_confidential`] returns false. - pub fn commitment(&self) -> Option { - match *self { - Value::Confidential(i) => Some(i), - _ => None, - } - } -} - -impl From for Value { - fn from(from: PedersenCommitment) -> Self { - Value::Confidential(from) - } -} - -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Value::Null => f.write_str("null"), - Value::Explicit(n) => write!(f, "{}", n), - Value::Confidential(commitment) => write!(f, "{:02x}", commitment), - } - } -} - -impl Encodable for Value { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - Value::Null => 0u8.consensus_encode(s), - Value::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + u64::swap_bytes(n).consensus_encode(&mut s)?) - } - Value::Confidential(commitment) => commitment.consensus_encode(&mut s), - } - } -} - -impl Encodable for PedersenCommitment { - fn consensus_encode(&self, mut e: W) -> Result { - e.write_all(&self.serialize())?; - Ok(33) - } -} - -impl Decodable for Value { - fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; - - match prefix { - 0 => Ok(Value::Null), - 1 => { - let explicit = u64::swap_bytes(Decodable::consensus_decode(&mut d)?); - Ok(Value::Explicit(explicit)) - } - p if p == 0x08 || p == 0x09 => { - let mut comm = [0u8; 33]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Value::Confidential(PedersenCommitment::from_slice(&comm)?)) - } - p => Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } -} - -impl Decodable for PedersenCommitment { - fn consensus_decode(d: D) -> Result { - let bytes = <[u8; 33]>::consensus_decode(d)?; - Ok(PedersenCommitment::from_slice(&bytes)?) - } -} - -#[cfg(feature = "serde")] -impl Serialize for Value { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = match *self { - Value::Null => 1, - Value::Explicit(_) | Value::Confidential(_) => 2 - }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - Value::Null => seq.serialize_element(&0u8)?, - Value::Explicit(n) => { - seq.serialize_element(&1u8)?; - seq.serialize_element(&u64::swap_bytes(n))?; - } - Value::Confidential(commitment) => { - seq.serialize_element(&2u8)?; - seq.serialize_element(&commitment)?; - } - } - seq.end() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Value { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, SeqAccess, Visitor}; - struct CommitVisitor; - - impl<'de> Visitor<'de> for CommitVisitor { - type Value = Value; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix = access.next_element::()?; - match prefix { - Some(0) => Ok(Value::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Value::Explicit(u64::swap_bytes(x))), - None => Err(A::Error::custom("missing explicit value")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Value::Confidential(x)), - None => Err(A::Error::custom("missing pedersen commitment")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")), - } - } - } - - d.deserialize_seq(CommitVisitor) - } -} - -/// A CT commitment to an asset -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum Asset { - /// No value - #[default] - Null, - /// Asset entropy is explicitly encoded - Explicit(AssetId), - /// Asset is committed - Confidential(Generator), -} - -impl Asset { - /// Create asset commitment. - pub fn new_confidential( - secp: &Secp256k1, - asset: AssetId, - bf: AssetBlindingFactor, - ) -> Self { - Asset::Confidential(Generator::new_blinded( - secp, - asset.into_tag(), - bf.into_inner(), - )) - } - - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { - match *self { - Asset::Null => 1, - Asset::Explicit(..) => 33, - Asset::Confidential(..) => 33, - } - } - - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Asset::Confidential(Generator::from_slice(bytes)?)) - } - - /// Check if the object is null. - pub fn is_null(&self) -> bool { - matches!(*self, Asset::Null) - } - - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - matches!(*self, Asset::Explicit(_)) - } - - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - matches!(*self, Asset::Confidential(_)) - } - - /// Returns the explicit inner value. - /// Returns [None] if [`Asset::is_explicit`] returns false. - pub fn explicit(&self) -> Option { - match *self { - Asset::Explicit(i) => Some(i), - _ => None, - } - } - - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [`Asset::is_confidential`] returns false. - pub fn commitment(&self) -> Option { - match *self { - Asset::Confidential(i) => Some(i), - _ => None, - } - } - - /// Internally used function for getting the generator from asset - /// Used in the amount verification check - /// Returns [`None`] is the asset is [`Asset::Null`] - /// Converts a explicit asset into a generator and returns the confidential - /// generator as is. - pub fn into_asset_gen ( - self, - secp: &Secp256k1, - ) -> Option { - match self { - // Only error is Null error which is dealt with later - // when we have more context information about it. - Asset::Null => None, - Asset::Explicit(x) => { - Some(Generator::new_unblinded(secp, x.into_tag())) - } - Asset::Confidential(gen) => Some(gen), - } - } -} - -impl From for Asset { - fn from(from: Generator) -> Self { - Asset::Confidential(from) - } -} - -impl fmt::Display for Asset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Asset::Null => f.write_str("null"), - Asset::Explicit(n) => write!(f, "{}", n), - Asset::Confidential(generator) => write!(f, "{:02x}", generator), - } - } -} - -impl Encodable for Asset { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - Asset::Null => 0u8.consensus_encode(s), - Asset::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + n.consensus_encode(&mut s)?) - } - Asset::Confidential(generator) => generator.consensus_encode(&mut s) - } - } -} - -impl Encodable for Generator { - fn consensus_encode(&self, mut e: W) -> Result { - e.write_all(&self.serialize())?; - Ok(33) - } -} - -impl Decodable for Asset { - fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; - - match prefix { - 0 => Ok(Asset::Null), - 1 => { - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Asset::Explicit(explicit)) - } - p if p == 0x0a || p == 0x0b => { - let mut comm = [0u8; 33]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Asset::Confidential(Generator::from_slice(&comm[..])?)) - } - p => Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } -} - -impl Decodable for Generator { - fn consensus_decode(d: D) -> Result { - let bytes = <[u8; 33]>::consensus_decode(d)?; - Ok(Generator::from_slice(&bytes)?) - } -} - - -#[cfg(feature = "serde")] -impl Serialize for Asset { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = match *self { - Asset::Null => 1, - Asset::Explicit(_) | Asset::Confidential(_) => 2 - }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - Asset::Null => seq.serialize_element(&0u8)?, - Asset::Explicit(n) => { - seq.serialize_element(&1u8)?; - seq.serialize_element(&n)?; - } - Asset::Confidential(commitment) => { - seq.serialize_element(&2u8)?; - seq.serialize_element(&commitment)?; - } - } - seq.end() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Asset { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, SeqAccess, Visitor}; - struct CommitVisitor; - - impl<'de> Visitor<'de> for CommitVisitor { - type Value = Asset; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix = access.next_element::()?; - match prefix { - Some(0) => Ok(Asset::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Asset::Explicit(x)), - None => Err(A::Error::custom("missing explicit asset")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Asset::Confidential(x)), - None => Err(A::Error::custom("missing generator")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")), - } - } - } - - d.deserialize_seq(CommitVisitor) - } -} - -/// A CT commitment to an output nonce (i.e. a public key) -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum Nonce { - /// No value - #[default] - Null, - /// There should be no such thing as an "explicit nonce", but Elements will deserialize - /// such a thing (and insists that its size be 32 bytes). So we stick a 32-byte type here - /// that implements all the traits we need. - Explicit([u8; 32]), - /// Nonce is committed - Confidential(PublicKey), -} - -impl Nonce { - /// Create nonce commitment. - pub fn new_confidential( - rng: &mut R, - secp: &Secp256k1, - receiver_blinding_pk: &PublicKey, - ) -> (Self, SecretKey) { - let ephemeral_sk = SecretKey::new(rng); - Self::with_ephemeral_sk(secp, ephemeral_sk, receiver_blinding_pk) - } - - /// Similar to [`Nonce::new_confidential`], but with a given `ephemeral_sk` - /// instead of sampling it from rng. - pub fn with_ephemeral_sk( - secp: &Secp256k1, - ephemeral_sk: SecretKey, - receiver_blinding_pk: &PublicKey - ) -> (Self, SecretKey) { - let sender_pk = PublicKey::from_secret_key(secp, &ephemeral_sk); - let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &ephemeral_sk); - (Nonce::Confidential(sender_pk), shared_secret) - } - - /// Calculate the shared secret. - pub fn shared_secret(&self, receiver_blinding_sk: &SecretKey) -> Option { - match self { - Nonce::Confidential(sender_pk) => { - Some(Self::make_shared_secret(sender_pk, receiver_blinding_sk)) - } - _ => None, - } - } - - /// Create the shared secret. - fn make_shared_secret(pk: &PublicKey, sk: &SecretKey) -> SecretKey { - let xy = secp256k1_zkp::ecdh::shared_secret_point(pk, sk); - let shared_secret = { - // Yes, what follows is the compressed representation of a Bitcoin public key. - // However, this is more by accident then by design, see here: https://github.com/rust-bitcoin/rust-secp256k1/pull/255#issuecomment-744146282 - - let mut dh_secret = [0u8; 33]; - dh_secret[0] = if xy.last().unwrap() % 2 == 0 { - 0x02 - } else { - 0x03 - }; - dh_secret[1..].copy_from_slice(&xy[0..32]); - - sha256d::Hash::hash(&dh_secret).to_byte_array() - }; - - SecretKey::from_slice(&shared_secret[..32]).expect("always has exactly 32 bytes") - } - - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { - match *self { - Nonce::Null => 1, - Nonce::Explicit(..) => 33, - Nonce::Confidential(..) => 33, - } - } - - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Nonce::Confidential( - PublicKey::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, - )) - } - - /// Check if the object is null. - pub fn is_null(&self) -> bool { - matches!(*self, Nonce::Null) - } - - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - matches!(*self, Nonce::Explicit(_)) - } - - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - matches!(*self, Nonce::Confidential(_)) - } - - /// Returns the explicit inner value. - /// Returns [None] if [`Nonce::is_explicit`] returns false. - pub fn explicit(&self) -> Option<[u8; 32]> { - match *self { - Nonce::Explicit(i) => Some(i), - _ => None, - } - } - - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [`Nonce::is_confidential`] returns false. - pub fn commitment(&self) -> Option { - match *self { - Nonce::Confidential(i) => Some(i), - _ => None, - } - } -} - -impl From for Nonce { - fn from(from: PublicKey) -> Self { - Nonce::Confidential(from) - } -} - -impl fmt::Display for Nonce { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Nonce::Null => f.write_str("null"), - Nonce::Explicit(n) => { - for b in &n { - write!(f, "{:02x}", b)?; - } - Ok(()) - } - Nonce::Confidential(pk) => write!(f, "{:02x}", pk), - } - } -} - -impl Encodable for Nonce { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - Nonce::Null => 0u8.consensus_encode(s), - Nonce::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + n.consensus_encode(&mut s)?) - } - Nonce::Confidential(commitment) => commitment.consensus_encode(&mut s), - } - } -} - -impl Encodable for PublicKey { - fn consensus_encode(&self, mut e: W) -> Result { - e.write_all(&self.serialize())?; - Ok(33) - } -} - -impl Decodable for Nonce { - fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; - - match prefix { - 0 => Ok(Nonce::Null), - 1 => { - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Nonce::Explicit(explicit)) - } - p if p == 0x02 || p == 0x03 => { - let mut comm = [0u8; 33]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Nonce::Confidential(PublicKey::from_slice(&comm)?)) - } - p => Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } -} - -impl Decodable for PublicKey { - fn consensus_decode(d: D) -> Result { - let bytes = <[u8; 33]>::consensus_decode(d)?; - Ok(PublicKey::from_slice(&bytes)?) - } -} - -#[cfg(feature = "serde")] -impl Serialize for Nonce { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = match *self { - Nonce::Null => 1, - Nonce::Explicit(_) | Nonce::Confidential(_) => 2 - }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - Nonce::Null => seq.serialize_element(&0u8)?, - Nonce::Explicit(n) => { - seq.serialize_element(&1u8)?; - seq.serialize_element(&n)?; - } - Nonce::Confidential(commitment) => { - seq.serialize_element(&2u8)?; - seq.serialize_element(&commitment)?; - } - } - seq.end() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Nonce { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, SeqAccess, Visitor}; - struct CommitVisitor; - - impl<'de> Visitor<'de> for CommitVisitor { - type Value = Nonce; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix = access.next_element::()?; - match prefix { - Some(0) => Ok(Nonce::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Nonce::Explicit(x)), - None => Err(A::Error::custom("missing explicit nonce")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Nonce::Confidential(x)), - None => Err(A::Error::custom("missing nonce")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")) - } - } - } - - d.deserialize_seq(CommitVisitor) - } -} - -/// Error decoding hexadecimal string into tweak-like value. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TweakHexDecodeError { - /// Invalid hexadecimal string. - InvalidHex(hex::DecodeFixedLengthBytesError), - /// Invalid tweak after decoding hexadecimal string. - InvalidTweak(secp256k1_zkp::Error), -} - -impl fmt::Display for TweakHexDecodeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - TweakHexDecodeError::InvalidHex(err) => { - write!(f, "Invalid hex: {}", err) - } - TweakHexDecodeError::InvalidTweak(err) => { - write!(f, "Invalid tweak: {}", err) - } - } - } -} - -#[doc(hidden)] -impl From for TweakHexDecodeError { - fn from(err: hex::DecodeFixedLengthBytesError) -> Self { - TweakHexDecodeError::InvalidHex(err) - } -} - -#[doc(hidden)] -impl From for TweakHexDecodeError { - fn from(err: secp256k1_zkp::Error) -> Self { - TweakHexDecodeError::InvalidTweak(err) - } -} - -impl From for encode::Error { - fn from(value: TweakHexDecodeError) -> Self { - match value { - TweakHexDecodeError::InvalidHex(err) => encode::Error::HexFixedError(err), - TweakHexDecodeError::InvalidTweak(err) => encode::Error::Secp256k1zkp(err), - } - } -} - -impl std::error::Error for TweakHexDecodeError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - TweakHexDecodeError::InvalidHex(err) => Some(err), - TweakHexDecodeError::InvalidTweak(err) => Some(err), - } - } -} - -/// Blinding factor used for asset commitments. -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct AssetBlindingFactor(pub(crate) Tweak); - -impl AssetBlindingFactor { - /// Generate random asset blinding factor. - pub fn new(rng: &mut R) -> Self { - AssetBlindingFactor(Tweak::new(rng)) - } - - /// Parse a blinding factor from a 64-character hex string. - #[deprecated(since = "0.27.0", note = "use s.parse() instead")] - pub fn from_hex(s: &str) -> Result { - s.parse() - } - - /// Create from bytes. - pub fn from_byte_array(bytes: [u8; 32]) -> Result { - Ok(AssetBlindingFactor(Tweak::from_inner(bytes)?)) - } - - /// Create from bytes. - pub fn from_slice(bytes: &[u8]) -> Result { - Ok(AssetBlindingFactor(Tweak::from_slice(bytes)?)) - } - - /// Returns the inner value. - pub fn into_inner(self) -> Tweak { - self.0 - } - - /// Get a unblinded/zero `AssetBlinding` factor - pub fn zero() -> Self { - AssetBlindingFactor(ZERO_TWEAK) - } -} - -impl core::borrow::Borrow<[u8]> for AssetBlindingFactor { - fn borrow(&self) -> &[u8] { &self.0[..] } -} - -hex::impl_fmt_traits! { - #[display_backward(true)] - impl fmt_traits for AssetBlindingFactor { - const LENGTH: usize = 32; - } -} - -impl str::FromStr for AssetBlindingFactor { - type Err = encode::Error; - - fn from_str(s: &str) -> Result { - let mut slice: [u8; 32] = hex::decode_to_array(s)?; - slice.reverse(); - - let inner = Tweak::from_inner(slice)?; - Ok(AssetBlindingFactor(inner)) - } -} - -#[cfg(feature = "serde")] -impl Serialize for AssetBlindingFactor { - fn serialize(&self, s: S) -> Result { - if s.is_human_readable() { - s.collect_str(&self) - } else { - s.serialize_bytes(&self.0[..]) - } - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for AssetBlindingFactor { - fn deserialize>(d: D) -> Result { - if d.is_human_readable() { - struct HexVisitor; - - impl ::serde::de::Visitor<'_> for HexVisitor { - type Value = AssetBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("an ASCII hex string") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if let Ok(hex) = ::std::str::from_utf8(v) { - hex.parse().map_err(E::custom) - } else { - Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) - } - } - - fn visit_str(self, v: &str) -> Result - where - E: ::serde::de::Error, - { - v.parse().map_err(E::custom) - } - } - - d.deserialize_str(HexVisitor) - } else { - struct BytesVisitor; - - impl ::serde::de::Visitor<'_> for BytesVisitor { - type Value = AssetBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("a bytestring") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - use core::convert::TryFrom; - - match <[u8; 32]>::try_from(v) { - Ok(ret) => { - let inner = Tweak::from_inner(ret).map_err(E::custom)?; - Ok(AssetBlindingFactor(inner)) - } - Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), - } - } - } - - d.deserialize_bytes(BytesVisitor) - } - } -} - -/// Blinding factor used for value commitments. -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct ValueBlindingFactor(pub(crate) Tweak); - -impl ValueBlindingFactor { - /// Generate random value blinding factor. - pub fn new(rng: &mut R) -> Self { - ValueBlindingFactor(Tweak::new(rng)) - } - - /// Parse a blinding factor from a 64-character hex string. - #[deprecated(since = "0.27.0", note = "use s.parse() instead")] - pub fn from_hex(s: &str) -> Result { - s.parse() - } - - /// Create the value blinding factor of the last output of a transaction. - pub fn last( - secp: &Secp256k1, - value: u64, - abf: AssetBlindingFactor, - inputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], - outputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], - ) -> Self { - let set_a = inputs - .iter() - .map(|(value, abf, vbf)| CommitmentSecrets { - value: *value, - value_blinding_factor: vbf.0, - generator_blinding_factor: abf.into_inner(), - }) - .collect::>(); - let set_b = outputs - .iter() - .map(|(value, abf, vbf)| CommitmentSecrets { - value: *value, - value_blinding_factor: vbf.0, - generator_blinding_factor: abf.into_inner(), - }) - .collect::>(); - - ValueBlindingFactor(compute_adaptive_blinding_factor( - secp, value, abf.0, &set_a, &set_b, - )) - } - - /// Create from bytes. - pub fn from_slice(bytes: &[u8]) -> Result { - Ok(ValueBlindingFactor(Tweak::from_slice(bytes)?)) - } - - /// Returns the inner value. - pub fn into_inner(self) -> Tweak { - self.0 - } - - /// Get a unblinded/zero `AssetBlinding` factor - pub fn zero() -> Self { - ValueBlindingFactor(ZERO_TWEAK) - } -} - -impl AddAssign for ValueBlindingFactor { - fn add_assign(&mut self, other: Self) { - if self.0.as_ref() == &[0u8; 32] { - *self = other; - } else if other.0.as_ref() == &[0u8; 32] { - // nothing to do - } else { - // Since libsecp does not expose low level APIs - // for scalar arethematic, we need to abuse secret key - // operations for this - let sk2 = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key"); - let sk = SecretKey::from_slice(other.into_inner().as_ref()).expect("Valid key"); - // The only reason that secret key addition can fail - // is when the keys add up to zero since we have already checked - // keys are in valid secret keys - match sk.add_tweak(&sk2.into()) { - Ok(sk_tweaked) => *self = ValueBlindingFactor::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), - Err(_) => *self = Self::zero(), - } - } - } -} - -impl Neg for ValueBlindingFactor { - type Output = Self; - - fn neg(self) -> Self::Output { - if self.0.as_ref() == &[0u8; 32] { - self - } else { - let sk = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key").negate(); - ValueBlindingFactor::from_slice(sk.as_ref()).expect("Valid Tweak") - } - } -} - -impl core::borrow::Borrow<[u8]> for ValueBlindingFactor { - fn borrow(&self) -> &[u8] { &self.0[..] } -} - -hex::impl_fmt_traits! { - #[display_backward(true)] - impl fmt_traits for ValueBlindingFactor { - const LENGTH: usize = 32; - } -} - -impl str::FromStr for ValueBlindingFactor { - type Err = encode::Error; - - fn from_str(s: &str) -> Result { - let mut slice: [u8; 32] = hex::decode_to_array(s)?; - slice.reverse(); - - let inner = Tweak::from_inner(slice)?; - Ok(ValueBlindingFactor(inner)) - } -} - -#[cfg(feature = "serde")] -impl Serialize for ValueBlindingFactor { - fn serialize(&self, s: S) -> Result { - if s.is_human_readable() { - s.collect_str(&self) - } else { - s.serialize_bytes(&self.0[..]) - } - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for ValueBlindingFactor { - fn deserialize>(d: D) -> Result { - if d.is_human_readable() { - struct HexVisitor; - - impl ::serde::de::Visitor<'_> for HexVisitor { - type Value = ValueBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("an ASCII hex string") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if let Ok(hex) = ::std::str::from_utf8(v) { - hex.parse().map_err(E::custom) - } else { - Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) - } - } - - fn visit_str(self, v: &str) -> Result - where - E: ::serde::de::Error, - { - v.parse().map_err(E::custom) - } - } - - d.deserialize_str(HexVisitor) - } else { - struct BytesVisitor; - - impl ::serde::de::Visitor<'_> for BytesVisitor { - type Value = ValueBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("a bytestring") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - use core::convert::TryFrom; - - match <[u8; 32]>::try_from(v) { - Ok(ret) => { - let inner = Tweak::from_inner(ret).map_err(E::custom)?; - Ok(ValueBlindingFactor(inner)) - } - Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), - } - } - } - - d.deserialize_bytes(BytesVisitor) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(feature = "serde")] - use std::str::FromStr; - - #[cfg(feature = "serde")] - use bincode; - - #[test] - fn encode_length() { - let vals = [ - Value::Null, - Value::Explicit(1000), - Value::from_commitment(&[ - 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, - ]) - .unwrap(), - ]; - for v in &vals[..] { - let mut x = vec![]; - assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); - assert_eq!(x.len(), v.encoded_length()); - } - - let nonces = [ - Nonce::Null, - Nonce::Explicit([0; 32]), - Nonce::from_commitment(&[ - 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, - ]) - .unwrap(), - ]; - for v in &nonces[..] { - let mut x = vec![]; - assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); - assert_eq!(x.len(), v.encoded_length()); - } - - let assets = [ - Asset::Null, - Asset::Explicit(AssetId::from_byte_array([0; 32])), - Asset::from_commitment(&[ - 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, - ]) - .unwrap(), - ]; - for v in &assets[..] { - let mut x = vec![]; - assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); - assert_eq!(x.len(), v.encoded_length()); - } - } - - #[test] - fn commitments() { - let x = Value::from_commitment(&[ - 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - ]) - .unwrap(); - let commitment = x.commitment().unwrap(); - let mut commitment = commitment.serialize(); - assert_eq!(x, Value::from_commitment(&commitment[..]).unwrap()); - commitment[0] = 42; - assert!(Value::from_commitment(&commitment[..]).is_err()); - - let x = Asset::from_commitment(&[ - 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - ]) - .unwrap(); - let commitment = x.commitment().unwrap(); - let mut commitment = commitment.serialize(); - assert_eq!(x, Asset::from_commitment(&commitment[..]).unwrap()); - commitment[0] = 42; - assert!(Asset::from_commitment(&commitment[..]).is_err()); - - let x = Nonce::from_commitment(&[ - 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - ]) - .unwrap(); - let commitment = x.commitment().unwrap(); - let mut commitment = commitment.serialize(); - assert_eq!(x, Nonce::from_commitment(&commitment[..]).unwrap()); - commitment[0] = 42; - assert!(Nonce::from_commitment(&commitment[..]).is_err()); - } - - #[cfg(feature = "serde")] - #[test] - fn value_serde() { - use serde_test::{assert_tokens, Configure, Token}; - - let value = Value::Explicit(100_000_000); - assert_tokens( - &value, - &[ - Token::Seq { len: Some(2) }, - Token::U8(1), - Token::U64(63_601_271_583_539_200), - Token::SeqEnd - ] - ); - - let value = Value::from_commitment(&[ - 0x08, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]).unwrap(); - assert_tokens( - &value.readable(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Str( - "080101010101010101010101010101010101010101010101010101010101010101" - ), - Token::SeqEnd - ] - ); - assert_tokens( - &value.compact(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Bytes( - &[ - 8, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - ] - ), - Token::SeqEnd - ] - ); - - let value = Value::Null; - assert_tokens( - &value, - &[ - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd - ] - ); - } - - #[cfg(feature = "serde")] - #[test] - fn asset_serde() { - use serde_test::{assert_tokens, Configure, Token}; - - let asset_id = AssetId::from_str( - "630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8" - ).unwrap(); - let asset = Asset::Explicit(asset_id); - assert_tokens( - &asset.readable(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(1), - Token::Str( - "630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8" - ), - Token::SeqEnd - ] - ); - assert_tokens( - &asset.compact(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(1), - Token::Bytes( - &[ - 248, 11, 176, 3, 143, 72, 34, 67, 32, 47, 11, 45, 207, 136, 217, 180, - 231, 249, 48, 164, 138, 63, 205, 192, 3, 175, 118, 177, 249, 214, 14, 99 - ] - ), - Token::SeqEnd - ] - ); - - let asset = Asset::from_commitment(&[ - 0x0a, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]).unwrap(); - assert_tokens( - &asset.readable(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Str( - "0a0101010101010101010101010101010101010101010101010101010101010101" - ), - Token::SeqEnd - ] - ); - assert_tokens( - &asset.compact(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Bytes( - &[ - 10, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - ] - ), - Token::SeqEnd - ] - ); - - let asset = Asset::Null; - assert_tokens( - &asset, - &[ - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd - ] - ); - } - - #[cfg(feature = "serde")] - #[test] - fn nonce_serde() { - use serde_test::{assert_tokens, Configure, Token}; - - let nonce = Nonce::Explicit([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]); - assert_tokens( - &nonce, - &[ - Token::Seq { len: Some(2) }, - Token::U8(1), - Token::Tuple { len: 32 }, - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::TupleEnd, - Token::SeqEnd - ] - ); - - let nonce = Nonce::from_commitment(&[ - 0x02, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]).unwrap(); - assert_tokens( - &nonce.readable(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Str( - "020101010101010101010101010101010101010101010101010101010101010101" - ), - Token::SeqEnd - ] - ); - assert_tokens( - &nonce.compact(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Tuple { len: 33 }, - Token::U8(2), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), - Token::TupleEnd, - Token::SeqEnd - ] - ); - - let nonce = Nonce::Null; - assert_tokens( - &nonce, - &[ - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd - ] - ); - } - - #[cfg(feature = "serde")] - #[test] - fn bf_serde() { - use serde_json; - use std::str::FromStr; - - let abf_str = "a5b3d111cdaa5fc111e2723df4caf315864f25fb4610cc737f10d5a55cd4096f"; - let abf_str_quoted = format!("\"{}\"", abf_str); - let abf_from_serde: AssetBlindingFactor = serde_json::from_str(&abf_str_quoted).unwrap(); - let abf_from_str = AssetBlindingFactor::from_str(abf_str).unwrap(); - assert_eq!(abf_from_serde, abf_from_str); - assert_eq!(abf_str_quoted, serde_json::to_string(&abf_from_serde).unwrap()); - - let vbf_str = "e36a4de359469f547571d117bc5509fb74fba73c84b0cdd6f4edfa7ff7fa457d"; - let vbf_str_quoted = format!("\"{}\"", vbf_str); - let vbf_from_serde: ValueBlindingFactor = serde_json::from_str(&vbf_str_quoted).unwrap(); - let vbf_from_str = ValueBlindingFactor::from_str(vbf_str).unwrap(); - assert_eq!(vbf_from_serde, vbf_from_str); - assert_eq!(vbf_str_quoted, serde_json::to_string(&vbf_from_serde).unwrap()); - } - - #[cfg(feature = "serde")] - #[test] - fn test_value_bincode_be() { - let value = Value::Explicit(500); - let bytes = bincode::serialize(&value).unwrap(); - let decoded: Value = bincode::deserialize(&bytes).unwrap(); - assert_eq!(value, decoded); - } - - #[cfg(feature = "serde")] - #[test] - fn test_value_bincode_le() { - use bincode::Options; - let value = Value::Explicit(500); - let bytes = bincode::DefaultOptions::default() - .with_little_endian() - .serialize(&value) - .unwrap(); - let decoded: Value = bincode::DefaultOptions::default() - .with_little_endian() - .deserialize(&bytes) - .unwrap(); - assert_eq!(value, decoded); - } -} diff --git a/src/confidential/asset.rs b/src/confidential/asset.rs new file mode 100644 index 00000000..5636b4bf --- /dev/null +++ b/src/confidential/asset.rs @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Confiential Assets + +use core::{fmt, str}; +use std::io; + +use secp256k1_zkp::rand::Rng; +use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use super::CommitmentEncoder; +use crate::encode::{self, Decodable, Encodable}; +use crate::encoding; +use crate::issuance::AssetId; + +type ExplicitInner = AssetId; +type ConfInner = Generator; + +const EXPLICIT_LEN: usize = 32; +const CONFIDENTIAL_LEN: usize = 33; +const CONFIDENTIAL_LEN_LESS_PREFIX: usize = CONFIDENTIAL_LEN - 1; +const CONF_PREFIX_1: u8 = 0x0a; +const CONF_PREFIX_2: u8 = 0x0b; + +/// A CT commitment to an asset +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Asset { + /// No value + #[default] + Null, + /// Asset entropy is explicitly encoded + Explicit(ExplicitInner), + /// Asset is committed + Confidential(ConfInner), +} + +impl Asset { + /// Create asset commitment. + pub fn new_confidential( + secp: &Secp256k1, + asset: AssetId, + bf: BlindingFactor, + ) -> Self { + Self::Confidential(ConfInner::new_blinded(secp, asset.into_tag(), bf.into_inner())) + } + + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Self::Null => 1, + Self::Explicit(..) => 1 + EXPLICIT_LEN, + Self::Confidential(..) => CONFIDENTIAL_LEN, + } + } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Self::Confidential(ConfInner::from_slice(bytes)?)) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { matches!(*self, Self::Explicit(_)) } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { matches!(*self, Self::Confidential(_)) } + + /// Returns the explicit inner value. + /// Returns [None] if [`Self::is_explicit`] returns false. + pub fn explicit(&self) -> Option { + match *self { + Self::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [`Self::is_confidential`] returns false. + pub fn commitment(&self) -> Option { + match *self { + Self::Confidential(i) => Some(i), + _ => None, + } + } + + /// Internally used function for getting the generator from asset + /// Used in the amount verification check + /// Returns [`None`] is the asset is [`Self::Null`] + /// Converts a explicit asset into a generator and returns the confidential + /// generator as is. + pub fn into_asset_gen( + self, + secp: &Secp256k1, + ) -> Option { + match self { + // Only error is Null error which is dealt with later + // when we have more context information about it. + Self::Null => None, + Self::Explicit(x) => Some(ConfInner::new_unblinded(secp, x.into_tag())), + Self::Confidential(gen) => Some(gen), + } + } +} + +impl From for Asset { + fn from(from: ConfInner) -> Self { Self::Confidential(from) } +} + +impl fmt::Display for Asset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Null => f.write_str("null"), + Self::Explicit(n) => write!(f, "{}", n), + Self::Confidential(generator) => write!(f, "{:02x}", generator), + } + } +} + +impl Encodable for Asset { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Self::Null => { + s.write_all(&[0u8])?; + Ok(1) + } + Self::Explicit(n) => { + s.write_all(&[1u8])?; + s.write_all(n.as_byte_array())?; + Ok(1 + EXPLICIT_LEN) + } + Self::Confidential(generator) => { + s.write_all(&generator.serialize())?; + Ok(CONFIDENTIAL_LEN) + } + } + } +} + +impl Decodable for Asset { + fn consensus_decode(mut d: D) -> Result { + let mut buf = [0u8; CONFIDENTIAL_LEN]; + d.read_exact(&mut buf[0..1])?; + + match buf[0] { + 0 => Ok(Self::Null), + 1 => { + let mut buf = [0; EXPLICIT_LEN]; + d.read_exact(&mut buf)?; + Ok(Self::Explicit(AssetId::from_byte_array(buf))) + } + p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { + d.read_exact(&mut buf[1..])?; + Ok(Self::Confidential(ConfInner::from_slice(&buf[..])?)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Asset { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = match *self { + Self::Null => 1, + Self::Explicit(_) | Self::Confidential(_) => 2, + }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Self::Null => seq.serialize_element(&0u8)?, + Self::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&n)?; + } + Self::Confidential(commitment) => { + seq.serialize_element(&2u8)?; + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Asset { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Asset; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix = access.next_element::()?; + match prefix { + Some(0) => Ok(Self::Value::Null), + Some(1) => match access.next_element()? { + Some(x) => Ok(Self::Value::Explicit(x)), + None => Err(A::Error::custom("missing explicit asset")), + }, + Some(2) => match access.next_element()? { + Some(x) => Ok(Self::Value::Confidential(x)), + None => Err(A::Error::custom("missing generator")), + }, + _ => Err(A::Error::custom("wrong or missing prefix")), + } + } + } + + d.deserialize_seq(CommitVisitor) + } +} + +/// Blinding factor used for asset commitments. +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct BlindingFactor(pub(crate) Tweak); + +impl BlindingFactor { + /// Generate random asset blinding factor. + pub fn new(rng: &mut R) -> Self { Self(Tweak::new(rng)) } + + /// Parse a blinding factor from a 64-character hex string. + #[deprecated(since = "0.27.0", note = "use s.parse() instead")] + pub fn from_hex(s: &str) -> Result { s.parse() } + + /// Create from bytes. + pub fn from_byte_array(bytes: [u8; 32]) -> Result { + Ok(Self(Tweak::from_inner(bytes)?)) + } + + /// Create from bytes. + pub fn from_slice(bytes: &[u8]) -> Result { + Ok(Self(Tweak::from_slice(bytes)?)) + } + + /// Returns the inner value. + pub fn into_inner(self) -> Tweak { self.0 } + + /// Get a unblinded/zero `AssetBlinding` factor + pub fn zero() -> Self { Self(ZERO_TWEAK) } +} + +impl core::borrow::Borrow<[u8]> for BlindingFactor { + fn borrow(&self) -> &[u8] { &self.0[..] } +} + +hex::impl_fmt_traits! { + #[display_backward(true)] + impl fmt_traits for BlindingFactor { + const LENGTH: usize = 32; + } +} + +impl str::FromStr for BlindingFactor { + type Err = encode::Error; + + fn from_str(s: &str) -> Result { + let mut slice: [u8; 32] = hex::decode_to_array(s)?; + slice.reverse(); + + let inner = Tweak::from_inner(slice)?; + Ok(Self(inner)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for BlindingFactor { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(&self) + } else { + s.serialize_bytes(&self.0[..]) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for BlindingFactor { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + struct HexVisitor; + + impl ::serde::de::Visitor<'_> for HexVisitor { + type Value = BlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an ASCII hex string") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + if let Ok(hex) = ::std::str::from_utf8(v) { + hex.parse().map_err(E::custom) + } else { + Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + v.parse().map_err(E::custom) + } + } + + d.deserialize_str(HexVisitor) + } else { + struct BytesVisitor; + + impl ::serde::de::Visitor<'_> for BytesVisitor { + type Value = BlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("a bytestring") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + use core::convert::TryFrom; + + match <[u8; 32]>::try_from(v) { + Ok(ret) => { + let inner = Tweak::from_inner(ret).map_err(E::custom)?; + Ok(BlindingFactor(inner)) + } + Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), + } + } + } + + d.deserialize_bytes(BytesVisitor) + } + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`Asset`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(CommitmentEncoder<'e>); +} + +impl encoding::Encode for Asset { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(match *self { + Self::Null => CommitmentEncoder::Null(0), + Self::Explicit(ref id) => CommitmentEncoder::Explicit32(Some(1), id.as_byte_array()), + Self::Confidential(ref gen) => CommitmentEncoder::Explicit33(gen.serialize()), + }) + } +} + +decoder_state_machine! { + /// A decoder for the [`Asset`] type. + pub struct Decoder(enum DecoderInner { + Done(Asset), + Errored, + DecodePrefix { + decoder: encoding::ArrayDecoder<1>, + => transition_decode_prefix(prefix, ...) -> Result { + match prefix { + [0] => Ok(DecoderInner::Done(Asset::Null)), + [1] => { + Ok(DecoderInner::DecodeExplicit { decoder: encoding::ArrayDecoder::default() }) + }, + [prefix @ (CONF_PREFIX_1 | CONF_PREFIX_2)] => { + Ok(DecoderInner::DecodeConfidential { decoder: encoding::ArrayDecoder::default(), prefix }) + }, + [prefix] => Err(DecoderErrorInner::InvalidConfidentialPrefix { prefix }) + } + } + }, + DecodeExplicit { + decoder: encoding::ArrayDecoder + => transition_decode_explicit(bytes, ...) -> Result { + Ok(DecoderInner::Done(Asset::Explicit(AssetId::from_byte_array(bytes)))) + } + }, + DecodeConfidential { + decoder: encoding::ArrayDecoder, + prefix: u8 + => transition_decode_confidential(x_coord, ...) -> Result { + let mut bytes = [0; CONFIDENTIAL_LEN]; + bytes[0] = prefix; + bytes[1..].copy_from_slice(&x_coord); + let gen = ConfInner::from_slice(&bytes) + .map_err(DecoderErrorInner::InvalidCommitment)?; + Ok(DecoderInner::Done(Asset::Confidential(gen))) + } + }, + }); + + /// A decoder error for the [`Asset`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + [macro-inserted decoder variants] + /// Confidential prefix was not one of the two allowable values. + InvalidConfidentialPrefix { + prefix: u8, + }, + /// Malformed confidential commitment. + InvalidCommitment(secp256k1_zkp::Error), + }); +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(_) => f.write_str("failed to decode prefix"), + Inner::DecodeExplicit(_) => f.write_str("failed to decode explicit value"), + Inner::DecodeConfidential(_) => f.write_str("failed to decode confidential value"), + Inner::InvalidConfidentialPrefix { prefix, .. } => { + write!( + f, + "confidential prefix 0x{:02x} was not one of 0, 1, 0x{:02x} or 0x{:02x}", + prefix, CONF_PREFIX_1, CONF_PREFIX_2, + ) + } + Inner::InvalidCommitment(_) => f.write_str("failed to parse confidential commitment"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(ref e) => Some(e), + Inner::DecodeExplicit(ref e) => Some(e), + Inner::DecodeConfidential(ref e) => Some(e), + Inner::InvalidConfidentialPrefix { .. } => None, + Inner::InvalidCommitment(ref e) => Some(e), + } + } +} + +impl Default for Decoder { + fn default() -> Self { + Self(DecoderInner::DecodePrefix { decoder: encoding::ArrayDecoder::default() }) + } +} diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs new file mode 100644 index 00000000..22fe9542 --- /dev/null +++ b/src/confidential/mod.rs @@ -0,0 +1,560 @@ +// Rust Elements Library +// Written in 2018 by +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # Confidential Commitments +//! +//! Structures representing Pedersen commitments of various types +//! + +#![warn(clippy::use_self)] + +mod asset; +mod nonce; +mod range_proof; +mod surjection_proof; +mod value; + +use core::{fmt, slice}; + +use secp256k1_zkp; + +pub use self::asset::{ + Asset, BlindingFactor as AssetBlindingFactor, Decoder as AssetDecoder, + DecoderError as AssetDecoderError, Encoder as AssetEncoder, +}; +pub use self::nonce::{ + Decoder as NonceDecoder, DecoderError as NonceDecoderError, Encoder as NonceEncoder, Nonce, +}; +pub use self::range_proof::{ + Decoder as RangeProofDecoder, DecoderError as RangeProofDecoderError, + Encoder as RangeProofEncoder, RangeProof, +}; +pub use self::surjection_proof::{ + Decoder as SurjectionProofDecoder, DecoderError as SurjectionProofDecoderError, + Encoder as SurjectionProofEncoder, SurjectionProof, +}; +pub use self::value::{ + BlindingFactor as ValueBlindingFactor, Decoder as ValueDecoder, + DecoderError as ValueDecoderError, Encoder as ValueEncoder, Value, +}; +use crate::issuance::AssetId; +use crate::{encode, encoding}; + +#[derive(Clone, Debug)] +enum CommitmentEncoder<'e> { + Null(u8), + Explicit8(Option, [u8; 8]), + Explicit32(Option, &'e [u8; 32]), + Explicit33([u8; 33]), +} + +impl encoding::Encoder for CommitmentEncoder<'_> { + fn current_chunk(&self) -> &[u8] { + match *self { + Self::Null(ref prefix) => slice::from_ref(prefix), + Self::Explicit8(ref prefix, ref arr) => prefix.as_ref().map_or(arr, slice::from_ref), + Self::Explicit32(ref prefix, arr) => prefix.as_ref().map_or(arr, slice::from_ref), + Self::Explicit33(ref arr) => arr, + } + } + + fn advance(&mut self) -> encoding::EncoderStatus { + match *self { + Self::Explicit8(ref mut prefix @ Some(_), _) + | Self::Explicit32(ref mut prefix @ Some(_), _) => { + *prefix = None; + encoding::EncoderStatus::HasMore + } + _ => encoding::EncoderStatus::Finished, + } + } +} + +impl encoding::ExactSizeEncoder for CommitmentEncoder<'_> { + fn len(&self) -> usize { + match *self { + Self::Null(_) => 1, + Self::Explicit8(Some(_), _) => 9, + Self::Explicit8(None, _) => 8, + Self::Explicit32(Some(_), _) => 33, + Self::Explicit32(None, _) => 32, + Self::Explicit33(_) => 33, + } + } +} + +/// Because the rust-secp256k1-zkp proof types have no `as_bytes()` method, we need +/// to serialize them to a byte vector before encoding them. +/// +/// This encoder accomplishes that -- this situation never happens in rust-bitcoin +/// so there is no "owned bytes encoder" shipped with bitcoin-consensus-encoding. +#[derive(Clone, Debug)] +struct PrefixedByteVecEncoder { + prefix_encoder: Option, + data: Vec, +} + +impl PrefixedByteVecEncoder { + pub fn new(data: Vec) -> Self { + Self { prefix_encoder: Some(encoding::CompactSizeEncoder::new(data.len())), data } + } +} + +impl encoding::Encoder for PrefixedByteVecEncoder { + fn current_chunk(&self) -> &[u8] { + if let Some(ref enc) = self.prefix_encoder { + return enc.current_chunk(); + } + &self.data + } + + fn advance(&mut self) -> encoding::EncoderStatus { + if let Some(ref mut enc) = self.prefix_encoder { + if enc.advance().has_finished() { + self.prefix_encoder = None; + if self.data.is_empty() { + return encoding::EncoderStatus::Finished; + } + } + encoding::EncoderStatus::HasMore + } else { + encoding::EncoderStatus::Finished + } + } +} + +impl encoding::ExactSizeEncoder for PrefixedByteVecEncoder { + fn len(&self) -> usize { + self.prefix_encoder.as_ref().map_or(0, encoding::CompactSizeEncoder::len) + self.data.len() + } +} + +/// Error decoding hexadecimal string into tweak-like value. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TweakHexDecodeError { + /// Invalid hexadecimal string. + InvalidHex(hex::DecodeFixedLengthBytesError), + /// Invalid tweak after decoding hexadecimal string. + InvalidTweak(secp256k1_zkp::Error), +} + +impl fmt::Display for TweakHexDecodeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::InvalidHex(err) => { + write!(f, "Invalid hex: {}", err) + } + Self::InvalidTweak(err) => { + write!(f, "Invalid tweak: {}", err) + } + } + } +} + +#[doc(hidden)] +impl From for TweakHexDecodeError { + fn from(err: hex::DecodeFixedLengthBytesError) -> Self { Self::InvalidHex(err) } +} + +#[doc(hidden)] +impl From for TweakHexDecodeError { + fn from(err: secp256k1_zkp::Error) -> Self { Self::InvalidTweak(err) } +} + +impl From for encode::Error { + fn from(value: TweakHexDecodeError) -> Self { + match value { + TweakHexDecodeError::InvalidHex(err) => Self::HexFixedError(err), + TweakHexDecodeError::InvalidTweak(err) => Self::Secp256k1zkp(err), + } + } +} + +impl std::error::Error for TweakHexDecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidHex(err) => Some(err), + Self::InvalidTweak(err) => Some(err), + } + } +} +#[cfg(test)] +mod tests { + #[cfg(feature = "serde")] + use std::str::FromStr; + + #[cfg(feature = "serde")] + use bincode; + + use super::*; + use crate::encode::Encodable as _; + use crate::encoding; + + const VALUE_EXPLICIT: [u8; 9] = [1, 0, 0, 0, 0, 0, 0, 3, 232]; + + const VALUE_COMMITMENT1: [u8; 33] = [ + 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const VALUE_COMMITMENT2: [u8; 33] = [ + 0x09, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const NONCE_EXPLICIT: [u8; 33] = [ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + const NONCE_COMMITMENT1: [u8; 33] = [ + 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const NONCE_COMMITMENT2: [u8; 33] = [ + 0x03, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const ASSET_EXPLICIT: [u8; 33] = [ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + const ASSET_COMMITMENT1: [u8; 33] = [ + 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const ASSET_COMMITMENT2: [u8; 33] = [ + 0x0b, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + #[test] + fn prefixed_byte_encoder() { + assert_eq!(encoding::drain_to_vec(&mut PrefixedByteVecEncoder::new(vec![])), [0]); + assert_eq!( + encoding::drain_to_vec(&mut PrefixedByteVecEncoder::new(vec![1, 2, 3])), + [3, 1, 2, 3] + ); + } + + #[test] + fn encode_length() { + let val_encodings = [ + vec![0], + VALUE_EXPLICIT.to_vec(), + VALUE_COMMITMENT1.to_vec(), + VALUE_COMMITMENT2.to_vec(), + ]; + let vals = [ + Value::Null, + Value::Explicit(1000), + Value::from_commitment(&VALUE_COMMITMENT1).unwrap(), + Value::from_commitment(&VALUE_COMMITMENT2).unwrap(), + ]; + for (v, enc) in vals.iter().zip(val_encodings.iter()) { + let mut x = vec![]; + assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); + assert_eq!(x.len(), v.encoded_length()); + assert_eq!(x, *enc); + + assert_eq!(encoding::encode_to_vec(v), *enc); + assert_eq!(encoding::decode_from_slice(enc), Ok(*v)); + } + + let nonce_encodings = [ + vec![0], + NONCE_EXPLICIT.to_vec(), + NONCE_COMMITMENT1.to_vec(), + NONCE_COMMITMENT2.to_vec(), + ]; + let nonces = [ + Nonce::Null, + Nonce::Explicit([0; 32]), + Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(), + Nonce::from_commitment(&NONCE_COMMITMENT2).unwrap(), + ]; + for (v, enc) in nonces.iter().zip(nonce_encodings.iter()) { + let mut x = vec![]; + assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); + assert_eq!(x.len(), v.encoded_length()); + assert_eq!(x, *enc); + + assert_eq!(encoding::encode_to_vec(v), *enc); + assert_eq!(encoding::decode_from_slice(enc), Ok(*v)); + } + + let asset_encodings = [ + vec![0], + ASSET_EXPLICIT.to_vec(), + ASSET_COMMITMENT1.to_vec(), + ASSET_COMMITMENT2.to_vec(), + ]; + let assets = [ + Asset::Null, + Asset::Explicit(AssetId::from_byte_array([0; 32])), + Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(), + Asset::from_commitment(&ASSET_COMMITMENT2).unwrap(), + ]; + for (v, enc) in assets.iter().zip(asset_encodings.iter()) { + let mut x = vec![]; + assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); + assert_eq!(x.len(), v.encoded_length()); + assert_eq!(x, *enc); + + assert_eq!(encoding::encode_to_vec(v), *enc); + assert_eq!(encoding::decode_from_slice(enc), Ok(*v)); + } + } + + #[test] + fn commitments() { + let x = Value::from_commitment(&VALUE_COMMITMENT1).unwrap(); + let commitment = x.commitment().unwrap(); + let mut commitment = commitment.serialize(); + assert_eq!(x, Value::from_commitment(&commitment[..]).unwrap()); + commitment[0] = 42; + assert!(Value::from_commitment(&commitment[..]).is_err()); + assert_eq!(encoding::encode_to_vec(&x), VALUE_COMMITMENT1); + + let x = Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(); + let commitment = x.commitment().unwrap(); + let mut commitment = commitment.serialize(); + assert_eq!(x, Asset::from_commitment(&commitment[..]).unwrap()); + commitment[0] = 42; + assert!(Asset::from_commitment(&commitment[..]).is_err()); + assert_eq!(encoding::encode_to_vec(&x), ASSET_COMMITMENT1); + + let x = Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(); + let commitment = x.commitment().unwrap(); + let mut commitment = commitment.serialize(); + assert_eq!(x, Nonce::from_commitment(&commitment[..]).unwrap()); + commitment[0] = 42; + assert!(Nonce::from_commitment(&commitment[..]).is_err()); + assert_eq!(encoding::encode_to_vec(&x), NONCE_COMMITMENT1); + } + + #[cfg(feature = "serde")] + #[test] + fn value_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + let value = Value::Explicit(100_000_000); + assert_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::U8(1), + Token::U64(63_601_271_583_539_200), + Token::SeqEnd, + ], + ); + + let value = Value::from_commitment(&VALUE_COMMITMENT1).unwrap(); + assert_tokens( + &value.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Str("080101010101010101010101010101010101010101010101010101010101010101"), + Token::SeqEnd, + ], + ); + assert_tokens( + &value.compact(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Bytes(&VALUE_COMMITMENT1), + Token::SeqEnd, + ], + ); + + let value = Value::Null; + assert_tokens(&value, &[Token::Seq { len: Some(1) }, Token::U8(0), Token::SeqEnd]); + } + + #[cfg(feature = "serde")] + #[test] + fn asset_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + let asset_id = + AssetId::from_str("630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8") + .unwrap(); + let asset = Asset::Explicit(asset_id); + assert_tokens( + &asset.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(1), + Token::Str("630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8"), + Token::SeqEnd, + ], + ); + assert_tokens( + &asset.compact(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(1), + Token::Bytes(&[ + 248, 11, 176, 3, 143, 72, 34, 67, 32, 47, 11, 45, 207, 136, 217, 180, 231, 249, + 48, 164, 138, 63, 205, 192, 3, 175, 118, 177, 249, 214, 14, 99, + ]), + Token::SeqEnd, + ], + ); + + let asset = Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(); + assert_tokens( + &asset.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Str("0a0101010101010101010101010101010101010101010101010101010101010101"), + Token::SeqEnd, + ], + ); + assert_tokens( + &asset.compact(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Bytes(&ASSET_COMMITMENT1), + Token::SeqEnd, + ], + ); + + let asset = Asset::Null; + assert_tokens(&asset, &[Token::Seq { len: Some(1) }, Token::U8(0), Token::SeqEnd]); + } + + #[cfg(feature = "serde")] + #[test] + #[rustfmt::skip] + fn nonce_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + let nonce = Nonce::Explicit([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]); + assert_tokens( + &nonce, + &[ + Token::Seq { len: Some(2) }, + Token::U8(1), + Token::Tuple { len: 32 }, + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::TupleEnd, + Token::SeqEnd + ] + ); + + let nonce = Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(); + assert_tokens( + &nonce.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Str( + "020101010101010101010101010101010101010101010101010101010101010101" + ), + Token::SeqEnd + ] + ); + assert_tokens( + &nonce.compact(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Tuple { len: 33 }, + Token::U8(2), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), + Token::TupleEnd, + Token::SeqEnd + ] + ); + + let nonce = Nonce::Null; + assert_tokens( + &nonce, + &[ + Token::Seq { len: Some(1) }, + Token::U8(0), + Token::SeqEnd + ] + ); + } + + #[cfg(feature = "serde")] + #[test] + fn bf_serde() { + use std::str::FromStr; + + use serde_json; + + let abf_str = "a5b3d111cdaa5fc111e2723df4caf315864f25fb4610cc737f10d5a55cd4096f"; + let abf_str_quoted = format!("\"{}\"", abf_str); + let abf_from_serde: AssetBlindingFactor = serde_json::from_str(&abf_str_quoted).unwrap(); + let abf_from_str = AssetBlindingFactor::from_str(abf_str).unwrap(); + assert_eq!(abf_from_serde, abf_from_str); + assert_eq!(abf_str_quoted, serde_json::to_string(&abf_from_serde).unwrap()); + + let vbf_str = "e36a4de359469f547571d117bc5509fb74fba73c84b0cdd6f4edfa7ff7fa457d"; + let vbf_str_quoted = format!("\"{}\"", vbf_str); + let vbf_from_serde: ValueBlindingFactor = serde_json::from_str(&vbf_str_quoted).unwrap(); + let vbf_from_str = ValueBlindingFactor::from_str(vbf_str).unwrap(); + assert_eq!(vbf_from_serde, vbf_from_str); + assert_eq!(vbf_str_quoted, serde_json::to_string(&vbf_from_serde).unwrap()); + } + + #[cfg(feature = "serde")] + #[test] + fn test_value_bincode_be() { + let value = Value::Explicit(500); + let bytes = bincode::serialize(&value).unwrap(); + let decoded: Value = bincode::deserialize(&bytes).unwrap(); + assert_eq!(value, decoded); + } + + #[cfg(feature = "serde")] + #[test] + fn test_value_bincode_le() { + use bincode::Options; + let value = Value::Explicit(500); + let bytes = + bincode::DefaultOptions::default().with_little_endian().serialize(&value).unwrap(); + let decoded: Value = + bincode::DefaultOptions::default().with_little_endian().deserialize(&bytes).unwrap(); + assert_eq!(value, decoded); + } +} diff --git a/src/confidential/nonce.rs b/src/confidential/nonce.rs new file mode 100644 index 00000000..12310e0c --- /dev/null +++ b/src/confidential/nonce.rs @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Confiential Nonces + +use core::fmt; +use std::io; + +use secp256k1_zkp::rand::{CryptoRng, RngCore}; +use secp256k1_zkp::{self, PublicKey, Secp256k1, SecretKey, Signing}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use super::CommitmentEncoder; +use crate::encode::{self, Decodable, Encodable}; +use crate::encoding; +use crate::hashes::sha256d; + +type ExplicitInner = [u8; 32]; +type ConfInner = PublicKey; + +const EXPLICIT_LEN: usize = 32; +const CONFIDENTIAL_LEN: usize = 33; +const CONFIDENTIAL_LEN_LESS_PREFIX: usize = CONFIDENTIAL_LEN - 1; +const CONF_PREFIX_1: u8 = 0x02; +const CONF_PREFIX_2: u8 = 0x03; + +/// A CT commitment to an output nonce (i.e. a public key) +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Nonce { + /// No value + #[default] + Null, + /// There should be no such thing as an "explicit nonce", but Elements will deserialize + /// such a thing (and insists that its size be 32 bytes). So we stick a 32-byte type here + /// that implements all the traits we need. + Explicit(ExplicitInner), + /// Nonce is committed + Confidential(ConfInner), +} + +impl Nonce { + /// Create nonce commitment. + pub fn new_confidential( + rng: &mut R, + secp: &Secp256k1, + receiver_blinding_pk: &ConfInner, + ) -> (Self, SecretKey) { + let ephemeral_sk = SecretKey::new(rng); + Self::with_ephemeral_sk(secp, ephemeral_sk, receiver_blinding_pk) + } + + /// Similar to [`Self::new_confidential`], but with a given `ephemeral_sk` + /// instead of sampling it from rng. + pub fn with_ephemeral_sk( + secp: &Secp256k1, + ephemeral_sk: SecretKey, + receiver_blinding_pk: &ConfInner, + ) -> (Self, SecretKey) { + let sender_pk = ConfInner::from_secret_key(secp, &ephemeral_sk); + let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &ephemeral_sk); + (Self::Confidential(sender_pk), shared_secret) + } + + /// Calculate the shared secret. + pub fn shared_secret(&self, receiver_blinding_sk: &SecretKey) -> Option { + match self { + Self::Confidential(sender_pk) => + Some(Self::make_shared_secret(sender_pk, receiver_blinding_sk)), + _ => None, + } + } + + /// Create the shared secret. + fn make_shared_secret(pk: &ConfInner, sk: &SecretKey) -> SecretKey { + let xy = secp256k1_zkp::ecdh::shared_secret_point(pk, sk); + let shared_secret = { + // Yes, what follows is the compressed representation of a Bitcoin public key. + // However, this is more by accident then by design, see here: https://github.com/rust-bitcoin/rust-secp256k1/pull/255#issuecomment-744146282 + + let mut dh_secret = [0u8; CONFIDENTIAL_LEN]; + dh_secret[0] = if xy.last().unwrap() % 2 == 0 { CONF_PREFIX_1 } else { CONF_PREFIX_2 }; + dh_secret[1..].copy_from_slice(&xy[0..32]); + + sha256d::Hash::hash(&dh_secret).to_byte_array() + }; + + SecretKey::from_slice(&shared_secret[..32]).expect("always has exactly 32 bytes") + } + + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Self::Null => 1, + Self::Explicit(..) => 1 + EXPLICIT_LEN, + Self::Confidential(..) => CONFIDENTIAL_LEN, + } + } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Self::Confidential( + ConfInner::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, + )) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { matches!(*self, Self::Explicit(_)) } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { matches!(*self, Self::Confidential(_)) } + + /// Returns the explicit inner value. + /// Returns [None] if [`Self::is_explicit`] returns false. + pub fn explicit(&self) -> Option { + match *self { + Self::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [`Self::is_confidential`] returns false. + pub fn commitment(&self) -> Option { + match *self { + Self::Confidential(i) => Some(i), + _ => None, + } + } +} + +impl From for Nonce { + fn from(from: ConfInner) -> Self { Self::Confidential(from) } +} + +impl fmt::Display for Nonce { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Null => f.write_str("null"), + Self::Explicit(n) => { + for b in &n { + write!(f, "{:02x}", b)?; + } + Ok(()) + } + Self::Confidential(pk) => write!(f, "{:02x}", pk), + } + } +} + +impl Encodable for Nonce { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Self::Null => { + s.write_all(&[0u8])?; + Ok(1) + } + Self::Explicit(n) => { + s.write_all(&[1u8])?; + s.write_all(&n)?; + Ok(1 + EXPLICIT_LEN) + } + Self::Confidential(commitment) => { + s.write_all(&commitment.serialize())?; + Ok(CONFIDENTIAL_LEN) + } + } + } +} + +impl Decodable for Nonce { + fn consensus_decode(mut d: D) -> Result { + let mut buf = [0u8; CONFIDENTIAL_LEN]; + d.read_exact(&mut buf[0..1])?; + + match buf[0] { + 0 => Ok(Self::Null), + 1 => { + let mut buf = [0; EXPLICIT_LEN]; + d.read_exact(&mut buf)?; + Ok(Self::Explicit(buf)) + } + p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { + d.read_exact(&mut buf[1..])?; + Ok(Self::Confidential(ConfInner::from_slice(&buf)?)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Nonce { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = match *self { + Self::Null => 1, + Self::Explicit(_) | Self::Confidential(_) => 2, + }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Self::Null => seq.serialize_element(&0u8)?, + Self::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&n)?; + } + Self::Confidential(commitment) => { + seq.serialize_element(&2u8)?; + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Nonce { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Nonce; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix = access.next_element::()?; + match prefix { + Some(0) => Ok(Self::Value::Null), + Some(1) => match access.next_element()? { + Some(x) => Ok(Self::Value::Explicit(x)), + None => Err(A::Error::custom("missing explicit nonce")), + }, + Some(2) => match access.next_element()? { + Some(x) => Ok(Self::Value::Confidential(x)), + None => Err(A::Error::custom("missing nonce")), + }, + _ => Err(A::Error::custom("wrong or missing prefix")), + } + } + } + + d.deserialize_seq(CommitVisitor) + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`Nonce`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(CommitmentEncoder<'e>); +} + +impl encoding::Encode for Nonce { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(match *self { + Self::Null => CommitmentEncoder::Null(0), + Self::Explicit(ref id) => CommitmentEncoder::Explicit32(Some(1), id), + Self::Confidential(ref gen) => CommitmentEncoder::Explicit33(gen.serialize()), + }) + } +} + +decoder_state_machine! { + /// A decoder for the [`Nonce`] type. + pub struct Decoder(enum DecoderInner { + Done(Nonce), + Errored, + DecodePrefix { + decoder: encoding::ArrayDecoder<1>, + => transition_decode_prefix(prefix, ...) -> Result { + match prefix { + [0] => Ok(DecoderInner::Done(Nonce::Null)), + [1] => { + Ok(DecoderInner::DecodeExplicit { decoder: encoding::ArrayDecoder::default() }) + }, + [prefix @ (CONF_PREFIX_1 | CONF_PREFIX_2)] => { + Ok(DecoderInner::DecodeConfidential { decoder: encoding::ArrayDecoder::default(), prefix }) + }, + [prefix] => Err(DecoderErrorInner::InvalidConfidentialPrefix { prefix }) + } + } + }, + DecodeExplicit { + decoder: encoding::ArrayDecoder + => transition_decode_explicit(bytes, ...) -> Result { + Ok(DecoderInner::Done(Nonce::Explicit(bytes))) + } + }, + DecodeConfidential { + decoder: encoding::ArrayDecoder, + prefix: u8 + => transition_decode_confidential(x_coord, ...) -> Result { + let mut bytes = [0; CONFIDENTIAL_LEN]; + bytes[0] = prefix; + bytes[1..].copy_from_slice(&x_coord); + let gen = ConfInner::from_slice(&bytes) + .map_err(DecoderErrorInner::InvalidCommitment)?; + Ok(DecoderInner::Done(Nonce::Confidential(gen))) + } + }, + }); + + /// A decoder error for the [`Nonce`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + [macro-inserted decoder variants] + /// Confidential prefix was not one of the two allowable values. + InvalidConfidentialPrefix { + prefix: u8, + }, + /// Malformed confidential commitment. + InvalidCommitment(bitcoin::secp256k1::Error), + }); +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(_) => f.write_str("failed to decode prefix"), + Inner::DecodeExplicit(_) => f.write_str("failed to decode explicit value"), + Inner::DecodeConfidential(_) => f.write_str("failed to decode confidential value"), + Inner::InvalidConfidentialPrefix { prefix, .. } => { + write!( + f, + "confidential prefix 0x{:02x} was not one of 0, 1, 0x{:02x} or 0x{:02x}", + prefix, CONF_PREFIX_1, CONF_PREFIX_2, + ) + } + Inner::InvalidCommitment(_) => f.write_str("failed to parse confidential commitment"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(ref e) => Some(e), + Inner::DecodeExplicit(ref e) => Some(e), + Inner::DecodeConfidential(ref e) => Some(e), + Inner::InvalidConfidentialPrefix { .. } => None, + Inner::InvalidCommitment(ref e) => Some(e), + } + } +} + +impl Default for Decoder { + fn default() -> Self { + Self(DecoderInner::DecodePrefix { decoder: encoding::ArrayDecoder::default() }) + } +} diff --git a/src/confidential/range_proof.rs b/src/confidential/range_proof.rs new file mode 100644 index 00000000..441b8c4f --- /dev/null +++ b/src/confidential/range_proof.rs @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Range Proofs + +use core::convert::TryInto; +use core::fmt; +use std::io; + +use secp256k1_zkp::rand::{CryptoRng, RngCore}; +use secp256k1_zkp::{self, Generator, PedersenCommitment, Secp256k1, SecretKey, Signing, Tweak}; +#[cfg(feature = "serde")] +use serde::{Deserializer, Serializer}; + +use crate::confidential::ValueBlindingFactor; +use crate::{encode, encoding}; + +/// A range proof, which represents a proof that a confidential value lies within +/// some range (typically `[0, 2^64)`). +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct RangeProof { + inner: Option>, +} + +impl RangeProof { + /// No range proof. + pub const EMPTY: Self = Self { inner: None }; + + /// Constructs a new [`RangeProof`]. + #[allow(clippy::too_many_arguments)] + pub fn new( + secp: &Secp256k1, + min_value: u64, + commitment: PedersenCommitment, + value: u64, + commitment_blinding: Tweak, + message: &[u8], + additional_commitment: &[u8], + sk: SecretKey, + exp: i32, + min_bits: u8, + additional_generator: Generator, + ) -> Result { + secp256k1_zkp::RangeProof::new( + secp, + min_value, + commitment, + value, + commitment_blinding, + message, + additional_commitment, + sk, + exp, + min_bits, + additional_generator, + ) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + + /// Parses a [`RangeProof`] from a byte slice (with no length prefix). + pub fn from_slice(sl: &[u8]) -> Result { + if sl.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::RangeProof::from_slice(sl) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + } + + /// Outputs a [`RangeProof`] proving that a commitment matches an exact value. + pub fn blind_value_proof( + rng: &mut R, + secp: &Secp256k1, + explicit_val: u64, + value_commit: PedersenCommitment, + asset_gen: Generator, + vbf: ValueBlindingFactor, + ) -> Result { + secp256k1_zkp::RangeProof::new( + secp, + explicit_val, // min_value + value_commit, // value_commit + explicit_val, // value + vbf.into_inner(), // blinding factor + &[], // message + &[], // add commitment + SecretKey::new(rng), // nonce + -1, // exp + 0, // min bits + asset_gen, // additional gen + ) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + + /// Verifies a [`RangeProof`] proving that a commitment matches an exact value. + pub fn blind_value_proof_verify( + &self, + secp: &Secp256k1, + explicit_val: u64, + asset_gen: Generator, + value_commit: PedersenCommitment, + ) -> bool { + let Some(inner) = self.inner.as_deref() else { + return false; + }; + if explicit_val == u64::MAX { + // FIXME upstream will panic on this input; we should be able to validate + // proofs with this value. + return false; + } + + let Ok(range) = inner.verify(secp, value_commit, &[], asset_gen) else { + return false; + }; + range == (explicit_val..explicit_val + 1) + } + + /// The length of the range proof (zero if it is empty/absent). + pub fn len(&self) -> usize { self.inner.as_deref().map_or(0, secp256k1_zkp::RangeProof::len) } + + /// Whether the range proof is absent. + pub fn is_empty(&self) -> bool { self.inner.is_none() } + + /// Serializes the range proof as a byte vector. + pub fn to_vec(&self) -> Vec { + match self.inner.as_deref() { + Some(prf) => secp256k1_zkp::RangeProof::serialize(prf), + None => Vec::new(), + } + } + + /// Extracts the minimum value encoded in the range proof. + pub fn minimim_value(&self) -> Option { + // inefficient, consider implementing index on rangeproof + let prf = self.to_vec(); + let byte0 = prf.first()?; + + let has_nonzero_range = byte0 & 64 == 64; + let has_min = byte0 & 32 == 32; + + if !has_min { + None + } else if has_nonzero_range { + let bytes: [u8; 8] = prf.get(2..10)?.try_into().ok()?; + Some(u64::from_be_bytes(bytes)) + } else { + let bytes: [u8; 8] = prf.get(1..9)?.try_into().ok()?; + Some(u64::from_be_bytes(bytes)) + } + } + + /// Obtains a reference to the underlying secp256k1-zkp object. + pub fn as_ref(&self) -> Option<&secp256k1_zkp::RangeProof> { self.inner.as_deref() } +} + +impl crate::encode::Encodable for RangeProof { + fn consensus_encode(&self, e: W) -> Result { + self.to_vec().consensus_encode(e) + } +} + +impl crate::encode::Decodable for RangeProof { + fn consensus_decode(d: D) -> Result { + let v = Vec::::consensus_decode(d)?; + if v.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::RangeProof::from_slice(&v) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + .map_err(encode::Error::Secp256k1zkp) + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for RangeProof { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for RangeProof { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer) + .map(|inner| Self { inner: inner.map(Box::new) }) + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`RangeProof`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(super::PrefixedByteVecEncoder); +} + +impl encoding::Encode for RangeProof { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(super::PrefixedByteVecEncoder::new(self.to_vec())) + } +} + +decoder_newtype! { + /// Decoder for the [`RangeProof`] type. + #[derive(Default)] + pub struct Decoder(encoding::ByteVecDecoder); + + /// Decoder error for the [`RangeProof`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + Decode(encoding::ByteVecDecoderError), + RangeProof(secp256k1_zkp::Error), + }); + + impl Decode for RangeProof { + fn convert_inner(v) -> Result<_, DecoderErrorInner> { + Self::Output::from_slice(&v).map_err(DecoderErrorInner::RangeProof) + } + } +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(..) => f.write_str("error decoding byte vector"), + Inner::RangeProof(..) => f.write_str("error decoding range proof"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(ref e) => Some(e), + Inner::RangeProof(ref e) => Some(e), + } + } +} diff --git a/src/confidential/surjection_proof.rs b/src/confidential/surjection_proof.rs new file mode 100644 index 00000000..9a9ceed5 --- /dev/null +++ b/src/confidential/surjection_proof.rs @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Surjection Proofs + +use core::fmt; +use std::io; + +use secp256k1_zkp::rand::{CryptoRng, RngCore}; +use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; +#[cfg(feature = "serde")] +use serde::{Deserializer, Serializer}; + +use crate::confidential::{AssetBlindingFactor, AssetId}; +use crate::{encode, encoding}; + +/// A surjection proof, proving that an asset commitment commits to the same asset ID +/// as a commitment from a given set. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SurjectionProof { + inner: Option>, +} + +impl SurjectionProof { + /// No surjection proof. + pub const EMPTY: Self = Self { inner: None }; + + /// Constructs a new [`SurjectionProof`]. + pub fn new( + secp: &Secp256k1, + rng: &mut R, + asset: AssetId, + asset_bf: AssetBlindingFactor, + inputs: S, + ) -> Result + where + R: RngCore + CryptoRng, + C: Signing, + S: AsRef<[(Generator, secp256k1_zkp::Tag, Tweak)]>, + { + secp256k1_zkp::SurjectionProof::new( + secp, + rng, + asset.into_tag(), + asset_bf.into_inner(), + inputs.as_ref(), + ) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + + /// Parses a [`SurjectionProof`] from a byte slice (with no length prefix). + pub fn from_slice(sl: &[u8]) -> Result { + if sl.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::SurjectionProof::from_slice(sl) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + } + + /// Serializes the surjection proof as a byte vector. + pub fn to_vec(&self) -> Vec { + match self.inner.as_deref() { + Some(prf) => secp256k1_zkp::SurjectionProof::serialize(prf), + None => Vec::new(), + } + } + + /// Outputs a [`SurjectionProof`] proving that an asset matches an exact asset ID. + pub fn blind_asset_proof( + rng: &mut R, + secp: &Secp256k1, + asset: AssetId, + abf: AssetBlindingFactor, + ) -> Result { + let gen = Generator::new_unblinded(secp, asset.into_tag()); + Self::new(secp, rng, asset, abf, [(gen, asset.into_tag(), ZERO_TWEAK)]) + } + + /// Verifies a [`SurjectionProof`] proving that an asset matches an exact asset ID. + pub fn blind_asset_proof_verify( + &self, + secp: &Secp256k1, + asset: AssetId, + asset_commit: Generator, + ) -> bool { + let gen = Generator::new_unblinded(secp, asset.into_tag()); + match self.inner.as_deref() { + Some(inner) => inner.verify(secp, asset_commit, &[gen]), + None => false, + } + } + + /// The length of the range proof (zero if it is empty/absent). + pub fn len(&self) -> usize { + self.inner.as_deref().map_or(0, secp256k1_zkp::SurjectionProof::len) + } + + /// Whether the surjectionproof is absent. + pub fn is_empty(&self) -> bool { self.inner.is_none() } + + /// Obtains a reference to the underlying secp256k1-zkp object. + pub fn as_ref(&self) -> Option<&secp256k1_zkp::SurjectionProof> { self.inner.as_deref() } +} + +impl crate::encode::Encodable for SurjectionProof { + fn consensus_encode(&self, e: W) -> Result { + match self.inner.as_ref() { + Some(prf) => secp256k1_zkp::SurjectionProof::serialize(prf).consensus_encode(e), + None => <[u8]>::consensus_encode(&[], e), + } + } +} + +impl crate::encode::Decodable for SurjectionProof { + fn consensus_decode(d: D) -> Result { + let v = Vec::::consensus_decode(d)?; + if v.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::SurjectionProof::from_slice(&v) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + .map_err(encode::Error::Secp256k1zkp) + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SurjectionProof { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for SurjectionProof { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer) + .map(|inner| Self { inner: inner.map(Box::new) }) + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`SurjectionProof`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(super::PrefixedByteVecEncoder); +} + +impl encoding::Encode for SurjectionProof { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(super::PrefixedByteVecEncoder::new(self.to_vec())) + } +} + +decoder_newtype! { + /// Decoder for the [`SurjectionProof`] type. + #[derive(Default)] + pub struct Decoder(encoding::ByteVecDecoder); + + /// Decoder error for the [`SurjectionProof`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + Decode(encoding::ByteVecDecoderError), + SurjectionProof(secp256k1_zkp::Error), + }); + + impl Decode for SurjectionProof { + fn convert_inner(v) -> Result<_, DecoderErrorInner> { + Self::Output::from_slice(&v).map_err(DecoderErrorInner::SurjectionProof) + } + } +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(..) => f.write_str("error decoding byte vector"), + Inner::SurjectionProof(..) => f.write_str("error decoding surjection proof"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(ref e) => Some(e), + Inner::SurjectionProof(ref e) => Some(e), + } + } +} diff --git a/src/confidential/value.rs b/src/confidential/value.rs new file mode 100644 index 00000000..930e20c3 --- /dev/null +++ b/src/confidential/value.rs @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Confiential Values + +use core::ops::{AddAssign, Neg}; +use core::{fmt, str}; +use std::io; + +use secp256k1_zkp::rand::Rng; +use secp256k1_zkp::{ + self, compute_adaptive_blinding_factor, CommitmentSecrets, Generator, PedersenCommitment, + Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use super::CommitmentEncoder; +use crate::confidential::AssetBlindingFactor; +use crate::encode::{self, Decodable, Encodable}; +use crate::encoding; +use crate::issuance::AssetId; + +type ExplicitInner = u64; +type ConfInner = PedersenCommitment; + +const EXPLICIT_LEN: usize = 8; +const CONFIDENTIAL_LEN: usize = 33; +const CONFIDENTIAL_LEN_LESS_PREFIX: usize = CONFIDENTIAL_LEN - 1; +const CONF_PREFIX_1: u8 = 0x08; +const CONF_PREFIX_2: u8 = 0x09; + +/// A CT commitment to an amount +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Value { + /// No value + #[default] + Null, + /// Value is explicitly encoded + Explicit(ExplicitInner), + /// Value is committed + Confidential(ConfInner), +} + +impl Value { + /// Create value commitment. + pub fn new_confidential( + secp: &Secp256k1, + value: u64, + asset: Generator, + bf: BlindingFactor, + ) -> Self { + Self::Confidential(ConfInner::new(secp, value, bf.0, asset)) + } + + /// Create value commitment from assetID, asset blinding factor, + /// value and value blinding factor + pub fn new_confidential_from_assetid( + secp: &Secp256k1, + value: u64, + asset: AssetId, + v_bf: BlindingFactor, + a_bf: AssetBlindingFactor, + ) -> Self { + let generator = Generator::new_blinded(secp, asset.into_tag(), a_bf.0); + let comm = ConfInner::new(secp, value, v_bf.0, generator); + + Self::Confidential(comm) + } + + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Self::Null => 1, + Self::Explicit(..) => 1 + EXPLICIT_LEN, + Self::Confidential(..) => CONFIDENTIAL_LEN, + } + } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Self::Confidential(ConfInner::from_slice(bytes)?)) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { matches!(*self, Self::Explicit(_)) } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { matches!(*self, Self::Confidential(_)) } + + /// Returns the explicit inner value. + /// Returns [None] if [`Self::is_explicit`] returns false. + pub fn explicit(&self) -> Option { + match *self { + Self::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [`Self::is_confidential`] returns false. + pub fn commitment(&self) -> Option { + match *self { + Self::Confidential(i) => Some(i), + _ => None, + } + } +} + +impl From for Value { + fn from(from: ConfInner) -> Self { Self::Confidential(from) } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Null => f.write_str("null"), + Self::Explicit(n) => write!(f, "{}", n), + Self::Confidential(commitment) => write!(f, "{:02x}", commitment), + } + } +} + +impl Encodable for Value { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Self::Null => { + s.write_all(&[0u8])?; + Ok(1) + } + Self::Explicit(n) => { + s.write_all(&[1u8])?; + s.write_all(&n.to_be_bytes())?; + Ok(1 + EXPLICIT_LEN) + } + Self::Confidential(commitment) => { + s.write_all(&commitment.serialize())?; + Ok(CONFIDENTIAL_LEN) + } + } + } +} + +impl Decodable for Value { + fn consensus_decode(mut d: D) -> Result { + let mut buf = [0u8; CONFIDENTIAL_LEN]; + d.read_exact(&mut buf[0..1])?; + + match buf[0] { + 0 => Ok(Self::Null), + 1 => { + let mut buf = [0; EXPLICIT_LEN]; + d.read_exact(&mut buf)?; + Ok(Self::Explicit(u64::from_be_bytes(buf))) + } + p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { + d.read_exact(&mut buf[1..])?; + Ok(Self::Confidential(ConfInner::from_slice(&buf)?)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Value { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = match *self { + Self::Null => 1, + Self::Explicit(_) | Self::Confidential(_) => 2, + }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Self::Null => seq.serialize_element(&0u8)?, + Self::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&u64::swap_bytes(n))?; + } + Self::Confidential(commitment) => { + seq.serialize_element(&2u8)?; + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Value { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Value; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix = access.next_element::()?; + match prefix { + Some(0) => Ok(Self::Value::Null), + Some(1) => match access.next_element()? { + Some(x) => Ok(Self::Value::Explicit(u64::swap_bytes(x))), + None => Err(A::Error::custom("missing explicit value")), + }, + Some(2) => match access.next_element()? { + Some(x) => Ok(Self::Value::Confidential(x)), + None => Err(A::Error::custom("missing pedersen commitment")), + }, + _ => Err(A::Error::custom("wrong or missing prefix")), + } + } + } + + d.deserialize_seq(CommitVisitor) + } +} + +/// Blinding factor used for value commitments. +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct BlindingFactor(pub(crate) Tweak); + +impl BlindingFactor { + /// Generate random value blinding factor. + pub fn new(rng: &mut R) -> Self { Self(Tweak::new(rng)) } + + /// Parse a blinding factor from a 64-character hex string. + #[deprecated(since = "0.27.0", note = "use s.parse() instead")] + pub fn from_hex(s: &str) -> Result { s.parse() } + + /// Create the value blinding factor of the last output of a transaction. + pub fn last( + secp: &Secp256k1, + value: u64, + abf: AssetBlindingFactor, + inputs: &[(u64, AssetBlindingFactor, Self)], + outputs: &[(u64, AssetBlindingFactor, Self)], + ) -> Self { + let set_a = inputs + .iter() + .map(|(value, abf, vbf)| CommitmentSecrets { + value: *value, + value_blinding_factor: vbf.0, + generator_blinding_factor: abf.into_inner(), + }) + .collect::>(); + let set_b = outputs + .iter() + .map(|(value, abf, vbf)| CommitmentSecrets { + value: *value, + value_blinding_factor: vbf.0, + generator_blinding_factor: abf.into_inner(), + }) + .collect::>(); + + Self(compute_adaptive_blinding_factor(secp, value, abf.0, &set_a, &set_b)) + } + + /// Create from bytes. + pub fn from_slice(bytes: &[u8]) -> Result { + Ok(Self(Tweak::from_slice(bytes)?)) + } + + /// Returns the inner value. + pub fn into_inner(self) -> Tweak { self.0 } + + /// Get a unblinded/zero `AssetBlinding` factor + pub fn zero() -> Self { Self(ZERO_TWEAK) } +} + +impl AddAssign for BlindingFactor { + fn add_assign(&mut self, other: Self) { + if self.0.as_ref() == &[0u8; 32] { + *self = other; + } else if other.0.as_ref() == &[0u8; 32] { + // nothing to do + } else { + // Since libsecp does not expose low level APIs + // for scalar arethematic, we need to abuse secret key + // operations for this + let sk2 = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key"); + let sk = SecretKey::from_slice(other.into_inner().as_ref()).expect("Valid key"); + // The only reason that secret key addition can fail + // is when the keys add up to zero since we have already checked + // keys are in valid secret keys + match sk.add_tweak(&sk2.into()) { + Ok(sk_tweaked) => + *self = Self::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), + Err(_) => *self = Self::zero(), + } + } + } +} + +impl Neg for BlindingFactor { + type Output = Self; + + fn neg(self) -> Self::Output { + if self.0.as_ref() == &[0u8; 32] { + self + } else { + let sk = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key").negate(); + Self::from_slice(sk.as_ref()).expect("Valid Tweak") + } + } +} + +impl core::borrow::Borrow<[u8]> for BlindingFactor { + fn borrow(&self) -> &[u8] { &self.0[..] } +} + +hex::impl_fmt_traits! { + #[display_backward(true)] + impl fmt_traits for BlindingFactor { + const LENGTH: usize = 32; + } +} + +impl str::FromStr for BlindingFactor { + type Err = encode::Error; + + fn from_str(s: &str) -> Result { + let mut slice: [u8; 32] = hex::decode_to_array(s)?; + slice.reverse(); + + let inner = Tweak::from_inner(slice)?; + Ok(Self(inner)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for BlindingFactor { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(&self) + } else { + s.serialize_bytes(&self.0[..]) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for BlindingFactor { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + struct HexVisitor; + + impl ::serde::de::Visitor<'_> for HexVisitor { + type Value = BlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an ASCII hex string") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + if let Ok(hex) = ::std::str::from_utf8(v) { + hex.parse().map_err(E::custom) + } else { + Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + v.parse().map_err(E::custom) + } + } + + d.deserialize_str(HexVisitor) + } else { + struct BytesVisitor; + + impl ::serde::de::Visitor<'_> for BytesVisitor { + type Value = BlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("a bytestring") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + use core::convert::TryFrom; + + match <[u8; 32]>::try_from(v) { + Ok(ret) => { + let inner = Tweak::from_inner(ret).map_err(E::custom)?; + Ok(BlindingFactor(inner)) + } + Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), + } + } + } + + d.deserialize_bytes(BytesVisitor) + } + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`Asset`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(CommitmentEncoder<'e>); +} + +impl encoding::Encode for Value { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(match *self { + Self::Null => CommitmentEncoder::Null(0), + Self::Explicit(ref id) => CommitmentEncoder::Explicit8(Some(1), id.to_be_bytes()), + Self::Confidential(ref gen) => CommitmentEncoder::Explicit33(gen.serialize()), + }) + } +} + +decoder_state_machine! { + /// A decoder for the [`Value`] type. + pub struct Decoder(enum DecoderInner { + Done(Value), + Errored, + DecodePrefix { + decoder: encoding::ArrayDecoder<1>, + => transition_decode_prefix(prefix, ...) -> Result { + match prefix { + [0] => Ok(DecoderInner::Done(Value::Null)), + [1] => { + Ok(DecoderInner::DecodeExplicit { decoder: encoding::ArrayDecoder::default() }) + }, + [prefix @ (CONF_PREFIX_1 | CONF_PREFIX_2)] => { + Ok(DecoderInner::DecodeConfidential { decoder: encoding::ArrayDecoder::default(), prefix }) + }, + [prefix] => Err(DecoderErrorInner::InvalidConfidentialPrefix { prefix }) + } + } + }, + DecodeExplicit { + decoder: encoding::ArrayDecoder + => transition_decode_explicit(bytes, ...) -> Result { + Ok(DecoderInner::Done(Value::Explicit(u64::from_be_bytes(bytes)))) + } + }, + DecodeConfidential { + decoder: encoding::ArrayDecoder, + prefix: u8 + => transition_decode_confidential(x_coord, ...) -> Result { + let mut bytes = [0; CONFIDENTIAL_LEN]; + bytes[0] = prefix; + bytes[1..].copy_from_slice(&x_coord); + let gen = ConfInner::from_slice(&bytes) + .map_err(DecoderErrorInner::InvalidCommitment)?; + Ok(DecoderInner::Done(Value::Confidential(gen))) + } + }, + }); + + /// A decoder error for the [`Value`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + [macro-inserted decoder variants] + /// Confidential prefix was not one of the two allowable values. + InvalidConfidentialPrefix { + prefix: u8, + }, + /// Malformed confidential commitment. + InvalidCommitment(secp256k1_zkp::Error), + }); +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(_) => f.write_str("failed to decode prefix"), + Inner::DecodeExplicit(_) => f.write_str("failed to decode explicit value"), + Inner::DecodeConfidential(_) => f.write_str("failed to decode confidential value"), + Inner::InvalidConfidentialPrefix { prefix, .. } => { + write!( + f, + "confidential prefix 0x{:02x} was not one of 0, 1, 0x{:02x} or 0x{:02x}", + prefix, CONF_PREFIX_1, CONF_PREFIX_2, + ) + } + Inner::InvalidCommitment(_) => f.write_str("failed to parse confidential commitment"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(ref e) => Some(e), + Inner::DecodeExplicit(ref e) => Some(e), + Inner::DecodeConfidential(ref e) => Some(e), + Inner::InvalidConfidentialPrefix { .. } => None, + Inner::InvalidCommitment(ref e) => Some(e), + } + } +} + +impl Default for Decoder { + fn default() -> Self { + Self(DecoderInner::DecodePrefix { decoder: encoding::ArrayDecoder::default() }) + } +} diff --git a/src/dynafed.rs b/src/dynafed.rs index e5e29d14..c2f0227c 100644 --- a/src/dynafed.rs +++ b/src/dynafed.rs @@ -23,7 +23,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::ser::{SerializeSeq, SerializeStruct}; use crate::encode::{self, Encodable, Decodable}; -use crate::hashes::{Hash, sha256d}; +use crate::hashes::sha256d; use crate::Script; impl_sha256_midstate_wrapper! { @@ -149,7 +149,7 @@ impl FullParams { let compact_root = crate::fast_merkle_root::fast_merkle_root(&leaves[..]); let leaves = [ - compact_root.to_byte_array(), + compact_root.to_parts().0, self.extra_root().to_byte_array(), ]; ParamsRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])) @@ -364,7 +364,7 @@ impl Params { serialize_hash(self.signblockscript().unwrap()).to_byte_array(), serialize_hash(&self.signblock_witness_limit().unwrap()).to_byte_array(), ]; - let compact_root = crate::fast_merkle_root::fast_merkle_root(&leaves[..]); + let compact_root = ElidedRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])); let leaves = [ compact_root.to_byte_array(), @@ -718,8 +718,8 @@ mod tests { signblock_witness: vec![], }, version: Default::default(), - prev_blockhash: BlockHash::all_zeros(), - merkle_root: TxMerkleNode::all_zeros(), + prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, + merkle_root: TxMerkleNode::from_byte_array([0; 32]), time: Default::default(), height: Default::default(), }; diff --git a/src/encode.rs b/src/encode.rs index a0bbc0af..578e2d87 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -20,7 +20,7 @@ use std::{any, error, fmt, io, mem}; use bitcoin::ScriptBuf; use hex::{DecodeFixedLengthBytesError, DecodeVariableLengthBytesError}; -use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak}; +use secp256k1_zkp::{self, Tweak}; use crate::hashes::{sha256, Hash}; use crate::pset; @@ -29,6 +29,39 @@ pub use bitcoin::{self, consensus::encode::MAX_VEC_SIZE}; use crate::taproot::TapLeafHash; +/// Adaptor to count bytes, used to implement Encodable/Decodable +/// in terms of the new Encode/Decode traits. +pub(crate) struct ByteCounter { + inner: W, + count: usize, +} + +impl ByteCounter { + pub(crate) fn new(inner: W) -> Self { + Self { inner, count: 0 } + } + + pub(crate) fn into_count(self) -> usize { + self.count + } +} + +impl io::Write for ByteCounter + where W: io::Write +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + let res = self.inner.write(buf); + if let Ok(size) = res { + self.count += size; + } + res + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + /// Encoding error #[derive(Debug)] pub enum Error { @@ -63,6 +96,10 @@ pub enum Error { BadLockTime(crate::LockTime), /// `VarInt` was encoded in a non-minimal way. NonMinimalVarInt, + /// Error decoding a pegin witness. + PeginWitness(crate::PeginWitnessDecoderError), + /// Error decoding a script witness. + Witness(crate::WitnessDecoderError), } impl fmt::Display for Error { @@ -90,6 +127,8 @@ impl fmt::Display for Error { Error::HexVariableError(ref e) => write!(f, "Hex variable error: {}", e), Error::BadLockTime(ref lt) => write!(f, "Invalid locktime {}", lt), Error::NonMinimalVarInt => write!(f, "non-minimal varint"), + Self::PeginWitness(..) => f.write_str("error decoding pegin witness"), + Self::Witness(..) => f.write_str("error decoding script witness"), } } } @@ -98,6 +137,8 @@ impl error::Error for Error { fn cause(&self) -> Option<&dyn error::Error> { match *self { Error::Secp256k1zkp(ref e) => Some(e), + Self::PeginWitness(ref e) => Some(e), + Self::Witness(ref e) => Some(e), _ => None, } } @@ -240,6 +281,24 @@ impl Decodable for crate::locktime::Time { } } +impl Encodable for crate::Witness { + fn consensus_encode(&self, e: W) -> Result { + let mut counter = ByteCounter::new(e); + crate::encoding::encode_to_writer(self, &mut counter)?; + Ok(counter.into_count()) + } +} + +impl Decodable for crate::Witness { + fn consensus_decode(d: D) -> Result { + match crate::encoding::decode_from_read_unbuffered(d) { + Ok(wit) => Ok(wit), + Err(crate::encoding::ReadError::Io(e)) => Err(Error::Io(e)), + Err(crate::encoding::ReadError::Decode(e)) => Err(Error::Witness(e)), + } + } +} + /// A variable sized integer. pub struct VarInt(pub u64); impl Encodable for VarInt { @@ -298,16 +357,14 @@ impl Decodable for bitcoin::ScriptBuf { } } -impl Encodable for bitcoin::hashes::sha256d::Hash { +impl Encodable for hashes::sha256d::Hash { fn consensus_encode(&self, mut w: W) -> Result { self.as_byte_array().consensus_encode(&mut w) } } -impl Decodable for bitcoin::hashes::sha256d::Hash { +impl Decodable for hashes::sha256d::Hash { fn consensus_decode(d: D) -> Result { - Ok(Self::from_byte_array( - <::Bytes>::consensus_decode(d)?, - )) + Ok(Self::from_byte_array(<[u8; 32]>::consensus_decode(d)?)) } } @@ -419,31 +476,6 @@ impl_array!(20); impl_array!(32); impl_array!(33); -macro_rules! impl_box_option { - ($type: ty) => { - impl Encodable for Option> { - #[inline] - fn consensus_encode(&self, e: W) -> Result { - match self { - None => Vec::::new().consensus_encode(e), - Some(v) => v.serialize().consensus_encode(e), - } - } - } - - impl Decodable for Option> { - #[inline] - fn consensus_decode(mut d: D) -> Result { - let v: Vec = Decodable::consensus_decode(&mut d)?; - if v.is_empty() { - Ok(None) - } else { - Ok(Some(Box::new(<$type>::from_slice(&v)?))) - } - } - } - }; -} // special implementations for elements only fields impl Encodable for Tweak { fn consensus_encode(&self, e: W) -> Result { @@ -457,32 +489,6 @@ impl Decodable for Tweak { } } -impl Encodable for RangeProof { - fn consensus_encode(&self, e: W) -> Result { - self.serialize().consensus_encode(e) - } -} - -impl Decodable for RangeProof { - fn consensus_decode(d: D) -> Result { - Ok(RangeProof::from_slice(&>::consensus_decode(d)?)?) - } -} - -impl Encodable for SurjectionProof { - fn consensus_encode(&self, e: W) -> Result { - self.serialize().consensus_encode(e) - } -} - -impl Decodable for SurjectionProof { - fn consensus_decode(d: D) -> Result { - Ok(SurjectionProof::from_slice(&>::consensus_decode( - d, - )?)?) - } -} - impl Encodable for sha256::Hash { fn consensus_encode(&self, s: S) -> Result { self.to_byte_array().consensus_encode(s) @@ -510,6 +516,3 @@ impl Decodable for TapLeafHash { )) } } - -impl_box_option!(RangeProof); -impl_box_option!(SurjectionProof); diff --git a/src/fast_merkle_root.rs b/src/fast_merkle_root.rs index 4d88ea81..7d7c5abe 100644 --- a/src/fast_merkle_root.rs +++ b/src/fast_merkle_root.rs @@ -12,7 +12,7 @@ // If not, see . // -use crate::hashes::{sha256, Hash, HashEngine}; +use crate::hashes::{sha256, HashEngine}; /// Calculate a single sha256 midstate hash of the given left and right leaves. #[inline] @@ -20,7 +20,7 @@ fn sha256midstate(left: &[u8], right: &[u8]) -> sha256::Midstate { let mut engine = sha256::Hash::engine(); engine.input(left); engine.input(right); - engine.midstate() + engine.midstate().expect("hashing exactly 64 bytes") } /// Compute the Merkle root of the give hashes using mid-state only. @@ -45,14 +45,14 @@ pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { let mut inner: [sha256::Midstate; 32] = Default::default(); let mut count: u32 = 0; while (count as usize) < leaves.len() { - let mut temp_hash = sha256::Midstate::from_byte_array(leaves[count as usize]); + let mut temp_hash = sha256::Midstate::new(leaves[count as usize], 64); count += 1; // For each of the lower bits in count that are 0, do 1 step. Each // corresponds to an inner value that existed before processing the // current leaf, and each needs a hash to combine it. let mut level = 0; while count & (1u32 << level) == 0 { - temp_hash = sha256midstate(&inner[level][..], &temp_hash[..]); + temp_hash = sha256midstate(inner[level].as_parts().0, temp_hash.as_parts().0); level += 1; } // Store the resulting hash at inner position level. @@ -81,7 +81,7 @@ pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { count += 1 << level; level += 1; while count & (1u32 << level) == 0 { - result_hash = sha256midstate(&inner[level][..], &result_hash[..]); + result_hash = sha256midstate(inner[level].as_parts().0, result_hash.as_parts().0); level += 1; } } @@ -92,11 +92,15 @@ pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { #[cfg(test)] mod tests { use super::fast_merkle_root; - use crate::hashes::sha256; - use std::str::FromStr; #[test] fn test_fast_merkle_root() { + fn decode_hex(hex: &str) -> [u8; 32] { + let mut ret = hex::decode_to_array(hex).unwrap(); + ret.reverse(); + ret + } + // unit test vectors from Elements Core let test_leaves = [ "b66b041650db0f297b53f8d93c0e8706925bf3323f8c59c14a6fac37bfdcd06f", @@ -116,9 +120,9 @@ mod tests { let mut leaves = vec![]; for i in 0..4 { let root = fast_merkle_root(&leaves); - assert_eq!(root, FromStr::from_str(test_roots[i]).unwrap(), "root #{}", i); - leaves.push(sha256::Midstate::from_str(test_leaves[i]).unwrap().to_byte_array()); + assert_eq!(root.to_parts().0, decode_hex(test_roots[i]), "root #{i}"); + leaves.push(decode_hex(test_leaves[i])); } - assert_eq!(fast_merkle_root(&leaves), FromStr::from_str(test_roots[4]).unwrap()); + assert_eq!(fast_merkle_root(&leaves).to_parts().0, decode_hex(test_roots[4])); } } diff --git a/src/genesis.rs b/src/genesis.rs index 74c7d4d9..d10367eb 100644 --- a/src/genesis.rs +++ b/src/genesis.rs @@ -15,13 +15,11 @@ //! Helpers to calculate the genesis block for a given network. use bitcoin::secp256k1::impl_array_newtype; -use secp256k1_zkp::Tweak; -use crate::hashes::{sha256, sha256d, Hash, HashEngine}; +use crate::hashes::{sha256, HashEngine}; use crate::opcodes::all::OP_RETURN; use crate::opcodes::OP_TRUE; -use crate::pset::serialize::Serialize; use crate::{confidential, script, AssetId, Block, BlockExtData, BlockHash, BlockHeader, LockTime, Script, Sequence, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness}; -use crate::{AssetIssuance, ContractHash, OutPoint, Txid}; +use crate::{AssetBlindingNonce, AssetEntropy, AssetIssuance, ContractHash, OutPoint, Txid}; use crate::confidential::Nonce; /// Parameters that influence chain consensus. The contents of the genesis block for a given network @@ -89,12 +87,18 @@ impl NetworkParams { } /// Hash commitment of network parameters for a given Network -pub fn commit_to_custom_network_parameters(params: &NetworkParams) -> Vec { +pub fn commit_to_custom_network_parameters(params: &NetworkParams) -> sha256::Hash { + use hex::{BytesToHexIter, Case}; + let mut eng = sha256::Hash::engine(); eng.input(params.network_id.clone().as_bytes()); - eng.input(format!("{:x}", params.fedpeg_script).as_bytes()); - eng.input(format!("{:x}", params.sign_block_script).as_bytes()); - sha256::Hash::from_engine(eng).serialize() + for ch in BytesToHexIter::new(params.fedpeg_script[..].iter(), Case::Lower).flatten() { + eng.input(&[ch.into()]); + } + for ch in BytesToHexIter::new(params.sign_block_script[..].iter(), Case::Lower).flatten() { + eng.input(&[ch.into()]); + } + sha256::Hash::from_engine(eng) } /// Produce the genesis transaction for a given elements Network @@ -105,7 +109,7 @@ fn liquid_genesis_tx(network_params: &NetworkParams) -> Transaction { previous_output: OutPoint::default(), is_pegin: false, script_sig: script::Builder::new() - .push_slice(commit.as_slice()) + .push_slice(commit.as_byte_array()) .into_script(), sequence: Sequence::default(), asset_issuance: AssetIssuance::default(), @@ -135,14 +139,14 @@ fn liquid_genesis_asset_tx(network_params: &NetworkParams) -> Option Block { let tx = liquid_genesis_tx(params); let mut txdata = vec![tx.clone()]; - let merkle_root: sha256d::Hash = + let merkle_root: crate::TxMerkleNode = if let Some(asset_tx) = liquid_genesis_asset_tx(params) { + // To use `bitcoin::merkle_tree::calculate_root` from bitcoin 0.32, we need a hash + // with fairly specific trait bounds, even though it's just used as a byte array. + // sha256d::Hash from the rust-bitcoin version of bitcoin_hashes works. + use bitcoin::hashes::sha256d::Hash as MerkleHash; + use bitcoin::hashes::Hash as _; + txdata.push(asset_tx.clone()); - let tx_hashes = vec![tx.txid().to_raw_hash(), asset_tx.txid().to_raw_hash()]; - bitcoin::merkle_tree::calculate_root(tx_hashes.into_iter()) - .expect("merkle root") + let tx_hashes = [ + MerkleHash::from_byte_array(tx.txid().to_byte_array()), + MerkleHash::from_byte_array(asset_tx.txid().to_byte_array()), + ]; + let root = bitcoin::merkle_tree::calculate_root(tx_hashes.iter().copied()) + .expect("merkle root"); + crate::TxMerkleNode::from_byte_array(root.to_byte_array()) } else { - tx.txid().to_raw_hash() + crate::TxMerkleNode::from_byte_array(tx.txid().to_byte_array()) }; Block { header: BlockHeader { version: 1, - prev_blockhash: BlockHash::all_zeros(), - merkle_root: merkle_root.into(), + prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, + merkle_root, time: 1_296_688_602, height: 0, ext: BlockExtData::Proof { @@ -234,7 +248,6 @@ impl ChainHash { #[cfg(test)] mod test { use crate::genesis::{genesis_block, ChainHash, NetworkParams}; - use crate::hashes::Hash; #[test] fn genesis_block_hash() { diff --git a/src/hash_types.rs b/src/hash_types.rs index b4ce1861..cf0b157f 100644 --- a/src/hash_types.rs +++ b/src/hash_types.rs @@ -18,7 +18,7 @@ //! to avoid mixing data of the same hash format (like `SHA256d`) but of different meaning //! (transaction id, block hash etc). -use crate::hashes::{hash160, hash_newtype, sha256, sha256d, Hash}; +use crate::hashes::{hash160, sha256, sha256d}; // Re-export bitcoin's pubkeyhash types. We already re-export bitcoin's `PublicKey` type. pub use bitcoin::{PubkeyHash, WPubkeyHash}; @@ -30,7 +30,7 @@ macro_rules! impl_hashencode { &self, w: W, ) -> Result { - self.0.consensus_encode(w) + self.as_byte_array().consensus_encode(w) } } @@ -44,32 +44,53 @@ macro_rules! impl_hashencode { }; } -hash_newtype! { +hashes::hash_newtype! { /// An elements transaction ID - pub struct Txid(sha256d::Hash); + pub struct Txid(pub(crate) sha256d::Hash); /// An elements witness transaction ID - pub struct Wtxid(sha256d::Hash); + pub struct Wtxid(pub(crate) sha256d::Hash); /// An elements blockhash - pub struct BlockHash(sha256d::Hash); + pub struct BlockHash(pub(crate) sha256d::Hash); /// "Hash of the transaction according to the signature algorithm" - pub struct Sighash(sha256d::Hash); + pub struct Sighash(pub(crate) sha256d::Hash); /// A hash of Bitcoin Script bytecode. - pub struct ScriptHash(hash160::Hash); + pub struct ScriptHash(pub(crate) hash160::Hash); /// SegWit version of a Bitcoin Script bytecode hash. - pub struct WScriptHash(sha256::Hash); + pub struct WScriptHash(pub(crate) sha256::Hash); /// A hash of the Merkle tree branch or root for transactions pub struct TxMerkleNode(sha256d::Hash); } + +hashes::impl_hex_for_newtype!(Txid, Wtxid, BlockHash, ScriptHash, WScriptHash, TxMerkleNode); +#[cfg(feature = "serde")] +hashes::impl_serde_for_newtype!(Txid, Wtxid, BlockHash, ScriptHash, WScriptHash, TxMerkleNode); +// We do not implement serde or display/fromstr for 'Sighash'. In general it's dangerous +// to deserialize sighashes; they must be the output of a cryptographic hash function. +hashes::impl_debug_only_for_newtype!(Sighash); + impl_hashencode!(Txid); impl_hashencode!(Wtxid); impl_hashencode!(Sighash); impl_hashencode!(BlockHash); impl_hashencode!(TxMerkleNode); +impl BlockHash { + /// Dummy hash used as the previous blockhash of the genesis block. + pub const GENESIS_PREVIOUS_BLOCK_HASH: Self = Self::from_byte_array([0; 32]); +} + +impl Txid { + /// The `Txid` used in a coinbase prevout. + /// + /// This is used as the "txid" of the dummy input of a coinbase transaction. This is not a real + /// TXID and should not be used in any other contexts. + pub const COINBASE_PREVOUT: Self = Self::from_byte_array([0; 32]); +} + impl ScriptHash { /// Computes the `ScriptHash` of a script. pub fn hash_script(s: &crate::Script) -> Self { diff --git a/src/internal_macros.rs b/src/internal_macros.rs index 7dfbcf38..3a31bb62 100644 --- a/src/internal_macros.rs +++ b/src/internal_macros.rs @@ -406,23 +406,23 @@ macro_rules! impl_sha256_midstate_wrapper { impl $ty { /// Constructs this wrapper struct from raw bytes. - pub fn from_byte_array(inner: [u8; 32]) -> Self { + pub const fn from_byte_array(inner: [u8; 32]) -> Self { Self(inner) } /// The raw bytes within the wrapper type. - pub fn as_byte_array(&self) -> &[u8; 32] { + pub const fn as_byte_array(&self) -> &[u8; 32] { &self.0 } /// The raw bytes within the wrapper type. - pub fn to_byte_array(self) -> [u8; 32] { + pub const fn to_byte_array(self) -> [u8; 32] { self.0 } /// (Private) convert a sha256 midstate to an object. - fn from_midstate(value: crate::hashes::sha256::Midstate) -> Self { - Self(value.to_byte_array()) + const fn from_midstate(value: crate::hashes::sha256::Midstate) -> Self { + Self(value.to_parts().0) } } @@ -527,3 +527,547 @@ macro_rules! hex_deserialize( macro_rules! hex_script( ($e:expr) => (crate::Script::from_hex_no_prefix($e).expect("hex decoding")) ); + +/// Generates a state machine decoder satisfying the [`crate::encoding::Decoder`] trait. +/// +/// This macro creates a decoder that processes input bytes through multiple states, +/// transitioning between states based on decoded data. It automatically generates +/// both the decoder struct and associated error types. +/// +/// # Syntax +/// +/// ```ignore +/// decoder_state_machine! { +/// /// Documentation for the decoder struct +/// pub struct DecoderName(enum InnerEnumName { +/// Done(TargetType), +/// Errored, +/// StateName { +/// decoder: DecoderType, +/// field1: FieldType1, +/// field2: FieldType2, +/// => transition_function_name(output, ...) -> Result { +/// // Transition logic that returns Ok(NextState) or Err(error) +/// } +/// }, +/// // ... more states +/// }); +/// +/// /// Documentation for the error struct +/// pub struct ErrorName(enum InnerErrorName { +/// [macro-inserted decoder variants] +/// CustomError1(ErrorType1), +/// CustomError2 { field: ErrorType2 }, +/// // ... custom error variants +/// }); +/// } +/// ``` +/// +/// # Generated Code +/// +/// The macro generates: +/// - A public decoder struct wrapping a private enum +/// - A private enum with states, Done, and Errored variants +/// - Transition functions for each state +/// - A public error struct wrapping a private error enum +/// - A private error enum with decoder errors and custom variants +/// - `Decoder` trait implementation with `push_bytes`, `end`, and `read_limit` methods +/// - `Decode` impl for the target type +/// +/// The macro does **not** generate a `fmt::Display` or `std::error::Error` impl +/// for `ErrorName`, so the user must implement these outside of the macro. +/// +/// The variants that the macro inserts into `ErrorName` have the same names +/// as the variants on `InnerEnumName`, and they contain a single value of +/// type `::Error`. +/// +/// # State Structure +/// +/// Each state must have: +/// - `decoder: SomeDecoderType` - The decoder for this state's data +/// - Optional additional fields to carry state between transitions +/// - A transition function that processes the decoder's output +/// +/// # Transition Functions +/// +/// Transition functions: +/// - Take the decoder's output as first parameter +/// - Take any additional state fields as subsequent parameters +/// - Return `Result` +/// - Are called automatically when the decoder completes +/// +/// # Error Handling +/// +/// The macro automatically: +/// - Creates error variants for each decoder type +/// - Maps decoder errors to the appropriate variant +/// +/// # Example Usage +/// +/// ```ignore +/// decoder_state_machine! { +/// /// Decodes a length-prefixed string +/// pub struct StringDecoder(enum StringDecoderInner { +/// Done(String), +/// Errored, +/// ReadLength { +/// decoder: encoding::ArrayDecoder<4> +/// => transition_read_length(length_bytes, ...) -> Result { +/// let length = u32::from_be_bytes(length_bytes) as usize; +/// if length > MAX_STRING_LENGTH { +/// return Err(StringDecoderErrorInner::StringTooLong { length }); +/// } +/// Ok(StringDecoderInner::ReadData { +/// decoder: encoding::VecDecoder::new(length), +/// expected_length: length +/// }) +/// } +/// }, +/// ReadData { +/// decoder: encoding::VecDecoder, +/// expected_length: usize +/// => transition_read_data(data, expected_length, ...) -> Result { +/// if data.len() != expected_length { +/// return Err(StringDecoderErrorInner::LengthMismatch); +/// } +/// let string = String::from_utf8(data) +/// .map_err(StringDecoderErrorInner::InvalidUtf8)?; +/// Ok(StringDecoderInner::Done(string)) +/// } +/// }, +/// }); +/// +/// /// Errors that can occur during string decoding +/// #[derive(Debug)] +/// pub struct StringDecoderError(enum StringDecoderErrorInner { +/// [macro-inserted decoder variants] +/// StringTooLong { length: usize }, +/// LengthMismatch, +/// InvalidUtf8(std::string::FromUtf8Error), +/// }); +/// } +/// ``` +macro_rules! decoder_state_machine { + ( + $(#[$($struct_attr:tt)*])* + pub struct $outer_ty:ident(enum $inner_ty:ident { + Done($target_ty:ty), + Errored, + $( + $variant:ident { + decoder: $decoder_ty:ty$(,)? + $(, $field:ident: $field_ty:ty)* + => $transition_fn:ident($output:ident, ...) -> Result { + $($transition_fn_inner:tt)* + } + }, + )* + }); + + $(#[$($error_struct_attr:tt)*])* + pub struct $error_ty:ident(enum $inner_error_ty:ident { + [macro-inserted decoder variants] + $($extra_variants:tt)* + }); + ) => { + $(#[$($struct_attr)*])* + pub struct $outer_ty($inner_ty); + + #[allow(clippy::large_enum_variant)] + enum $inner_ty { + $($variant { + decoder: $decoder_ty + $(, $field: $field_ty)* + },)* + Done($target_ty), + Errored, + } + + impl $inner_ty { + $( + #[inline] + #[allow(clippy::unnecessary_wraps)] // returns a Result even if it never returns Err + fn $transition_fn( + $output: <$decoder_ty as $crate::encoding::Decoder>::Output + $(, $field: $field_ty)* + ) -> Result { + $($transition_fn_inner)* + } + )* + } + + $(#[$($error_struct_attr)*])* + pub struct $error_ty($inner_error_ty); + + $(#[$($error_struct_attr)*])* + enum $inner_error_ty { + $( + $variant(<$decoder_ty as $crate::encoding::Decoder>::Error), + )* + $($extra_variants)* + } + + impl $crate::encoding::Decoder for $outer_ty { + type Output = $target_ty; + type Error = $error_ty; + + fn push_bytes( + &mut self, + bytes: &mut &[u8], + ) -> Result<$crate::encoding::DecoderStatus, Self::Error> { + use $inner_ty as Inner; + loop { + match core::mem::replace(&mut self.0, Inner::Errored) { + $( + Inner::$variant { mut decoder $(, $field)* } => { + if decoder + .push_bytes(bytes) + .map_err($inner_error_ty::$variant) + .map_err($error_ty)? + .needs_more() + { + self.0 = Inner::$variant { decoder $(, $field)* }; + return Ok($crate::encoding::DecoderStatus::NeedsMore); + } + + let output = decoder.end() + .map_err($inner_error_ty::$variant) + .map_err($error_ty)?; + self.0 = $inner_ty::$transition_fn(output $(, $field)*) + .map_err($error_ty)?; + } + )* + Inner::Done(out) => { + self.0 = Inner::Done(out); + return Ok($crate::encoding::DecoderStatus::Ready); + } + Inner::Errored => panic!("called push_bytes() on an errored decoder"), + + } + } + } + + fn end(self) -> Result { + use $inner_ty as Inner; + match self.0 { + $( + Inner::$variant { decoder, .. } => { + decoder.end() + .map_err($inner_error_ty::$variant) + .map_err($error_ty)?; + // This unreachable! can be hit by badly behaved decoders where push_bytes() + // returns DecoderStatus::NeedMore, but end() returns Ok. In general, end() + // should return Ok only if push_bytes has returned DecoderStatus::Ready. + unreachable!( + "end() succeeded on decoder for {} state, but we did not leave that state", + stringify!($variant), + ) + } + )* + Inner::Done(out) => Ok(out), + Inner::Errored => panic!("called end() on an errored decoder"), + } + } + + fn read_limit(&self) -> usize { + use $inner_ty as Inner; + match self.0 { + $( + Inner::$variant { ref decoder, .. } => decoder.read_limit(), + )* + Inner::Done(_) | Inner::Errored => 0, + } + } + } + + impl $crate::encoding::Decode for $target_ty { + type Decoder = $outer_ty; + } + }; +} + +/// Generates a simple decoder wrapper that converts output from an inner decoder. +/// +/// This macro creates a decoder that wraps an existing decoder and applies a conversion +/// function to transform the inner decoder's output into the target type. It's simpler +/// than `decoder_state_machine!` as it only wraps a single decoder without state transitions. +/// +/// # Syntax +/// +/// There are two invocation patterns: +/// +/// ## Direct Error Wrapping +/// ```ignore +/// decoder_newtype! { +/// /// Documentation for the decoder struct +/// pub struct DecoderName(InnerDecoderType); +/// +/// /// Documentation for the error struct +/// pub struct ErrorName(InnerErrorName); +/// const ERROR_DISPLAY = "output of the Display::fmt function"; +/// +/// impl Decode for TargetType { +/// fn convert_inner(output_var) -> Result<_, InnerErrorName> { +/// // Conversion logic -- returns `InnerErrorName` for errors! +/// } +/// } +/// } +/// ``` +/// +/// In this case `InnerErrorName` must be an already-existing error type. +/// +/// ## Error Enum Wrapping +/// ```ignore +/// decoder_newtype! { +/// /// Documentation for the decoder struct +/// pub struct DecoderName(InnerDecoderType); +/// +/// /// Documentation for the error struct +/// pub struct ErrorName(enum InnerErrorName { +/// // The first variant must be called Decode and hold the inner decoder type. +/// Decode(InnerDecoderErrorType), +/// // All other variants are free-form. +/// CustomError1(ErrorType1), +/// CustomError2 { field: ErrorType2 }, +/// // ... custom error variants +/// }); +/// +/// impl Decode for TargetType { +/// fn convert_inner(output_var) -> Result<_, ErrorName> { +/// // Conversion logic -- returns `ErrorName` for errors! +/// } +/// } +/// } +/// ``` +/// +/// In this case `InnerErrorName` will be generated as a private enum by the macro. +/// +/// # Generated Code +/// +/// The macro generates: +/// - A public decoder struct wrapping the inner decoder +/// - A public error struct wrapping a private error enum +/// - A private error enum with `Decode` variant and custom variants +/// - `Decoder` trait implementation with `push_bytes`, `end`, and `read_limit` methods +/// - `Decode` trait implementation for the target type +/// +/// **When using the enum pattern, the macro does not generate a `fmt::Display` or +/// `std::error::Error` impl for `ErrorName`. The user must implement these outside +/// of the macro.** When using the non-enum pattern, both `Display` and `Error` are +/// generated by the macro. +/// +/// # Parameters +/// +/// - `DecoderName` - The wrapper decoder struct name +/// - `InnerDecoderType` - The existing decoder type to wrap +/// - `ErrorName` - The error struct name +/// - `InnerErrorName` - The private error enum name +/// - `InnerDecoderErrorType` - The error type from the inner decoder +/// - `TargetType` - The final output type after conversion +/// - `convert_inner` - Function that converts inner output to target type +/// +/// # Conversion Function +/// +/// The conversion function: +/// - Takes the inner decoder's output as its parameter +/// - Returns `Result` for enum pattern, `Result` +/// for direct-wrapping pattern. +/// - Is called automatically when the inner decoder completes +/// - Can return custom errors defined in the error enum (enum pattern only) +/// +/// # Pattern Selection +/// +/// - Use **direct error wrapping** when conversion cannot fail or only needs to propagate the inner decoder's errors +/// - Use **error enum wrapping** when conversion can fail with custom error types +/// +/// # Error Handling +/// +/// The macro automatically: +/// - Creates a `Decode` variant containing the inner decoder's error +/// - Maps inner decoder errors to the `Decode` variant +/// - Allows custom error variants for conversion failures +/// +/// # Example Usage +/// +/// ## Direct Error Wrapping +/// ```ignore +/// decoder_newtype! { +/// /// Decoder for the [`TxOut`] type. +/// #[derive(Default)] +/// pub struct TxOutDecoder(Decoder4< crate::confidential::AssetDecoder, +/// crate::confidential::ValueDecoder, +/// crate::confidential::NonceDecoder, +/// crate::script::ScriptDecoder, +/// >); +/// +/// /// Decoder error for the [`TxOut`] type. +/// #[derive(Clone, PartialEq, Eq, Debug)] +/// pub struct TxOutDecoderError(Decoder4Error< +/// crate::confidential::AssetDecoderError, +/// crate::confidential::ValueDecoderError, +/// crate::confidential::NonceDecoderError, +/// crate::script::ScriptDecoderError, +/// >); +/// const ERROR_DISPLAY = "error decoding transaction output witness"; +/// +/// impl Decode for TxOut { +/// fn convert_inner(output) -> Result<_, TxOutDecoderErrorInner> { +/// let (asset, value, nonce, script_pubkey) = output; +/// Ok(TxOut { asset, value, nonce, script_pubkey, witness: TxOutWitness::empty() }) +/// } +/// } +/// } +/// ``` +/// +/// ## Error Enum Wrapping +/// ```ignore +/// decoder_newtype! { +/// /// Decoder for range proofs +/// #[derive(Default)] +/// pub struct Decoder(encoding::ByteVecDecoder); +/// +/// /// Decoder error for range proofs +/// #[derive(Clone, PartialEq, Eq, Debug)] +/// pub struct DecoderError(enum DecoderErrorInner { +/// Decode(encoding::ByteVecDecoderError), +/// RangeProof(secp256k1_zkp::Error), +/// }); +/// +/// impl Decode for RangeProof { +/// fn convert_inner(v) -> Result<_, DecoderError> { +/// RangeProof::from_slice(&v).map_err(DecoderError::RangeProof) +/// } +/// } +/// } +/// ``` +macro_rules! decoder_newtype { + // With directly wrapped inner error + ( + $(#[$($struct_attr:tt)*])* + pub struct $outer_ty:ident($inner_ty:ty); + + $(#[$($error_struct_attr:tt)*])* + pub struct $error_ty:ident($inner_ty_error:ty); + + const ERROR_DISPLAY = $error_display:expr; + + impl Decode for $target_ty:ty { + fn convert_inner($output:ident) -> Result<_, $error_inner1:ty> { + $($output_fn_inner:tt)* + } + } + ) => { + $(#[$($struct_attr)*])* + pub struct $outer_ty($inner_ty); + + $(#[$($error_struct_attr)*])* + pub struct $error_ty($inner_ty_error); + + impl core::fmt::Display for $error_ty { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str($error_display) + } + } + + impl std::error::Error for $error_ty { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.0) + } + } + + impl $crate::encoding::Decoder for $outer_ty { + type Output = $target_ty; + type Error = $error_ty; + + fn push_bytes( + &mut self, + bytes: &mut &[u8], + ) -> Result<$crate::encoding::DecoderStatus, Self::Error> { + self.0 + .push_bytes(bytes) + .map_err($error_ty) + } + + fn end(self) -> Result { + let $output = self.0 + .end() + .map_err($error_ty)?; + let converted = { + $($output_fn_inner)* + }; + converted.map_err($error_ty) + } + + fn read_limit(&self) -> usize { + self.0.read_limit() + } + } + + impl $crate::encoding::Decode for $target_ty{ + type Decoder = $outer_ty; + } + }; + // With inner error enum + ( + $(#[$($struct_attr:tt)*])* + pub struct $outer_ty:ident($inner_ty:ty); + + $(#[$($error_struct_attr:tt)*])* + pub struct $error_ty:ident(enum $inner_error_ty:ident { + Decode($inner_ty_error:ty), + $($extra_variants:tt)* + }); + + impl Decode for $target_ty:ty { + fn convert_inner($output:ident) -> Result<_, $error_inner1:ty> { + $($output_fn_inner:tt)* + } + } + ) => { + $(#[$($struct_attr)*])* + pub struct $outer_ty($inner_ty); + + $(#[$($error_struct_attr)*])* + pub struct $error_ty($inner_error_ty); + + $(#[$($error_struct_attr)*])* + enum $inner_error_ty { + Decode($inner_ty_error), + $($extra_variants)* + } + + impl $crate::encoding::Decoder for $outer_ty { + type Output = $target_ty; + type Error = $error_ty; + + fn push_bytes( + &mut self, + bytes: &mut &[u8], + ) -> Result<$crate::encoding::DecoderStatus, Self::Error> { + self.0 + .push_bytes(bytes) + .map_err($inner_error_ty::Decode) + .map_err($error_ty) + } + + fn end(self) -> Result { + let $output = self.0 + .end() + .map_err($inner_error_ty::Decode) + .map_err($error_ty)?; + let converted = { + $($output_fn_inner)* + }; + converted.map_err($error_ty) + } + + fn read_limit(&self) -> usize { + self.0.read_limit() + } + } + + impl $crate::encoding::Decode for $target_ty{ + type Decoder = $outer_ty; + } + }; +} diff --git a/src/issuance.rs b/src/issuance.rs index f1f789ee..5b043fe7 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -14,10 +14,12 @@ //! Asset Issuance +use core::fmt; use std::io; +use crate::confidential::AssetBlindingFactor; use crate::encode::{self, Encodable, Decodable}; -use crate::hashes::{hash_newtype, sha256, sha256d, Hash}; +use crate::hashes::{hash_newtype, sha256, sha256d}; use crate::fast_merkle_root::fast_merkle_root; use secp256k1_zkp::Tag; use crate::genesis::{commit_to_custom_network_parameters, NetworkParams}; @@ -42,13 +44,167 @@ hash_newtype!( #[hash_newtype(backward)] pub struct ContractHash(sha256::Hash); ); - +hashes::impl_hex_for_newtype!(ContractHash); +#[cfg(feature = "serde")] +hashes::impl_serde_for_newtype!(ContractHash); impl_sha256_midstate_wrapper! { /// A hash of some data used as "asset entropy" to seed the ID of a new asset. pub struct AssetEntropy([u8; 32]); } +impl AssetEntropy { + /// The all-zeroes "entropy" used for new issuances (vs reissuances). + pub const NEW_ISSUANCE: Self = Self([0; 32]); + + /// Re-interpret the asset entropy as a contract hash. + pub fn into_contract_hash(self) -> ContractHash { + ContractHash::from_byte_array(self.0) + } +} + +impl Encodable for AssetEntropy { + fn consensus_encode(&self, e: W) -> Result { + self.0.consensus_encode(e) + } +} + +impl Decodable for AssetEntropy { + fn consensus_decode(d: D) -> Result { + <[u8; 32]>::consensus_decode(d).map(Self) + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`AssetEntropyEncoder`] type. + #[derive(Clone, Debug)] + pub struct AssetEntropyEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>); +} + +impl encoding::Encode for AssetEntropy { + type Encoder<'e> = AssetEntropyEncoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + AssetEntropyEncoder::new(encoding::ArrayRefEncoder::without_length_prefix(&self.0)) + } +} + +decoder_newtype! { + /// Decoder for the [`AssetEntropy`] type. + #[derive(Default)] + pub struct AssetEntropyDecoder(encoding::ArrayDecoder<32>); + + /// Decoder error for the [`AssetEntropy`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct AssetEntropyDecoderError(encoding::UnexpectedEofError); + const ERROR_DISPLAY = "error decoding asset entropy"; + + impl Decode for AssetEntropy { + fn convert_inner(bytes) -> Result<_, UnexpectedEofError> { + Ok(AssetEntropy::from_byte_array(bytes)) + } + } +} + +/// The blinding factor used to derive an asset commitment from an asset. +/// +/// This type represents either [`Self::NEW_ISSUANCE`], indicating that an asset +/// issuance is of a new asset, or for a reissuance, the [`AssetBlindingFactor`] +/// used to blind the reissuance token (which must be blinded in order to be +/// spent, due to a quirk in the Elements consensus code.) +/// +/// Conceptually this can be thought of as an `Option`, except +/// that there are no invalid values; [`AssetBlindingNonce::from_byte_array`] will +/// always succeed. However, if an out-of-range value is used, the transaction will +/// fail validation no matter what reissuance token is used. +/// +/// Also, **unlike [`AssetBlindingFactor`], this type represents public data**. You +/// can convert a blinding factor into a "blinding nonce", and while this conversion +/// is technically a no-op, conceptually it represents choosing to make the blinding +/// factor public. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Default)] +pub struct AssetBlindingNonce([u8; 32]); + +impl AssetBlindingNonce { + /// A null blinding nonce, representing a new issuance (vs a reissuance). + pub const NEW_ISSUANCE: Self = Self([0; 32]); + + /// Constructs this wrapper struct from raw bytes. + pub const fn from_byte_array(inner: [u8; 32]) -> Self { + Self(inner) + } + + /// The raw bytes within the wrapper type. + pub const fn as_byte_array(&self) -> &[u8; 32] { + &self.0 + } + + /// The raw bytes within the wrapper type. + pub const fn to_byte_array(self) -> [u8; 32] { + self.0 + } + + /// Whether this is the null "new issuance" blinding nonce. + pub fn is_null(&self) -> bool { + // This is surprisingly annoying to make into a constfn, so we don't + // bother for now. + *self == Self::NEW_ISSUANCE + } + + /// Reinterpret an asset blinding factor as a [`AssetBlindingNonce`]. + /// + /// This is something of a dangerous function, since in general blinding factors should + /// be considered secret data, while blinding nonces are public (they are encoded on + /// the blockchain). So callers of this function should be sure that this is a blinding + /// factor that they intend to reveal.) + pub fn from_blinding_factor(bf: AssetBlindingFactor) -> Self { + Self(*bf.into_inner().as_ref()) + } +} + +impl Encodable for AssetBlindingNonce { + fn consensus_encode(&self, e: W) -> Result { + self.0.consensus_encode(e) + } +} + +impl Decodable for AssetBlindingNonce { + fn consensus_decode(d: D) -> Result { + <[u8; 32]>::consensus_decode(d).map(Self) + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`AssetBlindingNonce`] type. + #[derive(Clone, Debug)] + pub struct AssetBlindingNonceEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>); +} + +impl encoding::Encode for AssetBlindingNonce { + type Encoder<'e> = AssetBlindingNonceEncoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + AssetBlindingNonceEncoder::new(encoding::ArrayRefEncoder::without_length_prefix(&self.0)) + } +} + +decoder_newtype! { + /// Decoder for the [`AssetBlindingNonce`] type. + #[derive(Default)] + pub struct AssetBlindingNonceDecoder(encoding::ArrayDecoder<32>); + + /// Decoder error for the [`AssetBlindingNonce`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct AssetBlindingNonceDecoderError(encoding::UnexpectedEofError); + const ERROR_DISPLAY = "error decoding asset blinding nonce"; + + impl Decode for AssetBlindingNonce { + fn convert_inner(bytes) -> Result<_, UnexpectedEofError> { + Ok(AssetBlindingNonce::from_byte_array(bytes)) + } + } +} + impl_sha256_midstate_wrapper! { /// An issued asset ID. pub struct AssetId([u8; 32]); @@ -62,15 +218,17 @@ impl ContractHash { /// the hash. #[cfg(feature = "json-contract")] pub fn from_json_contract(json: &str) -> Result { + use crate::hashes::HashEngine as _; + // Parsing the JSON into a BTreeMap will recursively order object keys // lexicographically. This order is respected when we later serialize // it again. let ordered: ::std::collections::BTreeMap = ::serde_json::from_str(json)?; - let mut engine = ContractHash::engine(); + let mut engine = sha256::Hash::engine(); ::serde_json::to_writer(&mut engine, &ordered).expect("engines don't error"); - Ok(ContractHash::from_engine(engine)) + Ok(ContractHash(engine.finalize())) } } @@ -167,8 +325,8 @@ impl AssetId { /// a Regtest parent network fn pegged_asset_id_for_params_and_parent_chain_hash(params: &NetworkParams, parent_chainhash: bitcoin::blockdata::constants::ChainHash) -> AssetId { let commit = commit_to_custom_network_parameters(params); - let asset_outpoint = OutPoint::new(Txid::from_slice(commit.as_slice()).expect("txid"), 0); - let asset_entropy = AssetId::generate_asset_entropy(asset_outpoint, ContractHash::from_slice(parent_chainhash.to_bytes().as_slice()).unwrap()); + let asset_outpoint = OutPoint::new(Txid::from_byte_array(commit.to_byte_array()), 0); + let asset_entropy = AssetId::generate_asset_entropy(asset_outpoint, ContractHash::from_byte_array(*parent_chainhash.as_ref())); AssetId::from_entropy(asset_entropy) } } @@ -185,6 +343,37 @@ impl Decodable for AssetId { } } +encoding::encoder_newtype_exact! { + /// Encoder for the [`AssetId`] type. + #[derive(Clone, Debug)] + pub struct AssetIdEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>); +} + +impl encoding::Encode for AssetId { + type Encoder<'e> = AssetIdEncoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + AssetIdEncoder::new(encoding::ArrayRefEncoder::without_length_prefix(&self.0)) + } +} + +decoder_newtype! { + /// Decoder for the [`AssetId`] type. + #[derive(Default)] + pub struct AssetIdDecoder(encoding::ArrayDecoder<32>); + + /// Decoder error for the [`AssetId`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct AssetIdDecoderError(encoding::UnexpectedEofError); + const ERROR_DISPLAY = "error decoding asset ID"; + + impl Decode for AssetId { + fn convert_inner(bytes) -> Result<_, UnexpectedEofError> { + Ok(AssetId::from_byte_array(bytes)) + } + } +} + #[cfg(test)] mod test { use super::*; @@ -263,28 +452,28 @@ mod test { let tether = ContractHash::from_str("3c7f0a53c2ff5b99590620d7f6604a7a3a7bfbaaa6aa61f7bfc7833ca03cde82").unwrap(); let correct = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDt","version":0}"#; - let expected = ContractHash::hash(correct.as_bytes()); - assert_eq!(tether, expected); - assert_eq!(expected, ContractHash::from_json_contract(correct).unwrap()); + let expected = sha256::Hash::hash(correct.as_bytes()).to_byte_array(); + assert_eq!(tether.to_byte_array(), expected); + assert_eq!(expected, ContractHash::from_json_contract(correct).unwrap().to_byte_array()); let invalid_json = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey:"#; assert!(ContractHash::from_json_contract(invalid_json).is_err()); let unordered = r#"{"precision":8,"ticker":"USDt","entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","version":0}"#; - assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap()); + assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap().to_byte_array()); let unordered = r#"{"precision":8,"name":"Tether USD","ticker":"USDt","entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","version":0}"#; - assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap()); + assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap().to_byte_array()); let spaces = r#"{"precision":8, "name" : "Tether USD", "ticker":"USDt", "entity":{"domain":"tether.to" }, "issuer_pubkey" :"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","version":0} "#; - assert_eq!(expected, ContractHash::from_json_contract(spaces).unwrap()); + assert_eq!(expected, ContractHash::from_json_contract(spaces).unwrap().to_byte_array()); let nested_correct = r#"{"entity":{"author":"Tether Inc","copyright":2020,"domain":"tether.to","hq":"Mars"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDt","version":0}"#; - let nested_expected = ContractHash::hash(nested_correct.as_bytes()); - assert_eq!(nested_expected, ContractHash::from_json_contract(nested_correct).unwrap()); + let nested_expected = sha256::Hash::hash(nested_correct.as_bytes()).to_byte_array(); + assert_eq!(nested_expected, ContractHash::from_json_contract(nested_correct).unwrap().to_byte_array()); let nested_unordered = r#"{"ticker":"USDt","entity":{"domain":"tether.to","hq":"Mars","author":"Tether Inc","copyright":2020},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"version":0}"#; - assert_eq!(nested_expected, ContractHash::from_json_contract(nested_unordered).unwrap()); + assert_eq!(nested_expected, ContractHash::from_json_contract(nested_unordered).unwrap().to_byte_array()); } #[test] diff --git a/src/lib.rs b/src/lib.rs index 64b74ebe..ea5b713f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,10 @@ /// Re-export of bitcoin crate pub extern crate bitcoin; +/// Re-export of bitcoin-consensus-encoding crate +pub extern crate encoding; +/// Re-export of `bitcoin_hashes` crate +pub extern crate hashes; /// Re-export of hex crate pub extern crate hex; /// Re-export of secp256k1-zkp crate @@ -65,8 +69,6 @@ mod parse; pub mod pset; pub mod schnorr; pub mod script; -#[cfg(feature = "serde")] -mod serde_utils; pub mod sighash; pub mod taproot; mod transaction; @@ -74,26 +76,45 @@ mod transaction; mod endian; pub mod genesis; -// re-export bitcoin deps which we re-use -pub use bitcoin::hashes; // export everything at the top level so it can be used as `elements::Transaction` etc. +use internals::array_vec::ArrayVec; + pub use crate::address::{Address, AddressError, AddressParams}; pub use crate::blind::{ - BlindAssetProofs, BlindError, BlindValueProofs, ConfidentialTxOutError, RangeProofMessage, - SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, CtLocation, CtLocationType, + BlindError, ConfidentialTxOutError, CtLocation, CtLocationType, RangeProofMessage, + SurjectionInput, TxOutError, TxOutSecrets, UnblindError, VerificationError, }; -pub use crate::block::ExtData as BlockExtData; -pub use crate::block::{Block, BlockHeader, DynafedRoot}; +pub use crate::block::{Block, BlockHeader, DynafedRoot, ExtData as BlockExtData}; +pub use crate::confidential::{RangeProof, SurjectionProof}; pub use crate::ext::{ReadExt, WriteExt}; pub use crate::fast_merkle_root::fast_merkle_root; pub use crate::hash_types::*; -pub use crate::issuance::{AssetEntropy, AssetId, ContractHash}; +pub use crate::issuance::{ + AssetBlindingNonce, AssetBlindingNonceDecoder, AssetBlindingNonceDecoderError, + AssetBlindingNonceEncoder, AssetEntropy, AssetEntropyDecoder, AssetEntropyDecoderError, + AssetEntropyEncoder, AssetId, AssetIdDecoder, AssetIdDecoderError, AssetIdEncoder, + ContractHash, +}; pub use crate::locktime::LockTime; pub use crate::schnorr::{SchnorrSig, SchnorrSigError}; pub use crate::script::Script; pub use crate::sighash::SchnorrSighashType; -pub use crate::transaction::Sequence; pub use crate::transaction::{ - AssetIssuance, EcdsaSighashType, OutPoint, PeginData, PegoutData, Transaction, TxIn, - TxInWitness, TxOut, TxOutWitness, + AssetIssuance, AssetIssuanceDecoder, AssetIssuanceDecoderError, AssetIssuanceEncoder, + EcdsaSighashType, OutPoint, PeginData, PeginDataDecoder, PeginDataEncoder, PeginWitness, + PeginWitnessDecoder, PeginWitnessDecoderError, PeginWitnessEncoder, PegoutData, Sequence, + SequenceDecoder, SequenceDecoderError, SequenceEncoder, Transaction, TransactionDecoder, + TransactionDecoderError, TransactionEncoder, TxIn, TxInDecoder, TxInDecoderError, TxInEncoder, + TxInWitness, TxInWitnessDecoder, TxInWitnessDecoderError, TxInWitnessEncoder, TxOut, + TxOutDecoder, TxOutDecoderError, TxOutEncoder, TxOutWitness, TxOutWitnessDecoder, + TxOutWitnessDecoderError, TxOutWitnessEncoder, Witness, WitnessDecoder, WitnessDecoderError, + WitnessEncoder, }; + +// Encode a compact size to a slice without allocating +pub(crate) fn compact_size_encode(value: usize) -> ArrayVec { + use crate::encoding::Encoder as _; + + let encoder = encoding::CompactSizeEncoder::new(value); + ArrayVec::from_slice(encoder.current_chunk()) +} diff --git a/src/locktime.rs b/src/locktime.rs index 0fd8c043..40b58ae3 100644 --- a/src/locktime.rs +++ b/src/locktime.rs @@ -22,6 +22,7 @@ use std::cmp::{PartialOrd, Ordering}; use std::convert::TryFrom; use std::str::FromStr; use std::io::{Read, Write}; +use crate::encoding; use crate::error::ParseIntError; use crate::parse; @@ -66,7 +67,6 @@ pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000; /// ``` #[allow(clippy::derive_ord_xor_partial_ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum LockTime { /// A block height lock time value. /// @@ -275,6 +275,36 @@ impl fmt::Display for LockTime { } } +encoding::encoder_newtype_exact! { + /// Encoder for the [`LockTime`] type. + pub struct LockTimeEncoder<'e>(encoding::ArrayEncoder<4>); +} + +impl encoding::Encode for LockTime { + type Encoder<'e> = LockTimeEncoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + LockTimeEncoder::new(encoding::ArrayEncoder::without_length_prefix(self.to_consensus_u32().to_le_bytes())) + } +} + +decoder_newtype! { + /// Decoder for the [`LockTime`] type. + #[derive(Default)] + pub struct LockTimeDecoder(encoding::ArrayDecoder<4>); + + /// Decoder error for the [`LockTime`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct LockTimeDecoderError(encoding::UnexpectedEofError); + const ERROR_DISPLAY = "failed to decode locktime"; + + impl Decode for LockTime { + fn convert_inner(bytes) -> Result<_, UnexpectedEofError> { + Ok(LockTime::from_consensus(u32::from_le_bytes(bytes))) + } + } +} + impl Encodable for LockTime { #[inline] fn consensus_encode(&self, w: W) -> Result { @@ -292,7 +322,6 @@ impl Decodable for LockTime { /// An absolute block height, guaranteed to always contain a valid height value. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Height(u32); impl Height { @@ -377,7 +406,6 @@ impl TryFrom for Height { /// `to_consensus_u32()`. Said another way, `Time(x)` means 'x seconds since epoch' _not_ '(x - /// threshold) seconds since epoch'. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Time(u32); impl Time { diff --git a/src/opcodes.rs b/src/opcodes.rs index fb8648c4..d9cfec5b 100644 --- a/src/opcodes.rs +++ b/src/opcodes.rs @@ -20,8 +20,6 @@ #![allow(non_camel_case_types)] -#[cfg(feature = "serde")] use serde; - use std::fmt; // Note: I am deliberately not implementing PartialOrd or Ord on the @@ -862,16 +860,6 @@ impl fmt::Display for All { } } -#[cfg(feature = "serde")] -impl serde::Serialize for All { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - /// Empty stack is also FALSE pub static OP_FALSE: All = all::OP_PUSHBYTES_0; /// Number 1 is also TRUE diff --git a/src/pset/elip100.rs b/src/pset/elip100.rs index 56c1db00..d95e9166 100644 --- a/src/pset/elip100.rs +++ b/src/pset/elip100.rs @@ -221,7 +221,6 @@ mod test { use crate::encode::serialize; use crate::{OutPoint, Txid}; - use bitcoin::hashes::Hash; use hex::DisplayHex as _; use crate::{ diff --git a/src/pset/map/global.rs b/src/pset/map/global.rs index 0c1cb018..4ed267e3 100644 --- a/src/pset/map/global.rs +++ b/src/pset/map/global.rs @@ -58,7 +58,6 @@ const PSBT_ELEMENTS_GLOBAL_TX_MODIFIABLE: u8 = 0x01; /// Global transaction data #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TxData { /// Transaction version. Must be 2. pub version: u32, @@ -93,10 +92,8 @@ impl Default for TxData { /// A key-value map for global data. #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Global { /// Global transaction data - #[cfg_attr(feature = "serde", serde(flatten))] pub tx_data: TxData, /// The version number of this PSET. Must be present. pub version: u32, @@ -109,16 +106,8 @@ pub struct Global { /// Elements tx modifiable flag pub elements_tx_modifiable_flag: Option, /// Other Proprietary fields - #[cfg_attr( - feature = "serde", - serde(with = "crate::serde_utils::btreemap_as_seq_byte_values") - )] pub proprietary: BTreeMap>, /// Unknown global key-value pairs. - #[cfg_attr( - feature = "serde", - serde(with = "crate::serde_utils::btreemap_as_seq_byte_values") - )] pub unknown: BTreeMap>, } @@ -174,7 +163,7 @@ impl Map for Global { } self.scalars.push(scalar); } else { - return Err(Error::InvalidKey(raw_key))?; + return Err(Error::InvalidKey(raw_key).into()); } } else if prop_key.is_pset_key() && prop_key.subtype == PSBT_ELEMENTS_GLOBAL_TX_MODIFIABLE @@ -182,7 +171,7 @@ impl Map for Global { if prop_key.key.is_empty() && raw_value.len() == 1 { self.elements_tx_modifiable_flag = Some(raw_value[0]); } else { - return Err(Error::InvalidKey(raw_key))?; + return Err(Error::InvalidKey(raw_key).into()); } } else { match self.proprietary.entry(prop_key) { @@ -467,7 +456,7 @@ impl Decodable for Global { } scalars.push(scalar); } else { - return Err(Error::InvalidKey(raw_key))?; + return Err(Error::InvalidKey(raw_key).into()); } } else if prop_key.is_pset_key() && prop_key.subtype == PSBT_ELEMENTS_GLOBAL_TX_MODIFIABLE @@ -475,7 +464,7 @@ impl Decodable for Global { if prop_key.key.is_empty() && raw_value.len() == 1 { elements_tx_modifiable_flag = Some(raw_value[0]); } else { - return Err(Error::InvalidKey(raw_key))?; + return Err(Error::InvalidKey(raw_key).into()); } } else { match proprietary.entry(prop_key) { @@ -506,7 +495,7 @@ impl Decodable for Global { // Mandatory fields let version = version.ok_or(Error::IncorrectPsetVersion)?; if version != 2 { - return Err(Error::IncorrectPsetVersion)?; + return Err(Error::IncorrectPsetVersion.into()); } let tx_version = tx_version.ok_or(Error::MissingTxVersion)?; let input_count = input_count.ok_or(Error::MissingInputCount)?.0 as usize; diff --git a/src/pset/map/input.rs b/src/pset/map/input.rs index ca54d56a..ce8c56dc 100644 --- a/src/pset/map/input.rs +++ b/src/pset/map/input.rs @@ -21,7 +21,7 @@ use std::{ }; use crate::taproot::{ControlBlock, LeafVersion, TapNodeHash, TapLeafHash}; -use crate::{schnorr, AssetId, ContractHash}; +use crate::{schnorr, AssetId}; use crate::{confidential, locktime}; use crate::encode::{self, Decodable}; @@ -32,10 +32,10 @@ use crate::pset::raw; use crate::pset::serialize; use crate::pset::{self, error, Error}; use crate::{transaction::SighashTypeParseError, SchnorrSighashType}; -use crate::{AssetIssuance, BlockHash, EcdsaSighashType, Script, Transaction, TxIn, TxOut, Txid}; +use crate::{AssetBlindingNonce, AssetIssuance, BlockHash, EcdsaSighashType, PeginWitness, RangeProof, Script, Transaction, TxIn, TxOut, Txid, SurjectionProof}; use bitcoin::bip32::KeySource; use bitcoin::{PublicKey, key::XOnlyPublicKey}; -use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak, ZERO_TWEAK}; +use secp256k1_zkp; use crate::{OutPoint, Sequence}; @@ -169,10 +169,18 @@ const PSBT_ELEMENTS_IN_ASSET_PROOF: u8 = 0x14; /// Note that this does not indicate actual blinding status, /// but rather the expected blinding status prior to signing. const PSBT_ELEMENTS_IN_BLINDED_ISSUANCE: u8 = 0x15; + +/// Bit of [`Input::previous_output_index`] indicating that the input is a pegin. +const OUTPOINT_PEGIN_FLAG: u32 = 1 << 30; +/// Bit of [`Input::previous_output_index`] indicating that the input has an issuance. +const OUTPOINT_ISSUANCE_FLAG: u32 = 1 << 31; +/// The previous output index of a coinbase input, which is all 1's and therefore +/// carries no pegin/issuance flags. +const OUTPOINT_COINBASE_INDEX: u32 = 0xffff_ffff; + /// A key-value map for an input of the corresponding index in the unsigned /// transaction. #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Input { /// The non-witness transaction this input spends from. Should only be /// [`std::option::Option::Some`] for inputs which spend non-segwit outputs or @@ -184,10 +192,6 @@ pub struct Input { pub witness_utxo: Option, /// A map from public keys to their corresponding signature as would be /// pushed to the stack from a scriptSig or witness. - #[cfg_attr( - feature = "serde", - serde(with = "crate::serde_utils::btreemap_byte_values") - )] pub partial_sigs: BTreeMap>, /// The sighash type to be used for this input. Signatures for this input /// must use the sighash type. @@ -198,38 +202,21 @@ pub struct Input { pub witness_script: Option