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/.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 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/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; }; 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/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/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/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/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/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 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 */