From e69aeba2bff174f8213efa41acf10764626322a5 Mon Sep 17 00:00:00 2001 From: stringhandler Date: Fri, 9 Oct 2026 14:17:30 +0200 Subject: [PATCH 1/4] fix: put Bitcoin Simplicity padding first, sized for the branch the spend takes The Bitcoin finalize path appended the padding item after the control block, which is where the node reads the control block from. It also sized the padding from the satisfied program before pruning, whose cost bound is its heaviest branch. The node rejects both: a last item that isn't a control block, and padding the program doesn't need (SIMPLICITY_ERR_OVERWEIGHT; minCost is the budget with one byte less padding). Padding now goes first, and is the shortest item that fits the pruned program's cost. Programs cheap enough to need no padding, such as examples/bitcoin_covenant, were not affected. --- CHANGELOG.md | 4 ++ txmanifest_lib/src/covenant.rs | 71 ++++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83858d1..bc4b662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,10 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and - `validate` errors when a manifest uses a Simplicity program in a `tapleaf` or `simf_fn` compute, or a `programs` entry, without declaring `"requires": ["simplicity"]`. Previously only covenant `utxo_types` triggered this. Add `"simplicity"` to `requires` if `validate` now reports it. - `validate` also checks each program file: that it exists, matches its hash, meets its compiler requirement, and declares the witnesses the manifest supplies. +### Fixed + +- Bitcoin covenant spends whose program needs cost padding are now accepted by the node. The padding item came last, where the node expects the control block, and it was sized for every branch of the program rather than the one the spend takes, so the node rejected it as more padding than the program needs. Programs cheap enough to need no padding, such as `examples/bitcoin_covenant`, were not affected. + ## [0.3.0] - 2026-10-06 ### Breaking diff --git a/txmanifest_lib/src/covenant.rs b/txmanifest_lib/src/covenant.rs index 862f884..e033bf7 100644 --- a/txmanifest_lib/src/covenant.rs +++ b/txmanifest_lib/src/covenant.rs @@ -1460,23 +1460,23 @@ fn network_to_params(network: lwk_wollet::ElementsNetwork) -> &'static AddressPa /// /// The Bitcoin counterpart of [`finalize_covenant_input`], and the same shape: compile, /// build the taproot control block, satisfy the program against the real transaction, and -/// write the four-item Simplicity tapscript witness -/// `[witness, program, cmr, control_block]`. +/// write the Simplicity tapscript witness `[witness, program, cmr, control_block]`, with a +/// padding item in front of it when the program needs one. /// /// Three things differ, and each is a consequence of the chain rather than a choice: /// /// - **The environment.** `BitcoinEnv` takes the spent outputs and the input index, with /// no genesis hash โ€” Bitcoin's sighash does not commit to one. -/// - **No pruning.** SimplicityHL's `satisfy_with_env` is typed to `ElementsEnv`, so the -/// Bitcoin path satisfies without an environment and the program keeps every branch. -/// Harmless for a single-path program; a program with a `match` will carry dead branches -/// into the witness, costing size and budget. -/// - **Cost padding.** Bitcoin's Simplicity validator accepts a program only when its cost -/// falls inside `(minCost, budget]`, and a cheap program has to be padded *up* into that -/// window. Elements has no equivalent, so this is the one step with no counterpart -/// above. The padding is a plain all-zero stack item; it must not be built with -/// `Cost::get_padding_bytes`, whose Elements form is an annex (`[0x50] + zeros`) that a -/// regular stack item's leading byte would be misread as. +/// - **Pruning by hand.** SimplicityHL's `satisfy_with_env` is typed to `ElementsEnv`, so +/// the Bitcoin path satisfies without an environment and then prunes the result itself +/// with `RedeemNode::prune`, which is generic over the environment. +/// - **Cost padding.** A program that costs more than its witness stack buys needs a +/// padding item. On Elements that is the annex, at the end. On Bitcoin it is a plain +/// all-zero item at the *start*: the node reads the control block, CMR, program and +/// witness off the end of the stack and takes whatever is left as padding. The node also +/// requires the padding to be minimal (see [`bitcoin_padding_len`]). It must not be +/// built with `Cost::get_padding_bytes`, whose Elements form is an annex +/// (`[0x50] + zeros`). #[allow(clippy::too_many_arguments)] pub fn finalize_bitcoin_covenant_input( program: &ProgramSource, @@ -1598,19 +1598,48 @@ pub fn finalize_bitcoin_covenant_input( control_block.serialize(), ]; - // Pad the cost into the validator's acceptance window, if the program is too cheap. - // Sized from the *satisfied* program so it reflects which branches actually run. - // `None` means the program's cost already falls inside the budget its witness buys, so - // there is nothing to pad. Padding only ever raises a *cheap* program into the window. - if let Some(padding) = satisfied.required_padding_bytes(&stack) { - if !padding.is_empty() { - eprintln!("[covenant] cost padding: {} bytes", padding.len()); - stack.push(padding); - } + // Buy the budget the program's cost needs, if its witness stack does not already. + // + // Sized from the *pruned* program, the one the node runs. The satisfied program still + // holds the branches this spend does not take, and its bound is the largest of them: + // sized from it, a SHRINCS stateful spend gets the stateless branch's padding, about + // 31 KB too much, and the node rejects it as overweight. + if let Some(len) = bitcoin_padding_len(redeem.bounds().cost, &stack) { + eprintln!("[covenant] cost padding: {len} bytes"); + stack.insert(0, vec![0; len]); } Ok(stack) } +/// The length of the padding item a Bitcoin Simplicity spend needs, or `None` when +/// `stack` already buys the budget `cost` needs. +/// +/// The node accepts a cost in `(minCost, budget]`, where `budget` is what the whole stack +/// buys and `minCost` is what it would buy with one byte less padding: padding must be +/// minimal, or the spend fails with `SIMPLICITY_ERR_OVERWEIGHT`. So this is the shortest +/// item that fits. It starts from rust-simplicity's estimate and walks to the exact answer +/// rather than trusting the estimate where an item's CompactSize length prefix grows. +fn bitcoin_padding_len(cost: simplicity::Cost, stack: &[Vec]) -> Option { + if cost.is_budget_valid(stack) { + return None; + } + let mut padded = Vec::with_capacity(stack.len() + 1); + padded.push(Vec::new()); + padded.extend_from_slice(stack); + let mut fits = |len: usize| { + padded[0].resize(len, 0); + cost.is_budget_valid(&padded) + }; + let mut len = cost.get_padding_size(stack).unwrap_or(0); + while !fits(len) { + len += 1; + } + while len > 0 && fits(len - 1) { + len -= 1; + } + Some(len) +} + /// Resolve `"type": "Signature"` witnesses against a Bitcoin environment. /// /// The Elements counterpart is [`inject_computed_signatures`]; the two differ only in the From dbea9b48b2cc066b14ffb7877dc9ebb663af47da Mon Sep 17 00:00:00 2001 From: stringhandler Date: Fri, 9 Oct 2026 14:17:30 +0200 Subject: [PATCH 2/4] feat: run the SHRINCS post-quantum verifier on Bitcoin signet Vendor shrincs-simplicity-verifier at 9d3791a (CC0) under contrib/, and add two examples that flatten it with mcpp: - pq_shrincs_smoke: upstream's verifier, unmodified. It takes the key and the message as witness data, so the coin it locks is anyone-can-spend; it is a smoke test, and has been spent on the Simplicity signet (be93b56ddb24f54b4cc8d9378a4baa7db5f880f273af56e2e69f339b305db2f2). - pq_shrincs: a wrapper that pins the key in param::PK and takes the message from jet::sig_all_hash(). Its manifest comes with the signer. tests/shrincs_verifier.rs runs upstream's witnesses through the Bitcoin finalize path and checks each witness stack against the signet node's acceptance rule. --- contrib/shrincs-simplicity-verifier/README.md | 25 + .../upstream/LICENSE | 121 ++++ .../upstream/README.md | 60 ++ .../examples/lamport/lamport_main.simf | 7 + .../examples/lamport/lamport_main.wit | 14 + .../examples/shrincs/shrincs_main.simf | 5 + .../shrincs/shrincs_main_stateful.wit | 6 + .../shrincs/shrincs_main_stateless.wit | 6 + .../upstream/lamport.simf | 138 +++++ .../upstream/shrincs/shrincs.simf | 28 + .../upstream/shrincs/unbalanced_xmss.simf | 44 ++ .../upstream/sphincs/fors.simf | 63 ++ .../upstream/sphincs/sphincs.simf | 53 ++ .../upstream/sphincs/sphincs_wots_tw.simf | 67 ++ .../upstream/sphincs/sphincs_xmss.simf | 46 ++ .../upstream/templates/array_map.simf | 51 ++ .../upstream/templates/assert_arrays.simf | 29 + .../upstream/test.simf | 9 + .../upstream/types.simf | 57 ++ .../upstream/utils.simf | 273 +++++++++ .../upstream/wots.simf | 164 +++++ examples/README.md | 4 +- examples/pq_shrincs/build.sh | 12 + examples/pq_shrincs/shrincs_spend.simf | 574 ++++++++++++++++++ examples/pq_shrincs/src/shrincs_spend.simf | 19 + examples/pq_shrincs_smoke/README.md | 48 ++ examples/pq_shrincs_smoke/build.sh | 13 + examples/pq_shrincs_smoke/config.json | 10 + examples/pq_shrincs_smoke/lock.params.json | 4 + examples/pq_shrincs_smoke/shrincs_main.simf | 571 +++++++++++++++++ examples/pq_shrincs_smoke/spend.params.json | 3 + examples/pq_shrincs_smoke/txmanifest.json | 100 +++ txmanifest_lib/tests/shrincs_verifier.rs | 266 ++++++++ 33 files changed, 2889 insertions(+), 1 deletion(-) create mode 100644 contrib/shrincs-simplicity-verifier/README.md create mode 100644 contrib/shrincs-simplicity-verifier/upstream/LICENSE create mode 100644 contrib/shrincs-simplicity-verifier/upstream/README.md create mode 100644 contrib/shrincs-simplicity-verifier/upstream/examples/lamport/lamport_main.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/examples/lamport/lamport_main.wit create mode 100644 contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main_stateful.wit create mode 100644 contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main_stateless.wit create mode 100644 contrib/shrincs-simplicity-verifier/upstream/lamport.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/shrincs/shrincs.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/shrincs/unbalanced_xmss.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/sphincs/fors.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs_wots_tw.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs_xmss.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/templates/array_map.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/templates/assert_arrays.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/test.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/types.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/utils.simf create mode 100644 contrib/shrincs-simplicity-verifier/upstream/wots.simf create mode 100755 examples/pq_shrincs/build.sh create mode 100644 examples/pq_shrincs/shrincs_spend.simf create mode 100644 examples/pq_shrincs/src/shrincs_spend.simf create mode 100644 examples/pq_shrincs_smoke/README.md create mode 100755 examples/pq_shrincs_smoke/build.sh create mode 100644 examples/pq_shrincs_smoke/config.json create mode 100644 examples/pq_shrincs_smoke/lock.params.json create mode 100644 examples/pq_shrincs_smoke/shrincs_main.simf create mode 100644 examples/pq_shrincs_smoke/spend.params.json create mode 100644 examples/pq_shrincs_smoke/txmanifest.json create mode 100644 txmanifest_lib/tests/shrincs_verifier.rs diff --git a/contrib/shrincs-simplicity-verifier/README.md b/contrib/shrincs-simplicity-verifier/README.md new file mode 100644 index 0000000..8c7695c --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/README.md @@ -0,0 +1,25 @@ +# SHRINCS verifier (vendored) + +A copy of [shrincs-simplicity-verifier](https://github.com/BlockstreamResearch/shrincs-simplicity-verifier), +the SimplicityHL verifier for the SHRINCS hash-based signature scheme, used by the +post-quantum signature work. + +- `upstream/` is upstream at commit + `9d3791a2331928d6596abf922c5125ffec58aeeb` (2026-10-01), unmodified. It holds the + `.simf` sources, `examples/` with their witness files, `README.md` and `LICENSE` (CC0). + Upstream's bundled `simfony` binary, `docs/`, `scripts/` and `Makefile` are left out. + +Nothing here is compiled directly. Each example that uses the verifier flattens it into +its own directory with `mcpp -P`, as upstream's `scripts/` do, and commits the result. The +result is what gets compiled and pinned, and committing it means tests don't need the +preprocessor: + +- `examples/pq_shrincs_smoke/build.sh` flattens upstream's own example, unmodified. +- `examples/pq_shrincs/build.sh` flattens the wrapper that pins the key and the message. + +Both were run with mcpp 2.7.2. Other preprocessors work too, but need not produce the +same bytes, and the bytes are what a pin hashes. + +To update: copy the same files from a newer upstream commit into `upstream/`, update the +commit above, rerun both `build.sh` scripts, run `tx-manifest-wallet pin` on both +manifests, and run the tests. diff --git a/contrib/shrincs-simplicity-verifier/upstream/LICENSE b/contrib/shrincs-simplicity-verifier/upstream/LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/contrib/shrincs-simplicity-verifier/upstream/README.md b/contrib/shrincs-simplicity-verifier/upstream/README.md new file mode 100644 index 0000000..cd1e05e --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/README.md @@ -0,0 +1,60 @@ +# SHRINCS Simplicity Verifier +This repo contains hash-based signatures that can protect Bitcoin against quantum computers + +> *โš ๏ธ This project is a work in progress and is provided as-is for research, learning, and experimentation. It is not production-ready and has not undergone a formal security audit, code review, or verification process. This library may be incorrect, incomplete, or insecure.* + +## ๐Ÿ›  Requirements +We use `#include` and `#define` directives, so you may need to install `mcpp`: +```shell +brew install mcpp +``` + +> [!IMPORTANT] +> Currently, this project requires the bundled `./simfony` binary due to upstream incompatibilities in simfony-cli. This will be updated once the upstream issues are resolved. + +## ๐Ÿ” Signatures +| Signature Scheme | File Path | Description | +| :--- | :--- | :--- | +| **Lamport** | `lamport.simf` | Lamport one-time signature implementation. | +| **WOTS** | `wots.simf` | Winternitz One-Time Signature implementation. | +| **SHRINCS** | `shrincs/shrincs.simf` | SHRINCS implementation. [C++](https://github.com/BlockstreamResearch/shrincs-cpp/tree/main). [๐Ÿ“– Read docs](./docs/shrincs_spec/main.pdf) | + +> **Note:** Each signature contains method `X_verify` (where `X` is the scheme name) that verifies the validity of the signature for the corresponding message hash and public key + +## ๐Ÿงช Running tests +* `make test` - runs all tests + +* `make lamport_example` - runs Lamport verification example + +* `make shrincs_example MODE={stateful|stateless}` - runs SHRINCS verification example + * *Use `MODE=stateful` for XMSS-based stateful trees or `MODE=stateless` for SPHINCS-like stateless verification.* + +## ๐Ÿš€ Usage +To execute this code, import the required signature file using `#include` directive and then run: +```bash +mkdir -p target +mcpp -P -I . your-file.simf -o target/your-file.simf +``` + +To run the preprocessed file, run: +```bash +./simfony run --witness your-witness.wit target/your-file.simf +``` + +## ๐Ÿ’ป Code example +```rust +#include "./shrincs/shrincs.simf" + +fn main() { + let message: u256 = ...; + let pk: (u128, u128) = ...; + let signature: Either = ...; + let known_pk_part: u128 = ...; + + shrincs_verify((message, pk, signature, known_pk_part)); +} +``` + +> ๐Ÿงฉ **Type Definitions:** Wondering how `UXMSSSignature` or `SPHINCSSignature` are structured under the hood? Check out the full type definitions in [`types.simf`](./types.simf). + +> ๐Ÿ“‚ **More Examples:** For complete, runnable code including witness data setup, explore the [`examples/`](./examples) directory. diff --git a/contrib/shrincs-simplicity-verifier/upstream/examples/lamport/lamport_main.simf b/contrib/shrincs-simplicity-verifier/upstream/examples/lamport/lamport_main.simf new file mode 100644 index 0000000..bc7e7e5 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/examples/lamport/lamport_main.simf @@ -0,0 +1,7 @@ +#include "../../lamport.simf" + +fn main() { + let proof: LamportSignProof = (witness::MESSAGE_HASH, witness::SIGNATURE, witness::PUBLIC_KEY); + + lamport_verify(proof); +} diff --git a/contrib/shrincs-simplicity-verifier/upstream/examples/lamport/lamport_main.wit b/contrib/shrincs-simplicity-verifier/upstream/examples/lamport/lamport_main.wit new file mode 100644 index 0000000..b3ae6c4 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/examples/lamport/lamport_main.wit @@ -0,0 +1,14 @@ +{ + "MESSAGE_HASH": { + "value": "0", + "type": "u256" + }, + "SIGNATURE": { + "value": "[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, 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, 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, 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, 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, 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, 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, 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]", + "type": "[u256; 256]" + }, + "PUBLIC_KEY": { + "value": "[(0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35), (0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925, 0xd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35)]", + "type": "[(u256, u256); 256]" + } +} \ No newline at end of file diff --git a/contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main.simf b/contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main.simf new file mode 100644 index 0000000..690c6bd --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main.simf @@ -0,0 +1,5 @@ +#include "../../shrincs/shrincs.simf" + +fn main() { + shrincs_verify(witness::PROOF); +} diff --git a/contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main_stateful.wit b/contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main_stateful.wit new file mode 100644 index 0000000..611ede7 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main_stateful.wit @@ -0,0 +1,6 @@ +{ + "PROOF": { + "type": "(u256, (u128, u128), Either<((u256, u32, [u128; 64]), List, u32), ((u256, ([(u128, [u128; 22]); 4], [(u128, [u128; 22]); 1])), [((u256, u32, [u128; 64]), [u128; 12]); 2])>, u128)", + "value": "(0, (0xb635ef10af1b120cec1095d7745fec06, 0x0f8fbe02641f3479bd3131c0d5253926), Left(((104641417537887468096637857572606874578560029415901768884629032847089444701194, 25794, [279744406477161174898453954224454442384, 100191869207409547221336059044375388424, 95487581836991930323202054728502360641, 310083205617729613868304228069290666260, 257624011138800904600065605320700124623, 305048287911682963660119106030474941323, 204648430403993904544247919942139616291, 236750591000346099609440610110321591125, 196824814686909386917176141078796988140, 316511535116480497246692351945507598187, 330471767409887762817127111776559770793, 26967500738771566668058042323238163034, 201151028884802783401424680656204881081, 257866352908299405963560297226622347066, 277372625599346100057713231265699123429, 143941592513260640104086139532261994721, 101306460868014903048366311963619481515, 137677462207889313570924863844153834900, 146855367544317916540796729494957438220, 299702215678073717444317085756614384649, 299422392145965913549196361946514118205, 218267348603148341378372315458246547195, 205406908791210700830536879145049927703, 315354807768895916425915925012676471130, 253797929653540327311600971365633653052, 288765870696341440857070716368703814491, 249827258618266768941067032549741226812, 192065118615413155102250957616729117149, 244526319209537952179479378912532825266, 324682565723164264695735439393252049431, 74736776896544312869036542917523434181, 287911431987466938533741527115333590518, 338869510479812621968398559963281440567, 255456750741432516494190809935156548989, 117219694420124565797944818169934890343, 11142287029120057092849115208661560700, 269060538540596741121745983537279305329, 177615871167159170078660855577184128691, 146323455865231972475746867329643852058, 16048975939891095973544503220542676373, 334980985378384697937744926874944833920, 35273915146041816046806212010570904881, 260495355653033411698743528828240768400, 51175072147311734163306138557681194296, 253150511472509828354787312285225781, 26442791451334483792521024240862126546, 305201243147962042107300476363547998226, 276246860413834174591938349899170076364, 124311071434438674156195119383108865107, 182862399605165986399826600726163023653, 18998086256583136675550353879157721573, 5492767804387397942488402573919843270, 20917105423670140299946458262319265275, 214855000219476395616090914039712094137, 290878809507972983797879300848336773978, 321436532748391495336174492487827248582, 143797167348210565263256089940770242047, 309124555943212030562981020497532908190, 130650316130903867098038773654364014927, 217278390429756520909840890768050002622, 53300241174463360647441617499472912652, 209520507978057829011388360222931842804, 151028685387999325751378814616688404491, 83817881586650168117312150349556894295]), list![278383999423802883450225662559784840723], 1)), 0x9cf1c451ce3c9345581de66456b2b5f0)" + } +} \ No newline at end of file diff --git a/contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main_stateless.wit b/contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main_stateless.wit new file mode 100644 index 0000000..384810e --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main_stateless.wit @@ -0,0 +1,6 @@ +{ + "PROOF": { + "type": "(u256, (u128, u128), Either<((u256, u32, [u128; 64]), List, u32), ((u256, ([(u128, [u128; 22]); 4], [(u128, [u128; 22]); 1])), [((u256, u32, [u128; 64]), [u128; 12]); 2])>, u128)", + "value": "(0, (0xb635ef10af1b120cec1095d7745fec06, 0x0f8fbe02641f3479bd3131c0d5253926), Right(((66705275099189916218598764251535481673945985337793835479889350209808717894392, ([(273179908211548493554893834407589991808, [103359658531743259534072042904361318608, 256150691823863513106928953400451783116, 176700582184457210477410165720007859489, 316252516119637573383363588456472154497, 60589347788030737340165459220983098945, 140865274625018141330668668773222893543, 83947670658599844090951197491602342009, 99341263987669892621827265266865154592, 91500311362425364178878172132870633685, 279216036318799121278674845568940466793, 203070870551492339315271196008037082631, 50429302763325545512642941406898995709, 63717853510147930807132011484853599963, 333383119948984063902698334282456353132, 39766155272923788000584040245284014361, 121351552658670718998409328219753618209, 50816056444386251838592884688294859205, 43744361307131216565698209694797012662, 280125483461286391067557681242616890799, 232538677755667890756909739746050060503, 214347979240761324984668679185833787677, 237137076295301228655398845151582757815]), (300539421257228786447080973204034900245, [285823770292280755177421377609371696991, 160745732861863671002808454582542032731, 61844864600420726376357701611448516903, 101937784271203716634767406564547258917, 238832856613162842494202387741015646022, 98127065324019906614514847907104230241, 24720493033791236427968713164226376576, 137437860977934295924570359071052329412, 304321327701527163074971145336241883752, 271576705682753056466529106642865359227, 147264792635510070131219615106368160553, 168950513853088937144297054684893862013, 212980628285664213160996073623433008830, 187554433596950814230862720788710584220, 116232598803796372773511443577615935849, 160376131940217626957590336515437292721, 326515035799686890030973506835102837495, 208976983862008550004130369941484553784, 65330924101886976876940078615962716602, 69958115775871838587742510750094155427, 213545957903709481292444990250006147107, 23152465208858120993197083510581351013]), (302411090238423831577368667541530181948, [294231557500826152706242837163460507372, 11993537394937010581553295698113994054, 232932818683366585271782893912577441515, 89080805093390965058469747334534635256, 228411840414752392890666803592245087633, 129539470029285365030846930694641894000, 283360047899796028441335124220945379781, 73488510014786363660428979188694863554, 334676343040251652112263960705113863473, 263093786645567116089240117691426556278, 189327333380433555116894045388605966380, 65702915128960815329149372272381194136, 78326292023566456312156141772486056569, 108099315606710217328073966102747310719, 108784505991766674250445725264551893407, 334914177104004058695976474213147990459, 306776928307438249800254061448949169615, 146285513792772487518680297793943600268, 109902841397830443638376315588497702931, 149951916414018050520386579121371209896, 250503043273576538956746957766019249826, 168173631476885380927256007654361269429]), (50528313788942888040348135702811030338, [205362877148806261492244378442822528627, 105499815681221947783323403594907951731, 319048031244664071247551438928191880949, 260578954751137857439537062267590639503, 156189913870303378565964184334439185452, 35324616733801763211136336725630092158, 275052799665237847807908019791383008264, 144330534915849183574626278687349441018, 93768813266444518361758555557799378153, 127525450411643654712453638723456016727, 119596323670451839580636627911908405982, 24890202722613727864737163845241715504, 161792940127817239405432134385461780436, 290584001319393627534795416908374662958, 27591285865424011003710273058950498022, 187050693966300055462477636350358938304, 93310649283275522713357130106180867210, 195054488404530647100747871788917635360, 234383396230826511247323865476962221051, 165005706646047058695540274839164958227, 284025566188882865838300061906117958022, 164223330586104726548647816734205892117])], [(157687299057715278558144379637548872182, [45638619520207008434990102561422056781, 88091232630838890091549341806252880728, 75349492291075799149094534860646272721, 160752162031105198712609763870356096495, 67905472099819793128156043461348339575, 30340935550961882918952307689370565264, 333823343575179170238165749042059851470, 247631712366976325567821800862972956581, 293426305138007060149028410125021486408, 117114946873474508039830056268372828394, 142198119879793482189283318713392853697, 12936144714553681312468528946987237692, 164951591752448868399288039725517776558, 141646325985042586012632014511237556008, 120832100119057323259847398445271846521, 205476299579305796204256638788988122039, 151051488971781311405139090338808694810, 207311012010370688427206888625167282641, 11499360677305921660192194141437684746, 268492975033295537968342369612784350540, 28750681695934884060037671712738994522, 108706004422096554714930919401589461280])])), [((39721385067813651282372050722904889262206662192876120474783754992625082424484, 1719338, [119379264559529524637281001793159504106, 259562383675973738904956418242974666494, 8057660294623325999945647986462206147, 228310275542134514854384253827014318919, 198036343998579456671472050712572252339, 159587457920140723896337869701359817125, 10961997832774089029876928013769671719, 269123774865957589404083216580811525760, 133351448380490566701226357048785925706, 142723893259380459020230730572863463997, 333635361520727425152441428324938702534, 14681407434186077293510114712651810508, 203277489815531266718905432942007832613, 196914662741979997077790650369468205793, 139280528500899323756124314622707814118, 276203611072474049171805000212208723191, 259636846280905740837084518511848722534, 122593411729549132447069261201797453624, 91622217321254170545814911906501879280, 92344959827354418898725899729616074643, 317882168492618150265816471807753119612, 206424773563229936899589811236436440098, 180333168679511003892198376611540153489, 325222473677043451023087364356509899140, 30993308596749741978480643265034979036, 270497759437262734767600367465166006956, 168353091815702953796814146257380526493, 27606522998004713422717644824553384221, 191250327613927911186777604603198549634, 202062335610313475241746239982771546603, 211145830973165481036080270505976315158, 233582432219348943647863021485367734594, 200109694281721384043370396901550535796, 255582155595230750882137959658707708758, 256945536424978146106902323955711080121, 240547090934450114153242292427557389287, 234463490226984390022028983349223578160, 253376994892998847071998349554381139665, 320651652491478994562430541055525855778, 306652791958931171689407925436881333998, 191355654747760510733177281661279718679, 244415583703118290379050891972649614918, 209164302807371603961066753549931374972, 303757202848524563235331880280879288043, 315312456158159389919232339754323542358, 110195947814375686153068914668561812134, 284444402364000902751609010557298908457, 111786509530071353356815878372256502368, 292529175664063543281385345056846729544, 18475439492872637097083109566119044731, 211935699719176305732155946778851503188, 187654445666321276830180323767202213285, 336108066656498955400635463264115722732, 154146760262908162461266104376425544485, 30092729731324496248467385474967332913, 91881895021079536178199668109325345558, 116911738774515151156383243253481354267, 226361140641851170778489520890275139834, 202353826810444282445468345538035871845, 87927588303374460859054290877641394545, 316749076256289394169720281903326015209, 123977696459995719530305197135912549148, 170054704837319828544462951396233375518, 132354806364620712225352845227744508129]), [318319645967910266367267202033700005921, 269980605939145368215385016040853310051, 185635922844294796785769146310028216298, 109668162094540829108816466478334957153, 208830588658418529235016928700770362669, 25918811285379992492495805634127026609, 136226701309513137784667184600256232985, 149102574104286074506372105199862082901, 79982891389810553664365040067715097331, 157515064822436275235080116428735759013, 320939692132336818231296989900066549714, 11134479138039481291763022265510061228]), ((82501851620720044709543431581074512638692273187343308751577833968781549769030, 6898, [2057159950448252061456201509628073733, 115615356041894402149753800741686930593, 48137842336240756700735408364814003584, 141059366043276265688366507267430366448, 216063697401789716532343038389601627847, 309006184070673640888187617536544157581, 124791992627101796650823017494280293955, 14198840004023520765449726458682246333, 139319909677998604411371321032404850728, 96517073599954352966669094466477489388, 65178214765432126034379057306398073628, 258481041814998163317434419701944325806, 283658625030399513984925022926433571843, 183515793753717167879221190496889173588, 146815058946140356639997481588943177657, 223685525565477227093266957180806257414, 50196311943271281252756021876879807312, 334357079612360291841793118704378673238, 160411234696623458045829085582794011033, 255937922303078955457906684033520033869, 338818254537522417333145789763653345373, 261907111682796116904092350459257761617, 322797476141436389677048929099382338702, 71320196595824377522621145378258322995, 331320331148069958020907162680850144372, 83462523839590955072022995328304087751, 275663106998710917597095385011765980628, 29581817070421698617856947276156289900, 115927386827940657356482048264390297280, 262232225788993095272761363350974556001, 17860480457077683078366667846051529193, 131124291587207819051131237143418907125, 163619005749852120528430000713631974531, 145089146500301449930764731943413541691, 99766957391395987662323709620058062602, 18475493813925763736765870763783736402, 293348635890784859222710785575651360673, 256531007939241430310844679650702768234, 45844275200893436386021821174996998833, 288440638650330059327083144917405222484, 217987467211063113419756804018607176551, 263466263481227118563658400105639779782, 83189017096170151041222247701446564254, 137431928591240362634706531572819315780, 27938331106298095909740555785369841814, 290421884151957652887991789902947617007, 86404078402720529327641112346274065226, 82744236865855672117835297188708919982, 51268903456377324954375181624163548400, 242227110831250823340442286737930302946, 276009465562993783156633324391014637872, 154551137161261013142168770243539847308, 236874193747475489190012282494635464151, 42558030238956847866601692623073369582, 151976282512739014884617170178352050092, 327356096927241420336597027681642449200, 194416673505988245940675152849070097293, 59584847502969264347061967021665844765, 89615606198532684836093874875710927357, 15953630165170936344796875548959163483, 178574481541688590234892716994145315329, 46929154399846899133456818874502386136, 76127061657666528863483213433240049008, 272672113321219240965254730313866086803]), [52840326757527026651702550321274255254, 99976296801536878372933617211440858396, 9768330166592895245059311062702614358, 78389523792347984547196647878477420662, 270285485300946029194747707730592721324, 252612053767136501105971179107387911626, 263805612721705039517305912422658851058, 36545863235017900468838499633829579961, 174053812043212203193376997864092866091, 119044513041782897587342846700571148801, 39179481957615891372352411181077319979, 740399041953856919810726815820888902])])), 0xe24e6a43f74040ce1cbc1bcde351374b)" + } +} \ No newline at end of file diff --git a/contrib/shrincs-simplicity-verifier/upstream/lamport.simf b/contrib/shrincs-simplicity-verifier/upstream/lamport.simf new file mode 100644 index 0000000..1b11117 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/lamport.simf @@ -0,0 +1,138 @@ +#ifndef LAMPORT_SIGNATURE +#define LAMPORT_SIGNATURE + +#include "templates/array_map.simf" +#include "utils.simf" +#include "types.simf" + +fn get_lamport_pub_key(private: KeyPair, ctx: ()) -> (KeyPair, ()) { + let (key1, key2): KeyPair = private; + + ((sha256_of_256(key1), sha256_of_256(key2)), ()) +} + +fn get_lamport_signature(el: KeyPair, ctx: (u256, u8)) -> (u256, (u256, u8)) { + let (message_hash, idx): (u256, u8) = ctx; + let bit: u1 = get_bit_at(message_hash, idx); + + let (key1, key2): KeyPair = el; + let required_key: u256 = match jet::eq_1(bit, 0) { + true => key1, + false => key2, + }; + + let (_, new_idx): (bool, u8) = jet::add_8(idx, 1); + (required_key, (message_hash, new_idx)) +} + +DEFINE_MAP_ARR_1(KeyPair, KeyPair, (), get_lamport_pub_key) +DEFINE_MAP_ARR(KeyPair, KeyPair, (), get_lamport_pub_key, 2, 1) +DEFINE_MAP_ARR(KeyPair, KeyPair, (), get_lamport_pub_key, 4, 2) +DEFINE_MAP_ARR(KeyPair, KeyPair, (), get_lamport_pub_key, 8, 4) +DEFINE_MAP_ARR(KeyPair, KeyPair, (), get_lamport_pub_key, 16, 8) +DEFINE_MAP_ARR(KeyPair, KeyPair, (), get_lamport_pub_key, 32, 16) +DEFINE_MAP_ARR(KeyPair, KeyPair, (), get_lamport_pub_key, 64, 32) +DEFINE_MAP_ARR(KeyPair, KeyPair, (), get_lamport_pub_key, 128, 64) +DEFINE_MAP_ARR(KeyPair, KeyPair, (), get_lamport_pub_key, 256, 128) + +DEFINE_MAP_ARR_1(KeyPair, u256, (u256, u8), get_lamport_signature) +DEFINE_MAP_ARR(KeyPair, u256, (u256, u8), get_lamport_signature, 2, 1) +DEFINE_MAP_ARR(KeyPair, u256, (u256, u8), get_lamport_signature, 4, 2) +DEFINE_MAP_ARR(KeyPair, u256, (u256, u8), get_lamport_signature, 8, 4) +DEFINE_MAP_ARR(KeyPair, u256, (u256, u8), get_lamport_signature, 16, 8) +DEFINE_MAP_ARR(KeyPair, u256, (u256, u8), get_lamport_signature, 32, 16) +DEFINE_MAP_ARR(KeyPair, u256, (u256, u8), get_lamport_signature, 64, 32) +DEFINE_MAP_ARR(KeyPair, u256, (u256, u8), get_lamport_signature, 128, 64) +DEFINE_MAP_ARR(KeyPair, u256, (u256, u8), get_lamport_signature, 256, 128) + +fn get_lamport_public_key(private_key: [KeyPair; 256]) -> [KeyPair; 256] { + let (res, _): ([KeyPair; 256], ()) = get_lamport_pub_key_map_arr_256(private_key, ()); + res +} + +fn lamport_signature(message_hash: u256, private_key: [KeyPair; 256]) -> [u256; 256] { + let (signature, _): ([u256; 256], (u256, u8)) = get_lamport_signature_map_arr_256(private_key, (message_hash, 0)); + signature +} + +fn get_lamport_pub_key_iter(el: KeyPair, ctx: (u8, u8, u256, u1)) -> (u8, u8, u256, u1) { + let (idx, pos, hash, bit): (u8, u8, u256, u1) = ctx; + let (_, new_idx): (bool, u8) = jet::add_8(idx, 1); + match jet::eq_8(idx, pos) { + true => { + let (key1, key2): KeyPair = el; + let required_key: u256 = match jet::eq_1(bit, 0) { + true => key1, + false => key2, + }; + + (new_idx, pos, required_key, bit) + }, + false => (new_idx, pos, hash, bit), + } +} + +fn check_lamport_signature(el: u256, ctx: (u8, u256, [KeyPair; 256])) -> (u8, u256, [KeyPair; 256]) { + let (idx, message_hash, pub_key): (u8, u256, [KeyPair; 256]) = ctx; + let (_, bit_pos): (bool, u8) = jet::subtract_8(255, idx); + let bit: u1 = get_bit_at(message_hash, bit_pos); + let (_, _, pk_elem, _): (u8, u8, u256, u1) = array_fold::(pub_key, (0, idx, 0, bit)); + + assert!(jet::eq_256(sha256_of_256(el), pk_elem)); + + let (_, new_idx): (bool, u8) = jet::add_8(idx, 1); + (new_idx, message_hash, pub_key) +} + +fn lamport_verify(proof: LamportSignProof) { + let (message_hash, signature, public_key): LamportSignProof = proof; + let _: (u8, u256, [KeyPair; 256]) = array_fold::(signature, (0, message_hash, public_key)); +} + +#ifdef TESTING + +fn test_lamport_sign_verify() { + let private_key: [KeyPair; 256] = [ + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), + ]; + + let signature: [u256; 256] = lamport_signature(0, private_key); + + let proof: LamportSignProof = (0, signature, get_lamport_public_key(private_key)); + + lamport_verify(proof); +} + +#endif +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/shrincs/shrincs.simf b/contrib/shrincs-simplicity-verifier/upstream/shrincs/shrincs.simf new file mode 100644 index 0000000..030b2a9 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/shrincs/shrincs.simf @@ -0,0 +1,28 @@ +#ifndef SHRINCS +#define SHRINCS + +#include "unbalanced_xmss.simf" +#include "../sphincs/sphincs.simf" +#include "../types.simf" + +fn shrincs_verify(proof: SHRINCSSignProof) { + let (message, seed_root, signature, root_part): SHRINCSSignProof = proof; + let (public_parameter, expected_root): SPHINCSPK = seed_root; + + let midstate: u256 = jet::sha_256_block(jet::sha_256_iv(), <(u128, u128)>::into((public_parameter, 0)), 0); + + let roots: u256 = match signature { + Left(uxmss: UXMSSSignature) => { + <(u128, u128)>::into((restore_uxmss_signature((message, seed_root, uxmss), midstate), root_part)) + }, + Right(sphincs: SPHINCSSignature) => { + <(u128, u128)>::into((root_part, sphincs_verify((message, seed_root, sphincs), midstate))) + }, + }; + + let (root, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(midstate, adrs_256(0, 0, 0, 16, 0, 0, 0), roots), 1024)); + + assert!(eq_128(root, expected_root)); +} + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/shrincs/unbalanced_xmss.simf b/contrib/shrincs-simplicity-verifier/upstream/shrincs/unbalanced_xmss.simf new file mode 100644 index 0000000..b256b2a --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/shrincs/unbalanced_xmss.simf @@ -0,0 +1,44 @@ +#ifndef UNBALANCED_XMSS +#define UNBALANCED_XMSS + +#include "../sphincs/sphincs.simf" +#include "../types.simf" + +fn shrincs_get_root_from_path_iter(el: u128, ctx: (u128, bool, u256, u32)) -> (u128, bool, u256, u32) { + let (node, is_right_child, midstate, tree_height): (u128, bool, u256, u32) = ctx; + + let (left, right): (u128, u128) = match is_right_child { + true => (el, node), + false => (node, el), + }; + + let (hash, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(midstate, adrs_256(0, 0, 0, 2, 0, tree_height, 0), <(u128, u128)>::into((left, right))), 1024)); + + let (_, tree_height): (bool, u32) = jet::add_32(tree_height, 1); + + (hash, true, midstate, tree_height) +} + +fn restore_uxmss_signature(proof: UXMSSSignProof, midstate: u256) -> u128 { + let (message, pk, signature): UXMSSSignProof = proof; + let (pk_seed, root): SPHINCSPK = pk; + let (wots_signature, merkle_path, key_idx): UXMSSSignature = signature; + let (r, counter, message_signature): SPHINCSWOTSTWSignature = wots_signature; + + let (message, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(jet::sha_256_block(jet::sha_256_iv(), adrs_256(0, 0, 0, 4, 0, 0, 0), r), <(u128, u128)>::into((pk_seed, root)), message), 1024)); + + let (message, _): (u128, u128) = ::into(sha256_finish_4(jet::sha_256_block(jet::sha_256_iv(), adrs_256(0, 0, 0, 3, key_idx, 0, 0), <(u128, u128)>::into((pk_seed, message))), counter, 544)); + + let (pk, (_, checksum)): ([u128; 64], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_64(message_signature, (0, 0), (message, key_idx, midstate, 0, 0, 0, 0)); + assert!(jet::eq_8(checksum, 140)); + + // hsf + 1 + let (_, tree_height): (bool, u32) = match jet::eq_32(key_idx, 207) { + true => (false, 1), + false => jet::subtract_32(207, key_idx), + }; + + uxmss_path_root(fold::(merkle_path, (sphincs_hash_leaf_xmss(pk, (key_idx, midstate, 0, 0, 0, 1)), jet::eq_32(key_idx, 207), midstate, tree_height))) +} + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/sphincs/fors.simf b/contrib/shrincs-simplicity-verifier/upstream/sphincs/fors.simf new file mode 100644 index 0000000..f3198ac --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/sphincs/fors.simf @@ -0,0 +1,63 @@ +#ifndef SPHINCS_FORS +#define SPHINCS_FORS + +#include "../templates/array_map.simf" +#include "../utils.simf" +#include "../types.simf" + +fn sphincs_fors_root_from_path_iter(el: u128, ctx: (u128, u256, u32, u32, u32, u64, u32)) -> (u128, u256, u32, u32, u32, u64, u32) { + let (node, midstate, key_idx, tree_idx, tree_adrs_1, tree_adrs_2, height): (u128, u256, u32, u32, u32, u64, u32) = ctx; + + let (tree_idx, rem): (u32, u32) = jet::div_mod_32(tree_idx, 2); + let (left, right): (u128, u128) = match jet::eq_32(rem, 0) { + true => (node, el), + false => (el, node), + }; + + let (node, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(midstate, adrs_256(0, tree_adrs_1, tree_adrs_2, 7, key_idx, height, tree_idx), <(u128, u128)>::into((left, right))), 1024)); + + let (_, height): (bool, u32) = jet::add_32(height, 1); + (node, midstate, key_idx, tree_idx, tree_adrs_1, tree_adrs_2, height) +} + +fn get_22_bits_at(message: u256, idx: u8) -> u32 { + assert!(jet::lt_8(idx, 6)); + let (_, shift): (u8, u8) = ::into(jet::multiply_8(idx, 22)); + let (_, shift): (bool, u8) = jet::subtract_8(234, shift); + let shifted_msg: u256 = right_shift_256(message, shift); + let (_, (_, (_, shifted_msg))): (u128, (u64, (u32, u32))) = ::into(shifted_msg); + jet::and_32(shifted_msg, 0x003FFFFF) +} + +// Across the k FORS trees only the tree index changes; the midstate, the message +// and the address fields are identical, so they ride along as a read-only +// invariant rather than being threaded in and out of every recursion step. +fn sphincs_fors_root_from_path(part: SPHINCSFORSSignaturePart, ctx: u8, inv: (u256, u256, u32, u64)) -> (u128, u8) { + let idx_idx: u8 = ctx; + let (midstate, message, tree_adrs_1, tree_adrs_2): (u256, u256, u32, u64) = inv; + let (sk_value, path): SPHINCSFORSSignaturePart = part; + + let key_idx: u32 = get_22_bits_at(message, idx_idx); + let idx_idx_32: u32 = <(u16, (u8, u8))>::into((0, (0, idx_idx))); + + let (root, _, _, _, _, _, _): (u128, u256, u32, u32, u32, u64, u32) = array_fold::(path, (truncate_128(jet::sha_256_block(midstate, adrs_256(0, tree_adrs_1, tree_adrs_2, 6, idx_idx_32, 0, key_idx), sha256_tail_16(sk_value, 896))), midstate, idx_idx_32, key_idx, tree_adrs_1, tree_adrs_2, 1)); + + (root, wrapping_add_8(idx_idx, 1)) +} + +DEFINE_MAP_ARR_INV_1(SPHINCSFORSSignaturePart, u128, u8, (u256, u256, u32, u64), sphincs_fors_root_from_path) +DEFINE_MAP_ARR_INV(SPHINCSFORSSignaturePart, u128, u8, (u256, u256, u32, u64), sphincs_fors_root_from_path, 2, 1) +DEFINE_MAP_ARR_INV(SPHINCSFORSSignaturePart, u128, u8, (u256, u256, u32, u64), sphincs_fors_root_from_path, 4, 2) + +fn sphincs_get_fors_pub_key(signature: SPHINCSFORSSignature, midstate: u256, message: u256, tree_adrs_1: u32, tree_adrs_2: u64) -> u128 { + let (_, (parts_4, parts_1)): SPHINCSFORSSignature = signature; + + assert!(jet::eq_32(get_22_bits_at(message, 5), 0)); + + let ([root_0, root_1, root_2, root_3], _): ([u128; 4], u8) = sphincs_fors_root_from_path_map_arr_4(parts_4, 0, (midstate, message, tree_adrs_1, tree_adrs_2)); + let ([el], _): ([u128; 1], u8) = sphincs_fors_root_from_path_map_arr_1(parts_1, 4, (midstate, message, tree_adrs_1, tree_adrs_2)); + + truncate_128(jet::sha_256_block(jet::sha_256_block(midstate, adrs_256(0, tree_adrs_1, tree_adrs_2, 8, 0, 0, 0), <(u128, u128)>::into((root_0, root_1))), <(u128, u128)>::into((root_2, root_3)), sha256_tail_16(el, 1408))) +} + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs.simf b/contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs.simf new file mode 100644 index 0000000..cfb29d6 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs.simf @@ -0,0 +1,53 @@ +#ifndef SPHINCS +#define SPHINCS + +#include "sphincs_xmss.simf" +#include "fors.simf" +#include "../types.simf" + +// n = 16 +// h = 24 +// d = 2 +// a = 22 +// k = 6 +// lg(w) = 2 +// Sw,n = 140 + +fn sphincs_root_from_path_iter(el: XMSSSignature, ctx: (u128, u128, u32, u256, u32, u64, u32)) -> (u128, u128, u32, u256, u32, u64, u32) { + let (pk_seed, message, key_index, midstate, layer_adrs, tree_adrs_2, idx): (u128, u128, u32, u256, u32, u64, u32) = ctx; + let pub_key: u128 = sphincs_get_xmss_pub_key(el, pk_seed, message, key_index, midstate, layer_adrs, 0, tree_adrs_2); + + let key_index: u32 = idx; + let idx: u32 = jet::right_shift_32(12, idx); + let tree_adrs: u64 = <(u32, u32)>::into((0, jet::and_32(idx, 0x00000FFF))); + + let (_, layer_adrs): (bool, u32) = jet::add_32(layer_adrs, 1); + + (pk_seed, pub_key, key_index, midstate, layer_adrs, tree_adrs, idx) +} + +fn ht_verify(signature: XMSSHTSignature, pk_seed: u128, message: u128, midstate: u256, tree_adrs_2: u64, leaf_idx: u32, idx: u32) -> u128 { + let (_, root, _, _, _, _, _): (u128, u128, u32, u256, u32, u64, u32) = array_fold::(signature, (pk_seed, message, leaf_idx, midstate, 0, tree_adrs_2, idx)); + root +} + +fn sphincs_verify(proof: SPHINCSSignProof, midstate: u256) -> u128 { + let (message_hash, pk, signature): SPHINCSSignProof = proof; + let (pk_seed, pk_root): SPHINCSPK = pk; + let (fors_signature, xmss_ht_signature): SPHINCSSignature = signature; + let (r, _): SPHINCSFORSSignature = fors_signature; + + let message: u256 = sha256_finish(jet::sha_256_block(jet::sha_256_block(jet::sha_256_iv(), adrs_256(0, 0, 0, 14, 0, 0, 0), r), <(u128, u128)>::into((pk_seed, pk_root)), message_hash), 1024); + + let (_, ((leaf_part, _), _)): (u128, ((u32, u32), u64)) = ::into(message); + let idx: u32 = jet::right_shift_32(8, jet::left_shift_32(4, leaf_part)); + let leaf_adrs: u32 = jet::and_32(idx, 0x00000FFF); + let idx: u32 = jet::right_shift_32(12, idx); + + let (_, tree_adrs_2): (bool, u64) = jet::add_64(jet::multiply_32(idx, jet::left_shift_32(12, 1)), <(u32, u32)>::into((0, leaf_adrs))); + let fors_pk: u128 = sphincs_get_fors_pub_key(fors_signature, midstate, message, 0, tree_adrs_2); + + ht_verify(xmss_ht_signature, pk_seed, fors_pk, midstate, <(u32, u32)>::into((0, idx)), leaf_adrs, idx) +} + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs_wots_tw.simf b/contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs_wots_tw.simf new file mode 100644 index 0000000..fc34b49 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs_wots_tw.simf @@ -0,0 +1,67 @@ +// Winternitz parameter w = 4 +#ifndef SPHINCS_WOTS_TW_SIGNATURE +#define SPHINCS_WOTS_TW_SIGNATURE + +#include "../templates/array_map.simf" +#include "../templates/assert_arrays.simf" +#include "../utils.simf" +#include "../types.simf" + +// The counter type bounds the loop: for_while unrolls 2^N iterations for a u{N} +// counter. With w = 4 a chain is at most 3 hashes long, so u2 (4 iterations) is +// enough -- a u8 counter would unroll the body 256 times. +fn sphincs_wots_tw_hash_n_times_iter(key_hash: u128, ctx: (u2, u32, u32, u256, u2, u32, u32, u64, u32), counter: u2) -> Either { + let (n, chain_idx, key_index, midstate, from_idx, layer_adrs, tree_adrs_1, tree_adrs_2, wots_type): (u2, u32, u32, u256, u2, u32, u32, u64, u32) = ctx; + let (_, hashed_times): (bool, u8) = jet::add_8(<(u4, (u2, u2))>::into((0, (0, counter))), <(u4, (u2, u2))>::into((0, (0, from_idx)))); + + match jet::eq_8(<(u4, (u2, u2))>::into((0, (0, counter))), <(u4, (u2, u2))>::into((0, (0, n)))) { + true => Left(key_hash), + false => { + let (hash, _): (u128, u128) = ::into(jet::sha_256_block(midstate, adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, wots_type, key_index, chain_idx, <(u16, (u8, u8))>::into((0, (0, hashed_times)))), sha256_tail_16(key_hash, 896))); + + Right(hash) + } + } +} + +// Only (message_hash, idx, checksum) changes from chain to chain; the address +// fields and the midstate are the same for all 64 chains, so they travel as a +// read-only invariant instead of being copied back out of every recursion step. +fn sphincs_restore_wots_tw_signature(el: u128, ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> (u128, (u8, u8)) { + let (idx, checksum): (u8, u8) = ctx; + let (message_hash, key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, wots_type): (u128, u32, u256, u32, u32, u64, u32) = inv; + + let (n1, n2, n3, n4): (u2, u2, u2, u2) = ::into(get_byte_at_128(message_hash, jet::divide_8(idx, 4))); + let idx_mod: u8 = jet::modulo_8(idx, 4); + + let n: u2 = match jet::eq_8(idx_mod, 0) { + true => n1, + false => { + match jet::eq_8(idx_mod, 1) { + true => n2, + false => { + match jet::eq_8(idx_mod, 2) { + true => n3, + false => n4, + } + } + } + } + }; + + let (_, s_n): (bool, u8) = jet::subtract_8(3, <(u4, (u2, u2))>::into((0, (0, n)))); + let (_, (_, s_n)): (u4, (u2, u2)) = ::into(s_n); + let (_, checksum): (bool, u8) = jet::add_8(checksum, <(u4, (u2, u2))>::into((0, (0, n)))); + + (unwrap_left::(for_while::(el, (s_n, <(u16, (u8, u8))>::into((0, (0, idx))), key_idx, midstate, n, layer_adrs, tree_adrs_1, tree_adrs_2, wots_type))), (wrapping_add_8(idx, 1), checksum)) +} + +DEFINE_MAP_ARR_INV_1(u128, u128, (u8, u8), (u128, u32, u256, u32, u32, u64, u32), sphincs_restore_wots_tw_signature) +DEFINE_MAP_ARR_INV(u128, u128, (u8, u8), (u128, u32, u256, u32, u32, u64, u32), sphincs_restore_wots_tw_signature, 2, 1) +DEFINE_MAP_ARR_INV(u128, u128, (u8, u8), (u128, u32, u256, u32, u32, u64, u32), sphincs_restore_wots_tw_signature, 4, 2) +DEFINE_MAP_ARR_INV(u128, u128, (u8, u8), (u128, u32, u256, u32, u32, u64, u32), sphincs_restore_wots_tw_signature, 8, 4) +DEFINE_MAP_ARR_INV(u128, u128, (u8, u8), (u128, u32, u256, u32, u32, u64, u32), sphincs_restore_wots_tw_signature, 16, 8) +DEFINE_MAP_ARR_INV(u128, u128, (u8, u8), (u128, u32, u256, u32, u32, u64, u32), sphincs_restore_wots_tw_signature, 32, 16) +DEFINE_MAP_ARR_INV(u128, u128, (u8, u8), (u128, u32, u256, u32, u32, u64, u32), sphincs_restore_wots_tw_signature, 64, 32) + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs_xmss.simf b/contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs_xmss.simf new file mode 100644 index 0000000..e3f0f4f --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/sphincs/sphincs_xmss.simf @@ -0,0 +1,46 @@ +#ifndef SPHINCS_XMSS_SIGNATURE +#define SPHINCS_XMSS_SIGNATURE + +#include "sphincs_wots_tw.simf" +#include "../types.simf" + +fn sphincs_hash_leaf_xmss(public_key: SPHINCSWOTSTWKey, ctx: (u32, u256, u32, u32, u64, u32)) -> u128 { + let (key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, pk_type): (u32, u256, u32, u32, u64, u32) = ctx; + + let [k00, k01, k02, k03, k04, k05, k06, k07, k08, k09, k10, k11, k12, k13, k14, k15, k16, k17, k18, k19, k20, k21, k22, k23, k24, k25, k26, k27, k28, k29, k30, k31, k32, k33, k34, k35, k36, k37, k38, k39, k40, k41, k42, k43, k44, k45, k46, k47, k48, k49, k50, k51, k52, k53, k54, k55, k56, k57, k58, k59, k60, k61, k62, k63]: [u128; 64] = public_key; + + truncate_128(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(midstate, adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, pk_type, key_index, 0, 0), <(u128, u128)>::into((k00, k01))), <(u128, u128)>::into((k02, k03)), <(u128, u128)>::into((k04, k05))), <(u128, u128)>::into((k06, k07)), <(u128, u128)>::into((k08, k09))), <(u128, u128)>::into((k10, k11)), <(u128, u128)>::into((k12, k13))), <(u128, u128)>::into((k14, k15)), <(u128, u128)>::into((k16, k17))), <(u128, u128)>::into((k18, k19)), <(u128, u128)>::into((k20, k21))), <(u128, u128)>::into((k22, k23)), <(u128, u128)>::into((k24, k25))), <(u128, u128)>::into((k26, k27)), <(u128, u128)>::into((k28, k29))), <(u128, u128)>::into((k30, k31)), <(u128, u128)>::into((k32, k33))), <(u128, u128)>::into((k34, k35)), <(u128, u128)>::into((k36, k37))), <(u128, u128)>::into((k38, k39)), <(u128, u128)>::into((k40, k41))), <(u128, u128)>::into((k42, k43)), <(u128, u128)>::into((k44, k45))), <(u128, u128)>::into((k46, k47)), <(u128, u128)>::into((k48, k49))), <(u128, u128)>::into((k50, k51)), <(u128, u128)>::into((k52, k53))), <(u128, u128)>::into((k54, k55)), <(u128, u128)>::into((k56, k57))), <(u128, u128)>::into((k58, k59)), <(u128, u128)>::into((k60, k61))), <(u128, u128)>::into((k62, k63)), sha256_tail_0(8960))) +} + +fn sphincs_hash_node_xmss(left: u128, right: u128, ctx: (u32, u32, u256, u32, u32, u64)) -> u128 { + let (height, node_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2): (u32, u32, u256, u32, u32, u64) = ctx; + + truncate_128(sha256_finish(jet::sha_256_block(midstate, adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, 12, 0, height, node_idx), <(u128, u128)>::into((left, right))), 1024)) +} + +fn sphincs_xmss_root_from_path_iter(el: u128, ctx: (u128, u32, u32, u256, u32, u32, u64)) -> (u128, u32, u32, u256, u32, u32, u64) { + let (current_hash, height, key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2): (u128, u32, u32, u256, u32, u32, u64) = ctx; + + let (new_key_idx, rem): (u32, u32) = jet::div_mod_32(key_idx, 2); + let (left, right): (u128, u128) = match jet::eq_32(rem, 0) { + true => (current_hash, el), + false => (el, current_hash), + }; + + let (_, new_height): (bool, u32) = jet::add_32(height, 1); + (sphincs_hash_node_xmss(left, right, (height, new_key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2)), new_height, new_key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2) +} + +fn sphincs_get_xmss_pub_key(signature: XMSSSignature, pk_seed: u128, message: u128, key_index: u32, midstate: u256, layer_adrs: u32, tree_adrs_1: u32, tree_adrs_2: u64) -> u128 { + let (signature, merkle_path): XMSSSignature = signature; + let (_, counter, message_signature): SPHINCSWOTSTWSignature = signature; + + let (message, _): (u128, u128) = ::into(sha256_finish_4(jet::sha_256_block(jet::sha_256_iv(), adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, 13, key_index, 0, 0), <(u128, u128)>::into((pk_seed, message))), counter, 544)); + + let (pk, (_, checksum)): ([u128; 64], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_64(message_signature, (0, 0), (message, key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, 10)); + assert!(jet::eq_8(checksum, 140)); + + xmss_path_root(array_fold::(merkle_path, (sphincs_hash_leaf_xmss(pk, (key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, 11)), 1, key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2))) +} + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/templates/array_map.simf b/contrib/shrincs-simplicity-verifier/upstream/templates/array_map.simf new file mode 100644 index 0000000..bfdd16e --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/templates/array_map.simf @@ -0,0 +1,51 @@ +#ifndef UTILS_ARRAY_MAP_SIMF +#define UTILS_ARRAY_MAP_SIMF + +/// Map an array of size 1. +/// * `T1` is the type of the elements in the array. +/// * `T2` is the type of the elements in the array after mapping. +/// * `CTX` is the context of the mapping function. +/// * `F` is the mapping function: (T1, CTX) -> (T2, CTX). +/// Returns a tuple of the mapped array and the updated context. +#define DEFINE_MAP_ARR_1(T1, T2, CTX, F) \ +fn F##_map_arr_1(arr: [T1; 1], ctx: CTX) -> ([T2; 1], CTX) { \ + let (res, ctx): (T2, CTX) = F(<[T1; 1]>::into(arr), ctx); \ + ([res], ctx) \ +} + +/// Map an array of size N = 2^k. +/// * `T1` is the type of the elements in the array. +/// * `T2` is the type of the elements in the array after mapping. +/// * `N` is the size of the array. +/// * `HALF_N` is the size of the array after splitting in half. +/// * `CTX` is the context of the mapping function: (T1, CTX) -> (T2, CTX). +/// Returns a tuple of the mapped array and the updated context. +#define DEFINE_MAP_ARR(T1, T2, CTX, F, N, HALF_N) \ +fn F##_map_arr_##N(arr: [T1; N], ctx: CTX) -> ([T2; N], CTX) { \ + let (arr1, arr2): ([T1; HALF_N], [T1; HALF_N]) = <[T1; N]>::into(arr); \ + let (res1, ctx): ([T2; HALF_N], CTX) = F##_map_arr_##HALF_N(arr1, ctx); \ + let (res2, ctx): ([T2; HALF_N], CTX) = F##_map_arr_##HALF_N(arr2, ctx); \ + (<([T2; HALF_N], [T2; HALF_N])>::into((res1, res2)), ctx) \ +} + +/// Map an array of size 1, with a read-only invariant passed alongside the +/// threaded context. +/// * `INV` is data that is identical for every element. Keeping it out of `CTX` +/// means it is not part of the return type, so it is not copied back up +/// through the recursion tree. +#define DEFINE_MAP_ARR_INV_1(T1, T2, CTX, INV, F) \ +fn F##_map_arr_1(arr: [T1; 1], ctx: CTX, inv: INV) -> ([T2; 1], CTX) { \ + let (res, ctx): (T2, CTX) = F(<[T1; 1]>::into(arr), ctx, inv); \ + ([res], ctx) \ +} + +/// Map an array of size N = 2^k, with a read-only invariant. +#define DEFINE_MAP_ARR_INV(T1, T2, CTX, INV, F, N, HALF_N) \ +fn F##_map_arr_##N(arr: [T1; N], ctx: CTX, inv: INV) -> ([T2; N], CTX) { \ + let (arr1, arr2): ([T1; HALF_N], [T1; HALF_N]) = <[T1; N]>::into(arr); \ + let (res1, ctx): ([T2; HALF_N], CTX) = F##_map_arr_##HALF_N(arr1, ctx, inv); \ + let (res2, ctx): ([T2; HALF_N], CTX) = F##_map_arr_##HALF_N(arr2, ctx, inv); \ + (<([T2; HALF_N], [T2; HALF_N])>::into((res1, res2)), ctx) \ +} + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/templates/assert_arrays.simf b/contrib/shrincs-simplicity-verifier/upstream/templates/assert_arrays.simf new file mode 100644 index 0000000..fcc9d9e --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/templates/assert_arrays.simf @@ -0,0 +1,29 @@ +#ifndef UTILS_ARRAY_EQ_SIMF +#define UTILS_ARRAY_EQ_SIMF + +/// Assert if arrays of size 1 are equals. +/// * `T1` is the type of the elements in the first array. +/// * `T2` is the type of the elements in the second array. +/// * `F` is the function: (T1, T2) -> bool. +/// * `ID` is an identifier to make the group of functions unique. +#define DEFINE_ASSERT_EQ_ARR_1(T1, T2, F, ID) \ +fn ID##_assert_eq_arr_1(arr1: [T1; 1], arr2: [T2; 1]) { \ + assert!(F(<[T1; 1]>::into(arr1), <[T2; 1]>::into(arr2))); \ +} + +/// Assert if arrays are equals. +/// * `T1` is the type of the elements in the first array. +/// * `T2` is the type of the elements in the second array. +/// * `F` is the function: (T1, T2) -> bool. +/// * `N` is the size of the arrays. +/// * `HALF_N` is the size of the arrays after splitting in half. +/// * `ID` is an identifier to make the group of functions unique. +#define DEFINE_ASSERT_EQ_ARR(T1, T2, F, N, HALF_N, ID) \ +fn ID##_assert_eq_arr_##N(arr1: [T1; N], arr2: [T2; N]) { \ + let (arr1_1, arr1_2): ([T1; HALF_N], [T1; HALF_N]) = <[T1; N]>::into(arr1); \ + let (arr2_1, arr2_2): ([T2; HALF_N], [T2; HALF_N]) = <[T2; N]>::into(arr2); \ + ID##_assert_eq_arr_##HALF_N(arr1_1, arr2_1); \ + ID##_assert_eq_arr_##HALF_N(arr1_2, arr2_2); \ +} + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/test.simf b/contrib/shrincs-simplicity-verifier/upstream/test.simf new file mode 100644 index 0000000..e98d207 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/test.simf @@ -0,0 +1,9 @@ +#define TESTING +#include "lamport.simf" +#include "wots.simf" +#include "sphincs/sphincs.simf" + +fn main() { + test_lamport_sign_verify(); + test_wots_sign_verify(); +} diff --git a/contrib/shrincs-simplicity-verifier/upstream/types.simf b/contrib/shrincs-simplicity-verifier/upstream/types.simf new file mode 100644 index 0000000..1a0bbb6 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/types.simf @@ -0,0 +1,57 @@ +#ifndef TYPES_H +#define TYPES_H + +// WOTS +type WOTSSignature = ([u256; 64], [u256; 3]); +type WOTSKey = ([u256; 64], [u256; 3]); +// Represent signature proof as (message_hash, signature, public_key) +type WOTSSignProof = (u256, WOTSSignature, WOTSKey); + + +// Lamport +type KeyPair = (u256, u256); +// Represent signature proof as (message_hash, signature, public_key) +type LamportSignProof = (u256, [u256; 256], [KeyPair; 256]); + + +// SPHINCS + +// FORS +// (SecretValue, MerklePath len a) +type SPHINCSFORSSignaturePart = (u128, [u128; 22]); +// (R, k - 1 len array of sizes 2^x of FORS signature parts) +type SPHINCSFORSSignature = (u256, ([SPHINCSFORSSignaturePart; 4], [SPHINCSFORSSignaturePart; 1])); + +// SPHINCS WOTS +type SPHINCSWOTSTWKey = [u128; 64]; +type SPHINCSWOTSTWSignature = (u256, u32, [u128; 64]); +// Represent signature proof as (message_hash, signature, public_key, key_index, public_parameter) +type SPHINCSWOTSTWSignProof = (u128, SPHINCSWOTSTWSignature, SPHINCSWOTSTWKey, u32, u128); + +// XMSS +type SPHINCSMerklePath = [u128; 12]; +// (WOTSSign, MerklePath) +type XMSSSignature = (SPHINCSWOTSTWSignature, SPHINCSMerklePath); + +// (PK.seed, root) +type SPHINCSPK = (u128, u128); +// d len array of XMSS signatures +type XMSSHTSignature = [XMSSSignature; 2]; +// (FORSSign, HTSign) +type SPHINCSSignature = (SPHINCSFORSSignature, XMSSHTSignature); +// (Message, PK, Signature) +type SPHINCSSignProof = (u256, SPHINCSPK, SPHINCSSignature); + + +// SHRINCS + +// UXMSS +// (wots+c signature, merkle path up to 511 elements, signature index) +type UXMSSSignature = (SPHINCSWOTSTWSignature, List, u32); +// (Message, (PK.seed, Root), Signature) +type UXMSSSignProof = (u256, SPHINCSPK, UXMSSSignature); + +// (Message, (PK.seed, PK.root), stateless or stateful signature, root of unused signature) +type SHRINCSSignProof = (u256, SPHINCSPK, Either, u128); + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/utils.simf b/contrib/shrincs-simplicity-verifier/upstream/utils.simf new file mode 100644 index 0000000..4680f82 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/utils.simf @@ -0,0 +1,273 @@ +#ifndef IMPORTED_UTILS_SIMF +#define IMPORTED_UTILS_SIMF + +// The 32-byte ADRS, laid out as one half of a block. +fn adrs_256(layer: u32, tree_1: u32, tree_2: u64, adrs_type: u32, word_1: u32, word_2: u32, word_3: u32) -> u256 { + <(u128, u128)>::into(( + <(u64, u64)>::into((<(u32, u32)>::into((layer, tree_1)), tree_2)), + <(u64, u64)>::into((<(u32, u32)>::into((adrs_type, word_1)), <(u32, u32)>::into((word_2, word_3)))) + )) +} + +fn sha256_tail_16(value: u128, bit_len: u64) -> u256 { + <(u128, u128)>::into((value, <(u64, u64)>::into((0x8000000000000000, bit_len)))) +} + +fn sha256_tail_0(bit_len: u64) -> u256 { + <(u128, u128)>::into((0x80000000000000000000000000000000, <(u64, u64)>::into((0, bit_len)))) +} + +fn sha256_finish(midstate: u256, bit_len: u64) -> u256 { + jet::sha_256_block( + midstate, + 0x8000000000000000000000000000000000000000000000000000000000000000, + <(u128, u128)>::into((0, <(u64, u64)>::into((0, bit_len)))) + ) +} + +fn sha256_finish_4(midstate: u256, value: u32, bit_len: u64) -> u256 { + jet::sha_256_block( + midstate, + <(u128, u128)>::into((<(u32, u32, u32, u32)>::into((value, 0x80000000, 0, 0)), 0)), + <(u128, u128)>::into((0, <(u64, u64)>::into((0, bit_len)))) + ) +} + +fn sha256_of_256(value: u256) -> u256 { + jet::sha_256_block(jet::sha_256_iv(), value, sha256_tail_0(256)) +} + +fn truncate_128(value: u256) -> u128 { + let (high, _): (u128, u128) = ::into(value); + high +} + +fn wrapping_add_32(a: u32, b: u32) -> u32 { + let (_, sum): (bool, u32) = jet::add_32(a, b); + sum +} + +fn xmss_path_root(state: (u128, u32, u32, u256, u32, u32, u64)) -> u128 { + let (root, _, _, _, _, _, _): (u128, u32, u32, u256, u32, u32, u64) = state; + root +} + +fn uxmss_path_root(state: (u128, bool, u256, u32)) -> u128 { + let (root, _, _, _): (u128, bool, u256, u32) = state; + root +} + +fn wrapping_add_8(a: u8, b: u8) -> u8 { + let (_, sum): (bool, u8) = jet::add_8(a, b); + sum +} + +fn eq_128(val1: u128, val2: u128) -> bool { + jet::eq_256(<(u128, u128)>::into((0, val1)), <(u128, u128)>::into((0, val2))) +} + +fn right_shift_256(value: u256, shift: u8) -> u256 { + let (a, b, c, d): (u64, u64, u64, u64) = ::into(value); + + let (a, b, c, d): (u64, u64, u64, u64) = match jet::lt_8(shift, 64) { + true => (a, b, c, d), + false => { + match jet::lt_8(shift, 128) { + true => (0, a, b, c), + false => { + match jet::lt_8(shift, 192) { + true => (0, 0, a, b), + false => (0, 0, 0, a), + } + } + } + } + }; + + let shift: u8 = jet::modulo_8(shift, 64); + let (_, left_shift): (bool, u8) = jet::subtract_8(64, shift); + + let new_d: u64 = jet::or_64(jet::left_shift_64(left_shift, c), jet::right_shift_64(shift, d)); + let new_c: u64 = jet::or_64(jet::left_shift_64(left_shift, b), jet::right_shift_64(shift, c)); + let new_b: u64 = jet::or_64(jet::left_shift_64(left_shift, a), jet::right_shift_64(shift, b)); + let new_a: u64 = jet::right_shift_64(shift, a); + + <(u64, u64, u64, u64)>::into((new_a, new_b, new_c, new_d)) +} + +fn right_shift_128(value: u128, shift: u8) -> u128 { + let (a, b, c, d): (u32, u32, u32, u32) = ::into(value); + + let (a, b, c, d): (u32, u32, u32, u32) = match jet::lt_8(shift, 32) { + true => (a, b, c, d), + false => { + match jet::lt_8(shift, 64) { + true => (0, a, b, c), + false => { + match jet::lt_8(shift, 96) { + true => (0, 0, a, b), + false => (0, 0, 0, a), + } + } + } + } + }; + + let shift: u8 = jet::modulo_8(shift, 32); + let (_, left_shift): (bool, u8) = jet::subtract_8(32, shift); + + let new_d: u32 = jet::or_32(jet::left_shift_32(left_shift, c), jet::right_shift_32(shift, d)); + let new_c: u32 = jet::or_32(jet::left_shift_32(left_shift, b), jet::right_shift_32(shift, c)); + let new_b: u32 = jet::or_32(jet::left_shift_32(left_shift, a), jet::right_shift_32(shift, b)); + let new_a: u32 = jet::right_shift_32(shift, a); + + <(u32, u32, u32, u32)>::into((new_a, new_b, new_c, new_d)) +} + +fn get_bit_at(value: u256, possition: u8) -> u1 { + let (a, b, c, d): (u64, u64, u64, u64) = ::into(value); + + let part: u64 = match jet::lt_8(possition, 64) { + true => d, + false => { + match jet::lt_8(possition, 128) { + true => c, + false => { + match jet::lt_8(possition, 192) { + true => b, + false => a, + } + }, + } + }, + }; + + let (_, in_part_possition): (u8, u8) = jet::div_mod_8(possition, 64); + + ::into(jet::lt_64(0, jet::and_64(part, jet::left_shift_64(in_part_possition, 1)))) +} + +fn get_byte_at(value: u256, possition: u8) -> u8 { + assert!(jet::lt_8(possition, 32)); + let (a, b, c, d): (u64, u64, u64, u64) = ::into(value); + + let part: u64 = match jet::lt_8(possition, 8) { + true => d, + false => { + match jet::lt_8(possition, 16) { + true => c, + false => { + match jet::lt_8(possition, 24) { + true => b, + false => a, + } + }, + } + }, + }; + + let possition: u8 = jet::modulo_8(possition, 8); + let (a, b, c, d): (u16, u16, u16, u16) = ::into(part); + + let part: u16 = match jet::lt_8(possition, 2) { + true => d, + false => { + match jet::lt_8(possition, 4) { + true => c, + false => { + match jet::lt_8(possition, 6) { + true => b, + false => a, + } + }, + } + }, + }; + + let possition: u8 = jet::modulo_8(possition, 2); + let (a, b): (u8, u8) = ::into(part); + match jet::eq_8(possition, 0) { + true => b, + false => a, + } +} + +fn get_byte_at_128(value: u128, possition: u8) -> u8 { + assert!(jet::lt_8(possition, 16)); + let (a, b, c, d): (u32, u32, u32, u32) = ::into(value); + + let part: u32 = match jet::lt_8(possition, 4) { + true => a, + false => { + match jet::lt_8(possition, 8) { + true => b, + false => { + match jet::lt_8(possition, 12) { + true => c, + false => d, + } + }, + } + }, + }; + + let possition: u8 = jet::modulo_8(possition, 4); + let (a, b, c, d): (u8, u8, u8, u8) = ::into(part); + + match jet::eq_8(possition, 0) { + true => a, + false => { + match jet::eq_8(possition, 1) { + true => b, + false => { + match jet::eq_8(possition, 2) { + true => c, + false => d, + } + }, + } + }, + } +} + +fn get_at_array_iter(el: u256, ctx: (u8, u8, u256)) -> (u8, u8, u256) { + let (idx, pos, saved): (u8, u8, u256) = ctx; + match jet::eq_8(idx, pos) { + true => { + let (_, new_pos): (bool, u8) = jet::add_8(pos, 1); + (idx, new_pos, el) + }, + false => { + let (_, new_pos): (bool, u8) = jet::add_8(pos, 1); + (idx, new_pos, saved) + } + } +} + +#define DEFINE_GET_AT_ARR(N) \ +fn get_at_array_##N(arr: [u256; N], index: u8) -> u256 { \ + let (_, _, el): (u8, u8, u256) = array_fold::(arr, (index, 0, 0)); \ + el \ +} + +fn get_at_array_128_iter(el: u128, ctx: (u32, u32, u128)) -> (u32, u32, u128) { + let (idx, pos, saved): (u32, u32, u128) = ctx; + match jet::eq_32(idx, pos) { + true => { + let (_, new_pos): (bool, u32) = jet::add_32(pos, 1); + (idx, new_pos, el) + }, + false => { + let (_, new_pos): (bool, u32) = jet::add_32(pos, 1); + (idx, new_pos, saved) + } + } +} + +#define DEFINE_GET_AT_ARR_128(N) \ +fn get_at_array_128_##N(arr: [u128; N], index: u32) -> u128 { \ + let (_, _, el): (u32, u32, u128) = array_fold::(arr, (index, 0, 0)); \ + el \ +} + +#endif diff --git a/contrib/shrincs-simplicity-verifier/upstream/wots.simf b/contrib/shrincs-simplicity-verifier/upstream/wots.simf new file mode 100644 index 0000000..4a6c629 --- /dev/null +++ b/contrib/shrincs-simplicity-verifier/upstream/wots.simf @@ -0,0 +1,164 @@ +#ifndef WOTS_SIGNATURE +#define WOTS_SIGNATURE + +#include "templates/array_map.simf" +#include "templates/assert_arrays.simf" +#include "utils.simf" +#include "types.simf" + +// w = 16, so a chain is at most 15 hashes long: a u4 counter unrolls the body 16 +// times, where a u8 counter would unroll it 256 times. +fn wots_hash_n_times_iter(key_hash: u256, n: u8, counter: u4) -> Either { + match jet::eq_8(<(u4, u4)>::into((0, counter)), n) { + true => Left(key_hash), + false => { + Right(sha256_of_256(key_hash)) + } + } +} + +fn get_wots_signature(el: u256, ctx: (u256, u8, u16)) -> (u256, (u256, u8, u16)) { + let (message_hash, idx, checksum): (u256, u8, u16) = ctx; + let (_, byte_idx): (bool, u8) = jet::subtract_8(31, jet::divide_8(idx, 2)); + let n: u8 = get_byte_at(message_hash, byte_idx); + let n: u8 = match jet::eq_8(jet::modulo_8(idx, 2), 0) { + true => jet::right_shift_8(4, n), + false => jet::and_8(n, 0x0F), + }; + let sign_part: Either = for_while::(el, n); + + let (_, checksum): (bool, u16) = jet::add_16(checksum, 15); + let (_, checksum): (bool, u16) = jet::subtract_16(checksum, <(u8, u8)>::into((0, n))); + + let (_, new_idx): (bool, u8) = jet::add_8(idx, 1); + (unwrap_left::(sign_part), (message_hash, new_idx, checksum)) +} + +fn get_wots_checksum_signature(private_key: [u256; 3], checksum: u16) -> [u256; 3] { + let [key1, key2, key3]: [u256; 3] = private_key; + let (_, n1, n2, n3): (u4, u4, u4, u4) = ::into(checksum); + let sign_part_1: Either = for_while::(key1, <(u4, u4)>::into((0, n1))); + let sign_part_2: Either = for_while::(key2, <(u4, u4)>::into((0, n2))); + let sign_part_3: Either = for_while::(key3, <(u4, u4)>::into((0, n3))); + + [unwrap_left::(sign_part_1), unwrap_left::(sign_part_2), unwrap_left::(sign_part_3)] +} + +fn restore_wots_signature(el: u256, ctx: (u256, u8, u16)) -> (u256, (u256, u8, u16)) { + let (message_hash, idx, checksum): (u256, u8, u16) = ctx; + let (_, byte_idx): (bool, u8) = jet::subtract_8(31, jet::divide_8(idx, 2)); + let n: u8 = get_byte_at(message_hash, byte_idx); + let n: u8 = match jet::eq_8(jet::modulo_8(idx, 2), 0) { + true => jet::right_shift_8(4, n), + false => jet::and_8(n, 0x0F), + }; + let (_, n): (bool, u8) = jet::subtract_8(15, n); + let verify_part: Either = for_while::(el, n); + + let (_, checksum): (bool, u16) = jet::add_16(checksum, <(u8, u8)>::into((0, n))); + + let (_, new_idx): (bool, u8) = jet::add_8(idx, 1); + (unwrap_left::(verify_part), (message_hash, new_idx, checksum)) +} + +fn verify_wots_checksum(checksum_signature: [u256; 3], checksum: u16, checksum_pk: [u256; 3]) { + let (_, n1, n2, n3): (u4, u4, u4, u4) = ::into(checksum); + let [sig1, sig2, sig3]: [u256; 3] = checksum_signature; + let [pk1, pk2, pk3]: [u256; 3] = checksum_pk; + + let (_, n1): (bool, u8) = jet::subtract_8(15, <(u4, u4)>::into((0, n1))); + let (_, n2): (bool, u8) = jet::subtract_8(15, <(u4, u4)>::into((0, n2))); + let (_, n3): (bool, u8) = jet::subtract_8(15, <(u4, u4)>::into((0, n3))); + + let verify_part1: Either = for_while::(sig1, n1); + let verify_part2: Either = for_while::(sig2, n2); + let verify_part3: Either = for_while::(sig3, n3); + + assert!(jet::eq_256(unwrap_left::(verify_part1), pk1)); + assert!(jet::eq_256(unwrap_left::(verify_part2), pk2)); + assert!(jet::eq_256(unwrap_left::(verify_part3), pk3)); +} + +fn get_wots_pub_key(el: u256, ctx: ()) -> (u256, ()) { + let pk: Either = for_while::(el, 15); + (unwrap_left::(pk), ()) +} + +DEFINE_MAP_ARR_1(u256, u256, (u256, u8, u16), get_wots_signature) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), get_wots_signature, 2, 1) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), get_wots_signature, 4, 2) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), get_wots_signature, 8, 4) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), get_wots_signature, 16, 8) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), get_wots_signature, 32, 16) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), get_wots_signature, 64, 32) + +DEFINE_MAP_ARR_1(u256, u256, (u256, u8, u16), restore_wots_signature) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), restore_wots_signature, 2, 1) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), restore_wots_signature, 4, 2) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), restore_wots_signature, 8, 4) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), restore_wots_signature, 16, 8) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), restore_wots_signature, 32, 16) +DEFINE_MAP_ARR(u256, u256, (u256, u8, u16), restore_wots_signature, 64, 32) + +DEFINE_ASSERT_EQ_ARR_1(u256, u256, jet::eq_256, wots) +DEFINE_ASSERT_EQ_ARR(u256, u256, jet::eq_256, 2, 1, wots) +DEFINE_ASSERT_EQ_ARR(u256, u256, jet::eq_256, 4, 2, wots) +DEFINE_ASSERT_EQ_ARR(u256, u256, jet::eq_256, 8, 4, wots) +DEFINE_ASSERT_EQ_ARR(u256, u256, jet::eq_256, 16, 8, wots) +DEFINE_ASSERT_EQ_ARR(u256, u256, jet::eq_256, 32, 16, wots) +DEFINE_ASSERT_EQ_ARR(u256, u256, jet::eq_256, 64, 32, wots) + +DEFINE_MAP_ARR_1(u256, u256, (), get_wots_pub_key) +DEFINE_MAP_ARR(u256, u256, (), get_wots_pub_key, 2, 1) +DEFINE_MAP_ARR(u256, u256, (), get_wots_pub_key, 4, 2) +DEFINE_MAP_ARR(u256, u256, (), get_wots_pub_key, 8, 4) +DEFINE_MAP_ARR(u256, u256, (), get_wots_pub_key, 16, 8) +DEFINE_MAP_ARR(u256, u256, (), get_wots_pub_key, 32, 16) +DEFINE_MAP_ARR(u256, u256, (), get_wots_pub_key, 64, 32) + +fn get_wots_public_key(private_key: WOTSKey) -> WOTSKey { + let (private_key, checksum_key): WOTSKey = private_key; + let (pk, _): ([u256; 64], ()) = get_wots_pub_key_map_arr_64(private_key, ()); + + let [chk1, chk2, chk3]: [u256; 3] = checksum_key; + + let checksum_pk_1: Either = for_while::(chk1, 15); + let checksum_pk_2: Either = for_while::(chk2, 15); + let checksum_pk_3: Either = for_while::(chk3, 15); + + (pk, [unwrap_left::(checksum_pk_1), unwrap_left::(checksum_pk_2), unwrap_left::(checksum_pk_3)]) +} + +fn wots_signature(message_hash: u256, private_key: WOTSKey) -> WOTSSignature { + let (private_key, checksum_key): WOTSKey = private_key; + let (signature, (_, _, checksum)): ([u256; 64], (u256, u8, u16)) = get_wots_signature_map_arr_64(private_key, (message_hash, 0, 0)); + let checksum_signature: [u256; 3] = get_wots_checksum_signature(checksum_key, checksum); + (signature, checksum_signature) +} + +fn wots_verify(proof: WOTSSignProof) { + let (message_hash, (signature, checksum_signature), (public_key, checksum_pk)): WOTSSignProof = proof; + let (expected_pk, (_, _, checksum)): ([u256; 64], (u256, u8, u16)) = restore_wots_signature_map_arr_64(signature, (message_hash, 0, 0)); + wots_assert_eq_arr_64(expected_pk, public_key); + verify_wots_checksum(checksum_signature, checksum, checksum_pk); +} + +#ifdef TESTING + +fn test_wots_sign_verify() { + let private_key: WOTSKey = ([ + 4, 6, 1, 2, 6, 8, 4, 2, 3, 5, 7, 9, 0, 2, 4, 6, + 4, 6, 1, 2, 6, 8, 4, 2, 3, 5, 7, 9, 0, 2, 4, 6, + 4, 6, 1, 2, 6, 8, 4, 2, 3, 5, 7, 9, 0, 2, 4, 6, + 4, 6, 1, 2, 6, 8, 4, 2, 3, 5, 7, 9, 0, 2, 4, 6, + ], [4, 7, 3]); + + let signature: WOTSSignature = wots_signature(671235314, private_key); + + let proof: WOTSSignProof = (671235314, signature, get_wots_public_key(private_key)); + + wots_verify(proof); +} + +#endif +#endif diff --git a/examples/README.md b/examples/README.md index b14edfa..e9c57e3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,11 +11,13 @@ Roughly in order of complexity: | [`p2pk`](p2pk) | Liquid | The smallest covenant: lock coins to a key with a Simplicity checksig program, then spend them. Start here. | | [`bitcoin_pay`](bitcoin_pay) | Bitcoin | A plain payment with no covenant: what a manifest looks like on Bitcoin, and the `fee` keyword. | | [`bitcoin_covenant`](bitcoin_covenant) | Bitcoin | The `p2pk` program on Bitcoin, run on the Simplicity signet or a local regtest. | +| [`pq_shrincs_smoke`](pq_shrincs_smoke) | Bitcoin | A smoke test, not a pattern to copy: runs the SHRINCS post-quantum signature verifier on the Simplicity signet, unmodified, so the coin it locks is **anyone-can-spend**. | | [`last_will`](last_will) | Liquid | A recursive covenant with a timelocked inheritance path, a cold-key break-out and a hot-key refresh. | | [`dex`](dex) | Liquid | Tessera: a keyless atomic-swap offer anyone can fill, or refund after a timeout. | | [`deadcat_v3`](deadcat_v3) | Liquid | A binary prediction market with on-chain oracle resolution and confidential reissuance tokens. Derived from [Deadcat](https://github.com/Resolvr-io/deadcat); not interoperable with it. | | [`lending_v2`](lending_v2) | Liquid | Peer-to-peer collateralised lending, wire-compatible with [simplicity-lending](https://github.com/BlockstreamResearch/simplicity-lending). | | [`lending_v3`](lending_v3) | Liquid | Work in progress: the redesigned "issuance factory" version of simplicity-lending โ€” create a factory, then offer, accept, cancel, claim and repay loans. | -The examples with their own README (`bitcoin_pay`, `bitcoin_covenant`, `lending_v2`) +The examples with their own README (`bitcoin_pay`, `bitcoin_covenant`, `pq_shrincs_smoke`, +`lending_v2`) include full walkthroughs. diff --git a/examples/pq_shrincs/build.sh b/examples/pq_shrincs/build.sh new file mode 100755 index 0000000..e937d84 --- /dev/null +++ b/examples/pq_shrincs/build.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Flatten src/shrincs_spend.simf and the vendored SHRINCS verifier into shrincs_spend.simf, +# the one file the manifest pins. Rerun after changing either, then `tx-manifest-wallet pin`. +# Needs mcpp (brew install mcpp; apt install mcpp); see +# contrib/shrincs-simplicity-verifier/README.md for why the preprocessor matters. +set -eu +cd "$(dirname "$0")" +# mcpp -P drops comments, so the header that says where the file comes from is added here. +{ + echo "// Generated by build.sh from src/shrincs_spend.simf and the vendored SHRINCS verifier. Do not edit." + mcpp -P -I ../../contrib/shrincs-simplicity-verifier/upstream src/shrincs_spend.simf 2>/dev/null +} > shrincs_spend.simf diff --git a/examples/pq_shrincs/shrincs_spend.simf b/examples/pq_shrincs/shrincs_spend.simf new file mode 100644 index 0000000..ebeb3bd --- /dev/null +++ b/examples/pq_shrincs/shrincs_spend.simf @@ -0,0 +1,574 @@ +// Generated by build.sh from src/shrincs_spend.simf and the vendored SHRINCS verifier. Do not edit. + + + + +fn adrs_256(layer: u32, tree_1: u32, tree_2: u64, adrs_type: u32, word_1: u32, word_2: u32, word_3: u32) -> u256 { + <(u128, u128)>::into(( + <(u64, u64)>::into((<(u32, u32)>::into((layer, tree_1)), tree_2)), + <(u64, u64)>::into((<(u32, u32)>::into((adrs_type, word_1)), <(u32, u32)>::into((word_2, word_3)))) + )) +} + +fn sha256_tail_16(value: u128, bit_len: u64) -> u256 { + <(u128, u128)>::into((value, <(u64, u64)>::into((0x8000000000000000, bit_len)))) +} + +fn sha256_tail_0(bit_len: u64) -> u256 { + <(u128, u128)>::into((0x80000000000000000000000000000000, <(u64, u64)>::into((0, bit_len)))) +} + +fn sha256_finish(midstate: u256, bit_len: u64) -> u256 { + jet::sha_256_block( + midstate, + 0x8000000000000000000000000000000000000000000000000000000000000000, + <(u128, u128)>::into((0, <(u64, u64)>::into((0, bit_len)))) + ) +} + +fn sha256_finish_4(midstate: u256, value: u32, bit_len: u64) -> u256 { + jet::sha_256_block( + midstate, + <(u128, u128)>::into((<(u32, u32, u32, u32)>::into((value, 0x80000000, 0, 0)), 0)), + <(u128, u128)>::into((0, <(u64, u64)>::into((0, bit_len)))) + ) +} + +fn sha256_of_256(value: u256) -> u256 { + jet::sha_256_block(jet::sha_256_iv(), value, sha256_tail_0(256)) +} + +fn truncate_128(value: u256) -> u128 { + let (high, _): (u128, u128) = ::into(value); + high +} + +fn wrapping_add_32(a: u32, b: u32) -> u32 { + let (_, sum): (bool, u32) = jet::add_32(a, b); + sum +} + +fn xmss_path_root(state: (u128, u32, u32, u256, u32, u32, u64)) -> u128 { + let (root, _, _, _, _, _, _): (u128, u32, u32, u256, u32, u32, u64) = state; + root +} + +fn uxmss_path_root(state: (u128, bool, u256, u32)) -> u128 { + let (root, _, _, _): (u128, bool, u256, u32) = state; + root +} + +fn wrapping_add_8(a: u8, b: u8) -> u8 { + let (_, sum): (bool, u8) = jet::add_8(a, b); + sum +} + +fn eq_128(val1: u128, val2: u128) -> bool { + jet::eq_256(<(u128, u128)>::into((0, val1)), <(u128, u128)>::into((0, val2))) +} + +fn right_shift_256(value: u256, shift: u8) -> u256 { + let (a, b, c, d): (u64, u64, u64, u64) = ::into(value); + + let (a, b, c, d): (u64, u64, u64, u64) = match jet::lt_8(shift, 64) { + true => (a, b, c, d), + false => { + match jet::lt_8(shift, 128) { + true => (0, a, b, c), + false => { + match jet::lt_8(shift, 192) { + true => (0, 0, a, b), + false => (0, 0, 0, a), + } + } + } + } + }; + + let shift: u8 = jet::modulo_8(shift, 64); + let (_, left_shift): (bool, u8) = jet::subtract_8(64, shift); + + let new_d: u64 = jet::or_64(jet::left_shift_64(left_shift, c), jet::right_shift_64(shift, d)); + let new_c: u64 = jet::or_64(jet::left_shift_64(left_shift, b), jet::right_shift_64(shift, c)); + let new_b: u64 = jet::or_64(jet::left_shift_64(left_shift, a), jet::right_shift_64(shift, b)); + let new_a: u64 = jet::right_shift_64(shift, a); + + <(u64, u64, u64, u64)>::into((new_a, new_b, new_c, new_d)) +} + +fn right_shift_128(value: u128, shift: u8) -> u128 { + let (a, b, c, d): (u32, u32, u32, u32) = ::into(value); + + let (a, b, c, d): (u32, u32, u32, u32) = match jet::lt_8(shift, 32) { + true => (a, b, c, d), + false => { + match jet::lt_8(shift, 64) { + true => (0, a, b, c), + false => { + match jet::lt_8(shift, 96) { + true => (0, 0, a, b), + false => (0, 0, 0, a), + } + } + } + } + }; + + let shift: u8 = jet::modulo_8(shift, 32); + let (_, left_shift): (bool, u8) = jet::subtract_8(32, shift); + + let new_d: u32 = jet::or_32(jet::left_shift_32(left_shift, c), jet::right_shift_32(shift, d)); + let new_c: u32 = jet::or_32(jet::left_shift_32(left_shift, b), jet::right_shift_32(shift, c)); + let new_b: u32 = jet::or_32(jet::left_shift_32(left_shift, a), jet::right_shift_32(shift, b)); + let new_a: u32 = jet::right_shift_32(shift, a); + + <(u32, u32, u32, u32)>::into((new_a, new_b, new_c, new_d)) +} + +fn get_bit_at(value: u256, possition: u8) -> u1 { + let (a, b, c, d): (u64, u64, u64, u64) = ::into(value); + + let part: u64 = match jet::lt_8(possition, 64) { + true => d, + false => { + match jet::lt_8(possition, 128) { + true => c, + false => { + match jet::lt_8(possition, 192) { + true => b, + false => a, + } + }, + } + }, + }; + + let (_, in_part_possition): (u8, u8) = jet::div_mod_8(possition, 64); + + ::into(jet::lt_64(0, jet::and_64(part, jet::left_shift_64(in_part_possition, 1)))) +} + +fn get_byte_at(value: u256, possition: u8) -> u8 { + assert!(jet::lt_8(possition, 32)); + let (a, b, c, d): (u64, u64, u64, u64) = ::into(value); + + let part: u64 = match jet::lt_8(possition, 8) { + true => d, + false => { + match jet::lt_8(possition, 16) { + true => c, + false => { + match jet::lt_8(possition, 24) { + true => b, + false => a, + } + }, + } + }, + }; + + let possition: u8 = jet::modulo_8(possition, 8); + let (a, b, c, d): (u16, u16, u16, u16) = ::into(part); + + let part: u16 = match jet::lt_8(possition, 2) { + true => d, + false => { + match jet::lt_8(possition, 4) { + true => c, + false => { + match jet::lt_8(possition, 6) { + true => b, + false => a, + } + }, + } + }, + }; + + let possition: u8 = jet::modulo_8(possition, 2); + let (a, b): (u8, u8) = ::into(part); + match jet::eq_8(possition, 0) { + true => b, + false => a, + } +} + +fn get_byte_at_128(value: u128, possition: u8) -> u8 { + assert!(jet::lt_8(possition, 16)); + let (a, b, c, d): (u32, u32, u32, u32) = ::into(value); + + let part: u32 = match jet::lt_8(possition, 4) { + true => a, + false => { + match jet::lt_8(possition, 8) { + true => b, + false => { + match jet::lt_8(possition, 12) { + true => c, + false => d, + } + }, + } + }, + }; + + let possition: u8 = jet::modulo_8(possition, 4); + let (a, b, c, d): (u8, u8, u8, u8) = ::into(part); + + match jet::eq_8(possition, 0) { + true => a, + false => { + match jet::eq_8(possition, 1) { + true => b, + false => { + match jet::eq_8(possition, 2) { + true => c, + false => d, + } + }, + } + }, + } +} + +fn get_at_array_iter(el: u256, ctx: (u8, u8, u256)) -> (u8, u8, u256) { + let (idx, pos, saved): (u8, u8, u256) = ctx; + match jet::eq_8(idx, pos) { + true => { + let (_, new_pos): (bool, u8) = jet::add_8(pos, 1); + (idx, new_pos, el) + }, + false => { + let (_, new_pos): (bool, u8) = jet::add_8(pos, 1); + (idx, new_pos, saved) + } + } +} +fn get_at_array_128_iter(el: u128, ctx: (u32, u32, u128)) -> (u32, u32, u128) { + let (idx, pos, saved): (u32, u32, u128) = ctx; + match jet::eq_32(idx, pos) { + true => { + let (_, new_pos): (bool, u32) = jet::add_32(pos, 1); + (idx, new_pos, el) + }, + false => { + let (_, new_pos): (bool, u32) = jet::add_32(pos, 1); + (idx, new_pos, saved) + } + } +} + + + + +type WOTSSignature = ([u256; 64], [u256; 3]); +type WOTSKey = ([u256; 64], [u256; 3]); + +type WOTSSignProof = (u256, WOTSSignature, WOTSKey); + + + +type KeyPair = (u256, u256); + +type LamportSignProof = (u256, [u256; 256], [KeyPair; 256]); + + + + + + +type SPHINCSFORSSignaturePart = (u128, [u128; 22]); + +type SPHINCSFORSSignature = (u256, ([SPHINCSFORSSignaturePart; 4], [SPHINCSFORSSignaturePart; 1])); + + +type SPHINCSWOTSTWKey = [u128; 64]; +type SPHINCSWOTSTWSignature = (u256, u32, [u128; 64]); + +type SPHINCSWOTSTWSignProof = (u128, SPHINCSWOTSTWSignature, SPHINCSWOTSTWKey, u32, u128); + + +type SPHINCSMerklePath = [u128; 12]; + +type XMSSSignature = (SPHINCSWOTSTWSignature, SPHINCSMerklePath); + + +type SPHINCSPK = (u128, u128); + +type XMSSHTSignature = [XMSSSignature; 2]; + +type SPHINCSSignature = (SPHINCSFORSSignature, XMSSHTSignature); + +type SPHINCSSignProof = (u256, SPHINCSPK, SPHINCSSignature); + + + + + + +type UXMSSSignature = (SPHINCSWOTSTWSignature, List, u32); + +type UXMSSSignProof = (u256, SPHINCSPK, UXMSSSignature); + + +type SHRINCSSignProof = (u256, SPHINCSPK, Either, u128); + + + + +fn sphincs_wots_tw_hash_n_times_iter(key_hash: u128, ctx: (u2, u32, u32, u256, u2, u32, u32, u64, u32), counter: u2) -> Either { + let (n, chain_idx, key_index, midstate, from_idx, layer_adrs, tree_adrs_1, tree_adrs_2, wots_type): (u2, u32, u32, u256, u2, u32, u32, u64, u32) = ctx; + let (_, hashed_times): (bool, u8) = jet::add_8(<(u4, (u2, u2))>::into((0, (0, counter))), <(u4, (u2, u2))>::into((0, (0, from_idx)))); + + match jet::eq_8(<(u4, (u2, u2))>::into((0, (0, counter))), <(u4, (u2, u2))>::into((0, (0, n)))) { + true => Left(key_hash), + false => { + let (hash, _): (u128, u128) = ::into(jet::sha_256_block(midstate, adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, wots_type, key_index, chain_idx, <(u16, (u8, u8))>::into((0, (0, hashed_times)))), sha256_tail_16(key_hash, 896))); + + Right(hash) + } + } +} + + + + +fn sphincs_restore_wots_tw_signature(el: u128, ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> (u128, (u8, u8)) { + let (idx, checksum): (u8, u8) = ctx; + let (message_hash, key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, wots_type): (u128, u32, u256, u32, u32, u64, u32) = inv; + + let (n1, n2, n3, n4): (u2, u2, u2, u2) = ::into(get_byte_at_128(message_hash, jet::divide_8(idx, 4))); + let idx_mod: u8 = jet::modulo_8(idx, 4); + + let n: u2 = match jet::eq_8(idx_mod, 0) { + true => n1, + false => { + match jet::eq_8(idx_mod, 1) { + true => n2, + false => { + match jet::eq_8(idx_mod, 2) { + true => n3, + false => n4, + } + } + } + } + }; + + let (_, s_n): (bool, u8) = jet::subtract_8(3, <(u4, (u2, u2))>::into((0, (0, n)))); + let (_, (_, s_n)): (u4, (u2, u2)) = ::into(s_n); + let (_, checksum): (bool, u8) = jet::add_8(checksum, <(u4, (u2, u2))>::into((0, (0, n)))); + + (unwrap_left::(for_while::(el, (s_n, <(u16, (u8, u8))>::into((0, (0, idx))), key_idx, midstate, n, layer_adrs, tree_adrs_1, tree_adrs_2, wots_type))), (wrapping_add_8(idx, 1), checksum)) +} + +fn sphincs_restore_wots_tw_signature_map_arr_1 (arr: [u128; 1], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 1], (u8, u8)) { let (res, ctx): (u128, (u8, u8)) = sphincs_restore_wots_tw_signature(<[u128; 1]>::into(arr), ctx, inv); ([res], ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_2 (arr: [u128; 2], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 2], (u8, u8)) { let (arr1, arr2): ([u128; 1], [u128; 1]) = <[u128; 2]>::into(arr); let (res1, ctx): ([u128; 1], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_1 (arr1, ctx, inv); let (res2, ctx): ([u128; 1], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_1 (arr2, ctx, inv); (<([u128; 1], [u128; 1])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_4 (arr: [u128; 4], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 4], (u8, u8)) { let (arr1, arr2): ([u128; 2], [u128; 2]) = <[u128; 4]>::into(arr); let (res1, ctx): ([u128; 2], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_2 (arr1, ctx, inv); let (res2, ctx): ([u128; 2], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_2 (arr2, ctx, inv); (<([u128; 2], [u128; 2])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_8 (arr: [u128; 8], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 8], (u8, u8)) { let (arr1, arr2): ([u128; 4], [u128; 4]) = <[u128; 8]>::into(arr); let (res1, ctx): ([u128; 4], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_4 (arr1, ctx, inv); let (res2, ctx): ([u128; 4], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_4 (arr2, ctx, inv); (<([u128; 4], [u128; 4])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_16 (arr: [u128; 16], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 16], (u8, u8)) { let (arr1, arr2): ([u128; 8], [u128; 8]) = <[u128; 16]>::into(arr); let (res1, ctx): ([u128; 8], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_8 (arr1, ctx, inv); let (res2, ctx): ([u128; 8], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_8 (arr2, ctx, inv); (<([u128; 8], [u128; 8])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_32 (arr: [u128; 32], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 32], (u8, u8)) { let (arr1, arr2): ([u128; 16], [u128; 16]) = <[u128; 32]>::into(arr); let (res1, ctx): ([u128; 16], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_16 (arr1, ctx, inv); let (res2, ctx): ([u128; 16], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_16 (arr2, ctx, inv); (<([u128; 16], [u128; 16])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_64 (arr: [u128; 64], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 64], (u8, u8)) { let (arr1, arr2): ([u128; 32], [u128; 32]) = <[u128; 64]>::into(arr); let (res1, ctx): ([u128; 32], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_32 (arr1, ctx, inv); let (res2, ctx): ([u128; 32], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_32 (arr2, ctx, inv); (<([u128; 32], [u128; 32])>::into((res1, res2)), ctx) } + +fn sphincs_hash_leaf_xmss(public_key: SPHINCSWOTSTWKey, ctx: (u32, u256, u32, u32, u64, u32)) -> u128 { + let (key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, pk_type): (u32, u256, u32, u32, u64, u32) = ctx; + + let [k00, k01, k02, k03, k04, k05, k06, k07, k08, k09, k10, k11, k12, k13, k14, k15, k16, k17, k18, k19, k20, k21, k22, k23, k24, k25, k26, k27, k28, k29, k30, k31, k32, k33, k34, k35, k36, k37, k38, k39, k40, k41, k42, k43, k44, k45, k46, k47, k48, k49, k50, k51, k52, k53, k54, k55, k56, k57, k58, k59, k60, k61, k62, k63]: [u128; 64] = public_key; + + truncate_128(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(midstate, adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, pk_type, key_index, 0, 0), <(u128, u128)>::into((k00, k01))), <(u128, u128)>::into((k02, k03)), <(u128, u128)>::into((k04, k05))), <(u128, u128)>::into((k06, k07)), <(u128, u128)>::into((k08, k09))), <(u128, u128)>::into((k10, k11)), <(u128, u128)>::into((k12, k13))), <(u128, u128)>::into((k14, k15)), <(u128, u128)>::into((k16, k17))), <(u128, u128)>::into((k18, k19)), <(u128, u128)>::into((k20, k21))), <(u128, u128)>::into((k22, k23)), <(u128, u128)>::into((k24, k25))), <(u128, u128)>::into((k26, k27)), <(u128, u128)>::into((k28, k29))), <(u128, u128)>::into((k30, k31)), <(u128, u128)>::into((k32, k33))), <(u128, u128)>::into((k34, k35)), <(u128, u128)>::into((k36, k37))), <(u128, u128)>::into((k38, k39)), <(u128, u128)>::into((k40, k41))), <(u128, u128)>::into((k42, k43)), <(u128, u128)>::into((k44, k45))), <(u128, u128)>::into((k46, k47)), <(u128, u128)>::into((k48, k49))), <(u128, u128)>::into((k50, k51)), <(u128, u128)>::into((k52, k53))), <(u128, u128)>::into((k54, k55)), <(u128, u128)>::into((k56, k57))), <(u128, u128)>::into((k58, k59)), <(u128, u128)>::into((k60, k61))), <(u128, u128)>::into((k62, k63)), sha256_tail_0(8960))) +} + +fn sphincs_hash_node_xmss(left: u128, right: u128, ctx: (u32, u32, u256, u32, u32, u64)) -> u128 { + let (height, node_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2): (u32, u32, u256, u32, u32, u64) = ctx; + + truncate_128(sha256_finish(jet::sha_256_block(midstate, adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, 12, 0, height, node_idx), <(u128, u128)>::into((left, right))), 1024)) +} + +fn sphincs_xmss_root_from_path_iter(el: u128, ctx: (u128, u32, u32, u256, u32, u32, u64)) -> (u128, u32, u32, u256, u32, u32, u64) { + let (current_hash, height, key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2): (u128, u32, u32, u256, u32, u32, u64) = ctx; + + let (new_key_idx, rem): (u32, u32) = jet::div_mod_32(key_idx, 2); + let (left, right): (u128, u128) = match jet::eq_32(rem, 0) { + true => (current_hash, el), + false => (el, current_hash), + }; + + let (_, new_height): (bool, u32) = jet::add_32(height, 1); + (sphincs_hash_node_xmss(left, right, (height, new_key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2)), new_height, new_key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2) +} + +fn sphincs_get_xmss_pub_key(signature: XMSSSignature, pk_seed: u128, message: u128, key_index: u32, midstate: u256, layer_adrs: u32, tree_adrs_1: u32, tree_adrs_2: u64) -> u128 { + let (signature, merkle_path): XMSSSignature = signature; + let (_, counter, message_signature): SPHINCSWOTSTWSignature = signature; + + let (message, _): (u128, u128) = ::into(sha256_finish_4(jet::sha_256_block(jet::sha_256_iv(), adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, 13, key_index, 0, 0), <(u128, u128)>::into((pk_seed, message))), counter, 544)); + + let (pk, (_, checksum)): ([u128; 64], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_64(message_signature, (0, 0), (message, key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, 10)); + assert!(jet::eq_8(checksum, 140)); + + xmss_path_root(array_fold::(merkle_path, (sphincs_hash_leaf_xmss(pk, (key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, 11)), 1, key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2))) +} + +fn sphincs_fors_root_from_path_iter(el: u128, ctx: (u128, u256, u32, u32, u32, u64, u32)) -> (u128, u256, u32, u32, u32, u64, u32) { + let (node, midstate, key_idx, tree_idx, tree_adrs_1, tree_adrs_2, height): (u128, u256, u32, u32, u32, u64, u32) = ctx; + + let (tree_idx, rem): (u32, u32) = jet::div_mod_32(tree_idx, 2); + let (left, right): (u128, u128) = match jet::eq_32(rem, 0) { + true => (node, el), + false => (el, node), + }; + + let (node, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(midstate, adrs_256(0, tree_adrs_1, tree_adrs_2, 7, key_idx, height, tree_idx), <(u128, u128)>::into((left, right))), 1024)); + + let (_, height): (bool, u32) = jet::add_32(height, 1); + (node, midstate, key_idx, tree_idx, tree_adrs_1, tree_adrs_2, height) +} + +fn get_22_bits_at(message: u256, idx: u8) -> u32 { + assert!(jet::lt_8(idx, 6)); + let (_, shift): (u8, u8) = ::into(jet::multiply_8(idx, 22)); + let (_, shift): (bool, u8) = jet::subtract_8(234, shift); + let shifted_msg: u256 = right_shift_256(message, shift); + let (_, (_, (_, shifted_msg))): (u128, (u64, (u32, u32))) = ::into(shifted_msg); + jet::and_32(shifted_msg, 0x003FFFFF) +} + + + + +fn sphincs_fors_root_from_path(part: SPHINCSFORSSignaturePart, ctx: u8, inv: (u256, u256, u32, u64)) -> (u128, u8) { + let idx_idx: u8 = ctx; + let (midstate, message, tree_adrs_1, tree_adrs_2): (u256, u256, u32, u64) = inv; + let (sk_value, path): SPHINCSFORSSignaturePart = part; + + let key_idx: u32 = get_22_bits_at(message, idx_idx); + let idx_idx_32: u32 = <(u16, (u8, u8))>::into((0, (0, idx_idx))); + + let (root, _, _, _, _, _, _): (u128, u256, u32, u32, u32, u64, u32) = array_fold::(path, (truncate_128(jet::sha_256_block(midstate, adrs_256(0, tree_adrs_1, tree_adrs_2, 6, idx_idx_32, 0, key_idx), sha256_tail_16(sk_value, 896))), midstate, idx_idx_32, key_idx, tree_adrs_1, tree_adrs_2, 1)); + + (root, wrapping_add_8(idx_idx, 1)) +} + +fn sphincs_fors_root_from_path_map_arr_1 (arr: [SPHINCSFORSSignaturePart; 1], ctx: u8, inv: (u256, u256, u32, u64)) -> ([u128; 1], u8) { let (res, ctx): (u128, u8) = sphincs_fors_root_from_path(<[SPHINCSFORSSignaturePart; 1]>::into(arr), ctx, inv); ([res], ctx) } +fn sphincs_fors_root_from_path_map_arr_2 (arr: [SPHINCSFORSSignaturePart; 2], ctx: u8, inv: (u256, u256, u32, u64)) -> ([u128; 2], u8) { let (arr1, arr2): ([SPHINCSFORSSignaturePart; 1], [SPHINCSFORSSignaturePart; 1]) = <[SPHINCSFORSSignaturePart; 2]>::into(arr); let (res1, ctx): ([u128; 1], u8) = sphincs_fors_root_from_path_map_arr_1 (arr1, ctx, inv); let (res2, ctx): ([u128; 1], u8) = sphincs_fors_root_from_path_map_arr_1 (arr2, ctx, inv); (<([u128; 1], [u128; 1])>::into((res1, res2)), ctx) } +fn sphincs_fors_root_from_path_map_arr_4 (arr: [SPHINCSFORSSignaturePart; 4], ctx: u8, inv: (u256, u256, u32, u64)) -> ([u128; 4], u8) { let (arr1, arr2): ([SPHINCSFORSSignaturePart; 2], [SPHINCSFORSSignaturePart; 2]) = <[SPHINCSFORSSignaturePart; 4]>::into(arr); let (res1, ctx): ([u128; 2], u8) = sphincs_fors_root_from_path_map_arr_2 (arr1, ctx, inv); let (res2, ctx): ([u128; 2], u8) = sphincs_fors_root_from_path_map_arr_2 (arr2, ctx, inv); (<([u128; 2], [u128; 2])>::into((res1, res2)), ctx) } + +fn sphincs_get_fors_pub_key(signature: SPHINCSFORSSignature, midstate: u256, message: u256, tree_adrs_1: u32, tree_adrs_2: u64) -> u128 { + let (_, (parts_4, parts_1)): SPHINCSFORSSignature = signature; + + assert!(jet::eq_32(get_22_bits_at(message, 5), 0)); + + let ([root_0, root_1, root_2, root_3], _): ([u128; 4], u8) = sphincs_fors_root_from_path_map_arr_4(parts_4, 0, (midstate, message, tree_adrs_1, tree_adrs_2)); + let ([el], _): ([u128; 1], u8) = sphincs_fors_root_from_path_map_arr_1(parts_1, 4, (midstate, message, tree_adrs_1, tree_adrs_2)); + + truncate_128(jet::sha_256_block(jet::sha_256_block(midstate, adrs_256(0, tree_adrs_1, tree_adrs_2, 8, 0, 0, 0), <(u128, u128)>::into((root_0, root_1))), <(u128, u128)>::into((root_2, root_3)), sha256_tail_16(el, 1408))) +} + + + + + + + + + +fn sphincs_root_from_path_iter(el: XMSSSignature, ctx: (u128, u128, u32, u256, u32, u64, u32)) -> (u128, u128, u32, u256, u32, u64, u32) { + let (pk_seed, message, key_index, midstate, layer_adrs, tree_adrs_2, idx): (u128, u128, u32, u256, u32, u64, u32) = ctx; + let pub_key: u128 = sphincs_get_xmss_pub_key(el, pk_seed, message, key_index, midstate, layer_adrs, 0, tree_adrs_2); + + let key_index: u32 = idx; + let idx: u32 = jet::right_shift_32(12, idx); + let tree_adrs: u64 = <(u32, u32)>::into((0, jet::and_32(idx, 0x00000FFF))); + + let (_, layer_adrs): (bool, u32) = jet::add_32(layer_adrs, 1); + + (pk_seed, pub_key, key_index, midstate, layer_adrs, tree_adrs, idx) +} + +fn ht_verify(signature: XMSSHTSignature, pk_seed: u128, message: u128, midstate: u256, tree_adrs_2: u64, leaf_idx: u32, idx: u32) -> u128 { + let (_, root, _, _, _, _, _): (u128, u128, u32, u256, u32, u64, u32) = array_fold::(signature, (pk_seed, message, leaf_idx, midstate, 0, tree_adrs_2, idx)); + root +} + +fn sphincs_verify(proof: SPHINCSSignProof, midstate: u256) -> u128 { + let (message_hash, pk, signature): SPHINCSSignProof = proof; + let (pk_seed, pk_root): SPHINCSPK = pk; + let (fors_signature, xmss_ht_signature): SPHINCSSignature = signature; + let (r, _): SPHINCSFORSSignature = fors_signature; + + let message: u256 = sha256_finish(jet::sha_256_block(jet::sha_256_block(jet::sha_256_iv(), adrs_256(0, 0, 0, 14, 0, 0, 0), r), <(u128, u128)>::into((pk_seed, pk_root)), message_hash), 1024); + + let (_, ((leaf_part, _), _)): (u128, ((u32, u32), u64)) = ::into(message); + let idx: u32 = jet::right_shift_32(8, jet::left_shift_32(4, leaf_part)); + let leaf_adrs: u32 = jet::and_32(idx, 0x00000FFF); + let idx: u32 = jet::right_shift_32(12, idx); + + let (_, tree_adrs_2): (bool, u64) = jet::add_64(jet::multiply_32(idx, jet::left_shift_32(12, 1)), <(u32, u32)>::into((0, leaf_adrs))); + let fors_pk: u128 = sphincs_get_fors_pub_key(fors_signature, midstate, message, 0, tree_adrs_2); + + ht_verify(xmss_ht_signature, pk_seed, fors_pk, midstate, <(u32, u32)>::into((0, idx)), leaf_adrs, idx) +} + +fn shrincs_get_root_from_path_iter(el: u128, ctx: (u128, bool, u256, u32)) -> (u128, bool, u256, u32) { + let (node, is_right_child, midstate, tree_height): (u128, bool, u256, u32) = ctx; + + let (left, right): (u128, u128) = match is_right_child { + true => (el, node), + false => (node, el), + }; + + let (hash, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(midstate, adrs_256(0, 0, 0, 2, 0, tree_height, 0), <(u128, u128)>::into((left, right))), 1024)); + + let (_, tree_height): (bool, u32) = jet::add_32(tree_height, 1); + + (hash, true, midstate, tree_height) +} + +fn restore_uxmss_signature(proof: UXMSSSignProof, midstate: u256) -> u128 { + let (message, pk, signature): UXMSSSignProof = proof; + let (pk_seed, root): SPHINCSPK = pk; + let (wots_signature, merkle_path, key_idx): UXMSSSignature = signature; + let (r, counter, message_signature): SPHINCSWOTSTWSignature = wots_signature; + + let (message, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(jet::sha_256_block(jet::sha_256_iv(), adrs_256(0, 0, 0, 4, 0, 0, 0), r), <(u128, u128)>::into((pk_seed, root)), message), 1024)); + + let (message, _): (u128, u128) = ::into(sha256_finish_4(jet::sha_256_block(jet::sha_256_iv(), adrs_256(0, 0, 0, 3, key_idx, 0, 0), <(u128, u128)>::into((pk_seed, message))), counter, 544)); + + let (pk, (_, checksum)): ([u128; 64], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_64(message_signature, (0, 0), (message, key_idx, midstate, 0, 0, 0, 0)); + assert!(jet::eq_8(checksum, 140)); + + + let (_, tree_height): (bool, u32) = match jet::eq_32(key_idx, 207) { + true => (false, 1), + false => jet::subtract_32(207, key_idx), + }; + + uxmss_path_root(fold::(merkle_path, (sphincs_hash_leaf_xmss(pk, (key_idx, midstate, 0, 0, 0, 1)), jet::eq_32(key_idx, 207), midstate, tree_height))) +} + +fn shrincs_verify(proof: SHRINCSSignProof) { + let (message, seed_root, signature, root_part): SHRINCSSignProof = proof; + let (public_parameter, expected_root): SPHINCSPK = seed_root; + + let midstate: u256 = jet::sha_256_block(jet::sha_256_iv(), <(u128, u128)>::into((public_parameter, 0)), 0); + + let roots: u256 = match signature { + Left(uxmss: UXMSSSignature) => { + <(u128, u128)>::into((restore_uxmss_signature((message, seed_root, uxmss), midstate), root_part)) + }, + Right(sphincs: SPHINCSSignature) => { + <(u128, u128)>::into((root_part, sphincs_verify((message, seed_root, sphincs), midstate))) + }, + }; + + let (root, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(midstate, adrs_256(0, 0, 0, 16, 0, 0, 0), roots), 1024)); + + assert!(eq_128(root, expected_root)); +} + +fn main() { + let pk: SPHINCSPK = ::into(param::PK); + let sig: Either = witness::SIG; + let other_root: u128 = witness::OTHER_ROOT; + shrincs_verify((jet::sig_all_hash(), pk, sig, other_root)); +} diff --git a/examples/pq_shrincs/src/shrincs_spend.simf b/examples/pq_shrincs/src/shrincs_spend.simf new file mode 100644 index 0000000..3ee3ca2 --- /dev/null +++ b/examples/pq_shrincs/src/shrincs_spend.simf @@ -0,0 +1,19 @@ +// A SHRINCS-locked coin: spendable only with a SHRINCS signature, by the key pinned in +// `param::PK`, over this transaction's `sig_all_hash`. `PK` is the 32-byte SHRINCS public +// key, `PK.seed || PK.root`. +// +// Upstream's `shrincs_verify` takes the message and the public key as part of its proof, +// which is what makes its own example anyone-can-spend. This wrapper supplies both itself, +// so the spender provides only the signature. +// +// Not compiled directly: `build.sh` flattens it with the vendored verifier into +// `../shrincs_spend.simf`, the file the manifest pins. + +#include "shrincs/shrincs.simf" + +fn main() { + let pk: SPHINCSPK = ::into(param::PK); + let sig: Either = witness::SIG; + let other_root: u128 = witness::OTHER_ROOT; + shrincs_verify((jet::sig_all_hash(), pk, sig, other_root)); +} diff --git a/examples/pq_shrincs_smoke/README.md b/examples/pq_shrincs_smoke/README.md new file mode 100644 index 0000000..31bc4e8 --- /dev/null +++ b/examples/pq_shrincs_smoke/README.md @@ -0,0 +1,48 @@ +# SHRINCS smoke test (anyone-can-spend) + +Runs the [SHRINCS](https://github.com/BlockstreamResearch/shrincs-simplicity-verifier) +post-quantum signature verifier on the Simplicity signet. SHRINCS is a hash-based scheme. +The verifier is upstream's own example, unmodified, and the spend uses upstream's own +stateful signature. + +**This is a smoke test, not a pattern to copy.** Upstream's example takes the public key +and the message as witness data. So the program checks that *some* valid SHRINCS +signature was supplied, for any key and any message. Anyone who sees the spend could have +made it, and the coin it locks is anyone-can-spend. Lock only a few thousand sats. + +What it shows: +- the signet node runs a SHRINCS verification; +- it relays the spend, whose witness is about 18.8 KB, about 14.6 KB of it cost padding; +- the padding meets the node's rules. + +`examples/pq_shrincs` is the version that pins the key and signs the transaction. + +## Files + +- `shrincs_main.simf`: the verifier, flattened by `build.sh` from + [`contrib/shrincs-simplicity-verifier`](../../contrib/shrincs-simplicity-verifier). + Don't edit it; rerun `build.sh` and `tx-manifest-wallet pin`. +- `lock.params.json`: lock 20,000 sat at 0.1 sat/vB. +- `spend.params.json`: spend at `fee_rate` 15. The wallet's fee estimate assumes about + 1 KB of witness for a covenant input, and this one is about 18.8 KB. 15 sat/vB on that + estimate comes to about 5,300 sat, roughly 1.1 sat/vB on the real transaction of about + 4,790 vB. + +## Run it + +Use a funded signet wallet; see [`bitcoin_covenant`](../bitcoin_covenant) for creating +and funding one. + +```sh +CFG="--config examples/bitcoin_covenant/config.json" +W=/path/to/signet/wallet.json +M=examples/pq_shrincs_smoke +cargo run -p tx-manifest-wallet -- $CFG sync --wallet $W +cargo run -p tx-manifest-wallet -- $CFG run $M/txmanifest.json Lock \ + --wallet $W --params $M/lock.params.json --state-out /tmp/shrincs-smoke.state.json +cargo run -p tx-manifest-wallet -- $CFG run $M/txmanifest.json Spend \ + --wallet $W --params $M/spend.params.json \ + --state /tmp/shrincs-smoke.state.json --state-out /tmp/shrincs-smoke.state.2.json +``` + +The spend should print `cost padding: 14623 bytes`. diff --git a/examples/pq_shrincs_smoke/build.sh b/examples/pq_shrincs_smoke/build.sh new file mode 100755 index 0000000..b471814 --- /dev/null +++ b/examples/pq_shrincs_smoke/build.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Flatten upstream's unmodified SHRINCS example into shrincs_main.simf, the one file the +# manifest pins. Rerun after updating the vendored verifier, then `tx-manifest-wallet pin`. +# Needs mcpp (brew install mcpp; apt install mcpp); see +# contrib/shrincs-simplicity-verifier/README.md for why the preprocessor matters. +set -eu +cd "$(dirname "$0")" +# mcpp -P drops comments, so the header that says where the file comes from is added here. +{ + echo "// Generated by build.sh from the vendored SHRINCS verifier's examples/shrincs/shrincs_main.simf. Do not edit." + mcpp -P -I ../../contrib/shrincs-simplicity-verifier/upstream \ + ../../contrib/shrincs-simplicity-verifier/upstream/examples/shrincs/shrincs_main.simf 2>/dev/null +} > shrincs_main.simf diff --git a/examples/pq_shrincs_smoke/config.json b/examples/pq_shrincs_smoke/config.json new file mode 100644 index 0000000..1cdcdf0 --- /dev/null +++ b/examples/pq_shrincs_smoke/config.json @@ -0,0 +1,10 @@ +{ + "default_network": "bitcoin-signet", + "bitcoin_backend": "esplora", + "default_esplora": "https://signet.simplicity-lang.org/explorer/api", + "simplicity_activated": true, + "bitcoin_checkpoint": { + "height": 1296, + "hash": "00000091e713448edf2f57f61a9d9e03a5c35ac0902be6c89b0cc5ea8e7b8564" + } +} diff --git a/examples/pq_shrincs_smoke/lock.params.json b/examples/pq_shrincs_smoke/lock.params.json new file mode 100644 index 0000000..3e67394 --- /dev/null +++ b/examples/pq_shrincs_smoke/lock.params.json @@ -0,0 +1,4 @@ +{ + "amount_sat": "20000", + "fee_rate": "0.1" +} \ No newline at end of file diff --git a/examples/pq_shrincs_smoke/shrincs_main.simf b/examples/pq_shrincs_smoke/shrincs_main.simf new file mode 100644 index 0000000..d686123 --- /dev/null +++ b/examples/pq_shrincs_smoke/shrincs_main.simf @@ -0,0 +1,571 @@ +// Generated by build.sh from the vendored SHRINCS verifier's examples/shrincs/shrincs_main.simf. Do not edit. + + + + +fn adrs_256(layer: u32, tree_1: u32, tree_2: u64, adrs_type: u32, word_1: u32, word_2: u32, word_3: u32) -> u256 { + <(u128, u128)>::into(( + <(u64, u64)>::into((<(u32, u32)>::into((layer, tree_1)), tree_2)), + <(u64, u64)>::into((<(u32, u32)>::into((adrs_type, word_1)), <(u32, u32)>::into((word_2, word_3)))) + )) +} + +fn sha256_tail_16(value: u128, bit_len: u64) -> u256 { + <(u128, u128)>::into((value, <(u64, u64)>::into((0x8000000000000000, bit_len)))) +} + +fn sha256_tail_0(bit_len: u64) -> u256 { + <(u128, u128)>::into((0x80000000000000000000000000000000, <(u64, u64)>::into((0, bit_len)))) +} + +fn sha256_finish(midstate: u256, bit_len: u64) -> u256 { + jet::sha_256_block( + midstate, + 0x8000000000000000000000000000000000000000000000000000000000000000, + <(u128, u128)>::into((0, <(u64, u64)>::into((0, bit_len)))) + ) +} + +fn sha256_finish_4(midstate: u256, value: u32, bit_len: u64) -> u256 { + jet::sha_256_block( + midstate, + <(u128, u128)>::into((<(u32, u32, u32, u32)>::into((value, 0x80000000, 0, 0)), 0)), + <(u128, u128)>::into((0, <(u64, u64)>::into((0, bit_len)))) + ) +} + +fn sha256_of_256(value: u256) -> u256 { + jet::sha_256_block(jet::sha_256_iv(), value, sha256_tail_0(256)) +} + +fn truncate_128(value: u256) -> u128 { + let (high, _): (u128, u128) = ::into(value); + high +} + +fn wrapping_add_32(a: u32, b: u32) -> u32 { + let (_, sum): (bool, u32) = jet::add_32(a, b); + sum +} + +fn xmss_path_root(state: (u128, u32, u32, u256, u32, u32, u64)) -> u128 { + let (root, _, _, _, _, _, _): (u128, u32, u32, u256, u32, u32, u64) = state; + root +} + +fn uxmss_path_root(state: (u128, bool, u256, u32)) -> u128 { + let (root, _, _, _): (u128, bool, u256, u32) = state; + root +} + +fn wrapping_add_8(a: u8, b: u8) -> u8 { + let (_, sum): (bool, u8) = jet::add_8(a, b); + sum +} + +fn eq_128(val1: u128, val2: u128) -> bool { + jet::eq_256(<(u128, u128)>::into((0, val1)), <(u128, u128)>::into((0, val2))) +} + +fn right_shift_256(value: u256, shift: u8) -> u256 { + let (a, b, c, d): (u64, u64, u64, u64) = ::into(value); + + let (a, b, c, d): (u64, u64, u64, u64) = match jet::lt_8(shift, 64) { + true => (a, b, c, d), + false => { + match jet::lt_8(shift, 128) { + true => (0, a, b, c), + false => { + match jet::lt_8(shift, 192) { + true => (0, 0, a, b), + false => (0, 0, 0, a), + } + } + } + } + }; + + let shift: u8 = jet::modulo_8(shift, 64); + let (_, left_shift): (bool, u8) = jet::subtract_8(64, shift); + + let new_d: u64 = jet::or_64(jet::left_shift_64(left_shift, c), jet::right_shift_64(shift, d)); + let new_c: u64 = jet::or_64(jet::left_shift_64(left_shift, b), jet::right_shift_64(shift, c)); + let new_b: u64 = jet::or_64(jet::left_shift_64(left_shift, a), jet::right_shift_64(shift, b)); + let new_a: u64 = jet::right_shift_64(shift, a); + + <(u64, u64, u64, u64)>::into((new_a, new_b, new_c, new_d)) +} + +fn right_shift_128(value: u128, shift: u8) -> u128 { + let (a, b, c, d): (u32, u32, u32, u32) = ::into(value); + + let (a, b, c, d): (u32, u32, u32, u32) = match jet::lt_8(shift, 32) { + true => (a, b, c, d), + false => { + match jet::lt_8(shift, 64) { + true => (0, a, b, c), + false => { + match jet::lt_8(shift, 96) { + true => (0, 0, a, b), + false => (0, 0, 0, a), + } + } + } + } + }; + + let shift: u8 = jet::modulo_8(shift, 32); + let (_, left_shift): (bool, u8) = jet::subtract_8(32, shift); + + let new_d: u32 = jet::or_32(jet::left_shift_32(left_shift, c), jet::right_shift_32(shift, d)); + let new_c: u32 = jet::or_32(jet::left_shift_32(left_shift, b), jet::right_shift_32(shift, c)); + let new_b: u32 = jet::or_32(jet::left_shift_32(left_shift, a), jet::right_shift_32(shift, b)); + let new_a: u32 = jet::right_shift_32(shift, a); + + <(u32, u32, u32, u32)>::into((new_a, new_b, new_c, new_d)) +} + +fn get_bit_at(value: u256, possition: u8) -> u1 { + let (a, b, c, d): (u64, u64, u64, u64) = ::into(value); + + let part: u64 = match jet::lt_8(possition, 64) { + true => d, + false => { + match jet::lt_8(possition, 128) { + true => c, + false => { + match jet::lt_8(possition, 192) { + true => b, + false => a, + } + }, + } + }, + }; + + let (_, in_part_possition): (u8, u8) = jet::div_mod_8(possition, 64); + + ::into(jet::lt_64(0, jet::and_64(part, jet::left_shift_64(in_part_possition, 1)))) +} + +fn get_byte_at(value: u256, possition: u8) -> u8 { + assert!(jet::lt_8(possition, 32)); + let (a, b, c, d): (u64, u64, u64, u64) = ::into(value); + + let part: u64 = match jet::lt_8(possition, 8) { + true => d, + false => { + match jet::lt_8(possition, 16) { + true => c, + false => { + match jet::lt_8(possition, 24) { + true => b, + false => a, + } + }, + } + }, + }; + + let possition: u8 = jet::modulo_8(possition, 8); + let (a, b, c, d): (u16, u16, u16, u16) = ::into(part); + + let part: u16 = match jet::lt_8(possition, 2) { + true => d, + false => { + match jet::lt_8(possition, 4) { + true => c, + false => { + match jet::lt_8(possition, 6) { + true => b, + false => a, + } + }, + } + }, + }; + + let possition: u8 = jet::modulo_8(possition, 2); + let (a, b): (u8, u8) = ::into(part); + match jet::eq_8(possition, 0) { + true => b, + false => a, + } +} + +fn get_byte_at_128(value: u128, possition: u8) -> u8 { + assert!(jet::lt_8(possition, 16)); + let (a, b, c, d): (u32, u32, u32, u32) = ::into(value); + + let part: u32 = match jet::lt_8(possition, 4) { + true => a, + false => { + match jet::lt_8(possition, 8) { + true => b, + false => { + match jet::lt_8(possition, 12) { + true => c, + false => d, + } + }, + } + }, + }; + + let possition: u8 = jet::modulo_8(possition, 4); + let (a, b, c, d): (u8, u8, u8, u8) = ::into(part); + + match jet::eq_8(possition, 0) { + true => a, + false => { + match jet::eq_8(possition, 1) { + true => b, + false => { + match jet::eq_8(possition, 2) { + true => c, + false => d, + } + }, + } + }, + } +} + +fn get_at_array_iter(el: u256, ctx: (u8, u8, u256)) -> (u8, u8, u256) { + let (idx, pos, saved): (u8, u8, u256) = ctx; + match jet::eq_8(idx, pos) { + true => { + let (_, new_pos): (bool, u8) = jet::add_8(pos, 1); + (idx, new_pos, el) + }, + false => { + let (_, new_pos): (bool, u8) = jet::add_8(pos, 1); + (idx, new_pos, saved) + } + } +} +fn get_at_array_128_iter(el: u128, ctx: (u32, u32, u128)) -> (u32, u32, u128) { + let (idx, pos, saved): (u32, u32, u128) = ctx; + match jet::eq_32(idx, pos) { + true => { + let (_, new_pos): (bool, u32) = jet::add_32(pos, 1); + (idx, new_pos, el) + }, + false => { + let (_, new_pos): (bool, u32) = jet::add_32(pos, 1); + (idx, new_pos, saved) + } + } +} + + + + +type WOTSSignature = ([u256; 64], [u256; 3]); +type WOTSKey = ([u256; 64], [u256; 3]); + +type WOTSSignProof = (u256, WOTSSignature, WOTSKey); + + + +type KeyPair = (u256, u256); + +type LamportSignProof = (u256, [u256; 256], [KeyPair; 256]); + + + + + + +type SPHINCSFORSSignaturePart = (u128, [u128; 22]); + +type SPHINCSFORSSignature = (u256, ([SPHINCSFORSSignaturePart; 4], [SPHINCSFORSSignaturePart; 1])); + + +type SPHINCSWOTSTWKey = [u128; 64]; +type SPHINCSWOTSTWSignature = (u256, u32, [u128; 64]); + +type SPHINCSWOTSTWSignProof = (u128, SPHINCSWOTSTWSignature, SPHINCSWOTSTWKey, u32, u128); + + +type SPHINCSMerklePath = [u128; 12]; + +type XMSSSignature = (SPHINCSWOTSTWSignature, SPHINCSMerklePath); + + +type SPHINCSPK = (u128, u128); + +type XMSSHTSignature = [XMSSSignature; 2]; + +type SPHINCSSignature = (SPHINCSFORSSignature, XMSSHTSignature); + +type SPHINCSSignProof = (u256, SPHINCSPK, SPHINCSSignature); + + + + + + +type UXMSSSignature = (SPHINCSWOTSTWSignature, List, u32); + +type UXMSSSignProof = (u256, SPHINCSPK, UXMSSSignature); + + +type SHRINCSSignProof = (u256, SPHINCSPK, Either, u128); + + + + +fn sphincs_wots_tw_hash_n_times_iter(key_hash: u128, ctx: (u2, u32, u32, u256, u2, u32, u32, u64, u32), counter: u2) -> Either { + let (n, chain_idx, key_index, midstate, from_idx, layer_adrs, tree_adrs_1, tree_adrs_2, wots_type): (u2, u32, u32, u256, u2, u32, u32, u64, u32) = ctx; + let (_, hashed_times): (bool, u8) = jet::add_8(<(u4, (u2, u2))>::into((0, (0, counter))), <(u4, (u2, u2))>::into((0, (0, from_idx)))); + + match jet::eq_8(<(u4, (u2, u2))>::into((0, (0, counter))), <(u4, (u2, u2))>::into((0, (0, n)))) { + true => Left(key_hash), + false => { + let (hash, _): (u128, u128) = ::into(jet::sha_256_block(midstate, adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, wots_type, key_index, chain_idx, <(u16, (u8, u8))>::into((0, (0, hashed_times)))), sha256_tail_16(key_hash, 896))); + + Right(hash) + } + } +} + + + + +fn sphincs_restore_wots_tw_signature(el: u128, ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> (u128, (u8, u8)) { + let (idx, checksum): (u8, u8) = ctx; + let (message_hash, key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, wots_type): (u128, u32, u256, u32, u32, u64, u32) = inv; + + let (n1, n2, n3, n4): (u2, u2, u2, u2) = ::into(get_byte_at_128(message_hash, jet::divide_8(idx, 4))); + let idx_mod: u8 = jet::modulo_8(idx, 4); + + let n: u2 = match jet::eq_8(idx_mod, 0) { + true => n1, + false => { + match jet::eq_8(idx_mod, 1) { + true => n2, + false => { + match jet::eq_8(idx_mod, 2) { + true => n3, + false => n4, + } + } + } + } + }; + + let (_, s_n): (bool, u8) = jet::subtract_8(3, <(u4, (u2, u2))>::into((0, (0, n)))); + let (_, (_, s_n)): (u4, (u2, u2)) = ::into(s_n); + let (_, checksum): (bool, u8) = jet::add_8(checksum, <(u4, (u2, u2))>::into((0, (0, n)))); + + (unwrap_left::(for_while::(el, (s_n, <(u16, (u8, u8))>::into((0, (0, idx))), key_idx, midstate, n, layer_adrs, tree_adrs_1, tree_adrs_2, wots_type))), (wrapping_add_8(idx, 1), checksum)) +} + +fn sphincs_restore_wots_tw_signature_map_arr_1 (arr: [u128; 1], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 1], (u8, u8)) { let (res, ctx): (u128, (u8, u8)) = sphincs_restore_wots_tw_signature(<[u128; 1]>::into(arr), ctx, inv); ([res], ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_2 (arr: [u128; 2], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 2], (u8, u8)) { let (arr1, arr2): ([u128; 1], [u128; 1]) = <[u128; 2]>::into(arr); let (res1, ctx): ([u128; 1], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_1 (arr1, ctx, inv); let (res2, ctx): ([u128; 1], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_1 (arr2, ctx, inv); (<([u128; 1], [u128; 1])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_4 (arr: [u128; 4], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 4], (u8, u8)) { let (arr1, arr2): ([u128; 2], [u128; 2]) = <[u128; 4]>::into(arr); let (res1, ctx): ([u128; 2], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_2 (arr1, ctx, inv); let (res2, ctx): ([u128; 2], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_2 (arr2, ctx, inv); (<([u128; 2], [u128; 2])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_8 (arr: [u128; 8], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 8], (u8, u8)) { let (arr1, arr2): ([u128; 4], [u128; 4]) = <[u128; 8]>::into(arr); let (res1, ctx): ([u128; 4], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_4 (arr1, ctx, inv); let (res2, ctx): ([u128; 4], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_4 (arr2, ctx, inv); (<([u128; 4], [u128; 4])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_16 (arr: [u128; 16], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 16], (u8, u8)) { let (arr1, arr2): ([u128; 8], [u128; 8]) = <[u128; 16]>::into(arr); let (res1, ctx): ([u128; 8], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_8 (arr1, ctx, inv); let (res2, ctx): ([u128; 8], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_8 (arr2, ctx, inv); (<([u128; 8], [u128; 8])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_32 (arr: [u128; 32], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 32], (u8, u8)) { let (arr1, arr2): ([u128; 16], [u128; 16]) = <[u128; 32]>::into(arr); let (res1, ctx): ([u128; 16], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_16 (arr1, ctx, inv); let (res2, ctx): ([u128; 16], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_16 (arr2, ctx, inv); (<([u128; 16], [u128; 16])>::into((res1, res2)), ctx) } +fn sphincs_restore_wots_tw_signature_map_arr_64 (arr: [u128; 64], ctx: (u8, u8), inv: (u128, u32, u256, u32, u32, u64, u32)) -> ([u128; 64], (u8, u8)) { let (arr1, arr2): ([u128; 32], [u128; 32]) = <[u128; 64]>::into(arr); let (res1, ctx): ([u128; 32], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_32 (arr1, ctx, inv); let (res2, ctx): ([u128; 32], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_32 (arr2, ctx, inv); (<([u128; 32], [u128; 32])>::into((res1, res2)), ctx) } + +fn sphincs_hash_leaf_xmss(public_key: SPHINCSWOTSTWKey, ctx: (u32, u256, u32, u32, u64, u32)) -> u128 { + let (key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, pk_type): (u32, u256, u32, u32, u64, u32) = ctx; + + let [k00, k01, k02, k03, k04, k05, k06, k07, k08, k09, k10, k11, k12, k13, k14, k15, k16, k17, k18, k19, k20, k21, k22, k23, k24, k25, k26, k27, k28, k29, k30, k31, k32, k33, k34, k35, k36, k37, k38, k39, k40, k41, k42, k43, k44, k45, k46, k47, k48, k49, k50, k51, k52, k53, k54, k55, k56, k57, k58, k59, k60, k61, k62, k63]: [u128; 64] = public_key; + + truncate_128(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(jet::sha_256_block(midstate, adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, pk_type, key_index, 0, 0), <(u128, u128)>::into((k00, k01))), <(u128, u128)>::into((k02, k03)), <(u128, u128)>::into((k04, k05))), <(u128, u128)>::into((k06, k07)), <(u128, u128)>::into((k08, k09))), <(u128, u128)>::into((k10, k11)), <(u128, u128)>::into((k12, k13))), <(u128, u128)>::into((k14, k15)), <(u128, u128)>::into((k16, k17))), <(u128, u128)>::into((k18, k19)), <(u128, u128)>::into((k20, k21))), <(u128, u128)>::into((k22, k23)), <(u128, u128)>::into((k24, k25))), <(u128, u128)>::into((k26, k27)), <(u128, u128)>::into((k28, k29))), <(u128, u128)>::into((k30, k31)), <(u128, u128)>::into((k32, k33))), <(u128, u128)>::into((k34, k35)), <(u128, u128)>::into((k36, k37))), <(u128, u128)>::into((k38, k39)), <(u128, u128)>::into((k40, k41))), <(u128, u128)>::into((k42, k43)), <(u128, u128)>::into((k44, k45))), <(u128, u128)>::into((k46, k47)), <(u128, u128)>::into((k48, k49))), <(u128, u128)>::into((k50, k51)), <(u128, u128)>::into((k52, k53))), <(u128, u128)>::into((k54, k55)), <(u128, u128)>::into((k56, k57))), <(u128, u128)>::into((k58, k59)), <(u128, u128)>::into((k60, k61))), <(u128, u128)>::into((k62, k63)), sha256_tail_0(8960))) +} + +fn sphincs_hash_node_xmss(left: u128, right: u128, ctx: (u32, u32, u256, u32, u32, u64)) -> u128 { + let (height, node_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2): (u32, u32, u256, u32, u32, u64) = ctx; + + truncate_128(sha256_finish(jet::sha_256_block(midstate, adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, 12, 0, height, node_idx), <(u128, u128)>::into((left, right))), 1024)) +} + +fn sphincs_xmss_root_from_path_iter(el: u128, ctx: (u128, u32, u32, u256, u32, u32, u64)) -> (u128, u32, u32, u256, u32, u32, u64) { + let (current_hash, height, key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2): (u128, u32, u32, u256, u32, u32, u64) = ctx; + + let (new_key_idx, rem): (u32, u32) = jet::div_mod_32(key_idx, 2); + let (left, right): (u128, u128) = match jet::eq_32(rem, 0) { + true => (current_hash, el), + false => (el, current_hash), + }; + + let (_, new_height): (bool, u32) = jet::add_32(height, 1); + (sphincs_hash_node_xmss(left, right, (height, new_key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2)), new_height, new_key_idx, midstate, layer_adrs, tree_adrs_1, tree_adrs_2) +} + +fn sphincs_get_xmss_pub_key(signature: XMSSSignature, pk_seed: u128, message: u128, key_index: u32, midstate: u256, layer_adrs: u32, tree_adrs_1: u32, tree_adrs_2: u64) -> u128 { + let (signature, merkle_path): XMSSSignature = signature; + let (_, counter, message_signature): SPHINCSWOTSTWSignature = signature; + + let (message, _): (u128, u128) = ::into(sha256_finish_4(jet::sha_256_block(jet::sha_256_iv(), adrs_256(layer_adrs, tree_adrs_1, tree_adrs_2, 13, key_index, 0, 0), <(u128, u128)>::into((pk_seed, message))), counter, 544)); + + let (pk, (_, checksum)): ([u128; 64], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_64(message_signature, (0, 0), (message, key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, 10)); + assert!(jet::eq_8(checksum, 140)); + + xmss_path_root(array_fold::(merkle_path, (sphincs_hash_leaf_xmss(pk, (key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2, 11)), 1, key_index, midstate, layer_adrs, tree_adrs_1, tree_adrs_2))) +} + +fn sphincs_fors_root_from_path_iter(el: u128, ctx: (u128, u256, u32, u32, u32, u64, u32)) -> (u128, u256, u32, u32, u32, u64, u32) { + let (node, midstate, key_idx, tree_idx, tree_adrs_1, tree_adrs_2, height): (u128, u256, u32, u32, u32, u64, u32) = ctx; + + let (tree_idx, rem): (u32, u32) = jet::div_mod_32(tree_idx, 2); + let (left, right): (u128, u128) = match jet::eq_32(rem, 0) { + true => (node, el), + false => (el, node), + }; + + let (node, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(midstate, adrs_256(0, tree_adrs_1, tree_adrs_2, 7, key_idx, height, tree_idx), <(u128, u128)>::into((left, right))), 1024)); + + let (_, height): (bool, u32) = jet::add_32(height, 1); + (node, midstate, key_idx, tree_idx, tree_adrs_1, tree_adrs_2, height) +} + +fn get_22_bits_at(message: u256, idx: u8) -> u32 { + assert!(jet::lt_8(idx, 6)); + let (_, shift): (u8, u8) = ::into(jet::multiply_8(idx, 22)); + let (_, shift): (bool, u8) = jet::subtract_8(234, shift); + let shifted_msg: u256 = right_shift_256(message, shift); + let (_, (_, (_, shifted_msg))): (u128, (u64, (u32, u32))) = ::into(shifted_msg); + jet::and_32(shifted_msg, 0x003FFFFF) +} + + + + +fn sphincs_fors_root_from_path(part: SPHINCSFORSSignaturePart, ctx: u8, inv: (u256, u256, u32, u64)) -> (u128, u8) { + let idx_idx: u8 = ctx; + let (midstate, message, tree_adrs_1, tree_adrs_2): (u256, u256, u32, u64) = inv; + let (sk_value, path): SPHINCSFORSSignaturePart = part; + + let key_idx: u32 = get_22_bits_at(message, idx_idx); + let idx_idx_32: u32 = <(u16, (u8, u8))>::into((0, (0, idx_idx))); + + let (root, _, _, _, _, _, _): (u128, u256, u32, u32, u32, u64, u32) = array_fold::(path, (truncate_128(jet::sha_256_block(midstate, adrs_256(0, tree_adrs_1, tree_adrs_2, 6, idx_idx_32, 0, key_idx), sha256_tail_16(sk_value, 896))), midstate, idx_idx_32, key_idx, tree_adrs_1, tree_adrs_2, 1)); + + (root, wrapping_add_8(idx_idx, 1)) +} + +fn sphincs_fors_root_from_path_map_arr_1 (arr: [SPHINCSFORSSignaturePart; 1], ctx: u8, inv: (u256, u256, u32, u64)) -> ([u128; 1], u8) { let (res, ctx): (u128, u8) = sphincs_fors_root_from_path(<[SPHINCSFORSSignaturePart; 1]>::into(arr), ctx, inv); ([res], ctx) } +fn sphincs_fors_root_from_path_map_arr_2 (arr: [SPHINCSFORSSignaturePart; 2], ctx: u8, inv: (u256, u256, u32, u64)) -> ([u128; 2], u8) { let (arr1, arr2): ([SPHINCSFORSSignaturePart; 1], [SPHINCSFORSSignaturePart; 1]) = <[SPHINCSFORSSignaturePart; 2]>::into(arr); let (res1, ctx): ([u128; 1], u8) = sphincs_fors_root_from_path_map_arr_1 (arr1, ctx, inv); let (res2, ctx): ([u128; 1], u8) = sphincs_fors_root_from_path_map_arr_1 (arr2, ctx, inv); (<([u128; 1], [u128; 1])>::into((res1, res2)), ctx) } +fn sphincs_fors_root_from_path_map_arr_4 (arr: [SPHINCSFORSSignaturePart; 4], ctx: u8, inv: (u256, u256, u32, u64)) -> ([u128; 4], u8) { let (arr1, arr2): ([SPHINCSFORSSignaturePart; 2], [SPHINCSFORSSignaturePart; 2]) = <[SPHINCSFORSSignaturePart; 4]>::into(arr); let (res1, ctx): ([u128; 2], u8) = sphincs_fors_root_from_path_map_arr_2 (arr1, ctx, inv); let (res2, ctx): ([u128; 2], u8) = sphincs_fors_root_from_path_map_arr_2 (arr2, ctx, inv); (<([u128; 2], [u128; 2])>::into((res1, res2)), ctx) } + +fn sphincs_get_fors_pub_key(signature: SPHINCSFORSSignature, midstate: u256, message: u256, tree_adrs_1: u32, tree_adrs_2: u64) -> u128 { + let (_, (parts_4, parts_1)): SPHINCSFORSSignature = signature; + + assert!(jet::eq_32(get_22_bits_at(message, 5), 0)); + + let ([root_0, root_1, root_2, root_3], _): ([u128; 4], u8) = sphincs_fors_root_from_path_map_arr_4(parts_4, 0, (midstate, message, tree_adrs_1, tree_adrs_2)); + let ([el], _): ([u128; 1], u8) = sphincs_fors_root_from_path_map_arr_1(parts_1, 4, (midstate, message, tree_adrs_1, tree_adrs_2)); + + truncate_128(jet::sha_256_block(jet::sha_256_block(midstate, adrs_256(0, tree_adrs_1, tree_adrs_2, 8, 0, 0, 0), <(u128, u128)>::into((root_0, root_1))), <(u128, u128)>::into((root_2, root_3)), sha256_tail_16(el, 1408))) +} + + + + + + + + + +fn sphincs_root_from_path_iter(el: XMSSSignature, ctx: (u128, u128, u32, u256, u32, u64, u32)) -> (u128, u128, u32, u256, u32, u64, u32) { + let (pk_seed, message, key_index, midstate, layer_adrs, tree_adrs_2, idx): (u128, u128, u32, u256, u32, u64, u32) = ctx; + let pub_key: u128 = sphincs_get_xmss_pub_key(el, pk_seed, message, key_index, midstate, layer_adrs, 0, tree_adrs_2); + + let key_index: u32 = idx; + let idx: u32 = jet::right_shift_32(12, idx); + let tree_adrs: u64 = <(u32, u32)>::into((0, jet::and_32(idx, 0x00000FFF))); + + let (_, layer_adrs): (bool, u32) = jet::add_32(layer_adrs, 1); + + (pk_seed, pub_key, key_index, midstate, layer_adrs, tree_adrs, idx) +} + +fn ht_verify(signature: XMSSHTSignature, pk_seed: u128, message: u128, midstate: u256, tree_adrs_2: u64, leaf_idx: u32, idx: u32) -> u128 { + let (_, root, _, _, _, _, _): (u128, u128, u32, u256, u32, u64, u32) = array_fold::(signature, (pk_seed, message, leaf_idx, midstate, 0, tree_adrs_2, idx)); + root +} + +fn sphincs_verify(proof: SPHINCSSignProof, midstate: u256) -> u128 { + let (message_hash, pk, signature): SPHINCSSignProof = proof; + let (pk_seed, pk_root): SPHINCSPK = pk; + let (fors_signature, xmss_ht_signature): SPHINCSSignature = signature; + let (r, _): SPHINCSFORSSignature = fors_signature; + + let message: u256 = sha256_finish(jet::sha_256_block(jet::sha_256_block(jet::sha_256_iv(), adrs_256(0, 0, 0, 14, 0, 0, 0), r), <(u128, u128)>::into((pk_seed, pk_root)), message_hash), 1024); + + let (_, ((leaf_part, _), _)): (u128, ((u32, u32), u64)) = ::into(message); + let idx: u32 = jet::right_shift_32(8, jet::left_shift_32(4, leaf_part)); + let leaf_adrs: u32 = jet::and_32(idx, 0x00000FFF); + let idx: u32 = jet::right_shift_32(12, idx); + + let (_, tree_adrs_2): (bool, u64) = jet::add_64(jet::multiply_32(idx, jet::left_shift_32(12, 1)), <(u32, u32)>::into((0, leaf_adrs))); + let fors_pk: u128 = sphincs_get_fors_pub_key(fors_signature, midstate, message, 0, tree_adrs_2); + + ht_verify(xmss_ht_signature, pk_seed, fors_pk, midstate, <(u32, u32)>::into((0, idx)), leaf_adrs, idx) +} + +fn shrincs_get_root_from_path_iter(el: u128, ctx: (u128, bool, u256, u32)) -> (u128, bool, u256, u32) { + let (node, is_right_child, midstate, tree_height): (u128, bool, u256, u32) = ctx; + + let (left, right): (u128, u128) = match is_right_child { + true => (el, node), + false => (node, el), + }; + + let (hash, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(midstate, adrs_256(0, 0, 0, 2, 0, tree_height, 0), <(u128, u128)>::into((left, right))), 1024)); + + let (_, tree_height): (bool, u32) = jet::add_32(tree_height, 1); + + (hash, true, midstate, tree_height) +} + +fn restore_uxmss_signature(proof: UXMSSSignProof, midstate: u256) -> u128 { + let (message, pk, signature): UXMSSSignProof = proof; + let (pk_seed, root): SPHINCSPK = pk; + let (wots_signature, merkle_path, key_idx): UXMSSSignature = signature; + let (r, counter, message_signature): SPHINCSWOTSTWSignature = wots_signature; + + let (message, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(jet::sha_256_block(jet::sha_256_iv(), adrs_256(0, 0, 0, 4, 0, 0, 0), r), <(u128, u128)>::into((pk_seed, root)), message), 1024)); + + let (message, _): (u128, u128) = ::into(sha256_finish_4(jet::sha_256_block(jet::sha_256_iv(), adrs_256(0, 0, 0, 3, key_idx, 0, 0), <(u128, u128)>::into((pk_seed, message))), counter, 544)); + + let (pk, (_, checksum)): ([u128; 64], (u8, u8)) = sphincs_restore_wots_tw_signature_map_arr_64(message_signature, (0, 0), (message, key_idx, midstate, 0, 0, 0, 0)); + assert!(jet::eq_8(checksum, 140)); + + + let (_, tree_height): (bool, u32) = match jet::eq_32(key_idx, 207) { + true => (false, 1), + false => jet::subtract_32(207, key_idx), + }; + + uxmss_path_root(fold::(merkle_path, (sphincs_hash_leaf_xmss(pk, (key_idx, midstate, 0, 0, 0, 1)), jet::eq_32(key_idx, 207), midstate, tree_height))) +} + +fn shrincs_verify(proof: SHRINCSSignProof) { + let (message, seed_root, signature, root_part): SHRINCSSignProof = proof; + let (public_parameter, expected_root): SPHINCSPK = seed_root; + + let midstate: u256 = jet::sha_256_block(jet::sha_256_iv(), <(u128, u128)>::into((public_parameter, 0)), 0); + + let roots: u256 = match signature { + Left(uxmss: UXMSSSignature) => { + <(u128, u128)>::into((restore_uxmss_signature((message, seed_root, uxmss), midstate), root_part)) + }, + Right(sphincs: SPHINCSSignature) => { + <(u128, u128)>::into((root_part, sphincs_verify((message, seed_root, sphincs), midstate))) + }, + }; + + let (root, _): (u128, u128) = ::into(sha256_finish(jet::sha_256_block(midstate, adrs_256(0, 0, 0, 16, 0, 0, 0), roots), 1024)); + + assert!(eq_128(root, expected_root)); +} + +fn main() { + shrincs_verify(witness::PROOF); +} diff --git a/examples/pq_shrincs_smoke/spend.params.json b/examples/pq_shrincs_smoke/spend.params.json new file mode 100644 index 0000000..6033a7f --- /dev/null +++ b/examples/pq_shrincs_smoke/spend.params.json @@ -0,0 +1,3 @@ +{ + "fee_rate": "15" +} \ No newline at end of file diff --git a/examples/pq_shrincs_smoke/txmanifest.json b/examples/pq_shrincs_smoke/txmanifest.json new file mode 100644 index 0000000..b7a9208 --- /dev/null +++ b/examples/pq_shrincs_smoke/txmanifest.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://raw.githubusercontent.com/stringhandler/txmanifest-wallet/main/schema/txmanifest.schema.json", + "manifest_version": "0.3.1", + "protocol": "shrincs-smoke-test", + "description": "SMOKE TEST ONLY. Locks coins to upstream's SHRINCS verifier, unmodified, and spends them with upstream's own stateful witness. The verifier takes the public key and the message as witness data, so this coin is anyone-can-spend: it shows the Simplicity signet runs a SHRINCS verification, not that the coin is protected by one.", + "chain": "bitcoin", + "requires": [ + "simplicity" + ], + "simplicity_hl": { + "version": "0.7.1", + "debug_symbols": false + }, + "utxo_types": { + "shrincs_unbound": { + "description": "Upstream's SHRINCS verifier with nothing pinned. Anyone holding a valid SHRINCS signature for any key and any message can spend it.", + "script": { + "type": "simplicity", + "source": "./shrincs_main.simf", + "source_hash": "sha256:710146ef52609c4be82a95a08dcc8827257e2a6b38e817c21d55c4b6aa2aff3a" + } + } + }, + "actions": { + "Lock": { + "description": "Send coins to the unbound SHRINCS verifier.", + "intent": "Lock {params.amount_sat} sat under the SHRINCS smoke-test verifier", + "allow_change": "lbtc_only", + "params": { + "amount_sat": { + "type": "u64", + "description": "Amount to lock. Keep it small: anyone can spend it.", + "default": "20000" + } + }, + "inputs": [ + { + "id": "funding", + "utxo_source": "wallet", + "asset": "lbtc", + "amount_sat": { + "min_amount": "params.amount_sat" + }, + "ui": { + "label": "coin being locked", + "role": "funding" + } + } + ], + "outputs": [ + { + "id": "vault", + "amount_sat": "params.amount_sat", + "asset": "lbtc", + "destination": { + "utxo_type": "shrincs_unbound" + }, + "ui": { + "label": "coins locked under the verifier", + "role": "covenant" + } + } + ] + }, + "Spend": { + "description": "Spend the coin with upstream's stateful SHRINCS witness (q=1, message 0).", + "intent": "Spend the SHRINCS smoke-test coin back to this wallet", + "$comment": "The fee estimate assumes 1,024 WU for a covenant witness; this one is about 18,800 WU. Pass a high fee_rate in --params (about 15) so the transaction still relays.", + "inputs": [ + { + "id": "vault_in", + "utxo_source": { + "utxo_type": "shrincs_unbound" + }, + "witnesses": { + "PROOF": { + "type": "simplicityhl", + "value": "(0, (0xb635ef10af1b120cec1095d7745fec06, 0x0f8fbe02641f3479bd3131c0d5253926), Left(((104641417537887468096637857572606874578560029415901768884629032847089444701194, 25794, [279744406477161174898453954224454442384, 100191869207409547221336059044375388424, 95487581836991930323202054728502360641, 310083205617729613868304228069290666260, 257624011138800904600065605320700124623, 305048287911682963660119106030474941323, 204648430403993904544247919942139616291, 236750591000346099609440610110321591125, 196824814686909386917176141078796988140, 316511535116480497246692351945507598187, 330471767409887762817127111776559770793, 26967500738771566668058042323238163034, 201151028884802783401424680656204881081, 257866352908299405963560297226622347066, 277372625599346100057713231265699123429, 143941592513260640104086139532261994721, 101306460868014903048366311963619481515, 137677462207889313570924863844153834900, 146855367544317916540796729494957438220, 299702215678073717444317085756614384649, 299422392145965913549196361946514118205, 218267348603148341378372315458246547195, 205406908791210700830536879145049927703, 315354807768895916425915925012676471130, 253797929653540327311600971365633653052, 288765870696341440857070716368703814491, 249827258618266768941067032549741226812, 192065118615413155102250957616729117149, 244526319209537952179479378912532825266, 324682565723164264695735439393252049431, 74736776896544312869036542917523434181, 287911431987466938533741527115333590518, 338869510479812621968398559963281440567, 255456750741432516494190809935156548989, 117219694420124565797944818169934890343, 11142287029120057092849115208661560700, 269060538540596741121745983537279305329, 177615871167159170078660855577184128691, 146323455865231972475746867329643852058, 16048975939891095973544503220542676373, 334980985378384697937744926874944833920, 35273915146041816046806212010570904881, 260495355653033411698743528828240768400, 51175072147311734163306138557681194296, 253150511472509828354787312285225781, 26442791451334483792521024240862126546, 305201243147962042107300476363547998226, 276246860413834174591938349899170076364, 124311071434438674156195119383108865107, 182862399605165986399826600726163023653, 18998086256583136675550353879157721573, 5492767804387397942488402573919843270, 20917105423670140299946458262319265275, 214855000219476395616090914039712094137, 290878809507972983797879300848336773978, 321436532748391495336174492487827248582, 143797167348210565263256089940770242047, 309124555943212030562981020497532908190, 130650316130903867098038773654364014927, 217278390429756520909840890768050002622, 53300241174463360647441617499472912652, 209520507978057829011388360222931842804, 151028685387999325751378814616688404491, 83817881586650168117312150349556894295]), list![278383999423802883450225662559784840723], 1)), 0x9cf1c451ce3c9345581de66456b2b5f0)" + } + }, + "ui": { + "label": "smoke-test coin", + "role": "covenant" + } + } + ], + "outputs": [ + { + "id": "back", + "amount_sat": "vault_in.amount_sat - fee", + "destination": "wallet", + "ui": { + "label": "coins back to the wallet", + "role": "payment" + } + } + ] + } + } +} diff --git a/txmanifest_lib/tests/shrincs_verifier.rs b/txmanifest_lib/tests/shrincs_verifier.rs new file mode 100644 index 0000000..260ebcc --- /dev/null +++ b/txmanifest_lib/tests/shrincs_verifier.rs @@ -0,0 +1,266 @@ +//! The vendored SHRINCS verifier, run through the wallet's own Bitcoin finalize path. +//! +//! Phase 0, task T1 (`meta/pq-phase0/t1-verifier-spike.md`). The program is upstream's +//! `examples/shrincs/shrincs_main.simf`, unmodified, as `examples/pq_shrincs_smoke/build.sh` +//! flattens it, and the witnesses are upstream's own `.wit` files. Unmodified means the message and the public key are witness data, so the +//! program reads nothing from the transaction and any spending transaction will do. +//! +//! What this establishes is that our SimplicityHL fork compiles it under the Bitcoin jet set, +//! and that satisfying, pruning against a real `BitcoinEnv` and padding all succeed โ€” the +//! same calls a covenant spend makes. +//! +//! Task T2 adds `examples/pq_shrincs/shrincs_spend.simf`, the wrapper that pins the key in +//! `param::PK` and takes the message from `jet::sig_all_hash()`. Its tests are at the end. + +use std::collections::HashMap; +use std::path::{Path, PathBuf}; + +use lwk_wollet::elements::bitcoin::{ + absolute::LockTime, transaction::Version, Amount, OutPoint, ScriptBuf, Sequence, Transaction, + TxIn, TxOut, Witness, +}; +use simplicityhl::simplicity::{jet::Bitcoin, BitIter, Cost, RedeemNode}; +use tx_manifest_lib::chain::ChainFamily; +use tx_manifest_lib::covenant::{ + compute_tapleaf_hash, finalize_bitcoin_covenant_input, program_witness_types, CompileOpts, + ProgramSource, +}; + +fn verifier_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../contrib/shrincs-simplicity-verifier") +} + +fn program() -> ProgramSource { + ProgramSource::read_unpinned( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../examples/pq_shrincs_smoke/shrincs_main.simf"), + ) + .expect("read the flattened verifier; run examples/pq_shrincs_smoke/build.sh") +} + +/// An upstream `.wit` file, as a manifest's `witnesses` map. +/// +/// Upstream writes `{"NAME": {"type": , "value": โ€ฆ}}`. A manifest names +/// the kind of witness in `type` instead and takes the Simplicity type from the program. +fn upstream_witnesses(file: &str) -> serde_json::Value { + let path = verifier_dir().join("upstream/examples/shrincs").join(file); + let text = std::fs::read_to_string(&path).expect("read upstream witness file"); + let wit: serde_json::Map = + serde_json::from_str(&text).expect("upstream witness file is JSON"); + let mut out = serde_json::Map::new(); + for (name, def) in wit { + let value = def["value"] + .as_str() + .expect("witness value is a string") + .to_owned(); + out.insert( + name, + serde_json::json!({ "type": "simplicityhl", "value": value }), + ); + } + serde_json::Value::Object(out) +} + +/// A one-in, one-out transaction. The program reads nothing from it, but pruning needs an +/// environment, and an environment needs a transaction and the output it spends. +fn spend() -> (Transaction, Vec) { + let tx = Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint { + txid: format!("{:064x}", 1).parse().expect("txid"), + vout: 0, + }, + script_sig: ScriptBuf::new(), + sequence: Sequence::MAX, + witness: Witness::new(), + }], + output: vec![TxOut { + value: Amount::from_sat(90_000), + script_pubkey: ScriptBuf::from_bytes([&[0x51, 0x20][..], &[0x42; 32]].concat()), + }], + }; + let prevout = TxOut { + value: Amount::from_sat(100_000), + script_pubkey: ScriptBuf::from_bytes([&[0x51, 0x20][..], &[0x07; 32]].concat()), + }; + (tx, vec![prevout]) +} + +fn bitcoin() -> CompileOpts { + CompileOpts { + family: ChainFamily::Bitcoin, + ..CompileOpts::default() + } +} + +/// Satisfy, prune and pad the verifier with `witnesses`, returning the witness stack. +fn finalize(witnesses: &serde_json::Value) -> anyhow::Result>> { + let (tx, prevouts) = spend(); + finalize_bitcoin_covenant_input( + &program(), + &HashMap::new(), + &HashMap::new(), + &[], + Some(witnesses), + None, + &tx, + &prevouts, + 0, + bitcoin(), + ) +} + +/// The Simplicity signet node's acceptance rule for a Simplicity leaf spend, from +/// `src/script/interpreter.cpp` on delta1/bitcoin's `simplicity-inquisition` branch, the +/// node `contrib/regtest` builds. +/// +/// The node reads the stack from the end: the control block, the CMR, the program, the +/// witness, and then an optional padding item, so padding comes *first* on the wire. The +/// program's cost bound must fit the budget the whole stack buys. When there is padding, +/// the cost must also *not* fit the budget the stack would buy with one byte less of it: +/// padding has to be minimal, or the spend fails with `SIMPLICITY_ERR_OVERWEIGHT`. +fn node_check(stack: &[Vec]) -> Result { + let n = stack.len(); + if !(4..=5).contains(&n) { + return Err(format!( + "{n} stack items; a Simplicity spend has 4, or 5 with padding" + )); + } + // TAPROOT_CONTROL_BASE_SIZE + k * TAPROOT_CONTROL_NODE_SIZE, k โ‰ค 128. + let control = &stack[n - 1]; + if control.len() < 33 + || control.len() > 33 + 128 * 32 + || !(control.len() - 33).is_multiple_of(32) + { + return Err(format!( + "the last item ({} B) is not a control block, so the node cannot find the leaf", + control.len() + )); + } + let program = &stack[n - 3]; + let witness = &stack[n - 4]; + let padding = (n == 5).then(|| &stack[0]); + if padding.is_some_and(|p| p.iter().any(|&b| b != 0)) { + return Err("the first item is padding and must be all zeros".into()); + } + + let node = RedeemNode::decode::<_, _, Bitcoin>( + BitIter::from(&program[..]), + BitIter::from(&witness[..]), + ) + .map_err(|e| format!("cannot decode the program: {e:?}"))?; + let cost = node.bounds().cost; + if !cost.is_budget_valid(stack) { + return Err(format!("{cost:?} exceeds the budget the stack buys")); + } + if let Some(p) = padding { + if !p.is_empty() { + let mut shorter = stack.to_vec(); + shorter[0].pop(); + if cost.is_budget_valid(&shorter) { + return Err(format!( + "{cost:?} fits the budget with one byte less padding ({} B), so the \ + padding is not minimal", + p.len() + )); + } + } + } + Ok(cost) +} + +/// Item sizes of a finalized stack, for the record in the T1 task file. +fn describe(stack: &[Vec]) -> String { + let mut names = vec!["witness", "program", "cmr", "control block"]; + if stack.len() == 5 { + names.insert(0, "padding"); + } + let parts: Vec = stack + .iter() + .enumerate() + .map(|(i, item)| format!("{} {} B", names.get(i).unwrap_or(&"?"), item.len())) + .collect(); + let total: usize = stack.iter().map(Vec::len).sum(); + format!("{} (items total {total} B)", parts.join(", ")) +} + +/// Finalize with an upstream witness file, then hold the stack to the node's rules. +fn spend_with(file: &str) { + let stack = finalize(&upstream_witnesses(file)) + .unwrap_or_else(|e| panic!("{file} should verify: {e:#}")); + eprintln!("{file}: {}", describe(&stack)); + match node_check(&stack) { + Ok(cost) => eprintln!("{file}: the node would accept it; cost bound {cost:?}"), + Err(why) => panic!("{file}: the node would reject this spend: {why}"), + } +} + +#[test] +fn stateful_signature_verifies() { + spend_with("shrincs_main_stateful.wit"); +} + +#[test] +fn stateless_signature_verifies() { + spend_with("shrincs_main_stateless.wit"); +} + +/// The negative control: without it, a verifier that accepted everything would pass both +/// tests above. +#[test] +fn tampered_signature_is_rejected() { + let mut witnesses = upstream_witnesses("shrincs_main_stateful.wit"); + let proof = witnesses["PROOF"]["value"] + .as_str() + .expect("PROOF value") + .to_owned(); + // The proof opens `(msg, (PK.seed, PK.root), โ€ฆ)`, and upstream signs the message 0. + // Signing 1 instead changes the digest every later check depends on. + let tampered = proof.replacen("(0, (", "(1, (", 1); + assert_ne!( + tampered, proof, + "the message should be the proof's first field" + ); + witnesses["PROOF"]["value"] = serde_json::Value::String(tampered); + + let err = finalize(&witnesses).expect_err("a signature over another message must fail"); + eprintln!("tampered: {err:#}"); +} + +// โ”€โ”€ T2: the wrapper โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +fn wrapper() -> ProgramSource { + ProgramSource::read_unpinned( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/pq_shrincs/shrincs_spend.simf"), + ) + .expect("read the flattened wrapper; run examples/pq_shrincs/build.sh") +} + +/// Upstream's example key, `PK.seed || PK.root`, from `shrincs_main_stateful.wit`. +const UPSTREAM_PK: &str = "0xb635ef10af1b120cec1095d7745fec060f8fbe02641f3479bd3131c0d5253926"; + +/// The spender supplies the signature and nothing else: no key, no message. A witness +/// named here that the program also took as a param would be the anyone-can-spend hole +/// this wrapper exists to close. +#[test] +fn wrapper_takes_only_the_signature_as_witness() { + let types = program_witness_types(&wrapper(), bitcoin()).expect("wrapper compiles"); + let names: Vec<&str> = types.keys().map(String::as_str).collect(); + assert_eq!(names, ["OTHER_ROOT", "SIG"]); +} + +/// The key goes in as one 32-byte `bytes32` param, and different keys give different +/// leaves, so each key has its own address. +#[test] +fn wrapper_commits_to_the_key() { + let leaf = |pk: &str| { + let params = HashMap::from([("PK".to_string(), pk.to_string())]); + let hints = HashMap::from([("PK".to_string(), "bytes32".to_string())]); + compute_tapleaf_hash(&wrapper(), ¶ms, &hints, bitcoin()) + .unwrap_or_else(|e| panic!("wrapper compiles with PK={pk}: {e:#}")) + }; + let other = "0x0000000000000000000000000000000000000000000000000000000000000001"; + assert_ne!(leaf(UPSTREAM_PK), leaf(other)); +} From 11bd2267642e87496c52beea2c9fc3cddf82a46d Mon Sep 17 00:00:00 2001 From: stringhandler Date: Fri, 9 Oct 2026 17:12:14 +0200 Subject: [PATCH 3/4] feat: spend a coin locked to a SHRINCS key, signed outside the wallet Add contrib/shrincs-signer, a thin driver around shrincs-cpp that makes a SHRINCS-L key from a seed file and stateful signatures over a 32-byte message, and examples/pq_shrincs, which locks coins to a pinned SHRINCS public key and spends them with a signature over jet::sig_all_hash(). The signature has to be made outside the wallet, so a spend takes two runs. The new `run --print-sighash` (Bitcoin only) builds the transaction, prints the sig_all_hash each covenant input's program will check, and stops without broadcasting. The second run, with the same inputs, params and fee rate, builds the same transaction and carries the signature. tests/shrincs_verifier.rs runs the same spend offline with a test key (fixtures/shrincs/test_seed.hex, never to hold real coins). --- CHANGELOG.md | 1 + contrib/shrincs-signer/.gitignore | 2 + contrib/shrincs-signer/Makefile | 35 ++++ contrib/shrincs-signer/README.md | 49 +++++ contrib/shrincs-signer/shrincs-signer.cpp | 186 +++++++++++++++++ examples/README.md | 5 +- examples/pq_shrincs/README.md | 93 +++++++++ examples/pq_shrincs/config.json | 10 + examples/pq_shrincs/lock.params.json | 5 + examples/pq_shrincs/spend.params.json | 6 + examples/pq_shrincs/txmanifest.json | 134 ++++++++++++ txmanifest_lib/src/covenant.rs | 197 ++++++++++++------ txmanifest_lib/src/lifecycle.rs | 51 ++++- txmanifest_lib/tests/bitcoin_sighash.rs | 113 ++++++++++ .../tests/fixtures/shrincs/spend_q1.json | 6 + .../tests/fixtures/shrincs/test_seed.hex | 1 + txmanifest_lib/tests/shrincs_verifier.rs | 93 ++++++++- txmanifest_wallet/src/main.rs | 9 + 18 files changed, 929 insertions(+), 67 deletions(-) create mode 100644 contrib/shrincs-signer/.gitignore create mode 100644 contrib/shrincs-signer/Makefile create mode 100644 contrib/shrincs-signer/README.md create mode 100644 contrib/shrincs-signer/shrincs-signer.cpp create mode 100644 examples/pq_shrincs/README.md create mode 100644 examples/pq_shrincs/config.json create mode 100644 examples/pq_shrincs/lock.params.json create mode 100644 examples/pq_shrincs/spend.params.json create mode 100644 examples/pq_shrincs/txmanifest.json create mode 100644 txmanifest_lib/tests/bitcoin_sighash.rs create mode 100644 txmanifest_lib/tests/fixtures/shrincs/spend_q1.json create mode 100644 txmanifest_lib/tests/fixtures/shrincs/test_seed.hex diff --git a/CHANGELOG.md b/CHANGELOG.md index bc4b662..968aad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and - `pin` command: writes every program's current hash into a manifest without reformatting it; `pin --check` fails if any is missing or stale. - `validate --strict`: unpinned programs are errors rather than warnings. - `run --debug`: development mode. For now the same as `--allow-unpinned`; it may enable more development-only behaviour later. +- `run --print-sighash` (Bitcoin): build the transaction, print the `sig_all_hash` each covenant input's program will check, and stop without broadcasting. For a covenant witness signed outside the wallet: sign the hash, then run again with the same `--input`, params and `fee_rate`, so the second run builds the same transaction and the signature verifies. ### Changed diff --git a/contrib/shrincs-signer/.gitignore b/contrib/shrincs-signer/.gitignore new file mode 100644 index 0000000..07524c4 --- /dev/null +++ b/contrib/shrincs-signer/.gitignore @@ -0,0 +1,2 @@ +bin/ +deps/ diff --git a/contrib/shrincs-signer/Makefile b/contrib/shrincs-signer/Makefile new file mode 100644 index 0000000..5b9e40b --- /dev/null +++ b/contrib/shrincs-signer/Makefile @@ -0,0 +1,35 @@ +# shrincs-signer: a phase 0 command-line signer for examples/pq_shrincs, around shrincs-cpp. +# `make` fetches shrincs-cpp at the pinned commit into deps/ and builds bin/shrincs-signer. +# Needs a C++17 compiler and OpenSSL 3 (brew install openssl@3; apt install libssl-dev). + +SHRINCS_CPP_REPO := https://github.com/BlockstreamResearch/shrincs-cpp +# The commit whose FORS+C signatures upstream's main verifier accepts. Later commits moved +# to PORS+FP, which it rejects. +SHRINCS_CPP_COMMIT := fde3603fb4fe268a6095ca90bae3ae651fc69ff7 +SHRINCS_CPP := deps/shrincs-cpp + +OPENSSL_PREFIX ?= $(shell brew --prefix openssl@3 2>/dev/null) +CXX ?= c++ +# SHRINCS_L selects W=4, L=64, S=140, HSF=206, the parameters the verifier hard-codes. +# shrincs-cpp defaults to SHRINCS_B (W=256, HSF=158), whose signatures it would reject. +# `-include algorithm`: upstream's hash.cpp uses std::min without including , +# which GCC's headers happen to supply and libc++ (Apple clang) does not. +CXXFLAGS := -std=c++17 -O3 -Wall -Wno-deprecated-declarations -DSHRINCS_L -include algorithm \ + -I$(SHRINCS_CPP)/include $(if $(OPENSSL_PREFIX),-I$(OPENSSL_PREFIX)/include) +LDFLAGS := $(if $(OPENSSL_PREFIX),-L$(OPENSSL_PREFIX)/lib) -lcrypto + +bin/shrincs-signer: shrincs-signer.cpp $(SHRINCS_CPP)/.fetched + @mkdir -p bin + $(CXX) $(CXXFLAGS) shrincs-signer.cpp $(filter-out %/main.cpp,$(wildcard $(SHRINCS_CPP)/src/*.cpp)) \ + -o $@ $(LDFLAGS) + +$(SHRINCS_CPP)/.fetched: + rm -rf $(SHRINCS_CPP) && mkdir -p $(SHRINCS_CPP) + cd $(SHRINCS_CPP) && git init -q && git fetch -q --depth 1 $(SHRINCS_CPP_REPO) $(SHRINCS_CPP_COMMIT) \ + && git checkout -q FETCH_HEAD + touch $@ + +clean: + rm -rf bin deps + +.PHONY: clean diff --git a/contrib/shrincs-signer/README.md b/contrib/shrincs-signer/README.md new file mode 100644 index 0000000..e2505e4 --- /dev/null +++ b/contrib/shrincs-signer/README.md @@ -0,0 +1,49 @@ +# shrincs-signer + +A command-line SHRINCS signer for [`examples/pq_shrincs`](../../examples/pq_shrincs). It +is a thin driver around [shrincs-cpp](https://github.com/BlockstreamResearch/shrincs-cpp), +upstream's reference implementation; all the cryptography is upstream's. + +This is a stopgap. The plan is for signers to run inside the wallet as Simplicity +programs, after which this goes away. + +```sh +make # fetches shrincs-cpp into deps/, builds bin/shrincs-signer +bin/shrincs-signer new --seed key.seed --state key.q +bin/shrincs-signer pubkey --seed key.seed +bin/shrincs-signer sign --seed key.seed --state key.q --msg <32-byte hex> +``` + +- `new` writes a random 48-byte seed (`SK.seed โ€– SK.prf โ€– PK.seed`, as hex) and a state + file at q = 0, both mode 0600. It refuses to overwrite either. +- `pubkey` prints `PK = PK.seed โ€– PK.root`, the manifest's `bytes32` param. +- `sign` makes a stateful signature with the next leaf index. It prints + `{"SIG": โ€ฆ, "OTHER_ROOT": โ€ฆ}` as SimplicityHL values in the layout + `examples/pq_shrincs/shrincs_spend.simf` reads. It also checks the signature with + shrincs-cpp before printing it. + +## State + +The state file holds the last leaf index used. Each signature uses the next one, and a +key has 207 (1 to 206 in the unbalanced tree, then 207). **Reusing an index lets anyone +forge**, so: + +- `sign` writes and syncs the new index *before* printing the signature. A crash can + waste an index, but never reuses one. +- `sign` refuses to run without a state file rather than assume q = 0, because a lost + state file is how an index gets reused. +- Keep the state file with the seed, and never restore an old copy of it. + +Stateless signatures, which SHRINCS falls back to after 207 or when the state is lost, +aren't supported here. + +## Build notes + +- shrincs-cpp is pinned to `fde3603`. That commit produces the FORS+C signatures + upstream's `main` verifier accepts. Later commits moved to PORS+FP, which it rejects. +- `-DSHRINCS_L` selects the parameters the verifier hard-codes (W=4, L=64, S=140, + HSF=206). shrincs-cpp defaults to SHRINCS-B, whose signatures the verifier rejects. +- `-include algorithm` works around upstream's `hash.cpp` using `std::min` without + including ``. GCC's headers supply it; Apple clang's libc++ does not. +- It needs OpenSSL 3. On macOS the Makefile finds Homebrew's `openssl@3`; elsewhere, set + `OPENSSL_PREFIX` or install `libssl-dev`. diff --git a/contrib/shrincs-signer/shrincs-signer.cpp b/contrib/shrincs-signer/shrincs-signer.cpp new file mode 100644 index 0000000..ac5bb0b --- /dev/null +++ b/contrib/shrincs-signer/shrincs-signer.cpp @@ -0,0 +1,186 @@ +// shrincs-signer: a SHRINCS-L key from a seed file, and stateful signatures over 32-byte +// messages, printed as the witness values examples/pq_shrincs/shrincs_spend.simf reads. +// +// shrincs-signer new --seed FILE --state FILE write a fresh seed, and a state file +// at q = 0 +// shrincs-signer pubkey --seed FILE print PK (PK.seed || PK.root) +// shrincs-signer sign --seed FILE --state FILE --msg HEX +// print {"SIG": ..., "OTHER_ROOT": ...} +// +// A thin driver around shrincs-cpp (see the Makefile for the commit); the cryptography is +// all upstream's. Phase 0 only: the plan is for signers to run inside the wallet. +// +// Stateful signing only. Each signature uses the next leaf index q, and reusing one lets +// anyone forge, so the state file is rewritten and synced *before* the signature is +// printed: a crash can waste an index, never reuse one. `sign` refuses to run without a +// state file rather than assume q = 0, because a lost state file is exactly how an index +// gets reused. + +#include "shrincs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Parameters; +using namespace SHRINCS; + +static constexpr size_t SEED_LEN = 3 * N; // SK.seed || SK.prf || PK.seed + +[[noreturn]] static void die(const std::string& msg) { + fprintf(stderr, "shrincs-signer: %s\n", msg.c_str()); + exit(1); +} + +static std::string hex(const unsigned char* p, size_t n) { + static const char* d = "0123456789abcdef"; + std::string s; + s.reserve(2 * n); + for (size_t i = 0; i < n; i++) { + s.push_back(d[p[i] >> 4]); + s.push_back(d[p[i] & 15]); + } + return s; +} + +static std::vector unhex(const std::string& s, size_t want, const char* what) { + std::string h = s; + if (h.rfind("0x", 0) == 0) h = h.substr(2); + while (!h.empty() && (h.back() == '\n' || h.back() == '\r' || h.back() == ' ')) h.pop_back(); + if (h.size() != 2 * want) die(std::string(what) + ": expected " + std::to_string(want) + " bytes of hex"); + std::vector out(want); + for (size_t i = 0; i < want; i++) { + unsigned int b; + if (sscanf(h.c_str() + 2 * i, "%2x", &b) != 1) die(std::string(what) + ": not hex"); + out[i] = (unsigned char)b; + } + return out; +} + +static std::string read_file(const std::string& path) { + FILE* f = fopen(path.c_str(), "r"); + if (!f) die("cannot read " + path + ": " + strerror(errno)); + std::string s; + char buf[256]; + size_t n; + while ((n = fread(buf, 1, sizeof buf, f)) > 0) s.append(buf, n); + fclose(f); + return s; +} + +// Write `contents` to `path` durably: a temporary file, fsync, then rename over the old one. +static void write_file_synced(const std::string& path, const std::string& contents, mode_t mode) { + std::string tmp = path + ".tmp"; + int fd = open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode); + if (fd < 0) die("cannot write " + tmp + ": " + strerror(errno)); + if (write(fd, contents.data(), contents.size()) != (ssize_t)contents.size()) die("short write to " + tmp); + if (fsync(fd) != 0) die("cannot sync " + tmp + ": " + strerror(errno)); + close(fd); + if (rename(tmp.c_str(), path.c_str()) != 0) die("cannot replace " + path + ": " + strerror(errno)); +} + +static bool exists(const std::string& path) { + return access(path.c_str(), F_OK) == 0; +} + +static std::string flag(int argc, char** argv, const char* name, bool required = true) { + for (int i = 2; i + 1 < argc; i++) + if (strcmp(argv[i], name) == 0) return argv[i + 1]; + if (required) die(std::string("missing ") + name); + return ""; +} + +static void restore(const std::string& seed_path, PublicKey& pk, SecretKey& sk, State& state) { + auto seed = unhex(read_file(seed_path), SEED_LEN, "seed file"); + shrincs_restore(seed.data(), pk, sk, state); +} + +static int cmd_new(int argc, char** argv) { + std::string seed_path = flag(argc, argv, "--seed"); + std::string state_path = flag(argc, argv, "--state"); + if (exists(seed_path)) die(seed_path + " exists; refusing to overwrite a key"); + if (exists(state_path)) die(state_path + " exists; refusing to reset a key's state"); + unsigned char seed[SEED_LEN]; + generate_random_bytes(seed, SEED_LEN); + write_file_synced(seed_path, hex(seed, SEED_LEN) + "\n", 0600); + write_file_synced(state_path, "0\n", 0600); + fprintf(stderr, "wrote %s and %s (q = 0)\n", seed_path.c_str(), state_path.c_str()); + return 0; +} + +static int cmd_pubkey(int argc, char** argv) { + PublicKey pk; + SecretKey sk; + State state; + restore(flag(argc, argv, "--seed"), pk, sk, state); + printf("0x%s%s\n", hex(pk.seed.data(), N).c_str(), hex(pk.root.data(), N).c_str()); + return 0; +} + +static int cmd_sign(int argc, char** argv) { + std::string state_path = flag(argc, argv, "--state"); + auto msg = unhex(flag(argc, argv, "--msg"), 32, "--msg"); + if (!exists(state_path)) die(state_path + " does not exist; `new` creates it with the seed"); + + PublicKey pk; + SecretKey sk; + State state; + restore(flag(argc, argv, "--seed"), pk, sk, state); + + char* end = nullptr; + std::string text = read_file(state_path); + unsigned long last_q = strtoul(text.c_str(), &end, 10); + if (end == text.c_str()) die(state_path + ": expected the last leaf index used, a number"); + uint32_t q = (uint32_t)last_q + 1; + // Leaves 1..HSF are the unbalanced tree's; HSF + 1 is its last leaf, the one beside + // the stateless root (the verifier's `key_idx == HSF + 1` case). + if (q > HSF + 1) die("this key has used all " + std::to_string(HSF + 1) + " stateful signatures"); + + // Record the index before the signature exists. + write_file_synced(state_path, std::to_string(q) + "\n", 0600); + + state.q = (uint32_t)last_q; + state.valid = true; + unsigned char* sig = shrincs_sign_stateful(msg.data(), sk, state); + uint32_t path_nodes = q > HSF ? HSF : q; + uint32_t sig_len = N + WOTS_SIGN_LEN + path_nodes * N; + if (!shrincs_verify_stateful(msg.data(), sig, sig_len, pk)) die("the signature does not verify"); + + // sl_root(N) || R(R_LEN) || counter(4) || L chain values(N each) || path(N each), + // as optimize_fors's tools/vectors_to_wit.py reads it. + const unsigned char* p = sig; + std::string sl_root = hex(p, N); + p += N; + std::string r = hex(p, R_LEN); + p += R_LEN; + std::string counter = hex(p, 4); + p += 4; + std::string chains; + for (uint32_t i = 0; i < L; i++, p += N) chains += (i ? ", 0x" : "0x") + hex(p, N); + std::string path; + for (uint32_t i = 0; i < path_nodes; i++, p += N) path += (i ? ", 0x" : "0x") + hex(p, N); + delete[] sig; + + printf("{\n \"SIG\": \"Left(((0x%s, 0x%s, [%s]), list![%s], %u))\",\n \"OTHER_ROOT\": \"0x%s\"\n}\n", + r.c_str(), counter.c_str(), chains.c_str(), path.c_str(), q, sl_root.c_str()); + fprintf(stderr, "signed with leaf index q = %u; %s now records it\n", q, state_path.c_str()); + return 0; +} + +int main(int argc, char** argv) { + std::string cmd = argc > 1 ? argv[1] : ""; + if (cmd == "new") return cmd_new(argc, argv); + if (cmd == "pubkey") return cmd_pubkey(argc, argv); + if (cmd == "sign") return cmd_sign(argc, argv); + fprintf(stderr, + "usage:\n" + " shrincs-signer new --seed FILE --state FILE\n" + " shrincs-signer pubkey --seed FILE\n" + " shrincs-signer sign --seed FILE --state FILE --msg HEX\n"); + return 2; +} diff --git a/examples/README.md b/examples/README.md index e9c57e3..a2f3953 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,6 +11,7 @@ Roughly in order of complexity: | [`p2pk`](p2pk) | Liquid | The smallest covenant: lock coins to a key with a Simplicity checksig program, then spend them. Start here. | | [`bitcoin_pay`](bitcoin_pay) | Bitcoin | A plain payment with no covenant: what a manifest looks like on Bitcoin, and the `fee` keyword. | | [`bitcoin_covenant`](bitcoin_covenant) | Bitcoin | The `p2pk` program on Bitcoin, run on the Simplicity signet or a local regtest. | +| [`pq_shrincs`](pq_shrincs) | Bitcoin | Coins locked to a SHRINCS post-quantum public key and spent with a SHRINCS signature over the transaction, made outside the wallet by `contrib/shrincs-signer` in a two-run flow. | | [`pq_shrincs_smoke`](pq_shrincs_smoke) | Bitcoin | A smoke test, not a pattern to copy: runs the SHRINCS post-quantum signature verifier on the Simplicity signet, unmodified, so the coin it locks is **anyone-can-spend**. | | [`last_will`](last_will) | Liquid | A recursive covenant with a timelocked inheritance path, a cold-key break-out and a hot-key refresh. | | [`dex`](dex) | Liquid | Tessera: a keyless atomic-swap offer anyone can fill, or refund after a timeout. | @@ -18,6 +19,6 @@ Roughly in order of complexity: | [`lending_v2`](lending_v2) | Liquid | Peer-to-peer collateralised lending, wire-compatible with [simplicity-lending](https://github.com/BlockstreamResearch/simplicity-lending). | | [`lending_v3`](lending_v3) | Liquid | Work in progress: the redesigned "issuance factory" version of simplicity-lending โ€” create a factory, then offer, accept, cancel, claim and repay loans. | -The examples with their own README (`bitcoin_pay`, `bitcoin_covenant`, `pq_shrincs_smoke`, -`lending_v2`) +The examples with their own README (`bitcoin_pay`, `bitcoin_covenant`, `pq_shrincs`, +`pq_shrincs_smoke`, `lending_v2`) include full walkthroughs. diff --git a/examples/pq_shrincs/README.md b/examples/pq_shrincs/README.md new file mode 100644 index 0000000..f242475 --- /dev/null +++ b/examples/pq_shrincs/README.md @@ -0,0 +1,93 @@ +# SHRINCS: a post-quantum signature on Bitcoin + +Coins locked to a [SHRINCS](https://github.com/BlockstreamResearch/shrincs-simplicity-verifier) +public key, spendable only with a SHRINCS signature by that key over the spending +transaction. SHRINCS is a hash-based signature scheme, so its security doesn't rest on +discrete logs. A Simplicity program on the Simplicity signet verifies the signature. + +The program is upstream's verifier plus a small wrapper, +[`src/shrincs_spend.simf`](src/shrincs_spend.simf). The wrapper takes the public key from +`param::PK`, so it is part of the address, and the message from `jet::sig_all_hash()`, so +the signature covers this transaction. The spender supplies only the signature. + +> **Experimental.** Two limits apply beyond the wallet's usual warning. +> - These outputs are Taproot with an unspendable internal key. Someone who can compute +> discrete logs could still spend them through the key path, so this shows what a +> post-quantum spend costs; it doesn't make coins quantum-safe. +> - Signatures are stateful. Each one uses the next of a key's 207 leaf indexes, and +> reusing an index lets anyone forge. Keep the key's state file with its seed, and never +> copy or restore an old one. + +## Files + +- `shrincs_spend.simf`: the program the manifest pins. `build.sh` flattens it from + `src/shrincs_spend.simf` and the vendored verifier in + [`contrib/shrincs-simplicity-verifier`](../../contrib/shrincs-simplicity-verifier). + Don't edit it directly. +- `lock.params.json`, `spend.params.json`: templates to fill in. + +## Spending takes two runs + +The wallet doesn't sign SHRINCS itself yet. The signature comes from +[`contrib/shrincs-signer`](../../contrib/shrincs-signer), and it has to cover the +transaction, which doesn't exist until the wallet builds it. So `Spend` runs twice: + +1. With `--print-sighash`: build the transaction, print its `sig_all_hash`, stop. +2. With the signature: build the same transaction again, and broadcast it. + +Both runs must build the *same* transaction, or the signature won't verify. Use the same +state file and the same params, apart from `SIG` and `OTHER_ROOT`. The manifest helps: +the destination is a fixed address, and the fee comes from a fixed `fee_rate`. + +## Run it + +Use a funded signet wallet; see [`bitcoin_covenant`](../bitcoin_covenant) for creating and +funding one. + +```sh +CFG="--config examples/bitcoin_covenant/config.json" +W=/path/to/signet/wallet.json +M=examples/pq_shrincs +SIGNER=contrib/shrincs-signer/bin/shrincs-signer +KEY=~/.shrincs/key # the seed and its state file live here; keep them together + +# 1. Build the signer, and make a key. +make -C contrib/shrincs-signer +mkdir -p ~/.shrincs +$SIGNER new --seed $KEY.seed --state $KEY.q +$SIGNER pubkey --seed $KEY.seed # prints PK + +# 2. Lock coins to it. Put PK in a copy of lock.params.json. +cp $M/lock.params.json /tmp/pq.lock.json # then set "PK" +cargo run -p tx-manifest-wallet -- $CFG run $M/txmanifest.json Lock \ + --wallet $W --params /tmp/pq.lock.json --state-out /tmp/pq.state.json + +# 3. Print the sighash. Put PK and a destination address in a copy of spend.params.json +# (`info --wallet $W` shows one of yours). Leave SIG and OTHER_ROOT as placeholders. +cp $M/spend.params.json /tmp/pq.spend.json # then set "PK" and "dest" +cargo run -p tx-manifest-wallet -- $CFG run $M/txmanifest.json Spend \ + --wallet $W --params /tmp/pq.spend.json --state /tmp/pq.state.json --print-sighash +# โ†’ prints: vault_in + +# 4. Sign it, and put the signature into the params. +$SIGNER sign --seed $KEY.seed --state $KEY.q --msg > /tmp/pq.sig.json +python3 -c 'import json,sys; p=json.load(open(sys.argv[1])); p.update(json.load(open(sys.argv[2]))); json.dump(p,open(sys.argv[3],"w"),indent=2)' \ + /tmp/pq.spend.json /tmp/pq.sig.json /tmp/pq.spend.signed.json + +# 5. Spend. +cargo run -p tx-manifest-wallet -- $CFG run $M/txmanifest.json Spend \ + --wallet $W --params /tmp/pq.spend.signed.json --state /tmp/pq.state.json \ + --state-out /tmp/pq.state.2.json +``` + +Expect a witness of about 18.8 KB, about 14.7 KB of it cost padding, and around 4,800 vB. +`spend.params.json` sets `fee_rate` to 15 because the wallet's fee estimate assumes about +1 KB of witness for a covenant input. 15 on that estimate comes to roughly 1.1 sat/vB on +the real transaction. + +If step 5 reports that the program failed, the transaction differs from the one step 3 +printed. Run step 3 again: the printed hash should not change. Then sign the new hash; +each attempt uses another leaf index. + +`txmanifest_lib/tests/shrincs_verifier.rs` (`signed_spend_of_a_shrincs_locked_coin`) +runs the same spend offline with a test key. diff --git a/examples/pq_shrincs/config.json b/examples/pq_shrincs/config.json new file mode 100644 index 0000000..1cdcdf0 --- /dev/null +++ b/examples/pq_shrincs/config.json @@ -0,0 +1,10 @@ +{ + "default_network": "bitcoin-signet", + "bitcoin_backend": "esplora", + "default_esplora": "https://signet.simplicity-lang.org/explorer/api", + "simplicity_activated": true, + "bitcoin_checkpoint": { + "height": 1296, + "hash": "00000091e713448edf2f57f61a9d9e03a5c35ac0902be6c89b0cc5ea8e7b8564" + } +} diff --git a/examples/pq_shrincs/lock.params.json b/examples/pq_shrincs/lock.params.json new file mode 100644 index 0000000..a958184 --- /dev/null +++ b/examples/pq_shrincs/lock.params.json @@ -0,0 +1,5 @@ +{ + "PK": "0x", + "amount_sat": "20000", + "fee_rate": "0.1" +} \ No newline at end of file diff --git a/examples/pq_shrincs/spend.params.json b/examples/pq_shrincs/spend.params.json new file mode 100644 index 0000000..127966e --- /dev/null +++ b/examples/pq_shrincs/spend.params.json @@ -0,0 +1,6 @@ +{ + "PK": "0xdd1ed75a55375ef66e3286dc36702951b7f7ba287323b7172461c6f897d8b4a0", + "fee_rate": "15", + "SIG": "Left(((0x35c6e3f93634bbb055be4c7a190f7164391753e53bc22cb6061f3f38750737c4, 0x001dc793, [0xa44fc3807683c62d5d5555e8aa4d2b44, 0x7c3e80d9ae28113e94515083d6b8dc8d, 0xdd480af63bdc08cfed799738ae66a22d, 0x93fc18cb1d06f66e6c2a2aa91a6fa115, 0xbaf9a212bab2668108be152a25367464, 0x070f40bac84ec65691be4dc8f908ea81, 0xff4debac8d0af1c429b24624458b05e8, 0x24ee7e51f903f104f23152a36805c19a, 0x3f1df40af557cdd307a01459438e498d, 0x235103ac552bce255c1924d919ab0ffc, 0x96b401b74ec606b40768277843360998, 0x54965ab49c73e02dba7fd8b8d9fa7782, 0x0b0c0f7ab1f512dcf23aff7167e9ce93, 0xf7f1ab707e107fdb6a1d869587820ade, 0xdaf50d94da06cafb4cd69618f9a92be2, 0x7941b70b1da0b40ab9c714eb64afbad6, 0x463bb9cdc84795dd505e501d7373a5a7, 0xe46639f105961658851c6d2e1f864342, 0xe476c4802a09741b516f8e0f2991b119, 0x90e3b1dc87fd98bf8eed5c95431a522c, 0x9a8abbbda0a5108ee8c89e64beb2a65a, 0xc1b406ba0b1a096e4b54aae38a383412, 0x5cecf32f496e25441ae6c4c379795bb8, 0xb57407dd43cbf5ef58985c6f2244c4c1, 0x9588e3c8c86d37c34565f512a7a6335c, 0x2d7392ec679187785a88943a1cd0536f, 0x71a0e247a73e2f7bcff9ea596b1cc65d, 0x30a1eb35ef8632314659845491d6f1b7, 0x03b9120ce345526ec62dc95277b4f62f, 0xd0abb7a59932e54364cd165cf6357173, 0x76eea875131e75efee3a6483cbc7eb01, 0xaa4f5eccbd6c9b3e4fca488827ef80ff, 0x806551f94c3f6f22ec2a7854c58e2cef, 0x115e9f4673fb5c49f9e3ec40cb350815, 0x5a969f0271a1754b6b42b0a5e81eff56, 0xbf42b3ece3b8562347d695c805621aef, 0x3450047704b3a475a291f64de5bc109b, 0xcda2a9249f048c92a45ae8fc01604ede, 0x050ff976de0e9cb7c922ea81121cb1f7, 0xcc281bf0ee1fbc285ae944574adcafb9, 0x61294fd7ce7f5664369a107c662eb6f6, 0x837a712d7fd8afd6b6332b8119ce047b, 0x161159375bb9ef5f5d4a3dc4800e7353, 0xf25ce3a4617036665c80c3fc48b270b1, 0x1222278e0c778d198ce8701739ab4553, 0x963b9040d0534c5499ddd2eb870a0859, 0xea7974b0a4ae4d01004f3b1493a7fdb9, 0x0b68a56fcb438eaca1b382cd51491a19, 0x972102b7db3808c02cd8f26c8ccca5e3, 0x648498884a4a1c1bb62e795cc442c787, 0xe45bd125c439ec8117394ae24d325d8d, 0x5371a402bbc07d6e1b2be98c97a303db, 0x3bac9cb935f8018b633b0cc3f79236c1, 0xb32965d37431e5c62d1cbdd83cfc4a1f, 0xe50f155f2abd5bc8edebf7f4a0f98b27, 0x32bb39dd0021750a9cee6fc3b0905a8c, 0x01f485dbec76dd1a210ea7b4bd223c0d, 0x590d984d2700c42b551d86b6dae6e53f, 0x5fb1e2d7778621643f58377ba0eb4ae6, 0x8803ed9680643f0457411393df9ed1d2, 0x51dd2c2e2e726eeb864c4fc63ccff9bc, 0xf3b6255cc7ef92b032001f4ac7ac4aab, 0x9911cb29fb1feeab343b8b2acf2ab55b, 0x670f33601869264f124f285f3e473525]), list![0xeac0ffbd3f7dc3b5cdd318eadb364901, 0xda39911f507ff7a7c4d3d0eee5bad94a, 0x1525599a8c0597c4c54347318ef2531b], 3))", + "OTHER_ROOT": "0x8381b64d16c743cfd794011835333b8c" +} \ No newline at end of file diff --git a/examples/pq_shrincs/txmanifest.json b/examples/pq_shrincs/txmanifest.json new file mode 100644 index 0000000..8bc9d4e --- /dev/null +++ b/examples/pq_shrincs/txmanifest.json @@ -0,0 +1,134 @@ +{ + "$schema": "https://raw.githubusercontent.com/stringhandler/txmanifest-wallet/main/schema/txmanifest.schema.json", + "manifest_version": "0.3.1", + "protocol": "pq-shrincs", + "description": "Coins locked to a SHRINCS post-quantum public key (a hash-based signature scheme), spendable only with a SHRINCS signature by that key over the spending transaction. The signature is made outside the wallet, by contrib/shrincs-signer, so a spend takes two runs: one prints the sighash, the other carries the signature.", + "chain": "bitcoin", + "requires": [ + "simplicity" + ], + "simplicity_hl": { + "version": "0.7.1", + "debug_symbols": false + }, + "utxo_types": { + "shrincs_key": { + "description": "Coins spendable only with a SHRINCS signature by PK over the spending transaction's sig_all_hash.", + "params": { + "PK": { + "type": "bytes32", + "description": "SHRINCS public key, PK.seed || PK.root, as `shrincs-signer pubkey` prints it." + } + }, + "script": { + "type": "simplicity", + "source": "./shrincs_spend.simf", + "source_hash": "sha256:77247a9a93b6d0172fa608596ae82a8af0a0ce9e2d81737a4fa7e5e1b795ff6f" + } + } + }, + "actions": { + "Lock": { + "description": "Send coins to a SHRINCS public key.", + "intent": "Lock {params.amount_sat} sat to a SHRINCS post-quantum key", + "allow_change": "lbtc_only", + "params": { + "PK": { + "type": "bytes32", + "description": "SHRINCS public key to lock to, from `shrincs-signer pubkey`." + }, + "amount_sat": { + "type": "u64", + "description": "Amount to lock.", + "default": "20000" + } + }, + "inputs": [ + { + "id": "funding", + "utxo_source": "wallet", + "asset": "lbtc", + "amount_sat": { + "min_amount": "params.amount_sat" + }, + "ui": { + "label": "coin being locked", + "role": "funding" + } + } + ], + "outputs": [ + { + "id": "vault", + "amount_sat": "params.amount_sat", + "asset": "lbtc", + "destination": { + "utxo_type": "shrincs_key", + "args": { + "PK": "params.PK" + } + }, + "ui": { + "label": "coins locked to the SHRINCS key", + "role": "covenant" + } + } + ] + }, + "Spend": { + "description": "Spend a SHRINCS-locked coin with a signature made outside the wallet.", + "intent": "Spend a SHRINCS-locked coin to {params.dest}", + "$comment": "Run twice with the same state file and params. First with --print-sighash and placeholder SIG / OTHER_ROOT: it prints the sighash and stops. Then with the values `shrincs-signer sign` prints for that sighash. The destination is a fixed address and the fee comes from a fixed fee_rate, so the second run builds the same transaction and the signature verifies.", + "params": { + "PK": { + "type": "bytes32", + "description": "The SHRINCS public key the coin is locked to." + }, + "SIG": { + "type": "string", + "description": "The SHRINCS signature, as `shrincs-signer sign` prints it. Any placeholder for the --print-sighash run." + }, + "OTHER_ROOT": { + "type": "string", + "description": "The stateless half's root, as `shrincs-signer sign` prints it. Any placeholder for the --print-sighash run." + } + }, + "inputs": [ + { + "id": "vault_in", + "utxo_source": { + "utxo_type": "shrincs_key", + "args": { + "PK": "params.PK" + } + }, + "witnesses": { + "SIG": { + "type": "simplicityhl", + "value": "params.SIG" + }, + "OTHER_ROOT": { + "type": "simplicityhl", + "value": "params.OTHER_ROOT" + } + }, + "ui": { + "label": "SHRINCS-locked coin", + "role": "covenant" + } + } + ], + "outputs": [ + { + "id": "paid", + "amount_sat": "vault_in.amount_sat - fee", + "destination": "wallet", + "ui": { + "label": "payment, less the network fee", + "role": "payment" + } + } + ] + } + } +} diff --git a/txmanifest_lib/src/covenant.rs b/txmanifest_lib/src/covenant.rs index e033bf7..20f5597 100644 --- a/txmanifest_lib/src/covenant.rs +++ b/txmanifest_lib/src/covenant.rs @@ -1490,68 +1490,22 @@ pub fn finalize_bitcoin_covenant_input( input_index: u32, opts: impl Into, ) -> Result>> { - use lwk_wollet::elements::bitcoin as btc; - use simplicityhl::simplicity::jet::bitcoin::BitcoinEnv; - - let opts = opts.into(); - if opts.family != ChainFamily::Bitcoin { - anyhow::bail!( - "compile options target chain '{}' but a Bitcoin witness was requested", - opts.family - ); - } - - let source = program.text().to_owned(); - let args_json = build_args_json(compile_params, type_hints)?; - let arguments: Arguments = serde_json::from_str(&args_json) - .with_context(|| format!("Failed to parse Arguments from JSON:\n{args_json}"))?; - let compiled = compile_program(source, arguments, &opts)?; - let abi_meta = compiled - .generate_abi_meta() - .map_err(|e| anyhow::anyhow!("Cannot read program ABI: {e}"))?; - - let commit = compiled.commit(); - let script_cmr = commit.cmr(); - - // The taproot tree, folded exactly as the address derivation folds it โ€” the control - // block has to reproduce the merkle root the output committed to, or the spend fails - // with nothing to point at. - let merkle_root = covenant_merkle_root( + let PreparedBitcoinInput { + compiled, + witness_types, + script_cmr, + control_block, + env, + } = prepare_bitcoin_covenant_input( program, compile_params, type_hints, extra_leaf_payloads, - &opts, - )?; - let mut sibling_hashes: Vec = Vec::new(); - for payload in extra_leaf_payloads { - sibling_hashes.push(btc::taproot::TapNodeHash::from_byte_array(tapdata_hash( - payload, - ))); - } - - use btc::key::TapTweak as _; - let secp = btc::secp256k1::Secp256k1::new(); - let nums = btc::XOnlyPublicKey::from_slice(&NUMS_KEY_BYTES).context("Invalid NUMS key")?; - let root = btc::taproot::TapNodeHash::from_byte_array(merkle_root); - let (_, parity) = nums.tap_tweak(&secp, Some(root)); - - let control_block = btc::taproot::ControlBlock { - leaf_version: btc::taproot::LeafVersion::from_consensus(SIMPLICITY_LEAF_VERSION) - .expect("simplicity leaf version"), - output_key_parity: parity, - internal_key: nums, - merkle_branch: btc::taproot::TaprootMerkleBranch::try_from(sibling_hashes) - .map_err(|e| anyhow::anyhow!("taproot merkle branch is too long: {e}"))?, - }; - - let env = BitcoinEnv::new( - tx.clone(), + tx, witness_utxos, input_index, - script_cmr, - control_block.clone(), - ); + &opts.into(), + )?; // Signature witnesses are computed against the environment's own sighash, exactly as // on Elements โ€” the program will recompute it with `jet::sig_all_hash`, and a @@ -1567,9 +1521,8 @@ pub fn finalize_bitcoin_covenant_input( witnesses }; - let witness_values = - build_witness_values_from_types(effective_witnesses, &abi_meta.witness_types) - .context("Cannot build witness values")?; + let witness_values = build_witness_values_from_types(effective_witnesses, &witness_types) + .context("Cannot build witness values")?; let satisfied = compiled .satisfy(witness_values) @@ -1611,6 +1564,132 @@ pub fn finalize_bitcoin_covenant_input( Ok(stack) } +/// The `sig_all_hash` a Bitcoin covenant input's program will read, for the transaction +/// as built so far. +/// +/// For a witness signed outside the wallet: the signer signs this, and a transaction +/// rebuilt identically can carry the signature. The environment is the same one +/// [`finalize_bitcoin_covenant_input`] satisfies against, so the hash is by construction +/// the one `jet::sig_all_hash` returns there. No witness is needed to compute it, because +/// the sighash commits to the transaction and the leaf, not to any input's witness. +#[allow(clippy::too_many_arguments)] +pub fn bitcoin_covenant_sighash( + program: &ProgramSource, + compile_params: &HashMap, + type_hints: &HashMap, + extra_leaf_payloads: &[Vec], + tx: &lwk_wollet::elements::bitcoin::Transaction, + witness_utxos: &[lwk_wollet::elements::bitcoin::TxOut], + input_index: u32, + opts: impl Into, +) -> Result<[u8; 32]> { + let prepared = prepare_bitcoin_covenant_input( + program, + compile_params, + type_hints, + extra_leaf_payloads, + tx, + witness_utxos, + input_index, + &opts.into(), + )?; + Ok(prepared.env.c_tx_env().sighash_all().to_byte_array()) +} + +/// A Bitcoin covenant input up to the point its witness is needed: the compiled program, +/// the leaf it commits to, and the environment it runs in. +struct PreparedBitcoinInput { + compiled: CompiledProgram, + witness_types: WitnessTypes, + script_cmr: simplicity::Cmr, + control_block: lwk_wollet::elements::bitcoin::taproot::ControlBlock, + env: simplicityhl::simplicity::jet::bitcoin::BitcoinEnv< + lwk_wollet::elements::bitcoin::Transaction, + >, +} + +#[allow(clippy::too_many_arguments)] +fn prepare_bitcoin_covenant_input( + program: &ProgramSource, + compile_params: &HashMap, + type_hints: &HashMap, + extra_leaf_payloads: &[Vec], + tx: &lwk_wollet::elements::bitcoin::Transaction, + witness_utxos: &[lwk_wollet::elements::bitcoin::TxOut], + input_index: u32, + opts: &CompileOpts, +) -> Result { + use lwk_wollet::elements::bitcoin as btc; + use simplicityhl::simplicity::jet::bitcoin::BitcoinEnv; + + if opts.family != ChainFamily::Bitcoin { + anyhow::bail!( + "compile options target chain '{}' but a Bitcoin witness was requested", + opts.family + ); + } + + let source = program.text().to_owned(); + let args_json = build_args_json(compile_params, type_hints)?; + let arguments: Arguments = serde_json::from_str(&args_json) + .with_context(|| format!("Failed to parse Arguments from JSON:\n{args_json}"))?; + let compiled = compile_program(source, arguments, opts)?; + let abi_meta = compiled + .generate_abi_meta() + .map_err(|e| anyhow::anyhow!("Cannot read program ABI: {e}"))?; + + let commit = compiled.commit(); + let script_cmr = commit.cmr(); + + // The taproot tree, folded exactly as the address derivation folds it โ€” the control + // block has to reproduce the merkle root the output committed to, or the spend fails + // with nothing to point at. + let merkle_root = covenant_merkle_root( + program, + compile_params, + type_hints, + extra_leaf_payloads, + opts, + )?; + let mut sibling_hashes: Vec = Vec::new(); + for payload in extra_leaf_payloads { + sibling_hashes.push(btc::taproot::TapNodeHash::from_byte_array(tapdata_hash( + payload, + ))); + } + + use btc::key::TapTweak as _; + let secp = btc::secp256k1::Secp256k1::new(); + let nums = btc::XOnlyPublicKey::from_slice(&NUMS_KEY_BYTES).context("Invalid NUMS key")?; + let root = btc::taproot::TapNodeHash::from_byte_array(merkle_root); + let (_, parity) = nums.tap_tweak(&secp, Some(root)); + + let control_block = btc::taproot::ControlBlock { + leaf_version: btc::taproot::LeafVersion::from_consensus(SIMPLICITY_LEAF_VERSION) + .expect("simplicity leaf version"), + output_key_parity: parity, + internal_key: nums, + merkle_branch: btc::taproot::TaprootMerkleBranch::try_from(sibling_hashes) + .map_err(|e| anyhow::anyhow!("taproot merkle branch is too long: {e}"))?, + }; + + let env = BitcoinEnv::new( + tx.clone(), + witness_utxos, + input_index, + script_cmr, + control_block.clone(), + ); + + Ok(PreparedBitcoinInput { + compiled, + witness_types: abi_meta.witness_types, + script_cmr, + control_block, + env, + }) +} + /// The length of the padding item a Bitcoin Simplicity spend needs, or `None` when /// `stack` already buys the budget `cost` needs. /// diff --git a/txmanifest_lib/src/lifecycle.rs b/txmanifest_lib/src/lifecycle.rs index a2cba50..b9dd348 100644 --- a/txmanifest_lib/src/lifecycle.rs +++ b/txmanifest_lib/src/lifecycle.rs @@ -294,6 +294,9 @@ pub fn run( manual_inputs: bool, // If set, write signed PSET + finalized tx to this path as JSON instead of broadcasting. export_pset_path: Option<&Path>, + // Bitcoin only: build the transaction, print each covenant input's `sig_all_hash`, and + // stop before satisfying or broadcasting anything. See `run_bitcoin_build`. + print_sighash: bool, // If true, run the jet debugger on every covenant dry-run and print each jet's I/O. debug_jets: bool, // Whether a manifest whose programs are not all pinned may run. Only the CLI's @@ -308,6 +311,13 @@ pub fn run( let manifest: Manifest = Manifest::from_json_str(&raw) .with_context(|| format!("Failed to parse manifest file: {}", manifest_file.display()))?; + + if print_sighash && target.network.family() != crate::chain::ChainFamily::Bitcoin { + anyhow::bail!( + "--print-sighash is only supported on Bitcoin; this run targets {}", + target.network + ); + } // Refuse before anything is derived, signed or broadcast if the target cannot run // this manifest. `validate` cannot do this: it is offline and has no idea which node // the wallet points at, and Simplicity on Bitcoin is a property of the node. @@ -2261,6 +2271,7 @@ pub fn run( &req, bitcoin_run, export_pset_path, + print_sighash, &covenant_specs, ¶ms_snap, &action_params_snap, @@ -4506,6 +4517,7 @@ pub fn run_headless( data_dir, false, // manual_inputs Some(&export_path), + false, // print_sighash false, // debug_jets crate::programs::Unpinned::Refuse, ); @@ -4607,6 +4619,7 @@ fn run_bitcoin_build( req: &pset_builder::BuildPsetRequest, run: &crate::session::BitcoinRun, export_path: Option<&Path>, + print_sighash: bool, covenant_specs: &std::collections::HashMap, compile_params: &std::collections::HashMap, action_params: &std::collections::HashMap, @@ -4674,13 +4687,43 @@ fn run_bitcoin_build( ); } + let prevouts: Vec = + psbt_req.inputs.iter().map(|i| i.witness_utxo()).collect(); + let unsigned = built.psbt.unsigned_tx.clone(); + + // `--print-sighash`: the first of two runs, for a covenant witness signed outside the + // wallet. There is no witness yet, so stop before satisfying anything, and print what + // each program will read from `jet::sig_all_hash`. A second run that rebuilds this + // exact transaction (same inputs, outputs and fee) can then carry the signature. + if print_sighash { + if covenant_inputs.is_empty() { + anyhow::bail!("--print-sighash: this transaction spends no covenant input"); + } + println!(); + println!(" sig_all_hash of each covenant input (nothing was broadcast):"); + for (index, input_id) in &covenant_inputs { + let spec = covenant_specs.get(input_id).ok_or_else(|| { + anyhow::anyhow!("covenant input '{input_id}' has no resolved program") + })?; + let hash = covenant::bitcoin_covenant_sighash( + &spec.program, + &spec.params, + &spec.hints, + &spec.leaf_payloads, + &unsigned, + &prevouts, + *index as u32, + compile_opts, + ) + .with_context(|| format!("cannot compute the sighash of '{input_id}'"))?; + println!(" {input_id} {}", hex_bytes(&hash)); + } + return Ok(None); + } + // Covenant inputs, once every other input's witness is settled: the Simplicity program // reads the whole transaction, and `sig_all_hash` commits to it. if !covenant_inputs.is_empty() { - let prevouts: Vec = - psbt_req.inputs.iter().map(|i| i.witness_utxo()).collect(); - let unsigned = built.psbt.unsigned_tx.clone(); - for (index, input_id) in &covenant_inputs { let spec = covenant_specs.get(input_id).ok_or_else(|| { anyhow::anyhow!("covenant input '{input_id}' has no resolved program") diff --git a/txmanifest_lib/tests/bitcoin_sighash.rs b/txmanifest_lib/tests/bitcoin_sighash.rs new file mode 100644 index 0000000..c851087 --- /dev/null +++ b/txmanifest_lib/tests/bitcoin_sighash.rs @@ -0,0 +1,113 @@ +//! `bitcoin_covenant_sighash` returns exactly the hash `jet::sig_all_hash` gives the +//! program, which is what `run --print-sighash` relies on. +//! +//! The check is end to end, through `examples/bitcoin_covenant/p2pk.simf`, a BIP340 +//! checksig over `sig_all_hash`. Sign the computed hash outside the wallet, pass the +//! signature in as a plain literal witness rather than a `Signature` the engine computes, +//! and the program has to accept it. + +use std::collections::HashMap; +use std::path::Path; + +use lwk_wollet::elements::bitcoin::{ + absolute::LockTime, + secp256k1::{Keypair, Message, Secp256k1, SecretKey}, + transaction::Version, + Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, +}; +use tx_manifest_lib::chain::ChainFamily; +use tx_manifest_lib::covenant::{ + bitcoin_covenant_sighash, finalize_bitcoin_covenant_input, CompileOpts, ProgramSource, +}; + +fn hex(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{b:02x}")).collect() +} + +fn bitcoin() -> CompileOpts { + CompileOpts { + family: ChainFamily::Bitcoin, + ..CompileOpts::default() + } +} + +/// A one-in, one-out spend paying `value` sat. +fn spend(value: u64) -> (Transaction, Vec) { + let tx = Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint { + txid: format!("{:064x}", 7).parse().expect("txid"), + vout: 1, + }, + script_sig: ScriptBuf::new(), + sequence: Sequence::MAX, + witness: Witness::new(), + }], + output: vec![TxOut { + value: Amount::from_sat(value), + script_pubkey: ScriptBuf::from_bytes([&[0x51, 0x20][..], &[0x42; 32]].concat()), + }], + }; + let prevout = TxOut { + value: Amount::from_sat(100_000), + script_pubkey: ScriptBuf::from_bytes([&[0x51, 0x20][..], &[0x07; 32]].concat()), + }; + (tx, vec![prevout]) +} + +#[test] +fn a_signature_over_the_computed_sighash_satisfies_the_program() { + let secp = Secp256k1::new(); + let keypair = Keypair::from_secret_key( + &secp, + &SecretKey::from_slice(&[0x11; 32]).expect("secret key"), + ); + let (pubkey, _) = keypair.x_only_public_key(); + + let program = ProgramSource::read_unpinned( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/bitcoin_covenant/p2pk.simf"), + ) + .expect("read p2pk.simf"); + let params = HashMap::from([("PUB_KEY".to_string(), hex(&pubkey.serialize()))]); + let hints = HashMap::from([("PUB_KEY".to_string(), "pubkey".to_string())]); + + let finalize = |tx: &Transaction, prevouts: &[TxOut], sig: &str| { + let witnesses = serde_json::json!({ + "SIGNATURE": { "type": "simplicityhl", "value": format!("0x{sig}") } + }); + finalize_bitcoin_covenant_input( + &program, + ¶ms, + &hints, + &[], + Some(&witnesses), + None, + tx, + prevouts, + 0, + bitcoin(), + ) + }; + + // The first run: compute the hash and sign it, as an outside signer would. + let (tx, prevouts) = spend(90_000); + let hash = + bitcoin_covenant_sighash(&program, ¶ms, &hints, &[], &tx, &prevouts, 0, bitcoin()) + .expect("compute the sighash"); + let sig = secp.sign_schnorr_no_aux_rand(&Message::from_digest(hash), &keypair); + let sig = hex(sig.as_ref()); + + // The second run rebuilds the same transaction, so the signature verifies. + finalize(&tx, &prevouts, &sig) + .unwrap_or_else(|e| panic!("a signature over the computed hash should verify: {e:#}")); + + // A transaction that differs by one sat has another hash, so the same signature fails. + // This is why the second run has to rebuild the first run's transaction exactly. + let (other, prevouts) = spend(89_999); + assert!( + finalize(&other, &prevouts, &sig).is_err(), + "the signature must be bound to the transaction it was made for" + ); +} diff --git a/txmanifest_lib/tests/fixtures/shrincs/spend_q1.json b/txmanifest_lib/tests/fixtures/shrincs/spend_q1.json new file mode 100644 index 0000000..e87cd1d --- /dev/null +++ b/txmanifest_lib/tests/fixtures/shrincs/spend_q1.json @@ -0,0 +1,6 @@ +{ + "$comment": "A stateful SHRINCS signature (q = 1) by the key in test_seed.hex over msg, the sig_all_hash of the test transaction in tests/shrincs_verifier.rs. Made by contrib/shrincs-signer; see signed_spend_of_a_shrincs_locked_coin for how to regenerate it. A test key: never lock real coins to it.", + "msg": "2f69a277719598ef926559b5d234fd629a72fe43f0ae8f639d2fdc76b3f32c1d", + "SIG": "Left(((0x8c50e7430ff5eaf7499b26bbdb8648af9ef5b7066b4e831540fabe345a1130c9, 0x00117b12, [0x27ccf4d4b1ff5022cecb74d4d3b64bce, 0x2cf3a6c5bf85a5d94624e1a937824e9c, 0x8ce28b78e8793d9d555918a9f5b707bb, 0xc4840a4c92081025d8c68c25b485c943, 0x314b56d79778a7f52a8b2979be81693e, 0xbb6dade36bd751797f21fef43d509a8e, 0x3055a9bf6dfdf53b0327455f4e771159, 0xe383d3b51de5dd436034556c3d97a437, 0x6787c7f6f06ce7758ae75279c09f5fe5, 0x713fe23e009589ac40eb50241084a583, 0x2ce4436b607b851401fade7434db40ee, 0x64337b60bd823196681e694eb86fefcd, 0x5982c81de01c8b64dc2da0d0631884d6, 0xbc46c8c50dc72daf100a149af9bd5082, 0x47d8ec1a78e75b3ed4f54a5f8f67e386, 0xaa14fa98aa0f25faf818c1802b60e3d3, 0x9624fa4511b9a9bf46e20879c33ee512, 0x008e2581716399db6a223364f88c055a, 0x5b579deb16e77baa443b52d8e4a8e45f, 0x21b790ea1f5b4a63ea6a281476170ce7, 0x7bee7b15a6344e095062448f8287f6c7, 0xab943054494e681e9f03ac959a8672c4, 0x3981e5947066c84fee92fc5737da4952, 0xe1eadee9d74c869405204fd0379a6af6, 0x158cf7098c5456e984955ac45e84d9fc, 0xca53946a99da9842f10aa944890f0527, 0x0d20d7ea4ff7b5424658c7ac7bc250c3, 0x6ca243f550feebda1b47a681563b574d, 0xb3d483db741eb39b2411c362ea1f593a, 0x553c0042512f7c2ab28e05fe8843f155, 0xdef9d49c07baa341e6712b364f39d986, 0xf6ea27475120e2d2897a448706af1a01, 0x6998f7b1e8647c348c5bdece99de65ab, 0x8ad77db31d6daf568f65e3d3c3e39f32, 0x8299ed269389b03b95d53bfbb2b707b3, 0x8643cd2201610943f508711bfae33c8c, 0x33d7adbae6bcf257d22969a36235c89b, 0x2e1e2dc358bb5161f9803b5936817e52, 0x9b89313ce22d5718423154f4f46a6c40, 0x60f5d8bd440815677322ade2a3297d8d, 0x3f6d58fde808f87d0ae9e857267ce4cd, 0xce5a0d7e2d372114a36f31f5e37ef673, 0x64f660ea87d117d498d3a2e02e008d05, 0x36770052fce94e1335bb7a5a60b4dbf6, 0x394e98eb636f4862e027fb1625e775c9, 0x29c9f7797ff1763b06f521e917284f1d, 0x71d5104e4c439f8d243fb50a6acef2a6, 0xfec27cff3aaf964201afb0fcff733aa2, 0xcceec7cb1e02b888dab91daec4d2dd9c, 0x5035a4c0d63e8814cc8050fae542ccf6, 0x667c18b86b87a64d3b82aa14d4cd3ac4, 0xaa580beb67bd1a49e58658ae2e12e3e3, 0xe048aa321da0cb7e3f2732c65ddd0e2f, 0x3ef94f2cddb7c81e85619afb4c7cdfa3, 0xa76549eeccb58d8cba0e015be66d9925, 0xa121f6f07727c7af946707df471774f1, 0x86fab57d1b5acde85290b30eb9ec9182, 0xc0a391eaf9d979c4b3cdc57dffe31492, 0x45a1166265f3217d5a78651be6b0a1b9, 0xa85006e3bffb4e9c85a340935d40ab0f, 0xe4312bd9bd794f69aca3adb5ee0722cb, 0xc0ac1012f8fb4e1915e2a7c785107170, 0x078c9c6619b8dea526b268b9f049bbf7, 0xaa2d1ae3dbfc4c0fd7c694af71a1af2f]), list![0x5c405d8025ad58cf1b45c8039bedecc1], 1))", + "OTHER_ROOT": "0xaba5862f418ee903efccc95cab2ad388" +} diff --git a/txmanifest_lib/tests/fixtures/shrincs/test_seed.hex b/txmanifest_lib/tests/fixtures/shrincs/test_seed.hex new file mode 100644 index 0000000..ca81ffe --- /dev/null +++ b/txmanifest_lib/tests/fixtures/shrincs/test_seed.hex @@ -0,0 +1 @@ +000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f diff --git a/txmanifest_lib/tests/shrincs_verifier.rs b/txmanifest_lib/tests/shrincs_verifier.rs index 260ebcc..d2323bf 100644 --- a/txmanifest_lib/tests/shrincs_verifier.rs +++ b/txmanifest_lib/tests/shrincs_verifier.rs @@ -22,8 +22,8 @@ use lwk_wollet::elements::bitcoin::{ use simplicityhl::simplicity::{jet::Bitcoin, BitIter, Cost, RedeemNode}; use tx_manifest_lib::chain::ChainFamily; use tx_manifest_lib::covenant::{ - compute_tapleaf_hash, finalize_bitcoin_covenant_input, program_witness_types, CompileOpts, - ProgramSource, + bitcoin_covenant_sighash, compute_tapleaf_hash, finalize_bitcoin_covenant_input, + program_witness_types, CompileOpts, ProgramSource, }; fn verifier_dir() -> PathBuf { @@ -264,3 +264,92 @@ fn wrapper_commits_to_the_key() { let other = "0x0000000000000000000000000000000000000000000000000000000000000001"; assert_ne!(leaf(UPSTREAM_PK), leaf(other)); } + +// โ”€โ”€ T2 + T3: a real SHRINCS-locked spend โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +fn fixture(name: &str) -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/shrincs") + .join(name) +} + +fn hex(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{b:02x}")).collect() +} + +/// The public key of `fixtures/shrincs/test_seed.hex`, as +/// `contrib/shrincs-signer/bin/shrincs-signer pubkey` prints it. +const TEST_PK: &str = "0x202122232425262728292a2b2c2d2e2fdacfe7d7b4009ca34156124d54de2e72"; + +/// A coin locked by the wrapper to the test key, spent with a stateful signature (q = 1) +/// from `contrib/shrincs-signer` over the test transaction's `sig_all_hash`. +/// +/// The signature is a fixture, so the test needs no C++ toolchain. To regenerate it, take +/// the sighash this test reports and run, with a fresh state file at q = 0: +/// +/// ```text +/// printf '0\n' > /tmp/q.txt +/// contrib/shrincs-signer/bin/shrincs-signer sign \ +/// --seed txmanifest_lib/tests/fixtures/shrincs/test_seed.hex --state /tmp/q.txt --msg +/// ``` +/// +/// then put `msg`, `SIG` and `OTHER_ROOT` in `fixtures/shrincs/spend_q1.json`. +#[test] +fn signed_spend_of_a_shrincs_locked_coin() { + let text = std::fs::read_to_string(fixture("spend_q1.json")).expect("read the fixture"); + let signed: serde_json::Value = serde_json::from_str(&text).expect("fixture is JSON"); + let params = HashMap::from([("PK".to_string(), TEST_PK.to_string())]); + let hints = HashMap::from([("PK".to_string(), "bytes32".to_string())]); + let witnesses = serde_json::json!({ + "SIG": { "type": "simplicityhl", "value": signed["SIG"] }, + "OTHER_ROOT": { "type": "simplicityhl", "value": signed["OTHER_ROOT"] }, + }); + let finalize_wrapper = |tx: &Transaction, prevouts: &[TxOut]| { + finalize_bitcoin_covenant_input( + &wrapper(), + ¶ms, + &hints, + &[], + Some(&witnesses), + None, + tx, + prevouts, + 0, + bitcoin(), + ) + }; + + let (tx, prevouts) = spend(); + let hash = bitcoin_covenant_sighash( + &wrapper(), + ¶ms, + &hints, + &[], + &tx, + &prevouts, + 0, + bitcoin(), + ) + .expect("compute the sighash"); + assert_eq!( + signed["msg"].as_str(), + Some(hex(&hash).as_str()), + "the fixture signs another message; regenerate it (see this test's doc comment)" + ); + + let stack = finalize_wrapper(&tx, &prevouts) + .unwrap_or_else(|e| panic!("the signed spend should verify: {e:#}")); + eprintln!("signed spend: {}", describe(&stack)); + match node_check(&stack) { + Ok(cost) => eprintln!("signed spend: the node would accept it; cost bound {cost:?}"), + Err(why) => panic!("the node would reject the signed spend: {why}"), + } + + // The signature covers this transaction: moved to one that pays a sat less, it fails. + let mut other = tx.clone(); + other.output[0].value = Amount::from_sat(other.output[0].value.to_sat() - 1); + assert!( + finalize_wrapper(&other, &prevouts).is_err(), + "a signature over one transaction must not spend another" + ); +} diff --git a/txmanifest_wallet/src/main.rs b/txmanifest_wallet/src/main.rs index 9cb7db3..c4da02e 100644 --- a/txmanifest_wallet/src/main.rs +++ b/txmanifest_wallet/src/main.rs @@ -122,6 +122,13 @@ enum Commands { /// Useful for offline inspection with e.g. elements-cli or a PSET decoder. #[arg(long)] export_pset: Option, + /// Bitcoin only. Build the transaction, print the `sig_all_hash` each covenant + /// input's program will check, and stop: no witness is needed and nothing is + /// broadcast. For a witness signed outside the wallet: sign the hash, then run + /// again with the same inputs (`--input`), params and fee rate, so the second run + /// builds the same transaction and the signature verifies. + #[arg(long, conflicts_with = "export_pset")] + print_sighash: bool, /// Print every Simplicity jet call (name, inputs, outputs) during covenant dry-runs. /// Equality jets show lhs vs rhs so mismatches are immediately visible. #[arg(long)] @@ -1053,6 +1060,7 @@ fn main() -> Result<()> { inputs_file, manual_inputs, export_pset, + print_sighash, debug_jets, allow_unpinned, debug, @@ -1089,6 +1097,7 @@ fn main() -> Result<()> { &data_dir, manual_inputs, export_pset.as_deref(), + print_sighash, debug_jets, if allow_unpinned { tx_manifest_lib::programs::Unpinned::Allow From 702a975e932cb0a937628e35792aff620bdaa09e Mon Sep 17 00:00:00 2001 From: stringhandler Date: Fri, 9 Oct 2026 17:17:16 +0200 Subject: [PATCH 4/4] fix: size Bitcoin covenant fee estimates from the program The fee estimate assumed about 1 KB of witness for every covenant input. A SHRINCS spend carries about 19 KB, so its transaction fell below the relay fee unless the manifest asked for a fee_rate of about 15. Each covenant input's witness is now estimated from its program: the stack for every branch, with the widest value of each witness type, and the padding its costliest branch needs. It uses the program alone and never the witness values, so both runs of a --print-sighash spend arrive at the same fee. A program that branches on a witness is estimated for its heaviest branch, so the estimate errs high. The pq_shrincs examples now spend at fee_rate 1. Bitcoin runs print the txid when the transaction is built, so the two runs of a two-run spend can be compared. The node's witness-stack rules move from tests/shrincs_verifier.rs into covenant::bitcoin_node_check. shrincs-signer gains sign-raw, for test keys only: it signs at a given leaf index with no state file and prints the raw signature split into its fields, for the pq-contest runner. --- CHANGELOG.md | 3 +- contrib/shrincs-signer/README.md | 4 + contrib/shrincs-signer/shrincs-signer.cpp | 51 ++++++- examples/pq_shrincs/README.md | 18 ++- examples/pq_shrincs/spend.params.json | 2 +- examples/pq_shrincs_smoke/README.md | 7 +- examples/pq_shrincs_smoke/spend.params.json | 2 +- examples/pq_shrincs_smoke/txmanifest.json | 3 +- txmanifest_lib/src/bitcoin_review.rs | 1 + txmanifest_lib/src/covenant.rs | 155 +++++++++++++++++++ txmanifest_lib/src/lifecycle.rs | 159 +++++++++++++------- txmanifest_lib/src/psbt_builder.rs | 11 +- txmanifest_lib/src/pset_builder.rs | 6 + txmanifest_lib/tests/bitcoin_path.rs | 1 + txmanifest_lib/tests/bitcoin_sighash.rs | 11 +- txmanifest_lib/tests/shrincs_verifier.rs | 77 +++------- 16 files changed, 374 insertions(+), 137 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 968aad9..d2296b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and - `pin` command: writes every program's current hash into a manifest without reformatting it; `pin --check` fails if any is missing or stale. - `validate --strict`: unpinned programs are errors rather than warnings. - `run --debug`: development mode. For now the same as `--allow-unpinned`; it may enable more development-only behaviour later. -- `run --print-sighash` (Bitcoin): build the transaction, print the `sig_all_hash` each covenant input's program will check, and stop without broadcasting. For a covenant witness signed outside the wallet: sign the hash, then run again with the same `--input`, params and `fee_rate`, so the second run builds the same transaction and the signature verifies. +- `run --print-sighash` (Bitcoin): build the transaction, print the `sig_all_hash` each covenant input's program will check, and stop without broadcasting. Bitcoin runs now print the transaction's txid when it is built, so the two runs can be compared. For a covenant witness signed outside the wallet: sign the hash, then run again with the same `--input`, params and `fee_rate`, so the second run builds the same transaction and the signature verifies. ### Changed @@ -27,6 +27,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and ### Fixed +- Bitcoin fee estimates size each covenant input's witness from its program instead of assuming about 1 KB, so a large Simplicity witness no longer leaves the transaction below the relay fee. The estimate depends only on the program, so it errs high for programs that branch on a witness: it is sized for the costliest branch. - Bitcoin covenant spends whose program needs cost padding are now accepted by the node. The padding item came last, where the node expects the control block, and it was sized for every branch of the program rather than the one the spend takes, so the node rejected it as more padding than the program needs. Programs cheap enough to need no padding, such as `examples/bitcoin_covenant`, were not affected. ## [0.3.0] - 2026-10-06 diff --git a/contrib/shrincs-signer/README.md b/contrib/shrincs-signer/README.md index e2505e4..5141fd3 100644 --- a/contrib/shrincs-signer/README.md +++ b/contrib/shrincs-signer/README.md @@ -17,6 +17,10 @@ bin/shrincs-signer sign --seed key.seed --state key.q --msg <32-byte hex> - `new` writes a random 48-byte seed (`SK.seed โ€– SK.prf โ€– PK.seed`, as hex) and a state file at q = 0, both mode 0600. It refuses to overwrite either. - `pubkey` prints `PK = PK.seed โ€– PK.root`, the manifest's `bytes32` param. +- `sign-raw --seed FILE --q N --msg HEX` is for **test keys only**. It signs at leaf + index N with no state file, so it reuses indexes freely. It prints the raw signature + as JSON, split into the fields a contest converter receives (`q`, `msg`, `pk`, `raw`, + `sl_root`, `r`, `counter`, `chains`, `path`). The pq-contest runner uses it. - `sign` makes a stateful signature with the next leaf index. It prints `{"SIG": โ€ฆ, "OTHER_ROOT": โ€ฆ}` as SimplicityHL values in the layout `examples/pq_shrincs/shrincs_spend.simf` reads. It also checks the signature with diff --git a/contrib/shrincs-signer/shrincs-signer.cpp b/contrib/shrincs-signer/shrincs-signer.cpp index ac5bb0b..b33ed3c 100644 --- a/contrib/shrincs-signer/shrincs-signer.cpp +++ b/contrib/shrincs-signer/shrincs-signer.cpp @@ -6,6 +6,10 @@ // shrincs-signer pubkey --seed FILE print PK (PK.seed || PK.root) // shrincs-signer sign --seed FILE --state FILE --msg HEX // print {"SIG": ..., "OTHER_ROOT": ...} +// shrincs-signer sign-raw --seed FILE --q N --msg HEX +// TEST KEYS ONLY: sign at leaf index N, +// keeping no state, and print the raw +// signature split into its fields // // A thin driver around shrincs-cpp (see the Makefile for the commit); the cryptography is // all upstream's. Phase 0 only: the plan is for signers to run inside the wallet. @@ -172,15 +176,60 @@ static int cmd_sign(int argc, char** argv) { return 0; } +// For test harnesses such as the contest runner, which sign many leaf indexes of +// published test keys. It keeps no state, so it reuses indexes freely, which is exactly +// what makes it unsafe for any key that guards coins. +static int cmd_sign_raw(int argc, char** argv) { + auto msg = unhex(flag(argc, argv, "--msg"), 32, "--msg"); + char* end = nullptr; + std::string q_text = flag(argc, argv, "--q"); + unsigned long q = strtoul(q_text.c_str(), &end, 10); + if (end == q_text.c_str() || q < 1 || q > HSF + 1) + die("--q must be a leaf index from 1 to " + std::to_string(HSF + 1)); + + PublicKey pk; + SecretKey sk; + State state; + restore(flag(argc, argv, "--seed"), pk, sk, state); + state.q = (uint32_t)q - 1; + state.valid = true; + unsigned char* sig = shrincs_sign_stateful(msg.data(), sk, state); + uint32_t path_nodes = q > HSF ? HSF : (uint32_t)q; + uint32_t sig_len = N + WOTS_SIGN_LEN + path_nodes * N; + if (!shrincs_verify_stateful(msg.data(), sig, sig_len, pk)) die("the signature does not verify"); + + const unsigned char* p = sig; + std::string sl_root = hex(p, N); + p += N; + std::string r = hex(p, R_LEN); + p += R_LEN; + std::string counter = hex(p, 4); + p += 4; + std::string chains, path; + for (uint32_t i = 0; i < L; i++, p += N) chains += std::string(i ? ", " : "") + "\"" + hex(p, N) + "\""; + for (uint32_t i = 0; i < path_nodes; i++, p += N) path += std::string(i ? ", " : "") + "\"" + hex(p, N) + "\""; + + printf("{\"kind\": \"stateful\", \"q\": %lu, \"msg\": \"%s\", \"pk\": \"%s%s\", " + "\"pk_seed\": \"%s\", \"pk_root\": \"%s\", \"raw\": \"%s\", \"sl_root\": \"%s\", " + "\"r\": \"%s\", \"counter\": \"%s\", \"chains\": [%s], \"path\": [%s]}\n", + q, hex(msg.data(), 32).c_str(), hex(pk.seed.data(), N).c_str(), hex(pk.root.data(), N).c_str(), + hex(pk.seed.data(), N).c_str(), hex(pk.root.data(), N).c_str(), hex(sig, sig_len).c_str(), + sl_root.c_str(), r.c_str(), counter.c_str(), chains.c_str(), path.c_str()); + delete[] sig; + return 0; +} + int main(int argc, char** argv) { std::string cmd = argc > 1 ? argv[1] : ""; if (cmd == "new") return cmd_new(argc, argv); if (cmd == "pubkey") return cmd_pubkey(argc, argv); if (cmd == "sign") return cmd_sign(argc, argv); + if (cmd == "sign-raw") return cmd_sign_raw(argc, argv); fprintf(stderr, "usage:\n" " shrincs-signer new --seed FILE --state FILE\n" " shrincs-signer pubkey --seed FILE\n" - " shrincs-signer sign --seed FILE --state FILE --msg HEX\n"); + " shrincs-signer sign --seed FILE --state FILE --msg HEX\n" + " shrincs-signer sign-raw --seed FILE --q N --msg HEX (test keys only)\n"); return 2; } diff --git a/examples/pq_shrincs/README.md b/examples/pq_shrincs/README.md index f242475..f17d9d1 100644 --- a/examples/pq_shrincs/README.md +++ b/examples/pq_shrincs/README.md @@ -67,7 +67,7 @@ cargo run -p tx-manifest-wallet -- $CFG run $M/txmanifest.json Lock \ cp $M/spend.params.json /tmp/pq.spend.json # then set "PK" and "dest" cargo run -p tx-manifest-wallet -- $CFG run $M/txmanifest.json Spend \ --wallet $W --params /tmp/pq.spend.json --state /tmp/pq.state.json --print-sighash -# โ†’ prints: vault_in +# โ†’ prints the txid, then: vault_in # 4. Sign it, and put the signature into the params. $SIGNER sign --seed $KEY.seed --state $KEY.q --msg > /tmp/pq.sig.json @@ -81,13 +81,15 @@ cargo run -p tx-manifest-wallet -- $CFG run $M/txmanifest.json Spend \ ``` Expect a witness of about 18.8 KB, about 14.7 KB of it cost padding, and around 4,800 vB. -`spend.params.json` sets `fee_rate` to 15 because the wallet's fee estimate assumes about -1 KB of witness for a covenant input. 15 on that estimate comes to roughly 1.1 sat/vB on -the real transaction. - -If step 5 reports that the program failed, the transaction differs from the one step 3 -printed. Run step 3 again: the printed hash should not change. Then sign the new hash; -each attempt uses another leaf index. +The fee estimate sizes a covenant witness from its program, for its costliest branch. +Here that is the stateless one, about 50 k WU, so at `fee_rate` 1 the spend pays roughly +2.6 sat/vB on the real transaction. + +Both runs print `PSBT constructed (โ€ฆ, fee N sat, txid โ€ฆ)`. Before signing, check that +step 5 will build the same thing: the same params file, so the same `fee_rate`, and the +same state file. If step 5 shows a different txid, its program check fails. For example, +a `fee_rate` changed between the runs moves the fee and therefore the txid. Run step 3 +again with the final params and sign the new hash; each attempt uses another leaf index. `txmanifest_lib/tests/shrincs_verifier.rs` (`signed_spend_of_a_shrincs_locked_coin`) runs the same spend offline with a test key. diff --git a/examples/pq_shrincs/spend.params.json b/examples/pq_shrincs/spend.params.json index 127966e..8b52ec4 100644 --- a/examples/pq_shrincs/spend.params.json +++ b/examples/pq_shrincs/spend.params.json @@ -1,6 +1,6 @@ { "PK": "0xdd1ed75a55375ef66e3286dc36702951b7f7ba287323b7172461c6f897d8b4a0", - "fee_rate": "15", + "fee_rate": "1", "SIG": "Left(((0x35c6e3f93634bbb055be4c7a190f7164391753e53bc22cb6061f3f38750737c4, 0x001dc793, [0xa44fc3807683c62d5d5555e8aa4d2b44, 0x7c3e80d9ae28113e94515083d6b8dc8d, 0xdd480af63bdc08cfed799738ae66a22d, 0x93fc18cb1d06f66e6c2a2aa91a6fa115, 0xbaf9a212bab2668108be152a25367464, 0x070f40bac84ec65691be4dc8f908ea81, 0xff4debac8d0af1c429b24624458b05e8, 0x24ee7e51f903f104f23152a36805c19a, 0x3f1df40af557cdd307a01459438e498d, 0x235103ac552bce255c1924d919ab0ffc, 0x96b401b74ec606b40768277843360998, 0x54965ab49c73e02dba7fd8b8d9fa7782, 0x0b0c0f7ab1f512dcf23aff7167e9ce93, 0xf7f1ab707e107fdb6a1d869587820ade, 0xdaf50d94da06cafb4cd69618f9a92be2, 0x7941b70b1da0b40ab9c714eb64afbad6, 0x463bb9cdc84795dd505e501d7373a5a7, 0xe46639f105961658851c6d2e1f864342, 0xe476c4802a09741b516f8e0f2991b119, 0x90e3b1dc87fd98bf8eed5c95431a522c, 0x9a8abbbda0a5108ee8c89e64beb2a65a, 0xc1b406ba0b1a096e4b54aae38a383412, 0x5cecf32f496e25441ae6c4c379795bb8, 0xb57407dd43cbf5ef58985c6f2244c4c1, 0x9588e3c8c86d37c34565f512a7a6335c, 0x2d7392ec679187785a88943a1cd0536f, 0x71a0e247a73e2f7bcff9ea596b1cc65d, 0x30a1eb35ef8632314659845491d6f1b7, 0x03b9120ce345526ec62dc95277b4f62f, 0xd0abb7a59932e54364cd165cf6357173, 0x76eea875131e75efee3a6483cbc7eb01, 0xaa4f5eccbd6c9b3e4fca488827ef80ff, 0x806551f94c3f6f22ec2a7854c58e2cef, 0x115e9f4673fb5c49f9e3ec40cb350815, 0x5a969f0271a1754b6b42b0a5e81eff56, 0xbf42b3ece3b8562347d695c805621aef, 0x3450047704b3a475a291f64de5bc109b, 0xcda2a9249f048c92a45ae8fc01604ede, 0x050ff976de0e9cb7c922ea81121cb1f7, 0xcc281bf0ee1fbc285ae944574adcafb9, 0x61294fd7ce7f5664369a107c662eb6f6, 0x837a712d7fd8afd6b6332b8119ce047b, 0x161159375bb9ef5f5d4a3dc4800e7353, 0xf25ce3a4617036665c80c3fc48b270b1, 0x1222278e0c778d198ce8701739ab4553, 0x963b9040d0534c5499ddd2eb870a0859, 0xea7974b0a4ae4d01004f3b1493a7fdb9, 0x0b68a56fcb438eaca1b382cd51491a19, 0x972102b7db3808c02cd8f26c8ccca5e3, 0x648498884a4a1c1bb62e795cc442c787, 0xe45bd125c439ec8117394ae24d325d8d, 0x5371a402bbc07d6e1b2be98c97a303db, 0x3bac9cb935f8018b633b0cc3f79236c1, 0xb32965d37431e5c62d1cbdd83cfc4a1f, 0xe50f155f2abd5bc8edebf7f4a0f98b27, 0x32bb39dd0021750a9cee6fc3b0905a8c, 0x01f485dbec76dd1a210ea7b4bd223c0d, 0x590d984d2700c42b551d86b6dae6e53f, 0x5fb1e2d7778621643f58377ba0eb4ae6, 0x8803ed9680643f0457411393df9ed1d2, 0x51dd2c2e2e726eeb864c4fc63ccff9bc, 0xf3b6255cc7ef92b032001f4ac7ac4aab, 0x9911cb29fb1feeab343b8b2acf2ab55b, 0x670f33601869264f124f285f3e473525]), list![0xeac0ffbd3f7dc3b5cdd318eadb364901, 0xda39911f507ff7a7c4d3d0eee5bad94a, 0x1525599a8c0597c4c54347318ef2531b], 3))", "OTHER_ROOT": "0x8381b64d16c743cfd794011835333b8c" } \ No newline at end of file diff --git a/examples/pq_shrincs_smoke/README.md b/examples/pq_shrincs_smoke/README.md index 31bc4e8..b9947c7 100644 --- a/examples/pq_shrincs_smoke/README.md +++ b/examples/pq_shrincs_smoke/README.md @@ -23,10 +23,9 @@ What it shows: [`contrib/shrincs-simplicity-verifier`](../../contrib/shrincs-simplicity-verifier). Don't edit it; rerun `build.sh` and `tx-manifest-wallet pin`. - `lock.params.json`: lock 20,000 sat at 0.1 sat/vB. -- `spend.params.json`: spend at `fee_rate` 15. The wallet's fee estimate assumes about - 1 KB of witness for a covenant input, and this one is about 18.8 KB. 15 sat/vB on that - estimate comes to about 5,300 sat, roughly 1.1 sat/vB on the real transaction of about - 4,790 vB. +- `spend.params.json`: spend at `fee_rate` 1. The fee estimate sizes the witness for the + program's costliest branch (the stateless one, about 50 k WU), so the real + transaction, about 4,790 vB, pays roughly 2.6 sat/vB. ## Run it diff --git a/examples/pq_shrincs_smoke/spend.params.json b/examples/pq_shrincs_smoke/spend.params.json index 6033a7f..0368dfc 100644 --- a/examples/pq_shrincs_smoke/spend.params.json +++ b/examples/pq_shrincs_smoke/spend.params.json @@ -1,3 +1,3 @@ { - "fee_rate": "15" + "fee_rate": "1" } \ No newline at end of file diff --git a/examples/pq_shrincs_smoke/txmanifest.json b/examples/pq_shrincs_smoke/txmanifest.json index b7a9208..af3ad43 100644 --- a/examples/pq_shrincs_smoke/txmanifest.json +++ b/examples/pq_shrincs_smoke/txmanifest.json @@ -65,8 +65,7 @@ "Spend": { "description": "Spend the coin with upstream's stateful SHRINCS witness (q=1, message 0).", "intent": "Spend the SHRINCS smoke-test coin back to this wallet", - "$comment": "The fee estimate assumes 1,024 WU for a covenant witness; this one is about 18,800 WU. Pass a high fee_rate in --params (about 15) so the transaction still relays.", - "inputs": [ + "inputs": [ { "id": "vault_in", "utxo_source": { diff --git a/txmanifest_lib/src/bitcoin_review.rs b/txmanifest_lib/src/bitcoin_review.rs index e180bcb..651cfda 100644 --- a/txmanifest_lib/src/bitcoin_review.rs +++ b/txmanifest_lib/src/bitcoin_review.rs @@ -332,6 +332,7 @@ mod tests { ), amount: 1_000_000, sequence: None, + witness_wu: None, }; let t = tx( vec![out(999_700, w.script_pubkey(Branch::Receive, 3).unwrap())], diff --git a/txmanifest_lib/src/covenant.rs b/txmanifest_lib/src/covenant.rs index 20f5597..3ed40ae 100644 --- a/txmanifest_lib/src/covenant.rs +++ b/txmanifest_lib/src/covenant.rs @@ -1596,6 +1596,94 @@ pub fn bitcoin_covenant_sighash( Ok(prepared.env.c_tx_env().sighash_all().to_byte_array()) } +/// An upper bound on the witness weight a Bitcoin covenant input will carry, in weight +/// units, for the fee estimate. +/// +/// Computed from the program alone, never from witness values. A spend whose witness is +/// signed outside the wallet is built twice (`run --print-sighash`, then the signed run), +/// and the two must agree on the fee, so the estimate can't depend on anything the second +/// run knows and the first doesn't. +/// +/// That makes it an upper bound rather than an exact figure. It satisfies the program with +/// zero values, leaves it unpruned, and takes: +/// - the stack: every branch of the program, the widest value of each witness's type, +/// the CMR and the control block; +/// - the cost bound, which is the costliest branch, as the budget padding must buy. +/// +/// The larger of those is the estimate. A program with one path is estimated exactly, +/// apart from witness types of variable size. One with a `match` on a witness is estimated +/// for its heaviest branch: SHRINCS's stateful spend, for instance, is about 19 k WU, and +/// it is estimated as its stateless branch, about 50 k WU. Overshooting costs fee; +/// undershooting gives a transaction the network won't relay. +pub fn estimate_bitcoin_covenant_witness_wu( + program: &ProgramSource, + compile_params: &HashMap, + type_hints: &HashMap, + extra_leaf_payloads: &[Vec], + opts: impl Into, +) -> Result { + use simplicity::dag::{DagLike, InternalSharing}; + use simplicity::node::Inner; + + let opts = opts.into(); + let source = program.text().to_owned(); + let args_json = build_args_json(compile_params, type_hints)?; + let arguments: Arguments = serde_json::from_str(&args_json) + .with_context(|| format!("Failed to parse Arguments from JSON:\n{args_json}"))?; + let compiled = compile_program(source, arguments, &opts)?; + let abi_meta = compiled + .generate_abi_meta() + .map_err(|e| anyhow::anyhow!("Cannot read program ABI: {e}"))?; + let zeros: HashMap<_, _> = abi_meta + .witness_types + .iter() + .map(|(name, ty)| (name.shallow_clone(), zero_value_for_type(ty))) + .collect(); + let satisfied = compiled + .satisfy(WitnessValues::from(zeros)) + .map_err(|e| anyhow::anyhow!("Cannot satisfy the program with zero values: {e}"))?; + let redeem = satisfied.redeem(); + + let (program_bytes, _) = redeem.to_vec_with_witness(); + let witness_bits: usize = redeem + .as_ref() + .post_order_iter::() + .filter(|data| matches!(data.node.inner(), Inner::Witness(_))) + .map(|data| data.node.arrow().target.bit_width()) + .sum(); + // A control block is 33 bytes plus 32 for each sibling in the taproot tree. + let control_block = 33 + 32 * extra_leaf_payloads.len(); + let stack = [ + vec![0; witness_bits.div_ceil(8)], + program_bytes, + vec![0; 32], + vec![0; control_block], + ]; + let mut stack = stack.to_vec(); + if let Some(len) = bitcoin_padding_len(redeem.bounds().cost, &stack) { + stack.insert(0, vec![0; len]); + } + Ok(witness_stack_wu(&stack)) +} + +/// The serialized size of a witness stack, which is its weight: a CompactSize item count, +/// then each item with a CompactSize length prefix. +fn witness_stack_wu(stack: &[Vec]) -> usize { + fn compact_size(n: usize) -> usize { + match n { + 0..=0xfc => 1, + 0xfd..=0xffff => 3, + 0x1_0000..=0xffff_ffff => 5, + _ => 9, + } + } + compact_size(stack.len()) + + stack + .iter() + .map(|item| compact_size(item.len()) + item.len()) + .sum::() +} + /// A Bitcoin covenant input up to the point its witness is needed: the compiled program, /// the leaf it commits to, and the environment it runs in. struct PreparedBitcoinInput { @@ -1690,6 +1778,73 @@ fn prepare_bitcoin_covenant_input( }) } +/// Whether the Simplicity signet node would accept `stack` as the witness of a Simplicity +/// leaf spend, returning the program's cost bound if so. +/// +/// Transcribed from the node's own rules (delta1/bitcoin `simplicity-inquisition`, +/// `src/script/interpreter.cpp`), for checking a stack without broadcasting it: +/// - The node reads the stack from the end: the control block, the CMR, the program, the +/// witness, and then an optional padding item. So padding comes first on the wire. +/// - Padding must be all zeros. +/// - The program's cost bound must fit the budget the whole stack buys. +/// - When there is padding, the cost must *not* fit the budget the stack would buy with +/// one byte less of it. Padding has to be minimal, or the node fails the spend with +/// `SIMPLICITY_ERR_OVERWEIGHT`. +/// +/// It doesn't execute the program. Pruning against the environment, as +/// [`finalize_bitcoin_covenant_input`] does, is what runs it. +pub fn bitcoin_node_check(stack: &[Vec]) -> std::result::Result { + use simplicity::{jet::Bitcoin, BitIter, RedeemNode}; + + let n = stack.len(); + if !(4..=5).contains(&n) { + return Err(format!( + "{n} stack items; a Simplicity spend has 4, or 5 with padding" + )); + } + // TAPROOT_CONTROL_BASE_SIZE + k * TAPROOT_CONTROL_NODE_SIZE, k <= 128. + let control = &stack[n - 1]; + if control.len() < 33 + || control.len() > 33 + 128 * 32 + || !(control.len() - 33).is_multiple_of(32) + { + return Err(format!( + "the last item ({} B) is not a control block, so the node cannot find the leaf", + control.len() + )); + } + let program = &stack[n - 3]; + let witness = &stack[n - 4]; + let padding = (n == 5).then(|| &stack[0]); + if padding.is_some_and(|p| p.iter().any(|&b| b != 0)) { + return Err("the first item is padding and must be all zeros".into()); + } + + let node = RedeemNode::decode::<_, _, Bitcoin>( + BitIter::from(&program[..]), + BitIter::from(&witness[..]), + ) + .map_err(|e| format!("cannot decode the program: {e:?}"))?; + let cost = node.bounds().cost; + if !cost.is_budget_valid(stack) { + return Err(format!("{cost:?} exceeds the budget the stack buys")); + } + if let Some(p) = padding { + if !p.is_empty() { + let mut shorter = stack.to_vec(); + shorter[0].pop(); + if cost.is_budget_valid(&shorter) { + return Err(format!( + "{cost:?} fits the budget with one byte less padding ({} B), so the \ + padding is not minimal", + p.len() + )); + } + } + } + Ok(cost) +} + /// The length of the padding item a Bitcoin Simplicity spend needs, or `None` when /// `stack` already buys the budget `cost` needs. /// diff --git a/txmanifest_lib/src/lifecycle.rs b/txmanifest_lib/src/lifecycle.rs index b9dd348..e7e2dcd 100644 --- a/txmanifest_lib/src/lifecycle.rs +++ b/txmanifest_lib/src/lifecycle.rs @@ -1378,6 +1378,7 @@ pub fn run( issuance: iso_spec, sequence: input_sequence, blinding: None, + witness_wu: None, }); } else { println!( @@ -1521,6 +1522,7 @@ pub fn run( issuance: iso_spec, sequence: input_sequence, blinding: input_blinding, + witness_wu: None, }); } else { // Neither `"wallet"` nor `{"utxo_type": "..."}`. This arm used to be @@ -2132,6 +2134,96 @@ pub fn run( change_assets: change_assets.clone(), }; + // Bitcoin: resolve each covenant input's program and parameters now, where the + // manifest and the execution context are in scope, and before the fee is + // estimated. The estimate needs each program's witness weight, and the build + // below needs the specs. The Elements path does the same resolution in its own + // finalize step. + let mut covenant_specs: std::collections::HashMap = + std::collections::HashMap::new(); + if actx.family() == crate::chain::ChainFamily::Bitcoin { + let mut specs_ok = true; + for action_inp in action.inputs.as_deref().unwrap_or_default() { + let Some(type_name) = action_inp.utxo_type_name() else { + continue; + }; + let spec = (|| -> Result { + let ut = manifest.utxo_type(&type_name)?; + let site = resolve_utxo_site( + ut, + Some(&action_inp.utxo_source), + &compile_params_map, + &compile_param_type_hints, + action, + &ctx, + )?; + let program = programs + .for_script(ut.script.as_ref()) + .with_context(|| format!("utxo_type '{type_name}'"))? + .clone(); + Ok(CovenantSpendSpec { + program, + params: site.compile_params, + hints: site.type_hints, + leaf_payloads: site.leaf_payloads, + witnesses: action_inp + .witnesses + .as_ref() + .map(|w| eval::resolve_witness_refs(w, &ctx)), + witness_spec: action_inp.witnesses.clone(), + }) + })(); + match spec { + Ok(spec) => { + covenant_specs.insert(action_inp.id.clone(), spec); + } + Err(e) => { + println!( + " {} Covenant input '{}': {e}", + style("[error]").red(), + action_inp.id + ); + specs_ok = false; + } + } + } + if !specs_ok { + anyhow::bail!("a covenant input could not be resolved; nothing was built"); + } + + // A covenant witness can be far larger than the fixed allowance the fee + // estimate falls back to: a SHRINCS spend is about 19 k WU. The estimate is + // an upper bound from the program alone, never from witness values, so + // both runs of a two-run spend (`--print-sighash`) arrive at the same fee. + for input in req.inputs.iter_mut() { + if let pset_builder::PsetInput::Covenant { + input_id, + witness_wu, + .. + } = input + { + let Some(spec) = covenant_specs.get(input_id.as_str()) else { + continue; + }; + let wu = covenant::estimate_bitcoin_covenant_witness_wu( + &spec.program, + &spec.params, + &spec.hints, + &spec.leaf_payloads, + &compile_opts, + ) + .with_context(|| { + format!("cannot estimate the witness size of '{input_id}'") + })?; + println!( + " {} Covenant input '{input_id}': witness up to {wu} WU (fee estimate)", + style("ยท").dim() + ); + *witness_wu = Some(wu); + } + } + } + // Resolve the `fee` keyword: estimate the fee from the current (fee=0) // draft, then re-evaluate any output amount that referenced `fee`. The // amounts don't affect the tx vsize, so the draft gives the right size. @@ -2206,61 +2298,6 @@ pub fn run( // path to run through yet, and nothing downstream would know what to do with a // PSBT. if actx.family() == crate::chain::ChainFamily::Bitcoin { - // Resolve each covenant input's program and parameters here, where the - // manifest and the execution context are in scope. The Elements path does - // the same work in its own finalize step; this is the same resolution, - // moved to where the Bitcoin build can reach it. - let mut covenant_specs: std::collections::HashMap = - std::collections::HashMap::new(); - let mut specs_ok = true; - for action_inp in action.inputs.as_deref().unwrap_or_default() { - let Some(type_name) = action_inp.utxo_type_name() else { - continue; - }; - let spec = (|| -> Result { - let ut = manifest.utxo_type(&type_name)?; - let site = resolve_utxo_site( - ut, - Some(&action_inp.utxo_source), - &compile_params_map, - &compile_param_type_hints, - action, - &ctx, - )?; - let program = programs - .for_script(ut.script.as_ref()) - .with_context(|| format!("utxo_type '{type_name}'"))? - .clone(); - Ok(CovenantSpendSpec { - program, - params: site.compile_params, - hints: site.type_hints, - leaf_payloads: site.leaf_payloads, - witnesses: action_inp - .witnesses - .as_ref() - .map(|w| eval::resolve_witness_refs(w, &ctx)), - witness_spec: action_inp.witnesses.clone(), - }) - })(); - match spec { - Ok(spec) => { - covenant_specs.insert(action_inp.id.clone(), spec); - } - Err(e) => { - println!( - " {} Covenant input '{}': {e}", - style("[error]").red(), - action_inp.id - ); - specs_ok = false; - } - } - } - if !specs_ok { - anyhow::bail!("a covenant input could not be resolved; nothing was built"); - } - let params_snap = compile_params_map.clone(); let action_params_snap = action_params_map.clone(); let wallet_snap = loaded_wallet.clone(); @@ -4637,12 +4674,15 @@ fn run_bitcoin_build( let psbt_req = psbt_builder::from_pset_request(req, Some(change_script))?; let mut built = psbt_builder::build_psbt(&psbt_req)?; + // The txid excludes witnesses, so it is fixed from here on. Printing it lets a two-run + // spend (`--print-sighash`, then the signed run) show that both built one transaction. println!( - " {} PSBT constructed ({} inputs, {} outputs, fee {} sat).", + " {} PSBT constructed ({} inputs, {} outputs, fee {} sat, txid {}).", style("โœ“").green(), built.psbt.unsigned_tx.input.len(), built.psbt.unsigned_tx.output.len(), style(built.fee).yellow(), + built.psbt.unsigned_tx.compute_txid(), ); if let Some(idx) = built.change_index { println!(" Change at output #{idx}"); @@ -4700,7 +4740,11 @@ fn run_bitcoin_build( anyhow::bail!("--print-sighash: this transaction spends no covenant input"); } println!(); - println!(" sig_all_hash of each covenant input (nothing was broadcast):"); + println!( + " sig_all_hash of each covenant input of {} (nothing was broadcast).", + unsigned.compute_txid() + ); + println!(" The signed run must build this same txid, or the signature will not verify:"); for (index, input_id) in &covenant_inputs { let spec = covenant_specs.get(input_id).ok_or_else(|| { anyhow::anyhow!("covenant input '{input_id}' has no resolved program") @@ -5351,6 +5395,7 @@ mod tests { issuance: None, sequence: None, blinding: None, + witness_wu: None, }) .collect() }; diff --git a/txmanifest_lib/src/psbt_builder.rs b/txmanifest_lib/src/psbt_builder.rs index cc5983b..f8a2ee3 100644 --- a/txmanifest_lib/src/psbt_builder.rs +++ b/txmanifest_lib/src/psbt_builder.rs @@ -83,6 +83,9 @@ pub enum PsbtInput { script_pubkey: ScriptBuf, amount: u64, sequence: Option, + /// An upper bound on the witness weight, from the program. `None` falls back to + /// [`COVENANT_WITNESS_WU`]. + witness_wu: Option, }, } @@ -135,7 +138,7 @@ impl PsbtInput { fn estimated_witness_wu(&self) -> usize { match self { PsbtInput::Wallet { .. } => KEYSPEND_WITNESS_WU, - PsbtInput::Covenant { .. } => COVENANT_WITNESS_WU, + PsbtInput::Covenant { witness_wu, .. } => witness_wu.unwrap_or(COVENANT_WITNESS_WU), } } } @@ -602,6 +605,7 @@ mod tests { script_pubkey: p2tr(4), amount: 50_000, sequence: None, + witness_wu: None, }], vec![PsbtOutputSpec { script_pubkey: p2tr(1), @@ -672,6 +676,7 @@ mod tests { script_pubkey: p2tr(4), amount: 100_000, sequence: None, + witness_wu: None, }], outputs(), )) @@ -718,6 +723,7 @@ mod tests { issuance: None, sequence: Some(144), blinding: None, + witness_wu: None, }], outputs: vec![crate::pset_builder::PsetOutputSpec { script_pubkey: el_script(), @@ -933,6 +939,7 @@ mod tests { script_pubkey: p2tr(4), amount: 50_000, sequence: None, + witness_wu: None, }); r.outputs[0].amount = 140_000; @@ -1170,6 +1177,7 @@ pub fn from_pset_request( issuance, sequence, blinding, + witness_wu, .. } => { if issuance.is_some() { @@ -1187,6 +1195,7 @@ pub fn from_pset_request( script_pubkey: convert_script(script_pubkey), amount: *amount, sequence: *sequence, + witness_wu: *witness_wu, }); } } diff --git a/txmanifest_lib/src/pset_builder.rs b/txmanifest_lib/src/pset_builder.rs index adb7eee..f455e72 100644 --- a/txmanifest_lib/src/pset_builder.rs +++ b/txmanifest_lib/src/pset_builder.rs @@ -58,6 +58,10 @@ pub enum PsetInput { /// The factors this UTXO was blinded with, if it is confidential. Both halves /// must be present; see [`add_covenant_input`]. blinding: Option, + /// Bitcoin only: an upper bound on this input's witness weight, from its program + /// (`covenant::estimate_bitcoin_covenant_witness_wu`), for the fee estimate. + /// `None` falls back to a fixed allowance. The Elements builder doesn't read it. + witness_wu: Option, }, } @@ -588,6 +592,8 @@ fn build_inner( issuance, sequence, blinding, + // Bitcoin's fee estimate; this builder keeps its fixed allowance. + witness_wu: _, } => { let idx = add_covenant_input( &mut pset, diff --git a/txmanifest_lib/tests/bitcoin_path.rs b/txmanifest_lib/tests/bitcoin_path.rs index d44364b..07519a9 100644 --- a/txmanifest_lib/tests/bitcoin_path.rs +++ b/txmanifest_lib/tests/bitcoin_path.rs @@ -233,6 +233,7 @@ fn a_covenant_input_is_refused_rather_than_left_unsigned() { issuance: None, sequence: None, blinding: None, + witness_wu: None, }], outputs: vec![PsetOutputSpec { script_pubkey: lwk_wollet::elements::Script::from(recipient().to_bytes()), diff --git a/txmanifest_lib/tests/bitcoin_sighash.rs b/txmanifest_lib/tests/bitcoin_sighash.rs index c851087..91d4149 100644 --- a/txmanifest_lib/tests/bitcoin_sighash.rs +++ b/txmanifest_lib/tests/bitcoin_sighash.rs @@ -17,7 +17,8 @@ use lwk_wollet::elements::bitcoin::{ }; use tx_manifest_lib::chain::ChainFamily; use tx_manifest_lib::covenant::{ - bitcoin_covenant_sighash, finalize_bitcoin_covenant_input, CompileOpts, ProgramSource, + bitcoin_covenant_sighash, estimate_bitcoin_covenant_witness_wu, + finalize_bitcoin_covenant_input, CompileOpts, ProgramSource, }; fn hex(bytes: &[u8]) -> String { @@ -100,9 +101,15 @@ fn a_signature_over_the_computed_sighash_satisfies_the_program() { let sig = hex(sig.as_ref()); // The second run rebuilds the same transaction, so the signature verifies. - finalize(&tx, &prevouts, &sig) + let stack = finalize(&tx, &prevouts, &sig) .unwrap_or_else(|e| panic!("a signature over the computed hash should verify: {e:#}")); + // p2pk has one path and a fixed-size witness, so the fee estimate's witness weight, + // computed from the program alone, is exact. + let estimate = estimate_bitcoin_covenant_witness_wu(&program, ¶ms, &hints, &[], bitcoin()) + .expect("estimate the witness weight"); + assert_eq!(estimate, Witness::from_slice(&stack).size()); + // A transaction that differs by one sat has another hash, so the same signature fails. // This is why the second run has to rebuild the first run's transaction exactly. let (other, prevouts) = spend(89_999); diff --git a/txmanifest_lib/tests/shrincs_verifier.rs b/txmanifest_lib/tests/shrincs_verifier.rs index d2323bf..959f717 100644 --- a/txmanifest_lib/tests/shrincs_verifier.rs +++ b/txmanifest_lib/tests/shrincs_verifier.rs @@ -19,11 +19,12 @@ use lwk_wollet::elements::bitcoin::{ absolute::LockTime, transaction::Version, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, }; -use simplicityhl::simplicity::{jet::Bitcoin, BitIter, Cost, RedeemNode}; +use simplicityhl::simplicity::Cost; use tx_manifest_lib::chain::ChainFamily; use tx_manifest_lib::covenant::{ - bitcoin_covenant_sighash, compute_tapleaf_hash, finalize_bitcoin_covenant_input, - program_witness_types, CompileOpts, ProgramSource, + bitcoin_covenant_sighash, bitcoin_node_check, compute_tapleaf_hash, + estimate_bitcoin_covenant_witness_wu, finalize_bitcoin_covenant_input, program_witness_types, + CompileOpts, ProgramSource, }; fn verifier_dir() -> PathBuf { @@ -112,63 +113,9 @@ fn finalize(witnesses: &serde_json::Value) -> anyhow::Result>> { ) } -/// The Simplicity signet node's acceptance rule for a Simplicity leaf spend, from -/// `src/script/interpreter.cpp` on delta1/bitcoin's `simplicity-inquisition` branch, the -/// node `contrib/regtest` builds. -/// -/// The node reads the stack from the end: the control block, the CMR, the program, the -/// witness, and then an optional padding item, so padding comes *first* on the wire. The -/// program's cost bound must fit the budget the whole stack buys. When there is padding, -/// the cost must also *not* fit the budget the stack would buy with one byte less of it: -/// padding has to be minimal, or the spend fails with `SIMPLICITY_ERR_OVERWEIGHT`. +/// The signet node's acceptance rules, as tx-manifest-lib transcribes them. fn node_check(stack: &[Vec]) -> Result { - let n = stack.len(); - if !(4..=5).contains(&n) { - return Err(format!( - "{n} stack items; a Simplicity spend has 4, or 5 with padding" - )); - } - // TAPROOT_CONTROL_BASE_SIZE + k * TAPROOT_CONTROL_NODE_SIZE, k โ‰ค 128. - let control = &stack[n - 1]; - if control.len() < 33 - || control.len() > 33 + 128 * 32 - || !(control.len() - 33).is_multiple_of(32) - { - return Err(format!( - "the last item ({} B) is not a control block, so the node cannot find the leaf", - control.len() - )); - } - let program = &stack[n - 3]; - let witness = &stack[n - 4]; - let padding = (n == 5).then(|| &stack[0]); - if padding.is_some_and(|p| p.iter().any(|&b| b != 0)) { - return Err("the first item is padding and must be all zeros".into()); - } - - let node = RedeemNode::decode::<_, _, Bitcoin>( - BitIter::from(&program[..]), - BitIter::from(&witness[..]), - ) - .map_err(|e| format!("cannot decode the program: {e:?}"))?; - let cost = node.bounds().cost; - if !cost.is_budget_valid(stack) { - return Err(format!("{cost:?} exceeds the budget the stack buys")); - } - if let Some(p) = padding { - if !p.is_empty() { - let mut shorter = stack.to_vec(); - shorter[0].pop(); - if cost.is_budget_valid(&shorter) { - return Err(format!( - "{cost:?} fits the budget with one byte less padding ({} B), so the \ - padding is not minimal", - p.len() - )); - } - } - } - Ok(cost) + bitcoin_node_check(stack) } /// Item sizes of a finalized stack, for the record in the T1 task file. @@ -345,6 +292,18 @@ fn signed_spend_of_a_shrincs_locked_coin() { Err(why) => panic!("the node would reject the signed spend: {why}"), } + // The fee estimate's witness weight is an upper bound: it is sized for the costliest + // branch, the stateless one, so it covers this stateful spend with room to spare. + let estimate = + estimate_bitcoin_covenant_witness_wu(&wrapper(), ¶ms, &hints, &[], bitcoin()) + .expect("estimate the witness weight"); + let actual = Witness::from_slice(&stack).size(); + eprintln!("signed spend: witness {actual} WU, estimated up to {estimate} WU"); + assert!( + estimate >= actual, + "the estimate {estimate} must cover {actual}" + ); + // The signature covers this transaction: moved to one that pays a sat less, it fails. let mut other = tx.clone(); other.output[0].value = Amount::from_sat(other.output[0].value.to_sat() - 1);