diff --git a/CHANGELOG.md b/CHANGELOG.md index 83858d1..d2296b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,12 +18,18 @@ 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. 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 - `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 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 ### Breaking 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..5141fd3 --- /dev/null +++ b/contrib/shrincs-signer/README.md @@ -0,0 +1,53 @@ +# 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-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 + 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..b33ed3c --- /dev/null +++ b/contrib/shrincs-signer/shrincs-signer.cpp @@ -0,0 +1,235 @@ +// 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": ...} +// 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. +// +// 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; +} + +// 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-raw --seed FILE --q N --msg HEX (test keys only)\n"); + return 2; +} 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..a2f3953 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,11 +11,14 @@ 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. | | [`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`, +`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..f17d9d1 --- /dev/null +++ b/examples/pq_shrincs/README.md @@ -0,0 +1,95 @@ +# 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 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 +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. +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/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/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/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/spend.params.json b/examples/pq_shrincs/spend.params.json new file mode 100644 index 0000000..8b52ec4 --- /dev/null +++ b/examples/pq_shrincs/spend.params.json @@ -0,0 +1,6 @@ +{ + "PK": "0xdd1ed75a55375ef66e3286dc36702951b7f7ba287323b7172461c6f897d8b4a0", + "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/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/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/examples/pq_shrincs_smoke/README.md b/examples/pq_shrincs_smoke/README.md new file mode 100644 index 0000000..b9947c7 --- /dev/null +++ b/examples/pq_shrincs_smoke/README.md @@ -0,0 +1,47 @@ +# 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` 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 + +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..0368dfc --- /dev/null +++ b/examples/pq_shrincs_smoke/spend.params.json @@ -0,0 +1,3 @@ +{ + "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 new file mode 100644 index 0000000..af3ad43 --- /dev/null +++ b/examples/pq_shrincs_smoke/txmanifest.json @@ -0,0 +1,99 @@ +{ + "$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", + "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/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 862f884..3ed40ae 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, @@ -1490,10 +1490,226 @@ pub fn finalize_bitcoin_covenant_input( input_index: u32, opts: impl Into, ) -> Result>> { + let PreparedBitcoinInput { + compiled, + witness_types, + script_cmr, + control_block, + env, + } = prepare_bitcoin_covenant_input( + program, + compile_params, + type_hints, + extra_leaf_payloads, + tx, + witness_utxos, + input_index, + &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 + // signature over anything else simply does not verify. + let injected: Option; + let effective_witnesses = if let (Some(signer), Some(w)) = (sig_signer, witnesses) { + injected = Some( + inject_bitcoin_signatures(w, &env, signer) + .context("Failed to compute Signature witnesses")?, + ); + injected.as_ref() + } else { + witnesses + }; + + let witness_values = build_witness_values_from_types(effective_witnesses, &witness_types) + .context("Cannot build witness values")?; + + let satisfied = compiled + .satisfy(witness_values) + .map_err(|e| anyhow::anyhow!("Covenant satisfaction failed: {e}"))?; + + // Prune against the real transaction, dropping every branch this spend does not take. + // + // Not optional. Simplicity's anti-DoS rule requires a redeem program to contain no + // unexecuted nodes, so an unpruned program with a `match` is rejected outright — + // `mempool-script-verify-flag-failed (Anti-DOS check failed)`, which reads like a + // resource limit and is really "this program has branches you did not take". + // + // SimplicityHL's `satisfy_with_env` does this on the Elements side but is typed to + // `ElementsEnv`, so it cannot be used here. `RedeemNode::prune` underneath it is + // generic over the environment, which is what makes the Bitcoin path possible at all. + let redeem = satisfied + .redeem() + .prune(&env) + .map_err(|e| anyhow::anyhow!("Cannot prune covenant program: {e}"))?; + + let (prog, witness) = redeem.to_vec_with_witness(); + let mut stack = vec![ + witness, + prog, + script_cmr.as_ref().to_vec(), + control_block.serialize(), + ]; + + // 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 `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()) +} + +/// 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 { + 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; - let opts = opts.into(); if opts.family != ChainFamily::Bitcoin { anyhow::bail!( "compile options target chain '{}' but a Bitcoin witness was requested", @@ -1505,7 +1721,7 @@ pub fn finalize_bitcoin_covenant_input( 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 compiled = compile_program(source, arguments, opts)?; let abi_meta = compiled .generate_abi_meta() .map_err(|e| anyhow::anyhow!("Cannot read program ABI: {e}"))?; @@ -1521,7 +1737,7 @@ pub fn finalize_bitcoin_covenant_input( compile_params, type_hints, extra_leaf_payloads, - &opts, + opts, )?; let mut sibling_hashes: Vec = Vec::new(); for payload in extra_leaf_payloads { @@ -1553,62 +1769,109 @@ pub fn finalize_bitcoin_covenant_input( control_block.clone(), ); - // 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 - // signature over anything else simply does not verify. - let injected: Option; - let effective_witnesses = if let (Some(signer), Some(w)) = (sig_signer, witnesses) { - injected = Some( - inject_bitcoin_signatures(w, &env, signer) - .context("Failed to compute Signature witnesses")?, - ); - injected.as_ref() - } else { - witnesses - }; - - let witness_values = - build_witness_values_from_types(effective_witnesses, &abi_meta.witness_types) - .context("Cannot build witness values")?; - - let satisfied = compiled - .satisfy(witness_values) - .map_err(|e| anyhow::anyhow!("Covenant satisfaction failed: {e}"))?; - - // Prune against the real transaction, dropping every branch this spend does not take. - // - // Not optional. Simplicity's anti-DoS rule requires a redeem program to contain no - // unexecuted nodes, so an unpruned program with a `match` is rejected outright — - // `mempool-script-verify-flag-failed (Anti-DOS check failed)`, which reads like a - // resource limit and is really "this program has branches you did not take". - // - // SimplicityHL's `satisfy_with_env` does this on the Elements side but is typed to - // `ElementsEnv`, so it cannot be used here. `RedeemNode::prune` underneath it is - // generic over the environment, which is what makes the Bitcoin path possible at all. - let redeem = satisfied - .redeem() - .prune(&env) - .map_err(|e| anyhow::anyhow!("Cannot prune covenant program: {e}"))?; + Ok(PreparedBitcoinInput { + compiled, + witness_types: abi_meta.witness_types, + script_cmr, + control_block, + env, + }) +} - let (prog, witness) = redeem.to_vec_with_witness(); - let mut stack = vec![ - witness, - prog, - script_cmr.as_ref().to_vec(), - control_block.serialize(), - ]; +/// 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()); + } - // 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); + 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(stack) + Ok(cost) +} + +/// 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. diff --git a/txmanifest_lib/src/lifecycle.rs b/txmanifest_lib/src/lifecycle.rs index a2cba50..e7e2dcd 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. @@ -1368,6 +1378,7 @@ pub fn run( issuance: iso_spec, sequence: input_sequence, blinding: None, + witness_wu: None, }); } else { println!( @@ -1511,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 @@ -2122,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. @@ -2196,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(); @@ -2261,6 +2308,7 @@ pub fn run( &req, bitcoin_run, export_pset_path, + print_sighash, &covenant_specs, ¶ms_snap, &action_params_snap, @@ -4506,6 +4554,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 +4656,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, @@ -4624,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}"); @@ -4674,13 +4727,47 @@ 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 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") + })?; + 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") @@ -5308,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 new file mode 100644 index 0000000..91d4149 --- /dev/null +++ b/txmanifest_lib/tests/bitcoin_sighash.rs @@ -0,0 +1,120 @@ +//! `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, estimate_bitcoin_covenant_witness_wu, + 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. + 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); + 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 new file mode 100644 index 0000000..959f717 --- /dev/null +++ b/txmanifest_lib/tests/shrincs_verifier.rs @@ -0,0 +1,314 @@ +//! 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::Cost; +use tx_manifest_lib::chain::ChainFamily; +use tx_manifest_lib::covenant::{ + 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 { + 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 signet node's acceptance rules, as tx-manifest-lib transcribes them. +fn node_check(stack: &[Vec]) -> Result { + bitcoin_node_check(stack) +} + +/// 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)); +} + +// ── 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 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); + 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