UltrafastSecp256k1/scripts/valgrind_ct_check.sh
Vano Chkheidze 28a40d0a37
feat: v3.16.0 -- BIP-340 strict, OpenSSF hardening, FROST RFC 9591, audit infrastructure (#77)
* feat: v3.16.0 -- BIP-340 strict parsing, CT erasure, local Docker CI

Security:
- BIP-340 strict parsing: Scalar::parse_bytes_strict, FieldElement::parse_bytes_strict, SchnorrSignature::parse_strict
- CT buffer erasure via volatile function-pointer trick in schnorr_sign/ecdsa_sign
- lift_x deduplication, Y-parity fix (limbs()[0] & 1), pragma balance fix
- C ABI functions now use strict parsing internally

Audit:
- ct_sidechannel_smoke marked advisory (timing flakes on shared CI runners)
- carry_propagation test: cross-validation (generator vs generic path) + hex diagnostics for ARM64
- 31-test BIP-340 strict suite (test_bip340_strict.cpp)

Local CI (Docker):
- docker-compose.ci.yml: single-command orchestration for 14 CI jobs
- pre-push target: warnings + tests + ASan + audit in ~5 min
- audit job mirrors audit-report.yml (GCC-13 + Clang-17)
- ccache volume for fast rebuilds
- scripts/hooks/pre-push + scripts/pre-push-ci.ps1

Docs:
- COMPATIBILITY.md, BINDINGS_ERROR_MODEL.md updates
- SECURITY.md: library-side erasure, planned items checklist, API stability refs
- UFSECP_BITCOIN_STRICT CMake option
- packaging.yml release workflow race fix

Tests: 26/26 pass locally (0 failures)

* feat: ARM64 native dudect CI + ct-verif LLVM pass CI, docs update

CI:
- ct-arm64.yml: native Apple Silicon (M1) dudect -- smoke per-PR, full nightly
- ct-verif.yml: compile-time CT verification via LLVM pass (deterministic)

Docs:
- SECURITY.md: mark ARM64 dudect + ct-verif as done, update version table
- CT_VERIFICATION.md: update known limitations, planned improvements, v3.16.0
- CHANGELOG.md: add CT Verification CI section
- README.md: add CT ARM64 + CT-Verif badges

* audit: MuSig2/FROST dudect, Valgrind CT CI, SARIF output, perf regression gate

- test_ct_sidechannel.cpp: add group [9] MuSig2/FROST protocol timing
  tests (musig2_partial_sign, frost_sign, frost_lagrange_coefficient)
- unified_audit_runner.cpp: add write_sarif_report() + --sarif CLI flag
  for GitHub Code Scanning integration (SARIF v2.1.0)
- valgrind-ct.yml: new CI workflow wrapping scripts/valgrind_ct_check.sh
  (nightly + on push to main/dev)
- bench-regression.yml: per-commit benchmark regression gate (120% threshold,
  fail-on-alert: true)
- audit-report.yml: add --sarif flag + SARIF upload step for linux-gcc job,
  security-events:write permission
- SECURITY.md: check off Valgrind CT, MuSig2/FROST dudect, SARIF, perf gate
- CHANGELOG.md: document all new items under v3.16.0
- README.md: add Valgrind CT + Perf Gate workflow badges
- CT_VERIFICATION.md: check off dudect expansion + Valgrind CT taint

* v3.16.1: OpenSSF Scorecard hardening, FROST RFC 9591 tests, audit progress bar, community files

OpenSSF Scorecard (7.3 -> 9+ target):
- Pin all GitHub Actions to full SHA (codeql-action v4.32.4, upload-artifact v6.0.0)
- Add harden-runner to discord-commits, packaging RPM jobs
- Add persist-credentials: false to all checkout steps with write permissions
- Standardize action versions across 13 workflow files

FROST RFC 9591 Protocol Invariant Tests:
- test_rfc9591_invariants: 7 invariants (verification share, Lagrange interpolation,
  Feldman VSS, partial sig linearity, partial sig verification, wrong share rejection,
  nonce commitment consistency)
- test_rfc9591_3of5: exhaustive 3-of-5 signing over all C(5,3)=10 subsets

Audit Sub-test Progress Visibility:
- New audit_check.hpp: centralized CHECK macro with 20-char ASCII progress bar
- Migrated all 22 audit .cpp files to use shared CHECK macro
- Windows-safe unbuffered stdout (setvbuf _IONBF)

New Audit Modules:
- test_musig2_bip327_vectors.cpp: 35 BIP-327 reference tests
- test_ffi_round_trip.cpp: 103 FFI boundary tests
- test_fiat_crypto_vectors.cpp: expanded to 752 checks

Community Files:
- ADOPTERS.md with production/development/hobby categories
- 4 GitHub Discussion templates (Q&A, Show-and-Tell, Ideas, Integration Help)

Build: 24/26 CTest pass (2 ct_sidechannel = known Windows timing noise)
Audit: 48/49 AUDIT-READY (1 advisory dudect smoke)

* fix: valgrind_ct_check.sh binary path (audit/ not cpu/), update CHANGELOG for v3.16.0

* fix: valgrind_ct_check.sh grep -c double-zero bug (0\\n0 integer parse failure)

grep -c prints '0' on no match but exits 1. The || echo '0' fallback
appended a second '0', producing '0\n0' which broke bash [[ -eq 0 ]]
comparisons. Changed to || true with  default.
2026-03-01 17:09:31 +04:00

166 lines
5.6 KiB
Bash

#!/usr/bin/env bash
# ============================================================================
# Valgrind Memcheck CT Analysis
# Phase IV, Task 4.3.3 -- Detect secret-dependent branches via uninit tracking
# ============================================================================
# Uses Valgrind's --track-origins=yes to detect control flow that depends on
# uninitialized / "secret-tainted" memory. We mark secret key material as
# undefined, then check if any branches depend on it.
#
# Approach: Build with special VALGRIND_CT_CHECK define that:
# 1. Marks secret scalars as UNDEFINED via VALGRIND_MAKE_MEM_UNDEFINED
# 2. Runs CT operations (scalar_mul, ecdsa_sign, schnorr_sign)
# 3. Valgrind reports any "Conditional jump depends on uninitialised value"
# 4. Zero reports = CT proven at binary level
#
# Usage:
# ./scripts/valgrind_ct_check.sh [build_dir]
#
# Prerequisites:
# apt-get install valgrind
# Build with: -DCMAKE_BUILD_TYPE=Debug (no optimizations strip the checks)
#
# Exit codes:
# 0 = no secret-dependent branches detected
# 1 = potential CT violation found
# 2 = build/setup error
# ============================================================================
set -euo pipefail
BUILD_DIR="${1:-build/valgrind-ct}"
SRC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
REPORT_DIR="$BUILD_DIR/valgrind_reports"
VALGRIND_LOG="$REPORT_DIR/valgrind_ct.log"
VALGRIND_XML="$REPORT_DIR/valgrind_ct.xml"
echo "==========================================================="
echo " Valgrind CT Analysis"
echo "==========================================================="
echo " Source: $SRC_DIR"
echo " Build: $BUILD_DIR"
echo " Reports: $REPORT_DIR"
echo ""
# -- Check prerequisites ---------------------------------------------------
if ! command -v valgrind &>/dev/null; then
echo "ERROR: valgrind not found. Install with: apt-get install valgrind"
exit 2
fi
VALGRIND_VERSION=$(valgrind --version 2>/dev/null || echo "unknown")
echo " Valgrind: $VALGRIND_VERSION"
echo ""
# -- Build test binary with Valgrind CT checks ----------------------------
echo "[1/4] Configuring (Debug + Valgrind CT markers)..."
cmake -S "$SRC_DIR" -B "$BUILD_DIR" -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DSECP256K1_BUILD_TESTS=ON \
-DSECP256K1_USE_ASM=OFF \
-DCMAKE_CXX_FLAGS="-DVALGRIND_CT_CHECK=1 -g -O0" \
2>&1 | tail -5
echo "[2/4] Building test binary..."
cmake --build "$BUILD_DIR" --target test_ct_sidechannel_standalone -j"$(nproc)" 2>&1 | tail -3
TEST_BIN="$BUILD_DIR/audit/test_ct_sidechannel_standalone"
if [[ ! -x "$TEST_BIN" ]]; then
# Fallback: some CMake configs place it under cpu/
TEST_BIN="$BUILD_DIR/cpu/test_ct_sidechannel_standalone"
fi
if [[ ! -x "$TEST_BIN" ]]; then
echo "ERROR: Test binary not found in audit/ or cpu/ under $BUILD_DIR"
exit 2
fi
# -- Run under Valgrind ----------------------------------------------------
mkdir -p "$REPORT_DIR"
echo "[3/4] Running under Valgrind (this takes several minutes)..."
echo " Tracking: secret-dependent branches + memory errors"
echo ""
set +e
valgrind \
--tool=memcheck \
--track-origins=yes \
--leak-check=no \
--show-reachable=no \
--error-exitcode=42 \
--xml=yes \
--xml-file="$VALGRIND_XML" \
--log-file="$VALGRIND_LOG" \
--num-callers=20 \
--suppressions=/dev/null \
"$TEST_BIN" 2>&1 | tee "$REPORT_DIR/stdout.log"
VG_EXIT=$?
set -e
# -- Analyze results ------------------------------------------------------
echo ""
echo "[4/4] Analyzing Valgrind output..."
echo ""
# Count "Conditional jump or move depends on uninitialised value(s)"
# NOTE: grep -c prints "0" on no match but exits 1; use || true to avoid
# the fallback echo which would produce "0\n0" and break integer comparisons.
CT_ERRORS=$(grep -c "Conditional jump or move depends on uninitialised" "$VALGRIND_LOG" 2>/dev/null || true)
CT_ERRORS=${CT_ERRORS:-0}
# Count "Use of uninitialised value of size"
UNINIT_ERRORS=$(grep -c "Use of uninitialised value" "$VALGRIND_LOG" 2>/dev/null || true)
UNINIT_ERRORS=${UNINIT_ERRORS:-0}
# Count total errors
TOTAL_ERRORS=$(grep -c "ERROR SUMMARY:" "$VALGRIND_LOG" 2>/dev/null || true)
TOTAL_ERRORS=${TOTAL_ERRORS:-0}
ERROR_SUMMARY=$(grep "ERROR SUMMARY:" "$VALGRIND_LOG" 2>/dev/null | tail -1 || echo "N/A")
echo "-----------------------------------------------------------"
echo " Valgrind CT Analysis Results"
echo "-----------------------------------------------------------"
echo " Conditional branch on uninit: $CT_ERRORS"
echo " Use of uninit value: $UNINIT_ERRORS"
echo " $ERROR_SUMMARY"
echo ""
echo " Full log: $VALGRIND_LOG"
echo " XML report: $VALGRIND_XML"
echo "-----------------------------------------------------------"
# -- Generate JSON report -------------------------------------------------
cat > "$REPORT_DIR/valgrind_ct_report.json" <<EOF
{
"tool": "valgrind_ct_check",
"valgrind_version": "$VALGRIND_VERSION",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"binary": "$TEST_BIN",
"ct_branch_errors": $CT_ERRORS,
"uninit_value_errors": $UNINIT_ERRORS,
"valgrind_exit_code": $VG_EXIT,
"verdict": "$([ "$CT_ERRORS" -eq 0 ] && echo "PASS" || echo "FAIL")"
}
EOF
echo " JSON report: $REPORT_DIR/valgrind_ct_report.json"
echo ""
# -- Verdict --------------------------------------------------------------
if [[ "$CT_ERRORS" -eq 0 ]]; then
echo " OK No secret-dependent branches detected by Valgrind"
exit 0
else
echo " X POTENTIAL CT VIOLATION: $CT_ERRORS conditional branches on uninit data"
echo ""
echo " Offending locations:"
grep -A5 "Conditional jump or move depends on uninitialised" "$VALGRIND_LOG" | head -30
exit 1
fi