From e8ce91558f8699edfc299ca3d3bb79cebc32c523 Mon Sep 17 00:00:00 2001 From: pasta Date: Mon, 2 Nov 2026 16:43:35 +0000 Subject: [PATCH 1/4] pkc%test(bls): add the reference implementation as a submodule Pinned at v24.0.0-rc.5 for the programs that write the BLS corpora. It is only checked out on request, and Bazel, CodeQL and the crate package skip it, as its tree carries build files of its own. --- .bazelignore | 1 + .gitmodules | 5 +++++ MODULE.bazel.lock | 2 +- maint/codeql/rust/codeql-config.yml | 1 + pkgs/pkc/Cargo.toml | 1 + pkgs/pkc/contrib/dash | 1 + 6 files changed, 10 insertions(+), 1 deletion(-) create mode 160000 pkgs/pkc/contrib/dash diff --git a/.bazelignore b/.bazelignore index bc1620e4c..eaf6fe88d 100644 --- a/.bazelignore +++ b/.bazelignore @@ -1,5 +1,6 @@ .cache .venv docs/.site +pkgs/pkc/contrib/dash public target diff --git a/.gitmodules b/.gitmodules index 7da7727e6..d07d3d4e2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,8 @@ [submodule "fuzz/pow/sphlib"] path = fuzz/pow/sphlib url = https://github.com/pornin/sphlib +[submodule "pkgs/pkc/contrib/dash"] + path = pkgs/pkc/contrib/dash + url = https://github.com/dashpay/dash + shallow = true + update = none diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index e0372a598..42d07f4ce 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -511,7 +511,7 @@ "FILE:@@//pkgs/params/Cargo.toml e6a2f385a46ef872afd059ac2e7d69847034d9c32a5113f264f9821215cbce69", "FILE:@@//pkgs/pow/Cargo.toml 3f33e4320c8dc8836ac9655cbadd5e9cacee225b30950e309f265a66d5c29512", "FILE:@@//pkgs/primitives/Cargo.toml 7693a4e45c573f346126d8420e66b6cdfff3513d02234448737c03cfb4c9096a", - "FILE:@@//pkgs/pkc/Cargo.toml a16f6907ab4048fbe3aa7423f49aaa615ec5ecdc1faabf13ee4e958593cea8cf", + "FILE:@@//pkgs/pkc/Cargo.toml ebb12eab3046a828445d95fbb472195143784eee78a53629d9943adb8e8f0718", "FILE:@@//pkgs/script/Cargo.toml 4af90a885200f6b1397f7775f0079ae22d4fbbc8cc65b0f125a0624df46dcd48", "FILE:@@//pkgs/p2p_core/Cargo.toml 62ca5fa4bf9bf453d0b8948ed541363f3dff352f8aaf8ce52293549e283f29e8" ], diff --git a/maint/codeql/rust/codeql-config.yml b/maint/codeql/rust/codeql-config.yml index 033dc18df..ade5115e8 100644 --- a/maint/codeql/rust/codeql-config.yml +++ b/maint/codeql/rust/codeql-config.yml @@ -1,6 +1,7 @@ disable-default-queries: true paths-ignore: - .cache + - pkgs/pkc/contrib/dash - public - target queries: diff --git a/pkgs/pkc/Cargo.toml b/pkgs/pkc/Cargo.toml index aa67fe0db..47c097aca 100644 --- a/pkgs/pkc/Cargo.toml +++ b/pkgs/pkc/Cargo.toml @@ -11,6 +11,7 @@ categories = [ ] exclude = [ "bench", + "contrib", "corpus", "tests", ] diff --git a/pkgs/pkc/contrib/dash b/pkgs/pkc/contrib/dash new file mode 160000 index 000000000..8163290aa --- /dev/null +++ b/pkgs/pkc/contrib/dash @@ -0,0 +1 @@ +Subproject commit 8163290aa0307ed4bc2fc0ba15b78c20d61dfa88 From 76704ecf1464c2fbfa999addf13e10da959f68dc Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 3 Nov 2026 09:37:46 +0000 Subject: [PATCH 2/4] sdk%build(nix): add `corpora.nix` with CMake, Boost and GMP The C++ programs that write test corpora build with CMake against Boost's headers and GMP. The CI shell includes the module, and the dev shell with it. --- contrib/nix/mods/corpora.nix | 11 +++++++++++ contrib/nix/shell/ci.nix | 1 + contrib/nix/shell/common.nix | 1 + 3 files changed, 13 insertions(+) create mode 100644 contrib/nix/mods/corpora.nix diff --git a/contrib/nix/mods/corpora.nix b/contrib/nix/mods/corpora.nix new file mode 100644 index 000000000..5456034e7 --- /dev/null +++ b/contrib/nix/mods/corpora.nix @@ -0,0 +1,11 @@ +# Build dependencies of the C++ programs that write test corpora + +{ pkgs }: + +{ + packages = [ + pkgs.boost + pkgs.cmake + pkgs.gmp + ]; +} diff --git a/contrib/nix/shell/ci.nix b/contrib/nix/shell/ci.nix index ff1219072..b3a08885d 100644 --- a/contrib/nix/shell/ci.nix +++ b/contrib/nix/shell/ci.nix @@ -14,6 +14,7 @@ compose [ (cxx.forWasm commonTargets) mods.bazel mods.codeql + mods.corpora mods.cxx mods.fhs mods.nixpkgs diff --git a/contrib/nix/shell/common.nix b/contrib/nix/shell/common.nix index 6e6ec1639..b982a39a2 100644 --- a/contrib/nix/shell/common.nix +++ b/contrib/nix/shell/common.nix @@ -92,6 +92,7 @@ in mods = lib.mapAttrs (name: m: m // { _name = name; }) { bazel = import ../mods/bazel.nix { inherit pkgs; }; codeql = import ../mods/codeql.nix { inherit pkgs fhs; }; + corpora = import ../mods/corpora.nix { inherit pkgs; }; cxx = cxx.compiler; inherit fhs; nixpkgs = import ../mods/nixpkgs.nix { inherit pkgs llvm; }; From 4feb192ac9438c7b943f9fba8680deb65116a7f6 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 3 Nov 2026 09:37:46 +0000 Subject: [PATCH 3/4] pkc%test(bls): add programs that write the BLS corpora Each program links the pinned release's BLS library and writes one corpus. regen.py builds them with CMake and writes the nine corpora, and the C/C++ linters read its last build when there is one. --- maint/.clang-tidy | 2 +- maint/codeql/cpp/suppressions.toml | 6 + maint/common.py | 22 +- maint/lint/lint_codeql.py | 173 +++-- pkgs/pkc/contrib/CMakeLists.txt | 94 +++ pkgs/pkc/contrib/README.md | 49 ++ pkgs/pkc/contrib/gen_aggregate.cpp | 195 ++++++ pkgs/pkc/contrib/gen_hash.cpp | 109 ++++ pkgs/pkc/contrib/gen_ies.cpp | 90 +++ pkgs/pkc/contrib/gen_keygen.cpp | 37 ++ pkgs/pkc/contrib/gen_llmq_100.cpp | 308 +++++++++ pkgs/pkc/contrib/gen_secure_aggregate.cpp | 88 +++ pkgs/pkc/contrib/gen_serialize.cpp | 39 ++ pkgs/pkc/contrib/gen_sign.cpp | 105 ++++ pkgs/pkc/contrib/gen_threshold.cpp | 85 +++ pkgs/pkc/contrib/regen.py | 253 ++++++++ pkgs/pkc/contrib/util/common.hpp | 356 +++++++++++ pkgs/pkc/contrib/util/core.hpp | 48 ++ pkgs/pkc/contrib/util/no_random.cpp | 17 + pkgs/pkc/contrib/util/relic_conf.patch | 727 ++++++++++++++++++++++ 20 files changed, 2736 insertions(+), 67 deletions(-) create mode 100644 maint/codeql/cpp/suppressions.toml create mode 100644 pkgs/pkc/contrib/CMakeLists.txt create mode 100644 pkgs/pkc/contrib/README.md create mode 100644 pkgs/pkc/contrib/gen_aggregate.cpp create mode 100644 pkgs/pkc/contrib/gen_hash.cpp create mode 100644 pkgs/pkc/contrib/gen_ies.cpp create mode 100644 pkgs/pkc/contrib/gen_keygen.cpp create mode 100644 pkgs/pkc/contrib/gen_llmq_100.cpp create mode 100644 pkgs/pkc/contrib/gen_secure_aggregate.cpp create mode 100644 pkgs/pkc/contrib/gen_serialize.cpp create mode 100644 pkgs/pkc/contrib/gen_sign.cpp create mode 100644 pkgs/pkc/contrib/gen_threshold.cpp create mode 100755 pkgs/pkc/contrib/regen.py create mode 100644 pkgs/pkc/contrib/util/common.hpp create mode 100644 pkgs/pkc/contrib/util/core.hpp create mode 100644 pkgs/pkc/contrib/util/no_random.cpp create mode 100644 pkgs/pkc/contrib/util/relic_conf.patch diff --git a/maint/.clang-tidy b/maint/.clang-tidy index 6023bb9b1..2f503f6ef 100644 --- a/maint/.clang-tidy +++ b/maint/.clang-tidy @@ -1,5 +1,5 @@ HeaderFilterRegex: '.*' -ExcludeHeaderFilterRegex: '/fuzz/pow/sphlib/' +ExcludeHeaderFilterRegex: '/fuzz/pow/sphlib/|/target/bls-corpus/' WarningsAsErrors: '*' ExtraArgs: ['-Wdocumentation'] Checks: ' diff --git a/maint/codeql/cpp/suppressions.toml b/maint/codeql/cpp/suppressions.toml new file mode 100644 index 000000000..800edc0e8 --- /dev/null +++ b/maint/codeql/cpp/suppressions.toml @@ -0,0 +1,6 @@ +# The corpus programs link a stand-in for the release's strong random source +# that aborts, so a program that draws random bytes stops rather than writing +# a corpus that differs from run to run. +[[finding]] +path = "pkgs/pkc/contrib/util/no_random.cpp" +message = "Explicit termination of the program" diff --git a/maint/common.py b/maint/common.py index 00d201e0d..0502518b7 100644 --- a/maint/common.py +++ b/maint/common.py @@ -57,6 +57,21 @@ # Each entry mirrors its directory's build script and changes with it. CXX_INCLUDES: dict[str, tuple[str, ...]] = { "fuzz/pow": ("fuzz/pow/sphlib/c",), + # The last build of `regen.py`, absent until it runs. + "pkgs/pkc/contrib": ( + "pkgs/pkc/contrib", + "target/bls-corpus/current/build/dashbls/depends/relic/include", + "target/bls-corpus/current/build/generated", + "target/bls-corpus/current/src", + "target/bls-corpus/current/src/dashbls/depends/relic/include", + "target/bls-corpus/current/src/dashbls/include", + "target/bls-corpus/current/src/dashbls/include/dashbls", + ), +} + +# Standards of units built otherwise, keyed by the directory holding them. +CXX_STDS: dict[str, str] = { + "pkgs/pkc/contrib": "c++20", } # Assumed base branch for codebase. @@ -329,10 +344,13 @@ def cxx_flags(repo_root: Path, unit: Path) -> list[str]: """Return the flags *unit* is compiled with. Raises FileNotFoundError naming an absent include directory, which is - what an uninitialised submodule leaves behind. + what an uninitialised submodule or a build not yet run leaves behind. """ - std = C_STD if unit.suffix in C_UNITS else CPP_STD relative = unit.relative_to(repo_root) + std = next( + (s for holder, s in CXX_STDS.items() if relative.is_relative_to(holder)), + C_STD if unit.suffix in C_UNITS else CPP_STD, + ) includes = [ include for holder, held in CXX_INCLUDES.items() diff --git a/maint/lint/lint_codeql.py b/maint/lint/lint_codeql.py index 2eb18192a..342b87573 100755 --- a/maint/lint/lint_codeql.py +++ b/maint/lint/lint_codeql.py @@ -58,6 +58,11 @@ # `[[finding]]` names a `path` and a `message`, both matched whole. _SUPPRESSIONS = "suppressions.toml" +# Directories of standalone programs. A database joins its units into one +# program, so each unit directly in one of these is a program analysed in a +# database of its own, with the units below it, which every program links. +_PROGRAMS: tuple[str, ...] = ("pkgs/pkc/contrib",) + # QL a run writes into the tree, relative to a language's pack directory. _GENERATED_QL = ("lib", "source_lines.qll") @@ -227,8 +232,13 @@ def _print_csv_diagnostics( results_path: Path, suppressions: frozenset[tuple[str, str]], owned: frozenset[str], + seen: set[tuple[str, str, str]], ) -> int: - """Print the findings in *owned* files to stderr, returning how many.""" + """Print the findings in *owned* files to stderr, returning how many. + + A finding in *seen*, which an earlier database of the run reported, is + not printed again. + """ count = 0 with results_path.open(newline="") as f: for row in csv.reader(f): @@ -241,6 +251,9 @@ def _print_csv_diagnostics( msg = row[3].replace("\n", " ") if str(uri) not in owned or (str(uri), msg) in suppressions: continue + if (str(uri), line, msg) in seen: + continue + seen.add((str(uri), line, msg)) print(f"{uri}:{line}: {msg}", file=sys.stderr) count += 1 return count @@ -262,13 +275,32 @@ def _locked_pack(pack: str, query_dir: Path) -> str: return f"{pack}@{match.group(1)}" +def _targets(repo_root: Path, language: Language) -> dict[str, list[Path]]: + """Return the units of *language* by the database that analyses them.""" + patterns = [f"*{s}" for s in language.units] + targets = { + "tree": repo_files( + repo_root, + *patterns, + *(f":(exclude){d}" for d in _PROGRAMS), + ), + } + for directory in _PROGRAMS: + units = repo_files(repo_root, *(f"{directory}/{p}" for p in patterns)) + top = repo_root / directory + linked = [u for u in units if u.parent != top] + for program in (u for u in units if u.parent == top): + targets[program.stem] = [program, *linked] + return targets + + def _build_commands( repo_root: Path, language: Language, -) -> list[str]: +) -> dict[Path, str]: """Return the `--command` options compiling each unit of *language*.""" compiler = require_bin(language.compiler) - options: list[str] = [] + options: dict[Path, str] = {} for unit in repo_files(repo_root, *(f"*{s}" for s in language.units)): try: flags = cxx_flags(repo_root, unit) @@ -282,7 +314,7 @@ def _build_commands( command = subprocess.list2cmdline(argv) else: command = shlex.join(argv) - options.append(f"--command={command}") + options[unit] = f"--command={command}" return options @@ -300,7 +332,7 @@ def _workspace_dirs( *, cache: bool, ) -> Iterator[tuple[Path, Path]]: - """Yield ``(db_path, results_dir)`` for analysis. + """Yield ``(db_root, results_dir)``, each target's database in the root. When *cache* is false a temporary directory is used for both the database and the results so no persistent cache is created @@ -399,16 +431,22 @@ def _analyse( # Without a command the extractor parses the sources on disk, so a query # sees every cfg rather than one build's selection. - build: list[str] = ["--build-mode=none"] + builds: dict[str, list[str]] = {"tree": ["--build-mode=none"]} if language.units: - build = _build_commands(repo_root, language) - if not build: + commands = _build_commands(repo_root, language) + builds = { + name: options + for name, units in _targets(repo_root, language).items() + if (options := [commands[u] for u in units if u in commands]) + } + if not builds: print(f"no units for {language.name}, skipping", file=sys.stderr) return None print( f"checking {language.name}: {len(queries)} query(s), " - f"{len(language.standards) + len(args.suites)} suite(s)", + f"{len(language.standards) + len(args.suites)} suite(s), " + f"{len(builds)} database(s)", ) # Only Rust needs the pre-scanned source text; see the generator itself. @@ -441,72 +479,79 @@ def _analyse( ) cache_dir = query_dir / ".cache" + owned = frozenset( + str(p.relative_to(repo_root)) for p in repo_files(repo_root) + ) + suppressions = _load_suppressions(query_dir) + seen: set[tuple[str, str, str]] = set() + total_findings = 0 with _workspace_dirs(cache_dir, cache=args.cache) as ( - active_db, + db_root, results_dir, ): - db_yml = active_db / "codeql-database.yml" - if not db_yml.is_file(): - # Remove stale artifacts left behind by a previous failed run. - if active_db.exists(): - shutil.rmtree(active_db) - active_db.parent.mkdir(parents=True, exist_ok=True) - config_file = query_dir / "codeql-config.yml" - config = ( - [f"--codescanning-config={config_file}"] - if config_file.is_file() - else [] - ) - result = subprocess.run( # noqa: S603 + for target, build in builds.items(): + active_db = db_root / target + db_yml = active_db / "codeql-database.yml" + if not db_yml.is_file(): + # Remove stale artifacts left behind by a previous failed run. + if active_db.exists(): + shutil.rmtree(active_db) + active_db.parent.mkdir(parents=True, exist_ok=True) + config_file = query_dir / "codeql-config.yml" + config = ( + [f"--codescanning-config={config_file}"] + if config_file.is_file() + else [] + ) + result = subprocess.run( # noqa: S603 + [ + codeql_bin, + "database", + "create", + str(active_db), + f"--language={language.name}", + *build, + f"--source-root={repo_root}", + *config, + f"-j{usable_threads()}", + ], + cwd=str(repo_root), + env=cxx_environ() if language.units else None, + check=False, + ) + if result.returncode != RETCODE_PASS or not db_yml.is_file(): + raise RuntimeError( + f"codeql database create failed for {language.name} {target} " + f"(exit {result.returncode}); database manifest {db_yml} was not " + "produced", + ) + + # Run each query and collect diagnostics. + results_path = results_dir / f"{target}.csv" + + subprocess.run( # noqa: S603 [ codeql_bin, "database", - "create", + "analyze", str(active_db), - f"--language={language.name}", - *build, - f"--source-root={repo_root}", - *config, - f"-j{usable_threads()}", + *[str(q) for q in queries], + *suites, + "--format=csv", + f"--output={results_path}", + f"--threads={usable_threads()}", + f"--ram={usable_mem()}", ], - cwd=str(repo_root), - env=cxx_environ() if language.units else None, - check=False, + check=True, ) - if result.returncode != RETCODE_PASS or not db_yml.is_file(): - raise RuntimeError( - f"codeql database create failed for {language.name} (exit " - f"{result.returncode}); database manifest {db_yml} was not produced", - ) - # Run each query and collect diagnostics. - results_path = results_dir / "results.csv" - - subprocess.run( # noqa: S603 - [ - codeql_bin, - "database", - "analyze", - str(active_db), - *[str(q) for q in queries], - *suites, - "--format=csv", - f"--output={results_path}", - f"--threads={usable_threads()}", - f"--ram={usable_mem()}", - ], - check=True, - ) - - owned = frozenset( - str(p.relative_to(repo_root)) for p in repo_files(repo_root) - ) - total_findings = _print_csv_diagnostics( - results_path, - _load_suppressions(query_dir), - owned, - ) + total_findings += _print_csv_diagnostics( + results_path, + suppressions, + owned, + seen, + ) return RETCODE_ERR if total_findings > 0 else RETCODE_PASS diff --git a/pkgs/pkc/contrib/CMakeLists.txt b/pkgs/pkc/contrib/CMakeLists.txt new file mode 100644 index 000000000..4094e1263 --- /dev/null +++ b/pkgs/pkc/contrib/CMakeLists.txt @@ -0,0 +1,94 @@ +# +# Copyright (c) 2026-present, The Dash Core developers +# SPDX-License-Identifier: MIT +# See the accompanying file LICENSE or https://opensource.org/license/MIT +# + +# Builds the corpus programs against a release's BLS library. + +cmake_minimum_required(VERSION 3.18) +project(corpora LANGUAGES C CXX) + +set(DASH_SRC "" CACHE PATH "release src tree with relic's template restored") +if(NOT EXISTS "${DASH_SRC}/dashbls/depends/relic/include/relic_conf.h.in") + message(FATAL_ERROR "DASH_SRC is not a release's src with the template") +endif() + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# The library with the GMP backend a release has, and without its tests, +# benchmarks and bindings. +list(APPEND CMAKE_MODULE_PATH "${DASH_SRC}/dashbls/cmake_modules") +find_package(gmp REQUIRED) +set(ARITH "gmp" CACHE STRING "") +set(BUILD_BLS_BENCHMARKS 0 CACHE STRING "") +set(BUILD_BLS_PYTHON_BINDINGS 0 CACHE STRING "") +set(BUILD_BLS_TESTS 0 CACHE STRING "") +add_subdirectory("${DASH_SRC}/dashbls" dashbls EXCLUDE_FROM_ALL) + +# The release's sources refuse NDEBUG, which a release build type defines. +foreach(config RELEASE RELWITHDEBINFO MINSIZEREL) + string( + REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_${config} + "${CMAKE_CXX_FLAGS_${config}}" + ) +endforeach() + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# gen_llmq_100 includes the release's commitment hash function as it is, as +# the rest of its source file needs much more of the release. +set(generated "${CMAKE_CURRENT_BINARY_DIR}/generated") +file(READ "${DASH_SRC}/llmq/commitment.cpp" source) +string(FIND "${source}" "\nuint256 BuildCommitmentHash(" start) +if(start EQUAL -1) + message(FATAL_ERROR "no commitment hash function in the release") +endif() +string(SUBSTRING "${source}" ${start} -1 source) +string(FIND "${source}" "\n}" end) +if(end EQUAL -1) + message(FATAL_ERROR "the commitment hash function does not end") +endif() +math(EXPR end "${end} + 2") +string(SUBSTRING "${source}" 1 ${end} source) +# Written only when it changes, so the program is not rebuilt for nothing. +set(include "${generated}/commitment_hash.inc") +set(written "") +if(EXISTS "${include}") + file(READ "${include}" written) +endif() +if(NOT written STREQUAL source) + file(WRITE "${include}" "${source}") +endif() + +# The release's sources the programs link, and a stand-in for its random +# source, which no program draws from. The release's BLS wrapper allocates +# from a Boost pool. +find_package(Boost REQUIRED CONFIG) +add_library( + release OBJECT + "${DASH_SRC}/bls/bls.cpp" + "${DASH_SRC}/crypto/sha256.cpp" + "${DASH_SRC}/crypto/sha256_sse4.cpp" + "${DASH_SRC}/support/cleanse.cpp" + "${DASH_SRC}/support/lockedpool.cpp" + "${DASH_SRC}/uint256.cpp" + "${DASH_SRC}/util/strencodings.cpp" + util/no_random.cpp +) +target_include_directories( + release PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${generated}" "${DASH_SRC}" + "${DASH_SRC}/dashbls/include" +) +target_link_libraries(release PUBLIC dashbls PRIVATE Boost::headers) + +file(GLOB programs CONFIGURE_DEPENDS gen_*.cpp) +foreach(program IN LISTS programs) + get_filename_component(name "${program}" NAME_WE) + add_executable(${name} "${program}") + target_link_libraries(${name} PRIVATE release) +endforeach() diff --git a/pkgs/pkc/contrib/README.md b/pkgs/pkc/contrib/README.md new file mode 100644 index 000000000..b0bee585a --- /dev/null +++ b/pkgs/pkc/contrib/README.md @@ -0,0 +1,49 @@ +The programs here write the BLS corpora below in `pkgs/pkc/corpus` from a reference implementation release. Every value +comes from the release's BLS library (dashbls, with relic) or from its own sources, namely its BLS wrapper, +serialization and hashing, and commitment hash function, apart from fixed inputs at the top of each program and in +`util/common.hpp`. + +| Corpus | Program | +| --- | --- | +| `bls_aggregate.json5` | `gen_aggregate.cpp` | +| `bls_hash.json5` | `gen_hash.cpp` | +| `bls_ies.json5` | `gen_ies.cpp` | +| `bls_keygen.json5` | `gen_keygen.cpp` | +| `bls_llmq_100.json5` | `gen_llmq_100.cpp` | +| `bls_secure_aggregate.json5` | `gen_secure_aggregate.cpp` | +| `bls_serialize.json5` | `gen_serialize.cpp` | +| `bls_sign.json5` | `gen_sign.cpp` | +| `bls_threshold.json5` | `gen_threshold.cpp` | + +Each program prints its corpus on stdout. For `bls_llmq_100.json5`, its `member_pks` and `member_sig` values are fixed +inputs, which the program checks against the commitment hash it computes. For `bls_hash.json5`, the program repeats the +legacy map's hashing and reduction steps with relic, which the library does not expose, and the library's own point +anchors them. + +## Regenerating + +`regen.py` needs Python 3.11.4 or later, git, CMake, a C compiler, a C++20 compiler, Boost and GMP, all of which the +development shell provides. The `dash` submodule pins the release, and `regen.py` checks it out when it is not yet. It +extracts the release's `src` into `target/bls-corpus`, patches back in the relic CMake configuration template the +release leaves out, and builds the library and the programs there with CMake. The build is kept for later runs with the +same compilers, flags and generator. Nothing is written unless every program succeeds. + +```bash +pkgs/pkc/contrib/regen.py +``` + +To write one corpus, or to write somewhere else: + +```bash +pkgs/pkc/contrib/regen.py --program keygen --corpus /tmp/bls +``` + +`regen.py` stops when the submodule is not checked out at the staged pin, for example after a pull that moved the pin. +This checks it out at the staged pin: + +```bash +git submodule update --init --checkout -- pkgs/pkc/contrib/dash +``` + +To write the corpora from another release, check the submodule out first, then fetch and check out the release's tag in +it and stage it. diff --git a/pkgs/pkc/contrib/gen_aggregate.cpp b/pkgs/pkc/contrib/gen_aggregate.cpp new file mode 100644 index 000000000..6cb2753f8 --- /dev/null +++ b/pkgs/pkc/contrib/gen_aggregate.cpp @@ -0,0 +1,195 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_aggregate.json5, aggregates and their verdicts. + +#include "util/common.hpp" + +#include + +using namespace corpus; + +namespace { +//! The signer counts of the key, signature and digest aggregates. +constexpr std::array kSizes = {2, 3, 5}; +//! Signer k of a digest aggregate signs 32 bytes of k times this byte. +constexpr uint8_t kDigestByte = 0x11; + +//! Where the legacy message aggregate comes from. +constexpr Source kLegacySource = { + "Sourced from the pre-1.0 Chia bls-signatures \"Test vectors 1\"", + "https://github.com/Chia-Network/bls-signatures/blob/0.1.15/src/test.cpp#L31-L83", +}; +//! The legacy message aggregate, where one key signs two messages. +constexpr std::array kLegacyMessages = {{ + {"022fb42c08c12de3a6af053880199806532e79515f94e83461612101f9412f9e", "010203"}, + {"022fb42c08c12de3a6af053880199806532e79515f94e83461612101f9412f9e", "01020304"}, + {"502c5661f5af46ed48ddc3b5332e21b93cc7d0a84df46c4b9c7fe8f25ef48d66", "0102"}, +}}; + +//! Where the basic message aggregates come from. +constexpr Source kBasicSource = { + "Sourced from the dashbls basic scheme KAT, whose second key derives from 32 bytes " + "of 0x01 and is pinned upstream only by fingerprint b839add1.", + "https://github.com/dashpay/bls-signatures/blob/1.3.5/src/test.cpp#L336-L402", +}; +//! The basic scheme KAT aggregate, two keys that sign a message each. +constexpr std::array kBasicKat = {{ + {"4a353be3dac091a0a7e640620372f5e1e2e4401717c1e79cac6ffba8f6905604", "070809"}, + {"6fc9d9a2b05fd1f0e51bc91041a03be8657081f272ec281aff731624f0d1c220", "0a0b0c"}, +}}; +//! The KAT's keys over the legacy aggregate's messages. +constexpr std::array kBasicMessages = {{ + {"4a353be3dac091a0a7e640620372f5e1e2e4401717c1e79cac6ffba8f6905604", "010203"}, + {"4a353be3dac091a0a7e640620372f5e1e2e4401717c1e79cac6ffba8f6905604", "01020304"}, + {"6fc9d9a2b05fd1f0e51bc91041a03be8657081f272ec281aff731624f0d1c220", "0102"}, +}}; + +struct Signer { + bls::PrivateKey sk; + Bytes msg; +}; + +/** @return The signers of published @p vectors. */ +std::vector Signers(std::span vectors) +{ + std::vector out; + out.reserve(vectors.size()); + for (const auto& v : vectors) { + out.push_back({Sk(Unhex(v.sk)), Unhex(v.msg)}); + } + return out; +} + +/** Adds one aggregate signature over @p signers, checked by the library. */ +void Verify( + Json& rows, + const std::vector& signers, + bool legacy, + bool hash_msgs, + const bls::G2Element* agg_sig = nullptr +) +{ + std::vector pks; + std::vector msgs; + std::vector sigs; + for (const auto& s : signers) { + pks.push_back(s.sk.GetG1Element()); + msgs.push_back(s.msg); + sigs.push_back(hash_msgs ? SignMessage(s.sk, s.msg, legacy) : SignDigest(s.sk, s.msg, legacy)); + } + const auto sig = agg_sig ? *agg_sig : Mpl(legacy).Aggregate(sigs); + std::vector signed_msgs; + signed_msgs.reserve(msgs.size()); + std::vector digests; + digests.reserve(msgs.size()); + for (const auto& m : msgs) { + digests.push_back(hash_msgs && legacy ? Sha256(m) : m); + } + for (const auto& d : digests) { + signed_msgs.emplace_back(d); + } + + Json& row = rows.Push(Json::Object()); + Json& pk_list = row.Set("pks", Json::List()); + for (const auto& pk : pks) { + pk_list.Push(Json::Str(PkHex(pk, legacy))); + } + Json& msg_list = row.Set("msgs", Json::List()); + for (const auto& m : msgs) { + msg_list.Push(Json::Str(Hex(m))); + } + row.Set("agg_sig", Json::Str(SigHex(sig, legacy))); + row.Set("accepted", Json::Bool(Mpl(legacy).AggregateVerify(pks, signed_msgs, sig))); +} + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + Json root = Json::Object(); + Json& base = root.Set("base", Json::Object()).Set("sk", Json::List()); + for (size_t n : kSizes) { + std::vector sks; + for (size_t k = 1; k <= n; ++k) { + sks.push_back(SkOf(k)); + } + Json& row = base.Push(Json::Object()); + Json& list = row.Set("sks", Json::List()); + for (const auto& sk : sks) { + list.Push(Json::Str(SkHex(sk))); + } + row.Set("agg_sk", Json::Str(SkHex(bls::PrivateKey::Aggregate(sks)))); + } + + for (const auto& scheme : kSchemes) { + const bool legacy = scheme.legacy; + Json& arm = root.Set(scheme.name, Json::Object()); + + Json& pk_rows = arm.Set("pk", Json::List()); + for (size_t n : kSizes) { + std::vector pks; + for (size_t k = 1; k <= n; ++k) { + pks.push_back(SkOf(k).GetG1Element()); + } + Json& row = pk_rows.Push(Json::Object()); + Json& list = row.Set("pks", Json::List()); + for (const auto& pk : pks) { + list.Push(Json::Str(PkHex(pk, legacy))); + } + row.Set("agg_pk", Json::Str(PkHex(Mpl(legacy).Aggregate(pks), legacy))); + } + + Json& sig_rows = arm.Set("sig", Json::List()); + for (size_t n : kSizes) { + std::vector sigs; + for (size_t k = 1; k <= n; ++k) { + sigs.push_back(SignDigest(SkOf(k), Digests()[0], legacy)); + } + Json& row = sig_rows.Push(Json::Object()); + Json& list = row.Set("sigs", Json::List()); + for (const auto& sig : sigs) { + list.Push(Json::Str(SigHex(sig, legacy))); + } + row.Set("agg_sig", Json::Str(SigHex(Mpl(legacy).Aggregate(sigs), legacy))); + } + + // The last row's two signers sign one digest. + Json& verify = arm.Set("verify", Json::List()); + for (size_t n : kSizes) { + std::vector signers; + for (size_t k = 1; k <= n; ++k) { + signers.push_back({SkOf(k), Fill(kDigestByte * k, 32)}); + } + Verify(verify, signers, legacy, /*hash_msgs=*/false); + } + const Bytes digest = Fill(kDigestByte, 32); + Verify(verify, {{SkOf(1), digest}, {SkOf(2), digest}}, legacy, /*hash_msgs=*/false); + + Json& by_msg = arm.Set("verify_message", Json::List()); + if (legacy) { + by_msg.Cite(kLegacySource); + Verify(by_msg, Signers(kLegacyMessages), legacy, /*hash_msgs=*/true); + } else { + by_msg.Cite(kBasicSource); + const auto kat = Signers(kBasicKat); + Verify(by_msg, kat, legacy, /*hash_msgs=*/true); + const auto lone = SignMessage(kat.at(0).sk, kat.at(0).msg, legacy); + Verify(by_msg, kat, legacy, /*hash_msgs=*/true, &lone); + by_msg.Item(1).members.front().second.Note( + "The first signer's lone signature is not the aggregate." + ); + Verify(by_msg, Signers(kBasicMessages), legacy, /*hash_msgs=*/true); + by_msg.Item(2).members.front().second.Note("One key signs two distinct messages."); + } + } + return Print(root); +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/contrib/gen_hash.cpp b/pkgs/pkc/contrib/gen_hash.cpp new file mode 100644 index 000000000..a6aceb965 --- /dev/null +++ b/pkgs/pkc/contrib/gen_hash.cpp @@ -0,0 +1,109 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_hash.json5, the steps of the legacy hash to G2. + +#include "util/common.hpp" + +#include + +using namespace corpus; + +namespace { +/** A tag the legacy map hashes a message with, and its name in the corpus. */ +struct Tag { + std::string_view tag; + const char* name; +}; + +//! The tags, in the order the legacy map hashes them. +constexpr std::array kTags = {{ + {"G2_0_c0", "t00"}, + {"G2_0_c1", "t01"}, + {"G2_1_c0", "t10"}, + {"G2_1_c1", "t11"}, +}}; + +/** + * Repeats the legacy map's hashing of one tag, which the library does not + * expose. + * + * @return SHA-256(msg || tag || 0) || SHA-256(msg || tag || 1). + */ +Bytes TagHash(const Bytes& msg, std::string_view tag) +{ + Bytes input(msg); + input.insert(input.end(), tag.begin(), tag.end()); + input.push_back(0); + Bytes out(2 * RLC_MD_LEN); + md_map(out.data(), input.data(), input.size()); + input.back() = 1; + md_map(out.data() + RLC_MD_LEN, input.data(), input.size()); + return out; +} + +/** @return @p hash read as a big-endian integer, reduced to a field element. */ +Bytes ToField(const Bytes& hash) +{ + bn_t big; + fp_t elem; + bn_null(big); + fp_null(elem); + bn_new(big); + fp_new(elem); + bn_read_bin(big, hash.data(), hash.size()); + fp_prime_conv(elem, big); + Bytes out(RLC_FP_BYTES); + fp_write_bin(out.data(), out.size(), elem); + fp_free(elem); + bn_free(big); + return out; +} + +/** @return The library's legacy map of @p msg, which anchors the steps. */ +bls::G2Element MapLegacy(const Bytes& msg) +{ + g2_t point; + g2_null(point); + g2_new(point); + ep2_map_legacy(point, msg.data(), msg.size()); + const auto out = bls::G2Element::FromNative(point); + g2_free(point); + return out; +} + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + bls::BLS::Init(); + Json root = Json::Object(); + Json& rows = root.Set("chia", Json::List()); + for (const auto& msg : Digests()) { + Json& row = rows.Push(Json::Object()); + row.Set("msg", Json::Str(Hex(msg))); + std::vector hashes; + hashes.reserve(kTags.size()); + for (const auto& tag : kTags) { + hashes.push_back(TagHash(msg, tag.tag)); + } + for (size_t i = 0; i < kTags.size(); ++i) { + row.Set(std::string(kTags.at(i).name) + "_hash", Json::Str(Hex(hashes.at(i)))); + } + for (size_t i = 0; i < kTags.size(); ++i) { + row.Set(std::string(kTags.at(i).name) + "_fp", Json::Str(Hex(ToField(hashes.at(i))))); + } + const auto point = MapLegacy(msg); + row.Set("hash_to_g2_legacy", Json::Str(SigHex(point, /*legacy=*/true))); + row.Set("hash_to_g2_uncomp", Json::Str(SigHex(point, /*legacy=*/false))); + } + return Print(root); +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/contrib/gen_ies.cpp b/pkgs/pkc/contrib/gen_ies.cpp new file mode 100644 index 000000000..c71dcb026 --- /dev/null +++ b/pkgs/pkc/contrib/gen_ies.cpp @@ -0,0 +1,90 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_ies.json5, encrypted blobs as the release serializes them. + +#include "util/core.hpp" + +#include +#include + +using namespace corpus; + +namespace { +//! The byte the ephemeral key's 32-byte seed repeats. +constexpr uint8_t kEphemeralSeedByte = 0x01; +//! The byte the 32-byte IV seed repeats. +constexpr uint8_t kIvSeedByte = 0x01; +//! The byte a single blob repeats. +constexpr uint8_t kBlobByte = 0x11; +//! The byte the blob of recipient 0 repeats. Recipient i's repeats this +//! plus i. +constexpr uint8_t kRecipientByte = 0xa0; +//! The data length of each single blob. +constexpr std::array kBlobLens = {0, 16, 32, 252, 253}; +//! The data lengths of each multi-recipient set's blobs, by recipient. +constexpr std::array, 5> kMultiLens = { + {{}, {0}, {16}, {16, 0, 32}, {252, 253}} +}; + +template +std::string Image(const T& obj) +{ + CDataStream ds(SER_NETWORK, 0); + ds << obj; + return Hex(ds); +} + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + BLSInit(); + const auto eph_sk = bls::BasicSchemeMPL().KeyGen(Fill(kEphemeralSeedByte, 32)); + const Bytes seed = Fill(kIvSeedByte, 32); + + Json root = Json::Object(); + for (const auto& scheme : kSchemes) { + UseLegacy(scheme.legacy); + const auto eph_pk = + FromBytes(eph_sk.GetG1Element().Serialize(scheme.legacy), scheme.legacy); + const auto iv_seed = uint256(seed); + Json& arm = root.Set(scheme.name, Json::Object()); + + Json& blobs = arm.Set("blob", Json::List()); + for (size_t len : kBlobLens) { + CBLSIESEncryptedBlob blob; + blob.ephemeralPubKey = eph_pk; + blob.ivSeed = iv_seed; + blob.data = Fill(kBlobByte, len); + Json& row = blobs.Push(Json::Object()); + row.Set("data_len", Json::Int(len)); + row.Set("image", Json::Str(Image(blob))); + } + + Json& multis = arm.Set("multi", Json::List()); + for (const auto& lens : kMultiLens) { + CBLSIESMultiRecipientBlobs multi; + multi.ephemeralPubKey = eph_pk; + multi.ivSeed = iv_seed; + Json& row = multis.Push(Json::Object()); + Json& list = row.Set("blob_lens", Json::List()); + list.inline_list = true; + uint8_t byte = kRecipientByte; + for (size_t len : lens) { + multi.blobs.push_back(Fill(byte++, len)); + list.Push(Json::Int(len)); + } + row.Set("image", Json::Str(Image(multi))); + } + } + return Print(root); +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/contrib/gen_keygen.cpp b/pkgs/pkc/contrib/gen_keygen.cpp new file mode 100644 index 000000000..8a5f4e585 --- /dev/null +++ b/pkgs/pkc/contrib/gen_keygen.cpp @@ -0,0 +1,37 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_keygen.json5, the public key of each secret key. + +#include "util/common.hpp" + +using namespace corpus; + +namespace { +//! The secret keys whose 32 bytes all equal k, for k from 1 to this count. +constexpr uint8_t kKeys = 5; + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + Json root = Json::Object(); + for (const auto& scheme : kSchemes) { + Json& rows = root.Set(scheme.name, Json::Object()).Set("derive_pk", Json::List()); + for (uint8_t k = 1; k <= kKeys; ++k) { + const auto sk = Sk(Fill(k, 32)); + Json& row = rows.Push(Json::Object()); + row.Set("sk", Json::Str(SkHex(sk))); + row.Set("pk", Json::Str(PkHex(sk.GetG1Element(), scheme.legacy))); + } + } + return Print(root); +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/contrib/gen_llmq_100.cpp b/pkgs/pkc/contrib/gen_llmq_100.cpp new file mode 100644 index 000000000..b17cdff59 --- /dev/null +++ b/pkgs/pkc/contrib/gen_llmq_100.cpp @@ -0,0 +1,308 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_llmq_100.json5 for a 2-of-3 quorum of LLMQ type 100. + +#include "util/core.hpp" + +#include +#include + +namespace llmq { +#include "commitment_hash.inc" +} // namespace llmq + +using namespace corpus; + +namespace { +//! The LLMQ type of the quorum, the reference implementation's test type +//! of three members and a threshold of two. +constexpr int kType = 100; +//! How many members' shares recover a quorum signature. +constexpr int kThreshold = 2; +//! How many members the quorum has. +constexpr int kSize = 3; + +/** The fixed inputs of one scheme's quorum. */ +struct Arm { + //! The quorum hash, hex in display order. + const char* quorum_hash; + //! Each member's id, hex in display order. + std::array member_ids; + //! The secret coefficients of each member's polynomial, constant first. + std::array, kSize> coefficients; + //! The members whose shares recover the quorum signature, in order. + std::array signers; + //! The members' public keys, by member index. + std::array member_pks; + //! Each member's signature of the commitment hash the program computes, + //! by member index. The program stops if one does not verify. + std::array member_sigs; +}; + +//! The fixed inputs of each scheme's quorum, in the order of the schemes. +constexpr std::array kArms = {{ + {/*quorum_hash=*/"327186a034d2dac99bc5c3557fd204ffca52aa8d21cbe426fe39a09145b6dd59", + /*member_ids=*/ + {"8ba294ac671bb4f19508b0308afe57cb12edcc9f05d03f4339ca4b6b32732e3c", + "64755b06aa0774424a29b547f06a0f6c554103f3e43c70e68ecaa1b6f4b776d8", + "15d1f68ed3267d3282164450ce8bf86eeb679e9634ea82c43c6b79dbfb1173e0"}, + /*coefficients=*/ + {{{"34ba9ccd889dd456afe0b0bac4d51b7ee6352d47536b54555de2614dc86a0cd3", + "41ec9149967b855ac629956d819e5185c75a3371532cb95ec057aa44db851a98"}, + {"3f20a4745aa51ffe0b40ea8534ab964f314017749b5bc7d6e2603cebcb942e43", + "456b687f5b0656d8691600edfb91935874438e9b9cee6c2c754416bd9ebd12eb"}, + {"101e8a256089b2e5e2f953e1427cf1fe9d273eed41e1b315b2cbacbcf552bf9a", + "6b06d9085f587f38fe7c2300cc3714d3a5464efe48ca88e7b02e4a493768586c"}}}, + /*signers=*/{1, 2, 0}, + /*member_pks=*/ + { + "9973a604265f6e2e59ab6f6642960a85d123d0f37f8ab249" + "1e2f72b06936b42a97ff568b18a9a076bbd730041091daae", + "86bed97997445ada054bb5bcb8c15b2a4c4080b86abaf68c" + "71cbb0b8a09da93a2697c185132c60e08c6572af235867fd", + "09f39e961fdf1093ae49de44bb28b7cc9a89e2feb94c52c7" + "9a5eab93523ac19c0271faa4f011b688d4fe4fd031e9d324", + }, + /*member_sigs=*/ + { + "183afbe080b30d9e20995d0ecc94a71951f7efe550e32b73" + "86ef868a27d2275f9aecd8a5dd0acc380ef3fbb9152824d1" + "0def056f2f21896caa1369b1e1410fcfde255bcd8207ef7a" + "fb204ad45b401b92261e9c1dc4d812597f45d77f0ae69b74", + "8b02d6de4c7949fd60af64b84bd1dc247d7d9bf4019f5e01" + "30941969dd75d08c7871c5c025d345875d51f0e7a59e8045" + "10fd8314d02aa881f10dcd30033af7c06fdece361c36a4ba" + "26298751ae2a51f6a317a2bb20f454cbf0f908400f054583", + "9561404eca6c54278c5f888c3d1f72b7616aa411a2cd731d" + "6c1e7417b640c0e76db11296980b7d36dcd04dbff80a504d" + "0d948c1ddba8a71da336f31de2255bf6df2312d68f6e85dc" + "85455fdef841a5762a9f8d3ac8d7c61a7dab39fb675b3310", + }}, + {/*quorum_hash=*/"255258ad1a339cd05cbecb5517eac0e2511f12ead05b24390d4bc4db74895b8e", + /*member_ids=*/ + {"15d1f68ed3267d3282164450ce8bf86eeb679e9634ea82c43c6b79dbfb1173e0", + "64755b06aa0774424a29b547f06a0f6c554103f3e43c70e68ecaa1b6f4b776d8", + "8ba294ac671bb4f19508b0308afe57cb12edcc9f05d03f4339ca4b6b32732e3c"}, + /*coefficients=*/ + {{{"35464ff53ee40eda3bdb82f9c6443eadef4aa3acf87231ead18f36f2847bcf9a", + "19ee4a888e6c45b2bbb2d0472359698b65e8f1a840049e7ed46d0c1299ec20ca"}, + {"38b95e82390ee5d43f482e4e6947d8a94a63b60d1162aca07e9d42fee221fd5b", + "46bed8332a1fd5a070bbcaa35077916a91febe85d19fdb27b513519c07d4cd61"}, + {"144e33e1f2b96863bec50c06128af70e895d6e07f4de693bd5d6aa12fff9c391", + "317e2fcc12ce5c08f9e8af139fc41fdb48684e21d62acf1422571ebf62040e01"}}}, + /*signers=*/{1, 0, 2}, + /*member_pks=*/ + { + "89f39e961fdf1093ae49de44bb28b7cc9a89e2feb94c52c7" + "9a5eab93523ac19c0271faa4f011b688d4fe4fd031e9d324", + "a6bed97997445ada054bb5bcb8c15b2a4c4080b86abaf68c" + "71cbb0b8a09da93a2697c185132c60e08c6572af235867fd", + "b973a604265f6e2e59ab6f6642960a85d123d0f37f8ab249" + "1e2f72b06936b42a97ff568b18a9a076bbd730041091daae", + }, + /*member_sigs=*/ + { + "b28b59ec564fb4dc21ded35ab02e603c18db7df59d5ee9d4" + "276001b7162a211fc8a9c7b3d9d495ccdd355fb809521169" + "09c77c1b8db707fc298fd28c8970ff21703aa3f6d7af5578" + "199f283e1d8dab29c720f19aea1cba15bce1531785634959", + "8e088be294c6790ce4992fe27f5de14a3ce36bb01117ee2d" + "6376f02f6f1c691ef62f6518ae0150e8ab54d869255ad996" + "0977d7ae8002ba393a1571770135f1de8ee50f008ec866a4" + "238baf31e451e905c6035f87de3e5e6e057959c6fd9d90b6", + "97f8443abb24b1e9fa27a65640f34af4e91f3b2966dea94d" + "e244e39fe918813660d34f2283bc751cb4a75aa684c82d92" + "029843e514fd94b3de92054583e7c681eeb4089e1c5b55d7" + "bc3d02e71f19b8c493fea64b060c0139e88d42d8e77bbdf6", + }}, +}}; +static_assert(kArms.size() == kSchemes.size()); + +template +Json HexList(const std::vector& objs, bool legacy) +{ + Json list = Json::List(); + for (const auto& obj : objs) { + list.Push(Json::Str(WrapperHex(obj, legacy))); + } + return list; +} + +Json Header(const Arm& arm, int member_idx) +{ + Json row = Json::Object(); + row.Set("quorum_hash", Json::Str(arm.quorum_hash)); + row.Set("llmq_type", Json::Int(kType)); + if (member_idx >= 0) { + row.Set("member_idx", Json::Int(member_idx)); + } + return row; +} + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + BLSInit(); + Json root = Json::Object(); + for (size_t a = 0; a < kArms.size(); ++a) { + const bool legacy = kSchemes.at(a).legacy; + const Arm& arm = kArms.at(a); + UseLegacy(legacy); + const uint256 quorum_hash = uint256S(arm.quorum_hash); + + std::vector ids; + ids.reserve(arm.member_ids.size()); + for (const char* id : arm.member_ids) { + ids.emplace_back(uint256S(id)); + } + + // Each member's verification vector and the share it sends to every + // member. + std::vector> vvecs(kSize); + std::vector> shares(kSize); + for (int i = 0; i < kSize; ++i) { + std::vector poly; + for (const char* c : arm.coefficients.at(i)) { + poly.push_back(FromBytes(Unhex(c), legacy)); + } + for (const auto& sk : poly) { + vvecs.at(i).push_back(sk.GetPublicKey()); + } + for (const auto& id : ids) { + CBLSSecretKey share; + if (!share.SecretKeyShare(poly, id)) { + throw std::runtime_error("share failed"); + } + shares.at(i).push_back(share); + } + } + + Json& out = root.Set(kSchemes.at(a).name, Json::Object()); + Json& inputs = out.Set("inputs", Json::Object()); + inputs.Set("llmq_type", Json::Int(kType)); + inputs.Set("t", Json::Int(kThreshold)); + inputs.Set("n", Json::Int(kSize)); + inputs.Set( + "member_ids", + Json::Strs(std::vector(arm.member_ids.begin(), arm.member_ids.end())) + ); + + Json& contribute = out.Set("contribute", Json::List()); + for (int i = 0; i < kSize; ++i) { + Json& row = contribute.Push(Header(arm, i)); + row.Set("member_id", Json::Str(arm.member_ids.at(i))); + row.Set("vvec", HexList(vvecs.at(i), legacy)); + row.Set("sk_shares", HexList(shares.at(i), legacy)); + } + + Json& verify = out.Set("verify", Json::List()); + for (int j = 0; j < kSize; ++j) { + Json& row = verify.Push(Json::Object()); + row.Set("member_idx", Json::Int(j)); + Json& received = row.Set("received_vvecs", Json::List()); + std::vector got; + got.reserve(kSize); + for (int i = 0; i < kSize; ++i) { + received.Push(HexList(vvecs.at(i), legacy)); + got.push_back(shares.at(i).at(j)); + } + row.Set("received_sk_contributions", HexList(got, legacy)); + Json& verdicts = row.Set("verification_results", Json::List()); + for (int i = 0; i < kSize; ++i) { + CBLSPublicKey expected; + if (!expected.PublicKeyShare(vvecs.at(i), ids.at(j))) { + throw std::runtime_error("public share failed"); + } + verdicts.Push(Json::Bool(expected == shares.at(i).at(j).GetPublicKey())); + } + } + + // The quorum verification vector sums the members' vectors. + std::vector quorum_vvec; + for (int k = 0; k < kThreshold; ++k) { + std::vector terms; + terms.reserve(kSize); + for (int i = 0; i < kSize; ++i) { + terms.push_back(vvecs.at(i).at(k)); + } + quorum_vvec.push_back(CBLSPublicKey::AggregateInsecure(terms)); + } + const uint256 vvec_hash = ::SerializeHash(quorum_vvec); + const std::vector valid(kSize, true); + const uint256 commitment_hash = llmq::BuildCommitmentHash( + Consensus::LLMQType(kType), quorum_hash, valid, quorum_vvec.at(0), vvec_hash + ); + + std::vector sk_shares; + std::vector member_pks; + std::vector member_sigs; + std::vector quorum_sigs; + Json& commit = out.Set("commit", Json::List()); + for (int j = 0; j < kSize; ++j) { + std::vector got; + got.reserve(kSize); + for (int i = 0; i < kSize; ++i) { + got.push_back(shares.at(i).at(j)); + } + sk_shares.push_back(CBLSSecretKey::AggregateInsecure(got)); + member_pks.push_back(FromBytes(Unhex(arm.member_pks.at(j)), legacy)); + member_sigs.push_back(FromBytes(Unhex(arm.member_sigs.at(j)), legacy)); + if (!member_sigs.at(j).VerifyInsecure(member_pks.at(j), commitment_hash, legacy)) { + std::fprintf(stderr, "member %d signature does not match the commitment hash\n", j); + return 1; + } + quorum_sigs.push_back(sk_shares.at(j).Sign(commitment_hash, legacy)); + + Json& row = commit.Push(Header(arm, j)); + Json& valid_list = row.Set("valid_members", Json::List()); + for (bool v : valid) { + valid_list.Push(Json::Bool(v)); + } + row.Set("quorum_vvec", HexList(quorum_vvec, legacy)); + row.Set("sk_share", Json::Str(WrapperHex(sk_shares.at(j), legacy))); + row.Set("quorum_public_key", Json::Str(WrapperHex(quorum_vvec.at(0), legacy))); + row.Set("quorum_vvec_hash", Json::Str(vvec_hash.ToString())); + row.Set("commitment_hash", Json::Str(commitment_hash.ToString())); + row.Set("member_sig", Json::Str(WrapperHex(member_sigs.at(j), legacy))); + row.Set("quorum_sig_share", Json::Str(WrapperHex(quorum_sigs.at(j), legacy))); + } + + std::vector signer_ids; + std::vector signer_hex; + std::vector threshold_sigs; + std::vector signer_member_sigs; + std::vector signer_pks; + for (int j : arm.signers) { + signer_ids.push_back(ids.at(j)); + signer_hex.emplace_back(arm.member_ids.at(j)); + threshold_sigs.push_back(quorum_sigs.at(j)); + signer_member_sigs.push_back(member_sigs.at(j)); + signer_pks.push_back(member_pks.at(j)); + } + CBLSSignature recovered; + if (!recovered.Recover(threshold_sigs, signer_ids)) { + throw std::runtime_error("recovery failed"); + } + + Json& finalize = out.Set("finalize", Header(arm, /*member_idx=*/-1)); + finalize.Set("signer_ids", Json::Strs(signer_hex)); + finalize.Set("threshold_sigs", HexList(threshold_sigs, legacy)); + finalize.Set("recovered_quorum_sig", Json::Str(WrapperHex(recovered, legacy))); + finalize.Set("member_sigs", HexList(signer_member_sigs, legacy)); + finalize.Set("member_pks", HexList(signer_pks, legacy)); + const auto members_sig = + CBLSSignature::AggregateSecure(signer_member_sigs, signer_pks, commitment_hash); + finalize.Set("aggregated_members_sig", Json::Str(WrapperHex(members_sig, legacy))); + } + return Print(root); +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/contrib/gen_secure_aggregate.cpp b/pkgs/pkc/contrib/gen_secure_aggregate.cpp new file mode 100644 index 000000000..27a35919e --- /dev/null +++ b/pkgs/pkc/contrib/gen_secure_aggregate.cpp @@ -0,0 +1,88 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_secure_aggregate.json5, secure aggregates of signatures. + +#include "util/common.hpp" + +using namespace corpus; + +namespace { +//! The signer counts of the digest aggregates. +constexpr std::array kSizes = {3, 5}; + +//! Where the legacy message aggregate comes from. +constexpr Source kLegacySource = { + "Sourced from the pre-1.0 Chia bls-signatures \"Test vectors 1\"", + "https://github.com/Chia-Network/bls-signatures/blob/0.1.15/src/test.cpp#L31-L70", +}; +//! The keys of the legacy message aggregate. +constexpr std::array kLegacyKeys = { + "022fb42c08c12de3a6af053880199806532e79515f94e83461612101f9412f9e", + "502c5661f5af46ed48ddc3b5332e21b93cc7d0a84df46c4b9c7fe8f25ef48d66", +}; +//! The message both legacy keys sign. +constexpr const char* kLegacyMessage = "070809"; + +void Row( + Json& rows, const std::vector& sks, const Bytes& msg, bool legacy, bool hash_msg +) +{ + std::vector pks; + std::vector sigs; + for (const auto& sk : sks) { + pks.push_back(sk.GetG1Element()); + sigs.push_back(hash_msg ? SignMessage(sk, msg, legacy) : SignDigest(sk, msg, legacy)); + } + const Bytes digest = hash_msg && legacy ? Sha256(msg) : msg; + const auto agg = Mpl(legacy).AggregateSecure(pks, sigs, bls::Bytes(digest)); + + Json& row = rows.Push(Json::Object()); + row.Set("msg", Json::Str(Hex(msg))); + Json& pk_list = row.Set("pks", Json::List()); + for (const auto& pk : pks) { + pk_list.Push(Json::Str(PkHex(pk, legacy))); + } + Json& sig_list = row.Set("sigs", Json::List()); + for (const auto& sig : sigs) { + sig_list.Push(Json::Str(SigHex(sig, legacy))); + } + row.Set("agg_sig_secure", Json::Str(SigHex(agg, legacy))); +} + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + Json root = Json::Object(); + for (const auto& scheme : kSchemes) { + Json& arm = root.Set(scheme.name, Json::Object()); + Json& verify = arm.Set("verify", Json::List()); + for (size_t n : kSizes) { + std::vector sks; + for (size_t k = 1; k <= n; ++k) { + sks.push_back(SkOf(k)); + } + Row(verify, sks, Digests()[0], scheme.legacy, /*hash_msg=*/false); + } + if (scheme.legacy) { + Json& by_msg = arm.Set("verify_message", Json::List()); + by_msg.Cite(kLegacySource); + std::vector sks; + sks.reserve(kLegacyKeys.size()); + for (const char* key : kLegacyKeys) { + sks.push_back(Sk(Unhex(key))); + } + Row(by_msg, sks, Unhex(kLegacyMessage), /*legacy=*/true, /*hash_msg=*/true); + } + } + return Print(root); +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/contrib/gen_serialize.cpp b/pkgs/pkc/contrib/gen_serialize.cpp new file mode 100644 index 000000000..fe769a6b5 --- /dev/null +++ b/pkgs/pkc/contrib/gen_serialize.cpp @@ -0,0 +1,39 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_serialize.json5, keys and signatures under each scheme. + +#include "util/common.hpp" + +using namespace corpus; + +namespace { +//! The secret keys whose 32 bytes all equal k, for k from 1 to this count. +constexpr uint8_t kKeys = 5; + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + Json root = Json::Object(); + for (const auto& scheme : kSchemes) { + Json& arm = root.Set(scheme.name, Json::Object()); + Json& pks = arm.Set("pk", Json::List()); + Json& sigs = arm.Set("sig", Json::List()); + // Both arms write the legacy scheme's signatures, each in its own layout. + for (uint8_t k = 1; k <= kKeys; ++k) { + pks.Push(Json::Str(PkHex(SkOf(k).GetG1Element(), scheme.legacy))); + const auto sig = SignDigest(SkOf(k), Digests()[0], /*legacy=*/true); + sigs.Push(Json::Str(SigHex(sig, scheme.legacy))); + } + } + return Print(root); +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/contrib/gen_sign.cpp b/pkgs/pkc/contrib/gen_sign.cpp new file mode 100644 index 000000000..360b15263 --- /dev/null +++ b/pkgs/pkc/contrib/gen_sign.cpp @@ -0,0 +1,105 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_sign.json5, signatures over digests and messages. + +#include "util/common.hpp" + +using namespace corpus; + +namespace { +//! The secret keys whose 32 bytes all equal k sign each digest, for k from 1 +//! to this count. +constexpr uint8_t kKeys = 5; + +/** One scheme's published message vectors and where they come from. */ +struct Vectors { + Source source; + std::initializer_list rows; +}; + +//! The legacy scheme's message vectors. +constexpr Vectors kLegacyVectors = { + /*source=*/{ + "Sourced from the pre-1.0 Chia bls-signatures \"Test vectors 1\"", + "https://github.com/Chia-Network/bls-signatures/blob/0.1.15/src/test.cpp#L31-L62", + }, + /*rows=*/ + { + {"022fb42c08c12de3a6af053880199806532e79515f94e83461612101f9412f9e", "070809"}, + {"502c5661f5af46ed48ddc3b5332e21b93cc7d0a84df46c4b9c7fe8f25ef48d66", "070809"}, + }, +}; + +//! The basic scheme's message vectors, a py_ecc signature, then the two of +//! the basic scheme KAT. +constexpr Vectors kBasicVectors = { + /*source=*/{ + "Sourced from dashbls \"Pyecc vector\", a py_ecc basic scheme signature.", + "https://github.com/dashpay/bls-signatures/blob/1.3.5/src/test.cpp#L320-L331", + }, + /*rows=*/ + { + {"0101010101010101010101010101010101010101010101010101010101010101", "030104010509"}, + {"4a353be3dac091a0a7e640620372f5e1e2e4401717c1e79cac6ffba8f6905604", "070809"}, + {"6fc9d9a2b05fd1f0e51bc91041a03be8657081f272ec281aff731624f0d1c220", "0a0b0c"}, + }, +}; + +//! Where the basic scheme KAT, the second basic vector on, comes from. +constexpr Source kKatSource = { + "Sourced from the dashbls basic scheme KAT, whose second key derives from 32 bytes " + "of 0x01 and is pinned upstream only by fingerprint b839add1.", + "https://github.com/dashpay/bls-signatures/blob/1.3.5/src/test.cpp#L336-L402", +}; + +void Row( + Json& rows, const bls::PrivateKey& sk, const Bytes& msg, const bls::G2Element& sig, bool legacy +) +{ + Json& row = rows.Push(Json::Object()); + row.Set("sk", Json::Str(SkHex(sk))); + row.Set("msg", Json::Str(Hex(msg))); + row.Set("sig", Json::Str(SigHex(sig, legacy))); +} + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + Json root = Json::Object(); + for (const auto& scheme : kSchemes) { + Json& arm = root.Set(scheme.name, Json::Object()); + Json& sign = arm.Set("sign", Json::List()); + for (uint8_t k = 1; k <= kKeys; ++k) { + for (const auto& digest : Digests()) { + Row(sign, SkOf(k), digest, SignDigest(SkOf(k), digest, scheme.legacy), scheme.legacy); + } + } + + Json& msgs = arm.Set("sign_message", Json::List()); + auto sign_vectors = [&](const Vectors& vectors) { + msgs.Cite(vectors.source); + for (const auto& [key, text] : vectors.rows) { + const auto sk = Sk(Unhex(key)); + const auto msg = Unhex(text); + Row(msgs, sk, msg, SignMessage(sk, msg, scheme.legacy), scheme.legacy); + } + }; + if (scheme.legacy) { + sign_vectors(kLegacyVectors); + } else { + sign_vectors(kBasicVectors); + msgs.Item(1).Cite(kKatSource); + } + } + return Print(root); +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/contrib/gen_threshold.cpp b/pkgs/pkc/contrib/gen_threshold.cpp new file mode 100644 index 000000000..1d4a21020 --- /dev/null +++ b/pkgs/pkc/contrib/gen_threshold.cpp @@ -0,0 +1,85 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_threshold.json5, a 3-of-5 split and its recovery. + +#include "util/common.hpp" + +using namespace corpus; + +namespace { +//! How many shares recover a signature. +constexpr int kThreshold = 3; +//! How many shares the master key splits into. +constexpr int kSize = 5; +//! The members whose signature shares the recovery uses, numbered from 1. +constexpr std::array kRecoverIds = {1, 3, 5}; + +/** @return The id of member @p k, its 32-byte big-endian value. */ +Bytes Id(int k) +{ + Bytes id(32, 0); + id.back() = static_cast(k); + return id; +} + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + const auto& msg = Digests()[0]; + + std::vector master; + for (uint8_t k = 1; k <= kThreshold; ++k) { + master.push_back(SkOf(k)); + } + + Json root = Json::Object(); + for (const auto& scheme : kSchemes) { + Json& recover = root.Set(scheme.name, Json::Object()).Set("recover_sig", Json::Object()); + Json& inputs = recover.Set("inputs", Json::Object()); + inputs.Set("t", Json::Int(kThreshold)); + inputs.Set("n", Json::Int(kSize)); + inputs.Set("msg", Json::Str(Hex(msg))); + Json& master_sks = inputs.Set("master_sks", Json::List()); + for (const auto& sk : master) { + master_sks.Push(Json::Str(SkHex(sk))); + } + + Json& out = recover.Set("outputs", Json::List()).Push(Json::Object()); + Json& sk_shares = out.Set("sk_shares", Json::List()); + Json& sig_shares = out.Set("sig_shares", Json::List()); + std::vector shares; + for (int k = 1; k <= kSize; ++k) { + const auto ids = Id(k); + const auto share = bls::Threshold::PrivateKeyShare(master, bls::Bytes(ids)); + shares.push_back(SignDigest(share, msg, scheme.legacy)); + sk_shares.Push(Json::Str(SkHex(share))); + sig_shares.Push(Json::Str(SigHex(shares.back(), scheme.legacy))); + } + + Json& ids = out.Set("recover_ids", Json::List()); + std::vector picked; + std::vector picked_ids; + for (int k : kRecoverIds) { + ids.Push(Json::Int(k)); + picked.push_back(shares.at(k - 1)); + picked_ids.push_back(Id(k)); + } + std::vector id_refs(picked_ids.begin(), picked_ids.end()); + const auto recovered = bls::Threshold::SignatureRecover(picked, id_refs); + out.Set("recovered_sig", Json::Str(SigHex(recovered, scheme.legacy))); + out.Set( + "full_sig", Json::Str(SigHex(SignDigest(master.at(0), msg, scheme.legacy), scheme.legacy)) + ); + } + return Print(root); +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/contrib/regen.py b/pkgs/pkc/contrib/regen.py new file mode 100755 index 000000000..984cbc48a --- /dev/null +++ b/pkgs/pkc/contrib/regen.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +# coding: latin-1 + +# +# Copyright (c) 2026-present, The Dash Core developers +# SPDX-License-Identifier: MIT +# See the accompanying file LICENSE or https://opensource.org/license/MIT +# + +"""Write the BLS corpora from the pinned reference implementation release. + +Checks out the `dash` submodule next to this script if it is not yet, and +extracts the `src` tree of the release it pins into a build directory, with +relic's CMake configuration template patched back in. There CMake builds the +release's BLS library and each `gen_*.cpp` program here, and the build is kept +for later runs with the same compilers, flags and generator. The corpora are +written only when every program succeeds. + +Usage: + + ./pkgs/pkc/contrib/regen.py + ./pkgs/pkc/contrib/regen.py --program keygen --corpus /tmp/bls +""" + +from __future__ import annotations + +import argparse +import hashlib +import io +import os +import shutil +import subprocess +import sys +import tarfile +import tempfile +from pathlib import Path +from typing import BinaryIO + +HERE = Path(__file__).resolve().parent + +# The repository root, which holds the submodule's pin. +ROOT = HERE.parents[2] + +# The submodule pinned at the release the corpora are written from. +SUBMODULE = HERE / "dash" + +# Restores relic's CMake configuration template, which the release omits. +PATCH = HERE / "util" / "relic_conf.patch" + +# Where the corpora are written by default. +CORPUS = HERE.parent / "corpus" + +# Where the release is extracted and built by default. +BUILD = ROOT / "target" / "bls-corpus" + +# The corpora written, each by the program of the same name. +PROGRAMS = sorted( + path.stem.removeprefix("gen_") for path in HERE.glob("gen_*.cpp") +) + +# The environment CMake reads when it first configures a build. +BUILD_ENV = ["CC", "CFLAGS", "CMAKE_GENERATOR", "CXX", "CXXFLAGS", "LDFLAGS"] + +# The compilers CMake finds, by variable and the command it falls back to. +COMPILERS = {"CC": "cc", "CXX": "c++"} + + +class RegenError(Exception): + """A step that failed.""" + + +def run( + cmd: list[str | Path], + cwd: Path | None = None, + log: BinaryIO | None = None, + env: dict[str, str] | None = None, +) -> bytes: + """Run *cmd* in *cwd* and return its output, or fail. + + With *log*, the output and errors go to that file instead. + """ + try: + proc = subprocess.run( # noqa: S603 + cmd, + cwd=cwd, + check=True, + env=env, + stdout=log or subprocess.PIPE, + stderr=subprocess.STDOUT if log else None, + ) + except subprocess.CalledProcessError as err: + name = Path(cmd[0]).name + raise RegenError(f"{name} exited with {err.returncode}") from err + return proc.stdout or b"" + + +def pinned_release() -> str: + """Return the staged pin, checking the submodule out if it is not yet. + + A submodule checked out at another commit is left as it is, and stops the + run, so the corpora always come from the staged pin. + """ + path = SUBMODULE.relative_to(ROOT).as_posix() + git = ["git", "-C", ROOT] + pin = run([*git, "rev-parse", f":{path}"]).decode().strip() + if not (SUBMODULE / ".git").exists(): + update = ["submodule", "update", "--init", "--checkout", "--depth", "1"] + run([*git, *update, "--", path]) + head = run(["git", "-C", SUBMODULE, "rev-parse", "HEAD"]).decode().strip() + if head != pin: + fix = f"git submodule update --init --checkout -- {path}" + msg = f"the submodule is at {head}, not its staged pin {pin}" + raise RegenError(f"{msg}, stage it or run {fix}") + print(f"regen: writing from {pin}", file=sys.stderr) + return pin + + +def build_dir(build: Path, pin: str) -> Path: + """Return where *pin* is extracted and built under *build*. + + The name holds a digest of the environment CMake reads when it first + configures and of the compilers it finds there, so changing either, as a + new compiler does, builds again. + """ + env = [f"{n}={os.environ[n]}" for n in BUILD_ENV if os.environ.get(n)] + for name, fallback in COMPILERS.items(): + command = (os.environ.get(name, "").split() or [fallback])[0] + found = shutil.which(command) + env.append(f"{name}:{Path(found).resolve() if found else command}") + digest = hashlib.sha256("\0".join(env).encode()).hexdigest() + return build / f"{pin}-{digest[:12]}" + + +def extract(pin: str, src: Path) -> None: + """Extract `src` of *pin* into *src* and patch relic's template back in. + + The tree is prepared under a temporary name and then renamed, so an + existing *src* is always complete. + """ + src.parent.mkdir(parents=True, exist_ok=True) + stage = Path(tempfile.mkdtemp(prefix=".extract-", dir=src.parent)) + try: + archive = run(["git", "-C", SUBMODULE, "archive", pin, "src"]) + with tarfile.open(fileobj=io.BytesIO(archive)) as tar: + tar.extractall(stage, filter="data") + # The ceiling keeps git from finding the repository around the stage, + # whose root the patch's paths would then be read against. + env = {**os.environ, "GIT_CEILING_DIRECTORIES": str(stage.parent)} + run(["git", "apply", PATCH], cwd=stage, env=env) + (stage / "src").rename(src) + finally: + shutil.rmtree(stage, ignore_errors=True) + + +def link_current(tree: Path) -> None: + """Point `current` next to *tree* at it, for the C++ linters to read.""" + link = tree.with_name("current") + stage = tree.with_name(".current") + stage.unlink(missing_ok=True) + stage.symlink_to(tree.name, target_is_directory=True) + stage.replace(link) + + +def build(tree: Path, programs: list[str], jobs: int) -> Path: + """Build *programs* in *tree* and return the directory holding them. + + The output goes to a log next to the build, named in the error when the + build fails. + """ + out = tree / "build" + log = tree / "build.log" + configure = ["cmake", "-S", HERE, "-B", out, f"-DDASH_SRC={tree / 'src'}"] + targets = [f"gen_{name}" for name in programs] + with log.open("wb") as sink: + for cmd in [ + configure, + ["cmake", "--build", out, "--parallel", str(jobs), "--target", *targets], + ]: + try: + run(cmd, log=sink) + except RegenError as err: + msg = f"the build failed, see {log}; removing {tree} starts afresh" + raise RegenError(msg) from err + return out + + +def jobs_count(text: str) -> int: + """Parse a number of parallel jobs, at least one.""" + jobs = int(text) + if jobs < 1: + raise argparse.ArgumentTypeError(f"{jobs} is not at least 1") + return jobs + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=__doc__.splitlines()[0] if __doc__ else None + ) + parser.add_argument( + "--program", + action="append", + choices=PROGRAMS, + help="program whose corpus to write, by default every one", + ) + parser.add_argument( + "--corpus", + type=Path, + default=CORPUS, + help="directory to write the corpora to, by default the crate's", + ) + parser.add_argument( + "--build", + type=Path, + default=BUILD, + help=f"directory to build in, by default {BUILD.relative_to(ROOT)}", + ) + parser.add_argument( + "--jobs", + type=jobs_count, + default=os.cpu_count() or 1, + help="parallel build jobs, by default one per processor", + ) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + programs = list(dict.fromkeys(args.program or PROGRAMS)) + pin = pinned_release() + tree = build_dir(args.build.resolve(), pin) + if not (tree / "src").is_dir(): + extract(pin, tree / "src") + out = build(tree, programs, args.jobs) + link_current(tree) + output = {name: run([out / f"gen_{name}"]) for name in programs} + args.corpus.mkdir(parents=True, exist_ok=True) + for name, text in output.items(): + path = args.corpus / f"bls_{name}.json5" + # Each corpus is replaced only once it is written in full, so a failed + # run leaves the old one as it was. + tmp = path.with_name(f".{path.name}.tmp") + try: + tmp.write_bytes(text) + os.replace(tmp, path) + finally: + tmp.unlink(missing_ok=True) + + +if __name__ == "__main__": + try: + main() + except (RegenError, OSError, tarfile.TarError) as err: + sys.exit(f"regen: {err}") diff --git a/pkgs/pkc/contrib/util/common.hpp b/pkgs/pkc/contrib/util/common.hpp new file mode 100644 index 000000000..cfe4349ff --- /dev/null +++ b/pkgs/pkc/contrib/util/common.hpp @@ -0,0 +1,356 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Hex and corpus layout helpers shared by the corpus programs. + +#ifndef PKC_CONTRIB_COMMON_HPP +#define PKC_CONTRIB_COMMON_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace corpus { + +using Bytes = std::vector; + +inline std::string Hex(const uint8_t* data, size_t len) +{ + static constexpr std::string_view digits = "0123456789abcdef"; + std::string out; + out.reserve(len * 2); + for (size_t i = 0; i < len; ++i) { + out.push_back(digits.at(data[i] >> 4)); + out.push_back(digits.at(data[i] & 15)); + } + return out; +} + +template +std::string Hex(const T& bytes) +{ + return Hex(reinterpret_cast(bytes.data()), bytes.size()); +} + +inline Bytes Unhex(std::string_view text) +{ + auto nibble = [](char c) -> uint8_t { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + throw std::invalid_argument("not lowercase hex"); + }; + if (text.size() % 2 != 0) { + throw std::invalid_argument("odd hex length"); + } + Bytes out(text.size() / 2); + for (size_t i = 0; i < out.size(); ++i) { + out[i] = nibble(text.at(2 * i)) << 4 | nibble(text.at(2 * i + 1)); + } + return out; +} + +/** @return @p len copies of @p byte. */ +inline Bytes Fill(uint8_t byte, size_t len) +{ + return Bytes(len, byte); +} + +/** One of the two ways a release writes BLS bytes, legacy and basic. */ +struct Scheme { + const char* name; + bool legacy; +}; + +inline constexpr std::array kSchemes = {{{"chia", true}, {"ietf", false}}}; + +/** + * @return The digests most corpora sign, decoded on first use so that a bad + * one throws inside main rather than before it. + */ +inline const std::array& Digests() +{ + static const std::array digests = { + Unhex("289e5175e02c788c2d442cfe81d6be0533d8c13e253ef763fda45d37accfe4d4"), + Unhex("a78521e49048b6e0d368d3fba417fc20c7546272dafa78a8a173fcca6c81233b"), + Unhex("89da2bd31a5d008c84323c9693f12f09e62a75a688a55f2a6fd24660afba5660"), + }; + return digests; +} + +/** A secret key and a message from a published vector, both in hex. */ +struct KeyMessage { + const char* sk; + const char* msg; +}; + +/** Where published vectors come from, written as two notes before them. */ +struct Source { + //! What the vectors are. + const char* text; + //! A link to the vectors upstream. + const char* url; +}; + +inline bls::PrivateKey Sk(const Bytes& bytes) +{ + return bls::PrivateKey::FromByteVector(bytes); +} + +/** @return The secret key whose 32 bytes all equal @p k. */ +inline bls::PrivateKey SkOf(uint8_t k) +{ + return Sk(Fill(k, 32)); +} + +inline Bytes Sha256(const Bytes& msg) +{ + Bytes out(bls::BLS::MESSAGE_HASH_LEN); + bls::Util::Hash256(out.data(), msg.data(), msg.size()); + return out; +} + +inline std::string SkHex(const bls::PrivateKey& sk) +{ + return Hex(sk.Serialize()); +} + +inline std::string PkHex(const bls::G1Element& pk, bool legacy) +{ + return Hex(pk.Serialize(legacy)); +} + +inline std::string SigHex(const bls::G2Element& sig, bool legacy) +{ + return Hex(sig.Serialize(legacy)); +} + +/** @return The library's scheme object for the layout. */ +inline bls::CoreMPL& Mpl(bool legacy) +{ + static bls::LegacySchemeMPL legacy_mpl; + static bls::BasicSchemeMPL basic_mpl; + if (legacy) { + return legacy_mpl; + } + return basic_mpl; +} + +/** Signs a 32-byte digest the way each scheme signs a hash. */ +inline bls::G2Element SignDigest(const bls::PrivateKey& sk, const Bytes& digest, bool legacy) +{ + return Mpl(legacy).Sign(sk, bls::Bytes(digest)); +} + +/** + * Signs a message of any length. The legacy scheme signs its SHA-256 digest, + * the basic scheme the message itself. + */ +inline bls::G2Element SignMessage(const bls::PrivateKey& sk, const Bytes& msg, bool legacy) +{ + return SignDigest(sk, legacy ? Sha256(msg) : msg, legacy); +} + +/** + * A JSON value, laid out with two-space indents as the corpora are. Notes + * print as comment lines before a member or item. A list may print on one + * line and an object may keep a comma after its last member. + */ +struct Json { + enum class Kind { Object, List, String, Literal }; + + Kind kind{Kind::Object}; + std::string text; + //! Lists keep references to members and items valid as more are added. + std::list> members; + std::list items; + std::vector notes; + bool inline_list{false}; + bool trailing_comma{false}; + + static Json Object() + { + return Json{}; + } + static Json List() + { + Json v; + v.kind = Kind::List; + return v; + } + static Json Str(std::string s) + { + Json v; + v.kind = Kind::String; + v.text = std::move(s); + return v; + } + static Json Int(long long n) + { + Json v; + v.kind = Kind::Literal; + v.text = std::to_string(n); + return v; + } + static Json Bool(bool b) + { + Json v; + v.kind = Kind::Literal; + v.text = b ? "true" : "false"; + return v; + } + template + static Json Strs(const std::vector& values) + { + Json v = List(); + for (const auto& s : values) { + v.items.push_back(Str(s)); + } + return v; + } + + /** @return The member added as @p key. */ + Json& Set(std::string key, Json value) + { + members.emplace_back(std::move(key), std::move(value)); + return members.back().second; + } + + /** @return The item added. */ + Json& Push(Json value) + { + items.push_back(std::move(value)); + return items.back(); + } + + Json& Item(size_t i) + { + return *std::next(items.begin(), i); + } + + Json& Note(std::string line) + { + notes.push_back(std::move(line)); + return *this; + } + + /** Adds the notes that cite @p source. */ + Json& Cite(const Source& source) + { + return Note(source.text).Note(source.url); + } +}; + +inline void Emit(const Json& v, int indent, std::string& out) +{ + const std::string pad(indent + 2, ' '); + auto notes = [&](const Json& child) { + for (const auto& line : child.notes) { + out += pad + "// " + line + "\n"; + } + }; + switch (v.kind) { + case Json::Kind::String: + out += '"' + v.text + '"'; + return; + case Json::Kind::Literal: + out += v.text; + return; + case Json::Kind::List: + if (v.items.empty()) { + out += "[]"; + return; + } + if (v.inline_list) { + out += '['; + for (const auto& item : v.items) { + if (&item != &v.items.front()) { + out += ", "; + } + Emit(item, indent, out); + } + out += ']'; + return; + } + out += "[\n"; + for (const auto& item : v.items) { + notes(item); + out += pad; + Emit(item, indent + 2, out); + if (&item != &v.items.back() || v.trailing_comma) { + out += ','; + } + out += '\n'; + } + out += std::string(indent, ' ') + ']'; + return; + case Json::Kind::Object: + if (v.members.empty()) { + out += "{}"; + return; + } + out += "{\n"; + for (const auto& [key, member] : v.members) { + notes(member); + out += pad + '"' + key + "\": "; + Emit(member, indent + 2, out); + if (&member != &v.members.back().second || v.trailing_comma) { + out += ','; + } + out += '\n'; + } + out += std::string(indent, ' ') + '}'; + return; + } +} + +/** + * Writes the corpus text to stdout. + * + * @return The exit code, 1 when the write fails. + */ +inline int Print(const Json& root) +{ + std::string out; + Emit(root, 0, out); + out += '\n'; + const bool written = std::fwrite(out.data(), 1, out.size(), stdout) == out.size(); + return written && std::fflush(stdout) == 0 ? 0 : 1; +} + +/** + * Runs @p write, the work of a program's main, and catches what it throws. + * + * @return The exit code of @p write, or 1 after printing what it threw. + */ +inline int Run(int (*write)()) +{ + try { + return write(); + } catch (const std::exception& e) { + std::fprintf(stderr, "%s\n", e.what()); + } catch (...) { + std::fputs("unknown exception\n", stderr); + } + return 1; +} + +} // namespace corpus + +#endif // PKC_CONTRIB_COMMON_HPP diff --git a/pkgs/pkc/contrib/util/core.hpp b/pkgs/pkc/contrib/util/core.hpp new file mode 100644 index 000000000..68fe7580d --- /dev/null +++ b/pkgs/pkc/contrib/util/core.hpp @@ -0,0 +1,48 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Helpers for the programs that link the release's own sources. + +#ifndef PKC_CONTRIB_CORE_HPP +#define PKC_CONTRIB_CORE_HPP + +#include "common.hpp" + +#include + +namespace corpus { + +/** Sets the layout the release's wrapper writes, through its flag. */ +inline void UseLegacy(bool legacy) +{ + bls::bls_legacy_scheme.store(legacy); +} + +/** Reads a wrapper object from bytes, through the release's setter. */ +template +T FromBytes(const Bytes& bytes, bool legacy) +{ + T obj; + if constexpr (requires { obj.SetBytes(Span(bytes), legacy); }) { + obj.SetBytes(Span(bytes), legacy); + } else { + obj.SetByteVector(Span(bytes), legacy); + } + if (!obj.IsValid()) { + throw std::invalid_argument("bytes the release does not read"); + } + return obj; +} + +template +std::string WrapperHex(const T& obj, bool legacy) +{ + return Hex(obj.ToByteVector(legacy)); +} + +} // namespace corpus + +#endif // PKC_CONTRIB_CORE_HPP diff --git a/pkgs/pkc/contrib/util/no_random.cpp b/pkgs/pkc/contrib/util/no_random.cpp new file mode 100644 index 000000000..3fd1435db --- /dev/null +++ b/pkgs/pkc/contrib/util/no_random.cpp @@ -0,0 +1,17 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Stands in for the release's strong random source. + +#include + +#include + +/** Aborts, as no program draws random bytes. */ +void GetStrongRandBytes(Span) noexcept +{ + std::abort(); +} diff --git a/pkgs/pkc/contrib/util/relic_conf.patch b/pkgs/pkc/contrib/util/relic_conf.patch new file mode 100644 index 000000000..9b50f3a51 --- /dev/null +++ b/pkgs/pkc/contrib/util/relic_conf.patch @@ -0,0 +1,727 @@ +Restores relic's CMake configuration template, which the reference +implementation stops tracking in 8f111982e75 so that only its autotools build +runs. Its library updates restore it the same way. + +diff --git a/src/dashbls/depends/relic/include/relic_conf.h.in b/src/dashbls/depends/relic/include/relic_conf.h.in +new file mode 100644 +index 00000000000..7db6f5b509c +--- /dev/null ++++ b/src/dashbls/depends/relic/include/relic_conf.h.in +@@ -0,0 +1,717 @@ ++/* ++ * RELIC is an Efficient LIbrary for Cryptography ++ * Copyright (c) 2009 RELIC Authors ++ * ++ * This file is part of RELIC. RELIC is legal property of its developers, ++ * whose names are not listed here. Please refer to the COPYRIGHT file ++ * for contact information. ++ * ++ * RELIC is free software; you can redistribute it and/or modify it under the ++ * terms of the version 2.1 (or later) of the GNU Lesser General Public License ++ * as published by the Free Software Foundation; or version 2.0 of the Apache ++ * License as published by the Apache Software Foundation. See the LICENSE files ++ * for more details. ++ * ++ * RELIC is distributed in the hope that it will be useful, but WITHOUT ANY ++ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR ++ * A PARTICULAR PURPOSE. See the LICENSE files for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public or the ++ * Apache License along with RELIC. If not, see ++ * or . ++ */ ++ ++/** ++ * @file ++ * ++ * Project configuration. ++ * ++ * @version $Id: relic_conf.h.in 45 2009-07-04 23:45:48Z dfaranha $ ++ * @ingroup relic ++ */ ++ ++#ifndef RLC_CONF_H ++#define RLC_CONF_H ++ ++/** Project version. */ ++#define RLC_VERSION "@VERSION@" ++ ++/** Debugging support. */ ++#cmakedefine DEBUG ++/** Profiling support. */ ++#cmakedefine PROFL ++/** Error handling support. */ ++#cmakedefine CHECK ++/** Verbose error messages. */ ++#cmakedefine VERBS ++/** Build with overhead estimation. */ ++#cmakedefine OVERH ++/** Build documentation. */ ++#cmakedefine DOCUM ++/** Build only the selected algorithms. */ ++#cmakedefine STRIP ++/** Build with printing disabled. */ ++#cmakedefine QUIET ++/** Build with colored output. */ ++#cmakedefine COLOR ++/** Build with big-endian support. */ ++#cmakedefine BIGED ++/** Build shared library. */ ++#cmakedefine SHLIB ++/** Build static library. */ ++#cmakedefine STLIB ++ ++/** Number of times each test is ran. */ ++#define TESTS @TESTS@ ++/** Number of times each benchmark is ran. */ ++#define BENCH @BENCH@ ++/** Number of available cores. */ ++#define CORES @CORES@ ++ ++/** Atmel AVR ATMega128 8-bit architecture. */ ++#define AVR 1 ++/** MSP430 16-bit architecture. */ ++#define MSP 2 ++/** ARM 32-bit architecture. */ ++#define ARM 3 ++/** Intel x86-compatible 32-bit architecture. */ ++#define X86 4 ++/** AMD64-compatible 64-bit architecture. */ ++#define X64 5 ++/** Architecture. */ ++#cmakedefine ARCH @ARCH@ ++ ++/** Size of word in this architecture. */ ++#define WSIZE @WSIZE@ ++ ++/** Byte boundary to align digit vectors. */ ++#define ALIGN @ALIGN@ ++ ++/** Build multiple precision integer module. */ ++#cmakedefine WITH_BN ++/** Build prime field module. */ ++#cmakedefine WITH_FP ++/** Build prime field extension module. */ ++#cmakedefine WITH_FPX ++/** Build binary field module. */ ++#cmakedefine WITH_FB ++/** Build prime elliptic curve module. */ ++#cmakedefine WITH_EP ++/** Build prime field extension elliptic curve module. */ ++#cmakedefine WITH_EPX ++/** Build binary elliptic curve module. */ ++#cmakedefine WITH_EB ++/** Build elliptic Edwards curve module. */ ++#cmakedefine WITH_ED ++/** Build elliptic curve cryptography module. */ ++#cmakedefine WITH_EC ++/** Build pairings over prime curves module. */ ++#cmakedefine WITH_PP ++/** Build pairing-based cryptography module. */ ++#cmakedefine WITH_PC ++/** Build block ciphers. */ ++#cmakedefine WITH_BC ++/** Build hash functions. */ ++#cmakedefine WITH_MD ++/** Build cryptographic protocols. */ ++#cmakedefine WITH_CP ++/** Build Multi-party computation primitives. */ ++#cmakedefine WITH_MPC ++ ++/** Easy C-only backend. */ ++#define EASY 1 ++/** GMP backend. */ ++#define GMP 2 ++/** GMP constant-time backend. */ ++#define GMP_SEC 3 ++/** Arithmetic backend. */ ++#define ARITH @ARITH@ ++ ++/** Required precision in bits. */ ++#define BN_PRECI @BN_PRECI@ ++/** A multiple precision integer can store w words. */ ++#define SINGLE 0 ++/** A multiple precision integer can store the result of an addition. */ ++#define CARRY 1 ++/** A multiple precision integer can store the result of a multiplication. */ ++#define DOUBLE 2 ++/** Effective size of a multiple precision integer. */ ++#define BN_MAGNI @BN_MAGNI@ ++/** Number of Karatsuba steps. */ ++#define BN_KARAT @BN_KARAT@ ++ ++/** Schoolbook multiplication. */ ++#define BASIC 1 ++/** Comba multiplication. */ ++#define COMBA 2 ++/** Chosen multiple precision multiplication method. */ ++#define BN_MUL @BN_MUL@ ++ ++/** Schoolbook squaring. */ ++#define BASIC 1 ++/** Comba squaring. */ ++#define COMBA 2 ++/** Reuse multiplication for squaring. */ ++#define MULTP 4 ++/** Chosen multiple precision multiplication method. */ ++#define BN_SQR @BN_SQR@ ++ ++/** Division modular reduction. */ ++#define BASIC 1 ++/** Barrett modular reduction. */ ++#define BARRT 2 ++/** Montgomery modular reduction. */ ++#define MONTY 3 ++/** Pseudo-Mersenne modular reduction. */ ++#define PMERS 4 ++/** Chosen multiple precision modular reduction method. */ ++#define BN_MOD @BN_MOD@ ++ ++/** Binary modular exponentiation. */ ++#define BASIC 1 ++/** Sliding window modular exponentiation. */ ++#define SLIDE 2 ++/** Montgomery powering ladder. */ ++#define MONTY 3 ++/** Chosen multiple precision modular exponentiation method. */ ++#define BN_MXP @BN_MXP@ ++ ++/** Basic Euclidean GCD Algorithm. */ ++#define BASIC 1 ++/** Lehmer's fast GCD Algorithm. */ ++#define LEHME 2 ++/** Stein's binary GCD Algorithm. */ ++#define STEIN 3 ++/** Chosen multiple precision greatest common divisor method. */ ++#define BN_GCD @BN_GCD@ ++ ++/** Basic prime generation. */ ++#define BASIC 1 ++/** Safe prime generation. */ ++#define SAFEP 2 ++/** Strong prime generation. */ ++#define STRON 3 ++/** Chosen prime generation algorithm. */ ++#define BN_GEN @BN_GEN@ ++ ++/** Multiple precision arithmetic method */ ++#define BN_METHD "@BN_METHD@" ++ ++/** Prime field size in bits. */ ++#define FP_PRIME @FP_PRIME@ ++/** Number of Karatsuba steps. */ ++#define FP_KARAT @FP_KARAT@ ++/** Prefer Pseudo-Mersenne primes over random primes. */ ++#cmakedefine FP_PMERS ++/** Use -1 as quadratic non-residue. */ ++#cmakedefine FP_QNRES ++/** Width of window processing for exponentiation methods. */ ++#define FP_WIDTH @FP_WIDTH@ ++ ++/** Schoolbook addition. */ ++#define BASIC 1 ++/** Integrated modular addtion. */ ++#define INTEG 3 ++/** Chosen prime field multiplication method. */ ++#define FP_ADD @FP_ADD@ ++ ++/** Schoolbook multiplication. */ ++#define BASIC 1 ++/** Comba multiplication. */ ++#define COMBA 2 ++/** Integrated modular multiplication. */ ++#define INTEG 3 ++/** Chosen prime field multiplication method. */ ++#define FP_MUL @FP_MUL@ ++ ++/** Schoolbook squaring. */ ++#define BASIC 1 ++/** Comba squaring. */ ++#define COMBA 2 ++/** Integrated modular squaring. */ ++#define INTEG 3 ++/** Reuse multiplication for squaring. */ ++#define MULTP 4 ++/** Chosen prime field multiplication method. */ ++#define FP_SQR @FP_SQR@ ++ ++/** Division-based reduction. */ ++#define BASIC 1 ++/** Fast reduction modulo special form prime. */ ++#define QUICK 2 ++/** Montgomery modular reduction. */ ++#define MONTY 3 ++/** Chosen prime field reduction method. */ ++#define FP_RDC @FP_RDC@ ++ ++/** Inversion by Fermat's Little Theorem. */ ++#define BASIC 1 ++/** Binary inversion. */ ++#define BINAR 2 ++/** Integrated modular multiplication. */ ++#define MONTY 3 ++/** Extended Euclidean algorithm. */ ++#define EXGCD 4 ++/** Constant-time inversion by Bernstein-Yang division steps. */ ++#define DIVST 5 ++/** Use implementation provided by the lower layer. */ ++#define LOWER 8 ++/** Chosen prime field inversion method. */ ++#define FP_INV @FP_INV@ ++ ++/** Binary modular exponentiation. */ ++#define BASIC 1 ++/** Sliding window modular exponentiation. */ ++#define SLIDE 2 ++/** Constant-time Montgomery powering ladder. */ ++#define MONTY 3 ++/** Chosen multiple precision modular exponentiation method. */ ++#define FP_EXP @FP_EXP@ ++ ++/** Prime field arithmetic method */ ++#define FP_METHD "@FP_METHD@" ++ ++/** Basic quadratic extension field arithmetic. */ ++#define BASIC 1 ++/** Integrated extension field arithmetic. */ ++#define INTEG 3 ++/* Chosen extension field arithmetic method. */ ++#define FPX_QDR @FPX_QDR@ ++ ++/** Basic cubic extension field arithmetic. */ ++#define BASIC 1 ++/** Integrated extension field arithmetic. */ ++#define INTEG 3 ++/* Chosen extension field arithmetic method. */ ++#define FPX_CBC @FPX_CBC@ ++ ++/** Basic quadratic extension field arithmetic. */ ++#define BASIC 1 ++/** Lazy-reduced extension field arithmetic. */ ++#define LAZYR 2 ++/* Chosen extension field arithmetic method. */ ++#define FPX_RDC @FPX_RDC@ ++ ++/** Prime extension field arithmetic method */ ++#define FPX_METHD "@FPX_METHD@" ++ ++/** Irreducible polynomial size in bits. */ ++#define FB_POLYN @FB_POLYN@ ++/** Number of Karatsuba steps. */ ++#define FB_KARAT @FB_KARAT@ ++/** Prefer trinomials over pentanomials. */ ++#cmakedefine FB_TRINO ++/** Prefer square-root friendly polynomials. */ ++#cmakedefine FB_SQRTF ++/** Precompute multiplication table for sqrt(z). */ ++#cmakedefine FB_PRECO ++/** Width of window processing for exponentiation methods. */ ++#define FB_WIDTH @FB_WIDTH@ ++ ++/** Shift-and-add multiplication. */ ++#define BASIC 1 ++/** Lopez-Dahab multiplication. */ ++#define LODAH 2 ++/** Integrated modular multiplication. */ ++#define INTEG 3 ++/** Chosen binary field multiplication method. */ ++#define FB_MUL @FB_MUL@ ++ ++/** Basic squaring. */ ++#define BASIC 1 ++/** Table-based squaring. */ ++#define QUICK 2 ++/** Integrated modular squaring. */ ++#define INTEG 3 ++/** Chosen binary field squaring method. */ ++#define FB_SQR @FB_SQR@ ++ ++/** Shift-and-add modular reduction. */ ++#define BASIC 1 ++/** Fast reduction modulo a trinomial or pentanomial. */ ++#define QUICK 2 ++/** Chosen binary field modular reduction method. */ ++#define FB_RDC @FB_RDC@ ++ ++/** Square root by repeated squaring. */ ++#define BASIC 1 ++/** Fast square root extraction. */ ++#define QUICK 2 ++/** Chosen binary field modular reduction method. */ ++#define FB_SRT @FB_SRT@ ++ ++/** Trace by repeated squaring. */ ++#define BASIC 1 ++/** Fast trace computation. */ ++#define QUICK 2 ++/** Chosen trace computation method. */ ++#define FB_TRC @FB_TRC@ ++ ++/** Solve by half-trace computation. */ ++#define BASIC 1 ++/** Solve with precomputed half-traces. */ ++#define QUICK 2 ++/** Chosen method to solve a quadratic equation. */ ++#define FB_SLV @FB_SLV@ ++ ++/** Inversion by Fermat's Little Theorem. */ ++#define BASIC 1 ++/** Binary inversion. */ ++#define BINAR 2 ++/** Almost inverse algorithm. */ ++#define ALMOS 3 ++/** Extended Euclidean algorithm. */ ++#define EXGCD 4 ++/** Itoh-Tsuji inversion. */ ++#define ITOHT 5 ++/** Hardware-friendly inversion by Brunner-Curiger-Hofstetter.*/ ++#define BRUCH 6 ++/** Constant-time version of almost inverse. */ ++#define CTAIA 7 ++/** Use implementation provided by the lower layer. */ ++#define LOWER 8 ++/** Chosen binary field inversion method. */ ++#define FB_INV @FB_INV@ ++ ++/** Binary modular exponentiation. */ ++#define BASIC 1 ++/** Sliding window modular exponentiation. */ ++#define SLIDE 2 ++/** Constant-time Montgomery powering ladder. */ ++#define MONTY 3 ++/** Chosen multiple precision modular exponentiation method. */ ++#define FB_EXP @FB_EXP@ ++ ++/** Iterated squaring/square-root by consecutive squaring/square-root. */ ++#define BASIC 1 ++/** Iterated squaring/square-root by table-based method. */ ++#define QUICK 2 ++/** Chosen method to solve a quadratic equation. */ ++#define FB_ITR @FB_ITR@ ++ ++/** Binary field arithmetic method */ ++#define FB_METHD "@FB_METHD@" ++ ++/** Support for ordinary curves. */ ++#cmakedefine EP_PLAIN ++/** Support for supersingular curves. */ ++#cmakedefine EP_SUPER ++/** Support for prime curves with efficient endormorphisms. */ ++#cmakedefine EP_ENDOM ++/** Use mixed coordinates. */ ++#cmakedefine EP_MIXED ++/** Build precomputation table for generator. */ ++#cmakedefine EP_PRECO ++/** Enable isogeny map for SSWU map-to-curve. */ ++#cmakedefine EP_CTMAP ++/** Width of precomputation table for fixed point methods. */ ++#define EP_DEPTH @EP_DEPTH@ ++/** Width of window processing for unknown point methods. */ ++#define EP_WIDTH @EP_WIDTH@ ++ ++/** Affine coordinates. */ ++#define BASIC 1 ++/** Projective coordinates. */ ++#define PROJC 2 ++/** Jacobian coordinates. */ ++#define JACOB 3 ++/** Chosen prime elliptic curve coordinate method. */ ++#define EP_ADD @EP_ADD@ ++ ++/** Binary point multiplication. */ ++#define BASIC 1 ++/** Sliding window. */ ++#define SLIDE 2 ++/** Montgomery powering ladder. */ ++#define MONTY 3 ++/** Left-to-right Width-w NAF. */ ++#define LWNAF 4 ++/** Left-to-right Width-w NAF. */ ++#define LWREG 5 ++/** Chosen prime elliptic curve point multiplication method. */ ++#define EP_MUL @EP_MUL@ ++ ++/** Binary point multiplication. */ ++#define BASIC 1 ++/** Single-table comb method. */ ++#define COMBS 2 ++/** Double-table comb method. */ ++#define COMBD 3 ++/** Left-to-right Width-w NAF. */ ++#define LWNAF 4 ++/** Chosen prime elliptic curve point multiplication method. */ ++#define EP_FIX @EP_FIX@ ++ ++/** Basic simultaneouns point multiplication. */ ++#define BASIC 1 ++/** Shamir's trick. */ ++#define TRICK 2 ++/** Interleaving of w-(T)NAFs. */ ++#define INTER 3 ++/** Joint sparse form. */ ++#define JOINT 4 ++/** Chosen prime elliptic curve simulteanous point multiplication method. */ ++#define EP_SIM @EP_SIM@ ++ ++/** Prime elliptic curve arithmetic method. */ ++#define EP_METHD "@EP_METHD@" ++ ++/** Support for ordinary curves without endormorphisms. */ ++#cmakedefine EB_PLAIN ++/** Support for Koblitz anomalous binary curves. */ ++#cmakedefine EB_KBLTZ ++/** Use mixed coordinates. */ ++#cmakedefine EB_MIXED ++/** Build precomputation table for generator. */ ++#cmakedefine EB_PRECO ++/** Width of precomputation table for fixed point methods. */ ++#define EB_DEPTH @EB_DEPTH@ ++/** Width of window processing for unknown point methods. */ ++#define EB_WIDTH @EB_WIDTH@ ++ ++/** Binary elliptic curve arithmetic method. */ ++#define EB_METHD "@EB_METHD@" ++ ++/** Affine coordinates. */ ++#define BASIC 1 ++/** López-Dahab Projective coordinates. */ ++#define PROJC 2 ++/** Chosen binary elliptic curve coordinate method. */ ++#define EB_ADD @EB_ADD@ ++ ++/** Binary point multiplication. */ ++#define BASIC 1 ++/** L�pez-Dahab point multiplication. */ ++#define LODAH 2 ++/** Halving. */ ++#define HALVE 3 ++/** Left-to-right width-w (T)NAF. */ ++#define LWNAF 4 ++/** Right-to-left width-w (T)NAF. */ ++#define RWNAF 5 ++/** Chosen binary elliptic curve point multiplication method. */ ++#define EB_MUL @EB_MUL@ ++ ++/** Binary point multiplication. */ ++#define BASIC 1 ++/** Single-table comb method. */ ++#define COMBS 2 ++/** Double-table comb method. */ ++#define COMBD 3 ++/** Left-to-right Width-w NAF. */ ++#define LWNAF 4 ++/** Chosen binary elliptic curve point multiplication method. */ ++#define EB_FIX @EB_FIX@ ++ ++/** Basic simultaneouns point multiplication. */ ++#define BASIC 1 ++/** Shamir's trick. */ ++#define TRICK 2 ++/** Interleaving of w-(T)NAFs. */ ++#define INTER 3 ++/** Joint sparse form. */ ++#define JOINT 4 ++/** Chosen binary elliptic curve simulteanous point multiplication method. */ ++#define EB_SIM @EB_SIM@ ++ ++/** Build precomputation table for generator. */ ++#cmakedefine ED_PRECO ++/** Width of precomputation table for fixed point methods. */ ++#define ED_DEPTH @ED_DEPTH@ ++/** Width of window processing for unknown point methods. */ ++#define ED_WIDTH @ED_WIDTH@ ++ ++/** Edwards elliptic curve arithmetic method. */ ++#define ED_METHD "@ED_METHD@" ++ ++/** Affine coordinates. */ ++#define BASIC 1 ++/** Simple projective twisted Edwards coordinates */ ++#define PROJC 2 ++/** Extended projective twisted Edwards coordinates */ ++#define EXTND 3 ++/** Chosen binary elliptic curve coordinate method. */ ++#define ED_ADD @ED_ADD@ ++ ++/** Binary point multiplication. */ ++#define BASIC 1 ++/** Sliding window. */ ++#define SLIDE 2 ++/** Montgomery powering ladder. */ ++#define MONTY 3 ++/** Left-to-right Width-w NAF. */ ++#define LWNAF 4 ++/** Left-to-right Width-w NAF. */ ++#define LWREG 5 ++/** Chosen prime elliptic twisted Edwards curve point multiplication method. */ ++#define ED_MUL @ED_MUL@ ++ ++/** Binary point multiplication. */ ++#define BASIC 1 ++/** Single-table comb method. */ ++#define COMBS 2 ++/** Double-table comb method. */ ++#define COMBD 3 ++/** Left-to-right Width-w NAF. */ ++#define LWNAF 4 ++/** Chosen prime elliptic twisted Edwards curve point multiplication method. */ ++#define ED_FIX @ED_FIX@ ++ ++/** Basic simultaneouns point multiplication. */ ++#define BASIC 1 ++/** Shamir's trick. */ ++#define TRICK 2 ++/** Interleaving of w-(T)NAFs. */ ++#define INTER 3 ++/** Joint sparse form. */ ++#define JOINT 4 ++/** Chosen prime elliptic curve simulteanous point multiplication method. */ ++#define ED_SIM @ED_SIM@ ++ ++/** Prime curves. */ ++#define PRIME 1 ++/** Binary curves. */ ++#define CHAR2 2 ++/** Edwards curves */ ++#define EDDIE 3 ++/** Chosen elliptic curve type. */ ++#define EC_CUR @EC_CUR@ ++ ++/** Chosen elliptic curve cryptography method. */ ++#define EC_METHD "@EC_METHD@" ++/** Prefer curves with efficient endomorphisms. */ ++#cmakedefine EC_ENDOM ++ ++/** Basic quadratic extension field arithmetic. */ ++#define BASIC 1 ++/** Lazy-reduced extension field arithmetic. */ ++#define LAZYR 2 ++/* Chosen extension field arithmetic method. */ ++#define PP_EXT @PP_EXT@ ++ ++/** Bilinear pairing method. */ ++#define PP_METHD "@PP_METHD@" ++ ++/** Tate pairing. */ ++#define TATEP 1 ++/** Weil pairing. */ ++#define WEILP 2 ++/** Optimal ate pairing. */ ++#define OATEP 3 ++/** Chosen pairing method over prime elliptic curves. */ ++#define PP_MAP @PP_MAP@ ++ ++/** SHA-224 hash function. */ ++#define SH224 2 ++/** SHA-256 hash function. */ ++#define SH256 3 ++/** SHA-384 hash function. */ ++#define SH384 4 ++/** SHA-512 hash function. */ ++#define SH512 5 ++/** BLAKE2s-160 hash function. */ ++#define B2S160 6 ++/** BLAKE2s-256 hash function. */ ++#define B2S256 7 ++/** Chosen hash function. */ ++#define MD_MAP @MD_MAP@ ++ ++/** Choice of hash function. */ ++#define MD_METHD "@MD_METHD@" ++ ++/** Chosen RSA method. */ ++#cmakedefine CP_CRT ++/** RSA without padding. */ ++#define BASIC 1 ++/** RSA PKCS#1 v1.5 padding. */ ++#define PKCS1 2 ++/** RSA PKCS#1 v2.1 padding. */ ++#define PKCS2 3 ++/** Chosen RSA padding method. */ ++#define CP_RSAPD @CP_RSAPD@ ++ ++/** Automatic memory allocation. */ ++#define AUTO 1 ++/** Dynamic memory allocation. */ ++#define DYNAMIC 2 ++/** Chosen memory allocation policy. */ ++#define ALLOC @ALLOC@ ++ ++/** NIST HASH-DRBG generator. */ ++#define HASHD 1 ++/** Intel RdRand instruction. */ ++#define RDRND 2 ++/** Operating system underlying generator. */ ++#define UDEV 3 ++/** Override library generator with the callback. */ ++#define CALL 4 ++/** Chosen random generator. */ ++#define RAND @RAND@ ++ ++/** Standard C library generator. */ ++#define LIBC 1 ++/** Intel RdRand instruction. */ ++#define RDRND 2 ++/** Device node generator. */ ++#define UDEV 3 ++/** Use Windows' CryptGenRandom. */ ++#define WCGR 4 ++/** Chosen random generator seeder. */ ++#cmakedefine SEED @SEED@ ++ ++/** GNU/Linux operating system. */ ++#define LINUX 1 ++/** FreeBSD operating system. */ ++#define FREEBSD 2 ++/** Windows operating system. */ ++#define MACOSX 3 ++/** Windows operating system. */ ++#define WINDOWS 4 ++/** Android operating system. */ ++#define DROID 5 ++/** Arduino platform. */ ++#define DUINO 6 ++/** OpenBSD operating system. */ ++#define OPENBSD 7 ++/** Detected operation system. */ ++#cmakedefine OPSYS @OPSYS@ ++ ++/** OpenMP multithreading support. */ ++#define OPENMP 1 ++/** POSIX multithreading support. */ ++#define PTHREAD 2 ++/** Chosen multithreading API. */ ++#cmakedefine MULTI @MULTI@ ++ ++/** Per-process high-resolution timer. */ ++#define HREAL 1 ++/** Per-process high-resolution timer. */ ++#define HPROC 2 ++/** Per-thread high-resolution timer. */ ++#define HTHRD 3 ++/** POSIX-compatible timer. */ ++#define POSIX 4 ++/** ANSI-compatible timer. */ ++#define ANSI 5 ++/** Cycle-counting timer. */ ++#define CYCLE 6 ++/** Performance monitoring framework. */ ++#define PERF 7 ++/** Chosen timer. */ ++#cmakedefine TIMER @TIMER@ ++ ++/** Prefix to identity this build of the library. */ ++#cmakedefine LABEL @LABEL@ ++ ++#ifndef ASM ++ ++#include "relic_label.h" ++ ++/** ++ * Prints the project options selected at build time. ++ */ ++void conf_print(void); ++ ++#endif /* ASM */ ++ ++#endif /* !RLC_CONF_H */ From 5e0c21aeb88cc72beb1b52bae11dd844a41a0562 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 3 Nov 2026 09:37:46 +0000 Subject: [PATCH 4/4] sdk%ci(gha): build the corpus generators weekly The job writes the corpora with regen.py, checks them unchanged and lints and analyses the programs against that build, weekly and on changes to the programs, the corpora, their lint setup or Nix shells. --- .github/paths.yml | 16 ++++ .github/workflows/actions.lock | 8 ++ .github/workflows/build_corpora.yml | 131 ++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 .github/workflows/build_corpora.yml diff --git a/.github/paths.yml b/.github/paths.yml index f35732a77..c381c7f5f 100644 --- a/.github/paths.yml +++ b/.github/paths.yml @@ -24,6 +24,22 @@ bazel: - MODULE.bazel.lock - pkgs/** +corpora: + - .github/actions/cache_write/** + - .github/actions/gha_cache/** + - .github/paths.yml + - .github/workflows/build_corpora.yml + - .gitmodules + - contrib/nix/** + - maint/.clang-format + - maint/.clang-tidy + - maint/codeql/cpp/** + - maint/common.py + - maint/lint/lint_clang.py + - maint/lint/lint_codeql.py + - pkgs/pkc/contrib/** + - pkgs/pkc/corpus/** + msrv: - .github/actions/cache_write/** - .github/actions/gha_cache/** diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index b6b31035e..be366c3bb 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -9,6 +9,14 @@ workflows: - 'dorny/paths-filter@v4' - 'jlumbroso/free-disk-space@v2' - 'taiki-e/install-action@v2' + '.github/workflows/build_corpora.yml': + - 'actions/cache@v6' + - 'actions/checkout@v7' + - 'determinatesystems/nix-installer-action@v23' + - 'dorny/paths-filter@v4' + - 'jlumbroso/free-disk-space@v2' + - 'mic92/niks3-action@v1' + - 'runs-on/cache@v5.0.7' '.github/workflows/build_cross.yml': - 'actions/cache@v6' - 'actions/checkout@v7' diff --git a/.github/workflows/build_corpora.yml b/.github/workflows/build_corpora.yml new file mode 100644 index 000000000..ea07b6f2e --- /dev/null +++ b/.github/workflows/build_corpora.yml @@ -0,0 +1,131 @@ +name: Build corpus generators + +on: + workflow_dispatch: + push: + branches: [develop] + pull_request: + schedule: + - cron: "0 6 * * 1" + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + changes: + name: Detect changes + runs-on: ubuntu-slim + permissions: + contents: read + pull-requests: read + outputs: + run: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || steps.filter.outputs.corpora == 'true' }} + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 1 + filter: blob:none + sparse-checkout: .github/paths.yml + sparse-checkout-cone-mode: false + persist-credentials: false + + - name: Filter paths + id: filter + if: github.event_name == 'pull_request' || github.event_name == 'push' + uses: dorny/paths-filter@v4 + env: + GIT_CONFIG_COUNT: "1" + GIT_CONFIG_KEY_0: fetch.recurseSubmodules + GIT_CONFIG_VALUE_0: "false" + with: + filters: .github/paths.yml + + build: + name: Build and check the corpora + needs: changes + if: needs.changes.outputs.run == 'true' + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + + defaults: + run: + shell: nix develop ./contrib/nix#ci --command bash -eo pipefail {0} + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Check cache write access + id: cache_write + uses: $/.github/actions/cache_write + + - name: Free disk space + uses: jlumbroso/free-disk-space@v2 + with: + large-packages: false + + - name: Allow unprivileged user namespaces + shell: bash + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v23 + + - name: Cache Nix + uses: Mic92/niks3-action@v1 + if: vars.CACHE_NIX_URL != '' + with: + server-url: ${{ vars.CACHE_NIX_URL }} + skip-push: ${{ steps.cache_write.outputs.allowed != 'true' }} + + - name: Manage CodeQL packs + uses: $/.github/actions/gha_cache + with: + path: ~/.codeql + key: codeql-packs-${{ hashFiles('maint/codeql/*/codeql-pack.lock.yml') }} + restore-only: "true" + url: ${{ vars.CACHE_GHA_URL }} + bucket: ${{ vars.CACHE_GHA_BUCKET }} + region: ${{ vars.CACHE_GHA_REGION }} + key-id: ${{ vars.CACHE_GHA_KEY_ID }} + auth: ${{ steps.cache_write.outputs.allowed == 'true' && secrets.CACHE_GHA_AUTH || '' }} + + - name: Write the corpora + run: pkgs/pkc/contrib/regen.py + + - name: Show the build log + if: failure() + shell: bash + run: cat target/bls-corpus/*-*/build.log + + - name: Check the corpora are unchanged + run: | + git diff --exit-code -- pkgs/pkc/corpus + test -z "$(git status --porcelain -- pkgs/pkc/corpus)" + + - name: Lint the programs against the build + run: | + python3 maint/lint/lint_clang.py 2>&1 | tee "${RUNNER_TEMP}/lint_clang.log" + if grep -qF 'skipping the lint of pkgs/pkc/contrib' "${RUNNER_TEMP}/lint_clang.log"; then + echo '::error::the programs were not linted' + exit 1 + fi + + - name: Analyse the programs against the build + run: | + python3 maint/lint/lint_codeql.py run --lang=cpp --with-suite=cpp-security-and-quality 2>&1 | tee "${RUNNER_TEMP}/lint_codeql.log" + if grep -qF 'skipping the analysis of pkgs/pkc/contrib' "${RUNNER_TEMP}/lint_codeql.log"; then + echo '::error::the programs were not analysed' + exit 1 + fi