Bug fixes (bug-bounty pass): - export_assurance.py: test_coverage queried wrong table (edges instead of function_test_map) — all 135 CPU ABI functions now show coverage - export_assurance.py: add crash guard for missing v_symbol_reasoning view - build_project_graph.py: scan ufsecp_gpu.h for ABI functions (was missing 18 GPU functions from graph) - build_project_graph.py: add 'gpu' prefix to categorize_abi_func - preflight.py: include ufsecp_gpu.h in ABI surface check New: - docs/AUDIT_MANIFEST.md: 10 audit principles with severity levels, automation rules, and extension guide - scripts/audit_gate.py: automated audit gate implementing all 10 principles (P1-P10), CLI with per-check flags, JSON output for CI Graph rebuilt: 153 ABI functions (was 135), 0 coverage gaps.
3054 lines
156 KiB
Python
3054 lines
156 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
build_project_graph.py -- UltrafastSecp256k1 Project Knowledge Graph Builder
|
|
|
|
Creates a SQLite database (.project_graph.db) at the library root containing
|
|
the full project knowledge graph: source files, C ABI functions, include
|
|
dependencies, CMake targets, CI workflows, audit modules, constants, namespaces,
|
|
GPU backends, error codes, and rich cross-reference edges.
|
|
|
|
Usage:
|
|
python3 scripts/build_project_graph.py # build from library root
|
|
python3 scripts/build_project_graph.py --rebuild # drop & rebuild
|
|
|
|
The resulting DB is used by AI agents for instant context retrieval.
|
|
"""
|
|
|
|
import sqlite3
|
|
import os
|
|
import re
|
|
import sys
|
|
import json
|
|
import hashlib
|
|
import subprocess
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CONFIG
|
|
# ---------------------------------------------------------------------------
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
LIB_ROOT = SCRIPT_DIR.parent # libs/UltrafastSecp256k1/
|
|
DB_PATH = LIB_ROOT / ".project_graph.db"
|
|
|
|
SOURCE_EXTS = {'.cpp', '.hpp', '.h', '.cu', '.cuh', '.cl', '.metal', '.mm',
|
|
'.S', '.asm', '.py', '.sh', '.ps1', '.cmake'}
|
|
SKIP_DIRS = {'build-linux', 'build_rel', 'build_opencl', 'build-cuda',
|
|
'build-riscv-rel', '_research_repos', 'node_modules', '.git',
|
|
'build', 'build_bench', 'build_rel'}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SCHEMA
|
|
# ---------------------------------------------------------------------------
|
|
SCHEMA_SQL = """
|
|
-- ============================================================
|
|
-- UltrafastSecp256k1 Project Knowledge Graph -- SQLite
|
|
-- ============================================================
|
|
|
|
-- Metadata
|
|
CREATE TABLE IF NOT EXISTS meta (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT
|
|
);
|
|
|
|
-- Source files
|
|
CREATE TABLE IF NOT EXISTS source_files (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
path TEXT UNIQUE NOT NULL, -- relative to lib root
|
|
category TEXT, -- cpu_core, cpu_header, audit, cuda, opencl, metal, binding, example, script, test, abi, compat
|
|
subsystem TEXT, -- field, scalar, point, ecdsa, schnorr, ct, musig2, frost, ethereum, bip32, ...
|
|
file_type TEXT, -- cpp, hpp, h, cu, cuh, cl, metal, mm, S, asm
|
|
lines INTEGER DEFAULT 0,
|
|
sha256 TEXT,
|
|
layer TEXT -- fast, ct, both, abi, gpu, tool
|
|
);
|
|
|
|
-- Namespaces
|
|
CREATE TABLE IF NOT EXISTS namespaces (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT UNIQUE NOT NULL, -- e.g. secp256k1::fast, secp256k1::ct
|
|
purpose TEXT
|
|
);
|
|
|
|
-- C++ types (classes, structs, enums) in public headers
|
|
CREATE TABLE IF NOT EXISTS cpp_types (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
kind TEXT, -- class, struct, enum, using
|
|
namespace TEXT,
|
|
header_path TEXT,
|
|
description TEXT
|
|
);
|
|
|
|
-- C ABI functions (ufsecp_*)
|
|
CREATE TABLE IF NOT EXISTS c_abi_functions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT UNIQUE NOT NULL,
|
|
category TEXT, -- context, seckey, pubkey, ecdsa, schnorr, ecdh, hash, ...
|
|
signature TEXT,
|
|
line_no INTEGER,
|
|
layer TEXT, -- fast, ct, both
|
|
inputs TEXT, -- JSON array of param names
|
|
outputs TEXT -- JSON array of output params
|
|
);
|
|
|
|
-- Include dependencies (source -> header)
|
|
CREATE TABLE IF NOT EXISTS include_deps (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
source_file TEXT NOT NULL, -- relative path of .cpp
|
|
included_file TEXT NOT NULL, -- the #included path
|
|
is_local BOOLEAN DEFAULT 1, -- 1 = "..." local, 0 = <...> system
|
|
UNIQUE(source_file, included_file)
|
|
);
|
|
|
|
-- CMake targets
|
|
CREATE TABLE IF NOT EXISTS cmake_targets (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT UNIQUE NOT NULL,
|
|
type TEXT, -- executable, static_library, shared_library, interface_library, test
|
|
category TEXT, -- core, bench, audit, gpu, example, binding, tool
|
|
timeout INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- CTest test targets
|
|
CREATE TABLE IF NOT EXISTS test_targets (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT UNIQUE NOT NULL,
|
|
executable TEXT,
|
|
category TEXT, -- cpu_core, audit_always, audit_conditional, gpu
|
|
timeout INTEGER DEFAULT 300,
|
|
labels TEXT -- JSON array of CTest labels
|
|
);
|
|
|
|
-- CI workflows
|
|
CREATE TABLE IF NOT EXISTS ci_workflows (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
filename TEXT UNIQUE NOT NULL,
|
|
name TEXT,
|
|
triggers TEXT, -- JSON: {push: [...], pull_request: [...], schedule: ...}
|
|
category TEXT, -- merge_blocking, advisory, release
|
|
jobs TEXT -- JSON array of job names
|
|
);
|
|
|
|
-- Audit modules (from unified_audit_runner)
|
|
CREATE TABLE IF NOT EXISTS audit_modules (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
module_id TEXT UNIQUE NOT NULL,
|
|
name TEXT,
|
|
section TEXT, -- math_invariants, ct_analysis, differential, vectors, fuzzing, protocol, memory, performance
|
|
section_no INTEGER,
|
|
runner TEXT DEFAULT 'unified_audit_runner' -- or gpu_audit_runner, opencl_audit_runner, metal_audit_runner
|
|
);
|
|
|
|
-- Project constants
|
|
CREATE TABLE IF NOT EXISTS constants (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
value TEXT NOT NULL,
|
|
category TEXT, -- curve, precomp, abi_size, error_code, cmake_option
|
|
context TEXT -- where defined or used
|
|
);
|
|
|
|
-- Error codes
|
|
CREATE TABLE IF NOT EXISTS error_codes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code INTEGER UNIQUE NOT NULL,
|
|
name TEXT NOT NULL,
|
|
symbol TEXT, -- UFSECP_ERR_*
|
|
meaning TEXT
|
|
);
|
|
|
|
-- GPU backends
|
|
CREATE TABLE IF NOT EXISTS gpu_backends (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
backend TEXT UNIQUE NOT NULL, -- cuda, opencl, metal
|
|
sources TEXT, -- JSON array of source files
|
|
kernels TEXT, -- JSON array of kernel files
|
|
targets TEXT, -- JSON array of CMake targets
|
|
features TEXT -- JSON: {ecdsa: true, schnorr: true, ct: true, ...}
|
|
);
|
|
|
|
-- Cross-reference edges (generic relation graph)
|
|
CREATE TABLE IF NOT EXISTS edges (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
src_type TEXT NOT NULL, -- source_file, c_abi_function, test_target, audit_module, ...
|
|
src_id TEXT NOT NULL, -- name or path
|
|
dst_type TEXT NOT NULL,
|
|
dst_id TEXT NOT NULL,
|
|
relation TEXT NOT NULL, -- includes, tests, implements, depends_on, calls, covers
|
|
weight REAL DEFAULT 1.0
|
|
);
|
|
|
|
-- Build configurations
|
|
CREATE TABLE IF NOT EXISTS build_configs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT UNIQUE NOT NULL, -- cmake option name
|
|
default_value TEXT,
|
|
type TEXT, -- BOOL, STRING, INTEGER
|
|
effect TEXT
|
|
);
|
|
|
|
-- Platform dispatch
|
|
CREATE TABLE IF NOT EXISTS platform_dispatch (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
source_file TEXT NOT NULL,
|
|
platform TEXT NOT NULL, -- x86_64, arm64, riscv64, esp32, wasm, msvc
|
|
mechanism TEXT, -- ifdef, constexpr_if, asm_file
|
|
description TEXT
|
|
);
|
|
|
|
-- Documentation index
|
|
CREATE TABLE IF NOT EXISTS docs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
path TEXT UNIQUE NOT NULL,
|
|
title TEXT,
|
|
category TEXT, -- architecture, api, build, security, audit, testing, benchmark, binding, release, optimization
|
|
topics TEXT -- JSON array of topic tags
|
|
);
|
|
|
|
-- Semantic tags for richer project navigation and AI retrieval
|
|
CREATE TABLE IF NOT EXISTS semantic_tags (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
tag TEXT UNIQUE NOT NULL,
|
|
domain TEXT,
|
|
description TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS entity_tags (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
entity_type TEXT NOT NULL, -- source_file, c_abi_function, doc
|
|
entity_id TEXT NOT NULL, -- path / symbol / doc path
|
|
tag TEXT NOT NULL,
|
|
confidence REAL DEFAULT 1.0,
|
|
origin TEXT DEFAULT 'derived',
|
|
UNIQUE(entity_type, entity_id, tag)
|
|
);
|
|
|
|
-- C++ public methods (extracted from headers)
|
|
CREATE TABLE IF NOT EXISTS cpp_methods (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
class_name TEXT NOT NULL, -- FieldElement, Scalar, Point, etc.
|
|
method TEXT NOT NULL, -- method name
|
|
signature TEXT, -- return_type method(params)
|
|
is_static BOOLEAN DEFAULT 0,
|
|
is_const BOOLEAN DEFAULT 0,
|
|
is_noexcept BOOLEAN DEFAULT 0,
|
|
header_path TEXT,
|
|
line_no INTEGER,
|
|
layer TEXT -- fast, ct
|
|
);
|
|
|
|
-- Security-critical pattern locations
|
|
CREATE TABLE IF NOT EXISTS security_patterns (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
pattern TEXT NOT NULL, -- secure_erase, value_barrier, CLASSIFY, DECLASSIFY
|
|
source_file TEXT NOT NULL,
|
|
line_no INTEGER NOT NULL,
|
|
context TEXT -- brief snippet or purpose
|
|
);
|
|
|
|
-- ABI routing map (which internal function each ufsecp_* calls)
|
|
CREATE TABLE IF NOT EXISTS abi_routing (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
abi_function TEXT NOT NULL, -- ufsecp_ecdsa_sign
|
|
internal_call TEXT NOT NULL, -- ct::ecdsa_sign or fast::ecdsa_verify
|
|
layer TEXT NOT NULL, -- ct, fast
|
|
impl_line INTEGER -- line in ufsecp_impl.cpp
|
|
);
|
|
|
|
-- Binding language metadata
|
|
CREATE TABLE IF NOT EXISTS binding_languages (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
language TEXT UNIQUE NOT NULL,
|
|
directory TEXT NOT NULL,
|
|
file_count INTEGER DEFAULT 0,
|
|
status TEXT, -- stable, active, supported, community, experimental
|
|
package_name TEXT, -- pip name, crate name, pod name, etc.
|
|
ffi_method TEXT -- cffi, ctypes, cgo, JNI, N-API, P/Invoke, FFI
|
|
);
|
|
|
|
-- Compile-time macros and defines
|
|
CREATE TABLE IF NOT EXISTS macros (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
value TEXT,
|
|
file_path TEXT NOT NULL,
|
|
line_no INTEGER,
|
|
category TEXT -- platform_guard, size_constant, feature_flag, ct_marker, debug
|
|
);
|
|
|
|
-- Per-file one-line summaries so agents never need to open a file just to understand its role
|
|
CREATE TABLE IF NOT EXISTS file_summaries (
|
|
path TEXT PRIMARY KEY,
|
|
summary TEXT NOT NULL -- max ~120 chars, enough for instant context
|
|
);
|
|
|
|
-- Function/method line ranges: agents can read_file with exact ranges, no guessing
|
|
CREATE TABLE IF NOT EXISTS function_index (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
file_path TEXT NOT NULL,
|
|
name TEXT NOT NULL, -- function/method name
|
|
start_line INTEGER NOT NULL,
|
|
end_line INTEGER NOT NULL,
|
|
kind TEXT DEFAULT 'function', -- function, method, constructor, operator
|
|
class_name TEXT, -- NULL for free functions
|
|
UNIQUE(file_path, name, start_line)
|
|
);
|
|
|
|
-- FTS5 full-text search on key tables
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS fts_files USING fts5(
|
|
path, category, subsystem, layer, content=source_files, content_rowid=id
|
|
);
|
|
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS fts_functions USING fts5(
|
|
name, category, signature, layer, content=c_abi_functions, content_rowid=id
|
|
);
|
|
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS fts_docs USING fts5(
|
|
path, title, category, topics, content=docs, content_rowid=id
|
|
);
|
|
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS fts_tags USING fts5(
|
|
entity_type, entity_id, tag, domain, description
|
|
);
|
|
|
|
-- Triggers to keep FTS in sync
|
|
CREATE TRIGGER IF NOT EXISTS source_files_ai AFTER INSERT ON source_files BEGIN
|
|
INSERT INTO fts_files(rowid, path, category, subsystem, layer) VALUES (new.id, new.path, new.category, new.subsystem, new.layer);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS c_abi_functions_ai AFTER INSERT ON c_abi_functions BEGIN
|
|
INSERT INTO fts_functions(rowid, name, category, signature, layer) VALUES (new.id, new.name, new.category, new.signature, new.layer);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS docs_ai AFTER INSERT ON docs BEGIN
|
|
INSERT INTO fts_docs(rowid, path, title, category, topics) VALUES (new.id, new.path, new.title, new.category, new.topics);
|
|
END;
|
|
|
|
-- FTS5 on methods (search by class, method name, signature)
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS fts_methods USING fts5(
|
|
class_name, method, signature, layer, content=cpp_methods, content_rowid=id
|
|
);
|
|
CREATE TRIGGER IF NOT EXISTS cpp_methods_ai AFTER INSERT ON cpp_methods BEGIN
|
|
INSERT INTO fts_methods(rowid, class_name, method, signature, layer) VALUES (new.id, new.class_name, new.method, new.signature, new.layer);
|
|
END;
|
|
|
|
-- FTS5 on ABI routing (search by function name, internal call, layer)
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS fts_routing USING fts5(
|
|
abi_function, internal_call, layer, content=abi_routing, content_rowid=id
|
|
);
|
|
CREATE TRIGGER IF NOT EXISTS abi_routing_ai AFTER INSERT ON abi_routing BEGIN
|
|
INSERT INTO fts_routing(rowid, abi_function, internal_call, layer) VALUES (new.id, new.abi_function, new.internal_call, new.layer);
|
|
END;
|
|
|
|
-- Useful views
|
|
CREATE VIEW IF NOT EXISTS v_file_deps AS
|
|
SELECT sf.path AS source, d.included_file AS dependency, d.is_local
|
|
FROM include_deps d
|
|
JOIN source_files sf ON sf.path = d.source_file;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_subsystem_files AS
|
|
SELECT subsystem, COUNT(*) AS file_count, SUM(lines) AS total_lines
|
|
FROM source_files
|
|
WHERE subsystem IS NOT NULL
|
|
GROUP BY subsystem
|
|
ORDER BY total_lines DESC;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_layer_summary AS
|
|
SELECT layer, COUNT(*) AS file_count, SUM(lines) AS total_lines
|
|
FROM source_files
|
|
WHERE layer IS NOT NULL
|
|
GROUP BY layer;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_test_coverage AS
|
|
SELECT t.name AS test, t.category,
|
|
GROUP_CONCAT(e.dst_id) AS covers_files
|
|
FROM test_targets t
|
|
LEFT JOIN edges e ON e.src_type='test_target' AND e.src_id=t.name AND e.relation='covers'
|
|
GROUP BY t.name;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_abi_routing AS
|
|
SELECT ar.abi_function, ar.internal_call, ar.layer,
|
|
e.dst_id AS impl_file
|
|
FROM abi_routing ar
|
|
LEFT JOIN edges e ON e.src_type='c_abi_function' AND e.src_id=ar.abi_function AND e.relation='implements';
|
|
|
|
CREATE VIEW IF NOT EXISTS v_security_hotspots AS
|
|
SELECT source_file, pattern, COUNT(*) AS count,
|
|
GROUP_CONCAT(line_no) AS lines
|
|
FROM security_patterns
|
|
GROUP BY source_file, pattern
|
|
ORDER BY count DESC;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_class_methods AS
|
|
SELECT class_name, layer, COUNT(*) AS method_count,
|
|
GROUP_CONCAT(method, ', ') AS methods
|
|
FROM cpp_methods
|
|
GROUP BY class_name, layer;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_impact_analysis AS
|
|
SELECT sf.path, sf.subsystem, sf.lines,
|
|
(SELECT COUNT(*) FROM include_deps WHERE source_file=sf.path) AS dep_count,
|
|
(SELECT COUNT(*) FROM include_deps WHERE included_file LIKE '%'||REPLACE(sf.path,'/','%')) AS rdep_count,
|
|
(SELECT COUNT(*) FROM edges WHERE dst_id=sf.path AND relation='covers') AS test_count,
|
|
(SELECT COUNT(*) FROM edges WHERE dst_id=sf.path AND relation='implements') AS abi_func_count,
|
|
(SELECT COUNT(*) FROM security_patterns WHERE source_file=sf.path) AS security_pattern_count
|
|
FROM source_files sf
|
|
WHERE sf.category IN ('cpu_core', 'abi')
|
|
ORDER BY sf.lines DESC;
|
|
|
|
-- Coverage gaps: core files with no test coverage, sorted by risk (lines DESC)
|
|
CREATE VIEW IF NOT EXISTS v_coverage_gaps AS
|
|
SELECT sf.path, sf.subsystem, sf.layer, sf.lines,
|
|
(SELECT COUNT(*) FROM security_patterns WHERE source_file=sf.path) AS security_patterns,
|
|
(SELECT COUNT(*) FROM edges WHERE dst_id=sf.path AND relation='implements') AS abi_functions
|
|
FROM source_files sf
|
|
WHERE sf.category = 'cpu_core'
|
|
AND sf.file_type IN ('cpp', 'source')
|
|
AND sf.lines > 50
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM edges WHERE dst_id=sf.path AND relation='covers'
|
|
)
|
|
ORDER BY sf.lines DESC;
|
|
|
|
-- Edge type summary
|
|
CREATE VIEW IF NOT EXISTS v_edge_summary AS
|
|
SELECT relation, COUNT(*) AS cnt,
|
|
COUNT(DISTINCT src_id) AS unique_sources,
|
|
COUNT(DISTINCT dst_id) AS unique_targets
|
|
FROM edges GROUP BY relation ORDER BY cnt DESC;
|
|
|
|
-- ABI security map: which ABI functions touch CT code
|
|
CREATE VIEW IF NOT EXISTS v_abi_security AS
|
|
SELECT ar.abi_function, ar.layer, ar.internal_call,
|
|
(SELECT GROUP_CONCAT(DISTINCT sp.pattern)
|
|
FROM security_patterns sp
|
|
JOIN edges e ON e.dst_id = sp.source_file
|
|
WHERE e.src_id = ar.abi_function AND e.relation = 'routes_through'
|
|
) AS security_patterns_in_impl
|
|
FROM abi_routing ar
|
|
WHERE ar.layer = 'ct'
|
|
ORDER BY ar.abi_function;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- PHASE 4 ADDITIONS: call graph, config bindings, symbol aliases, hotspot
|
|
-- scores, reachability, runtime entrypoints, function-test
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- IMPROVEMENT 1: Function-level call graph (who calls whom)
|
|
CREATE TABLE IF NOT EXISTS call_edges (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
caller_file TEXT NOT NULL,
|
|
caller_func TEXT NOT NULL,
|
|
callee_func TEXT NOT NULL,
|
|
callee_file TEXT,
|
|
call_line INTEGER,
|
|
UNIQUE(caller_file, caller_func, callee_func, call_line)
|
|
);
|
|
|
|
-- IMPROVEMENT 2: Config / CMake option -> code symbol binding
|
|
CREATE TABLE IF NOT EXISTS config_bindings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
config_file TEXT NOT NULL, -- CMakeLists.txt, config.json, etc.
|
|
config_key TEXT NOT NULL, -- option name / JSON field
|
|
code_symbol TEXT NOT NULL, -- #define / function / macro that handles it
|
|
code_file TEXT,
|
|
binding_type TEXT, -- cmake_option, json_field, build_flag, project_constant
|
|
description TEXT,
|
|
UNIQUE(config_file, config_key, code_symbol)
|
|
);
|
|
|
|
-- IMPROVEMENT 3: Symbol aliases and typo/variant detection
|
|
CREATE TABLE IF NOT EXISTS symbol_aliases (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
canonical TEXT NOT NULL,
|
|
alias TEXT NOT NULL,
|
|
similarity REAL, -- 0.0-1.0 SequenceMatcher ratio
|
|
kind TEXT, -- typo, variant, abbreviation
|
|
source_file TEXT,
|
|
UNIQUE(canonical, alias)
|
|
);
|
|
|
|
-- IMPROVEMENT 4: Per-file hotspot risk scores
|
|
CREATE TABLE IF NOT EXISTS hotspot_scores (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
file_path TEXT UNIQUE NOT NULL,
|
|
coupling_score REAL DEFAULT 0, -- fan-in + fan-out from include_deps
|
|
security_density REAL DEFAULT 0, -- security_patterns / lines * 100
|
|
null_risk_score REAL DEFAULT 0, -- raw pointer / reinterpret patterns
|
|
test_coverage_gap REAL DEFAULT 0, -- 1 = no tests, 0 = covered
|
|
hotspot_score REAL DEFAULT 0, -- weighted composite (0-10)
|
|
reasons TEXT -- JSON array of contributing factors
|
|
);
|
|
|
|
-- IMPROVEMENT 5: Dead-code / reachability analysis
|
|
CREATE TABLE IF NOT EXISTS reachability (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
symbol TEXT NOT NULL,
|
|
file_path TEXT NOT NULL,
|
|
is_reachable INTEGER DEFAULT 1,
|
|
reach_via TEXT, -- caller that makes it reachable
|
|
dead_reason TEXT, -- no_caller_in_call_graph, etc.
|
|
UNIQUE(symbol, file_path)
|
|
);
|
|
|
|
-- IMPROVEMENT 6: Runtime entrypoints and startup file loaders
|
|
CREATE TABLE IF NOT EXISTS runtime_entrypoints (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
binary TEXT NOT NULL,
|
|
entrypoint_func TEXT NOT NULL,
|
|
loads_file TEXT,
|
|
load_mechanism TEXT, -- fopen, compiled-in, cmake-option, string_ref
|
|
source_file TEXT,
|
|
line_no INTEGER
|
|
);
|
|
|
|
-- IMPROVEMENT 7: Function -> test target mapping (function-level coverage)
|
|
CREATE TABLE IF NOT EXISTS function_test_map (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
function_name TEXT NOT NULL,
|
|
function_file TEXT NOT NULL,
|
|
test_target TEXT NOT NULL,
|
|
coverage_type TEXT, -- indirect, kat, fuzzing
|
|
UNIQUE(function_name, function_file, test_target)
|
|
);
|
|
|
|
-- Phase 5: crypto reasoning layers for symbol-level analysis
|
|
CREATE TABLE IF NOT EXISTS symbol_semantics (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
symbol_name TEXT NOT NULL,
|
|
file_path TEXT NOT NULL,
|
|
category TEXT,
|
|
math_core TEXT,
|
|
backend TEXT,
|
|
coordinate_model TEXT,
|
|
secret_class TEXT,
|
|
abi_surface INTEGER DEFAULT 0,
|
|
generator_path INTEGER DEFAULT 0,
|
|
varpoint_path INTEGER DEFAULT 0,
|
|
bip340_related INTEGER DEFAULT 0,
|
|
bip352_related INTEGER DEFAULT 0,
|
|
UNIQUE(symbol_name, file_path)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS symbol_security (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
symbol_name TEXT NOT NULL,
|
|
file_path TEXT NOT NULL,
|
|
uses_secret_input INTEGER DEFAULT 0,
|
|
must_be_constant_time INTEGER DEFAULT 0,
|
|
public_data_only INTEGER DEFAULT 0,
|
|
device_secret_upload INTEGER DEFAULT 0,
|
|
requires_zeroization INTEGER DEFAULT 0,
|
|
invalid_input_sensitive INTEGER DEFAULT 0,
|
|
notes TEXT,
|
|
UNIQUE(symbol_name, file_path)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS symbol_performance (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
symbol_name TEXT NOT NULL,
|
|
file_path TEXT NOT NULL,
|
|
hotness_score REAL DEFAULT 0,
|
|
estimated_cost REAL DEFAULT 0,
|
|
batchable INTEGER DEFAULT 0,
|
|
vectorizable INTEGER DEFAULT 0,
|
|
gpu_candidate INTEGER DEFAULT 0,
|
|
memory_bound INTEGER DEFAULT 0,
|
|
compute_bound INTEGER DEFAULT 0,
|
|
duplicated_backends INTEGER DEFAULT 0,
|
|
UNIQUE(symbol_name, file_path)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS symbol_audit_coverage (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
symbol_name TEXT NOT NULL,
|
|
file_path TEXT NOT NULL,
|
|
covered_by_unit_test INTEGER DEFAULT 0,
|
|
covered_by_fuzz INTEGER DEFAULT 0,
|
|
covered_by_invalid_vectors INTEGER DEFAULT 0,
|
|
covered_by_ct_test INTEGER DEFAULT 0,
|
|
covered_by_cross_impl_diff INTEGER DEFAULT 0,
|
|
covered_by_gpu_equivalence INTEGER DEFAULT 0,
|
|
covered_by_regression_test INTEGER DEFAULT 0,
|
|
last_audit_result TEXT DEFAULT 'unknown',
|
|
times_failed_historically INTEGER DEFAULT 0,
|
|
known_fragile INTEGER DEFAULT 0,
|
|
UNIQUE(symbol_name, file_path)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS symbol_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
symbol_name TEXT NOT NULL,
|
|
file_path TEXT NOT NULL,
|
|
times_modified INTEGER DEFAULT 0,
|
|
recently_modified INTEGER DEFAULT 0,
|
|
bug_fix_count INTEGER DEFAULT 0,
|
|
performance_tuning_count INTEGER DEFAULT 0,
|
|
audit_related_changes INTEGER DEFAULT 0,
|
|
last_modified TEXT,
|
|
UNIQUE(symbol_name, file_path)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS symbol_scores (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
symbol_name TEXT NOT NULL,
|
|
file_path TEXT NOT NULL,
|
|
risk_score REAL DEFAULT 0,
|
|
gain_score REAL DEFAULT 0,
|
|
optimization_priority REAL DEFAULT 0,
|
|
risk_reasons TEXT,
|
|
gain_reasons TEXT,
|
|
UNIQUE(symbol_name, file_path)
|
|
);
|
|
|
|
-- Views for new tables
|
|
CREATE VIEW IF NOT EXISTS v_call_graph AS
|
|
SELECT caller_file, caller_func, callee_func, callee_file, call_line
|
|
FROM call_edges ORDER BY caller_file, caller_func, call_line;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_hotspot_top AS
|
|
SELECT file_path, hotspot_score, coupling_score, security_density,
|
|
test_coverage_gap, null_risk_score, reasons
|
|
FROM hotspot_scores ORDER BY hotspot_score DESC LIMIT 30;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_dead_code AS
|
|
SELECT symbol, file_path, dead_reason
|
|
FROM reachability WHERE is_reachable = 0
|
|
ORDER BY file_path, symbol;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_function_coverage AS
|
|
SELECT ftm.function_name, ftm.function_file, ftm.test_target, ftm.coverage_type,
|
|
fi.start_line, fi.end_line
|
|
FROM function_test_map ftm
|
|
LEFT JOIN function_index fi ON fi.file_path = ftm.function_file
|
|
AND fi.name = ftm.function_name
|
|
ORDER BY ftm.function_file, ftm.function_name;
|
|
|
|
CREATE VIEW IF NOT EXISTS v_symbol_reasoning AS
|
|
SELECT ss.symbol_name, ss.file_path, ss.category, ss.math_core, ss.backend,
|
|
ss.coordinate_model, ss.secret_class, ss.abi_surface,
|
|
sec.uses_secret_input, sec.must_be_constant_time, sec.public_data_only,
|
|
perf.hotness_score, perf.gpu_candidate, perf.batchable,
|
|
perf.compute_bound, perf.memory_bound,
|
|
cov.covered_by_unit_test, cov.covered_by_fuzz, cov.covered_by_ct_test,
|
|
hist.times_modified, hist.recently_modified,
|
|
score.risk_score, score.gain_score, score.optimization_priority
|
|
FROM symbol_semantics ss
|
|
LEFT JOIN symbol_security sec
|
|
ON sec.symbol_name = ss.symbol_name AND sec.file_path = ss.file_path
|
|
LEFT JOIN symbol_performance perf
|
|
ON perf.symbol_name = ss.symbol_name AND perf.file_path = ss.file_path
|
|
LEFT JOIN symbol_audit_coverage cov
|
|
ON cov.symbol_name = ss.symbol_name AND cov.file_path = ss.file_path
|
|
LEFT JOIN symbol_history hist
|
|
ON hist.symbol_name = ss.symbol_name AND hist.file_path = ss.file_path
|
|
LEFT JOIN symbol_scores score
|
|
ON score.symbol_name = ss.symbol_name AND score.file_path = ss.file_path;
|
|
"""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# HELPERS
|
|
# ---------------------------------------------------------------------------
|
|
def file_sha256(filepath: Path) -> str:
|
|
h = hashlib.sha256()
|
|
try:
|
|
with open(filepath, 'rb') as f:
|
|
for chunk in iter(lambda: f.read(8192), b''):
|
|
h.update(chunk)
|
|
return h.hexdigest()[:16] # short hash
|
|
except Exception:
|
|
return ""
|
|
|
|
def count_lines(filepath: Path) -> int:
|
|
try:
|
|
with open(filepath, 'rb') as f:
|
|
return sum(1 for _ in f)
|
|
except Exception:
|
|
return 0
|
|
|
|
|
|
def should_skip_dir(dirname: str) -> bool:
|
|
"""Filter generated or irrelevant directories during graph traversal."""
|
|
return dirname.startswith('.') or dirname in SKIP_DIRS or dirname.startswith('build')
|
|
|
|
def classify_file(rel_path: str):
|
|
"""Return (category, subsystem, layer) for a relative path."""
|
|
p = rel_path.lower()
|
|
|
|
# Category
|
|
if p.startswith('cpu/src/'):
|
|
cat = 'cpu_core'
|
|
elif p.startswith('cpu/include/'):
|
|
cat = 'cpu_header'
|
|
elif p.startswith('cpu/tests/') or p.startswith('cpu/test'):
|
|
cat = 'cpu_test'
|
|
elif p.startswith('cpu/bench/'):
|
|
cat = 'benchmark'
|
|
elif p.startswith('cpu/fuzz/'):
|
|
cat = 'fuzz'
|
|
elif p.startswith('audit/'):
|
|
cat = 'audit'
|
|
elif p.startswith('cuda/'):
|
|
cat = 'cuda'
|
|
elif p.startswith('opencl/'):
|
|
cat = 'opencl'
|
|
elif p.startswith('metal/'):
|
|
cat = 'metal'
|
|
elif p.startswith('include/ufsecp/'):
|
|
cat = 'abi'
|
|
elif p.startswith('bindings/'):
|
|
cat = 'binding'
|
|
elif p.startswith('examples/'):
|
|
cat = 'example'
|
|
elif p.startswith('scripts/'):
|
|
cat = 'script'
|
|
elif p.startswith('compat/'):
|
|
cat = 'compat'
|
|
elif p.startswith('wasm/'):
|
|
cat = 'wasm'
|
|
elif p.startswith('android/'):
|
|
cat = 'android'
|
|
elif p.startswith('tests/'):
|
|
cat = 'test_integration'
|
|
else:
|
|
cat = 'other'
|
|
|
|
# Subsystem
|
|
basename = os.path.basename(p)
|
|
sub = None
|
|
subsystem_map = {
|
|
'field': ['field'],
|
|
'scalar': ['scalar'],
|
|
'point': ['point'],
|
|
'ecdsa': ['ecdsa'],
|
|
'schnorr': ['schnorr'],
|
|
'ecdh': ['ecdh'],
|
|
'musig2': ['musig2'],
|
|
'frost': ['frost'],
|
|
'adaptor': ['adaptor'],
|
|
'taproot': ['taproot'],
|
|
'ecies': ['ecies'],
|
|
'bip32': ['bip32'],
|
|
'bip39': ['bip39'],
|
|
'bip340': ['bip340'],
|
|
'ethereum': ['ethereum', 'eth_signing', 'keccak'],
|
|
'hash': ['sha256', 'sha512', 'hash_accel', 'hash160', 'keccak256', 'tagged_hash'],
|
|
'pedersen': ['pedersen'],
|
|
'zk': ['zk', 'range_proof'],
|
|
'glv': ['glv'],
|
|
'precompute': ['precompute', 'ecmult_gen_comb'],
|
|
'recovery': ['recovery'],
|
|
'multiscalar': ['multiscalar', 'pippenger'],
|
|
'batch': ['batch_verify', 'batch_add_affine'],
|
|
'wallet': ['wallet', 'coin_address', 'coin_hd', 'coin_params', 'address', 'message_signing', 'wif'],
|
|
'selftest': ['selftest'],
|
|
'ct': ['ct_field', 'ct_scalar', 'ct_point', 'ct_sign', 'ct_ops', 'ct_utils', 'ct_zk'],
|
|
}
|
|
for subsys, keywords in subsystem_map.items():
|
|
for kw in keywords:
|
|
if kw in basename:
|
|
sub = subsys
|
|
break
|
|
if sub:
|
|
break
|
|
|
|
# Layer
|
|
# Use startswith to avoid false matches like "project_" containing "ct_"
|
|
if 'ct/' in p or basename.startswith('ct_'):
|
|
layer = 'ct'
|
|
elif cat in ('cuda', 'opencl', 'metal'):
|
|
layer = 'gpu'
|
|
elif cat == 'abi':
|
|
layer = 'abi'
|
|
elif cat in ('script', 'benchmark'):
|
|
layer = 'tool'
|
|
elif cat in ('audit', 'cpu_test', 'fuzz', 'test_integration'):
|
|
layer = 'test'
|
|
else:
|
|
layer = 'fast'
|
|
|
|
return cat, sub, layer
|
|
|
|
def extract_includes(filepath: Path):
|
|
"""Extract #include directives from a source file."""
|
|
includes = []
|
|
try:
|
|
with open(filepath, 'r', errors='replace') as f:
|
|
for line in f:
|
|
m = re.match(r'\s*#include\s+([<"])(.*?)[>"]', line)
|
|
if m:
|
|
is_local = m.group(1) == '"'
|
|
includes.append((m.group(2), is_local))
|
|
except Exception:
|
|
pass
|
|
return includes
|
|
|
|
def categorize_abi_func(name: str):
|
|
"""Categorize a ufsecp_* function name."""
|
|
n = name.replace('ufsecp_', '')
|
|
categories = {
|
|
'gpu': 'gpu', 'ctx': 'context', 'seckey': 'seckey', 'pubkey': 'pubkey',
|
|
'ecdsa': 'ecdsa', 'schnorr': 'schnorr', 'ecdh': 'ecdh',
|
|
'sha256': 'hash', 'sha512': 'hash', 'hash160': 'hash',
|
|
'tagged_hash': 'hash', 'keccak256': 'ethereum',
|
|
'addr': 'address', 'wif': 'wif',
|
|
'bip32': 'bip32', 'bip39': 'bip39',
|
|
'taproot': 'taproot', 'musig2': 'musig2', 'frost': 'frost',
|
|
'adaptor': 'adaptor', 'pedersen': 'pedersen', 'zk': 'zk',
|
|
'coin': 'wallet', 'btc_message': 'wallet',
|
|
'silent_payment': 'silent_payments',
|
|
'ecies': 'ecies', 'eth': 'ethereum',
|
|
'shamir': 'multiscalar', 'multi_scalar': 'multiscalar',
|
|
'last_error': 'error', 'error': 'error',
|
|
}
|
|
for prefix, cat in categories.items():
|
|
if n.startswith(prefix):
|
|
return cat
|
|
return 'other'
|
|
|
|
def abi_layer(name: str):
|
|
"""Which layer does a C ABI function route to?"""
|
|
ct_funcs = {'ecdsa_sign', 'ecdsa_sign_verified', 'schnorr_sign',
|
|
'schnorr_sign_verified', 'ecdh', 'ecdh_xonly', 'ecdh_raw',
|
|
'bip32_master', 'bip32_derive', 'bip32_derive_path',
|
|
'seckey_negate', 'seckey_tweak_add', 'seckey_tweak_mul',
|
|
'musig2_partial_sign', 'frost_sign',
|
|
'schnorr_adaptor_sign', 'ecdsa_adaptor_sign',
|
|
'ecies_encrypt', 'ecies_decrypt',
|
|
'eth_sign', 'silent_payment_create_output',
|
|
'taproot_tweak_seckey'}
|
|
n = name.replace('ufsecp_', '')
|
|
if n in ct_funcs:
|
|
return 'ct'
|
|
elif 'verify' in n or 'batch' in n or 'parse' in n or 'create' in n:
|
|
return 'fast'
|
|
return 'both'
|
|
|
|
|
|
SEMANTIC_TAGS = {
|
|
'constant_time': ('security', 'Touches secret-bearing constant-time paths or CT verification logic.'),
|
|
'verification': ('crypto', 'Signature, proof, or validity verification logic.'),
|
|
'signing': ('crypto', 'Secret-key signing, nonce generation, or signing session flow.'),
|
|
'parser_boundary': ('security', 'Strict parsing, decoding, or malformed-input boundary handling.'),
|
|
'wallet_flow': ('protocol', 'Wallet, address, mnemonic, or HD derivation user-facing flow.'),
|
|
'protocol_multisig': ('protocol', 'MuSig2, FROST, adaptor, or multi-party signing logic.'),
|
|
'privacy_protocol': ('protocol', 'Silent payments, ECIES, Pedersen, or privacy-preserving flow.'),
|
|
'zk_proof': ('protocol', 'Zero-knowledge proof generation or verification.'),
|
|
'gpu_acceleration': ('platform', 'GPU backend, kernels, host API, or backend dispatch logic.'),
|
|
'cross_platform': ('platform', 'Platform dispatch, compatibility, or architecture-specific implementation.'),
|
|
'ffi_surface': ('ecosystem', 'Public C ABI, bindings, or host-language interop surface.'),
|
|
'audit_evidence': ('tooling', 'Audit runner, assurance export, CI evidence, or self-audit infrastructure.'),
|
|
'benchmarking': ('tooling', 'Benchmark harness, performance measurement, or regression gate.'),
|
|
'build_release': ('tooling', 'Build, release, packaging, or reproducibility flow.'),
|
|
'hashing': ('crypto', 'SHA-2, Keccak, tagged hash, hash160, or hash acceleration paths.'),
|
|
'ethereum_stack': ('protocol', 'Ethereum signing, recovery, addressing, or Keccak-backed APIs.'),
|
|
'taproot_stack': ('protocol', 'Taproot, BIP-340, x-only pubkeys, or taproot tweaking logic.'),
|
|
'differential_testing': ('tooling', 'Cross-lib differential, KAT, vector, or equivalence testing.'),
|
|
}
|
|
|
|
|
|
def derive_semantic_tags_for_source(path: str, category: str, subsystem: str, layer: str):
|
|
"""Derive semantic tags for a source/docs artifact from path/category/layer hints."""
|
|
p = path.lower()
|
|
tags = {}
|
|
|
|
def add(tag: str, confidence: float = 1.0):
|
|
if tag in SEMANTIC_TAGS:
|
|
tags[tag] = max(tags.get(tag, 0.0), confidence)
|
|
|
|
if layer == 'ct' or '/ct/' in p or os.path.basename(p).startswith('ct_'):
|
|
add('constant_time', 1.0)
|
|
if category in ('abi', 'binding') or p.startswith('bindings/') or 'ufsecp' in p:
|
|
add('ffi_surface', 0.95)
|
|
if category in ('audit', 'cpu_test', 'fuzz', 'test_integration') or 'audit' in p:
|
|
add('audit_evidence', 0.95)
|
|
if category == 'benchmark' or 'bench' in p:
|
|
add('benchmarking', 0.95)
|
|
if category in ('cuda', 'opencl', 'metal') or p.startswith('gpu/'):
|
|
add('gpu_acceleration', 1.0)
|
|
if category in ('compat', 'android', 'wasm') or any(x in p for x in ('arm64', 'riscv', 'esp32', 'wasm', 'msvc', 'cross_platform')):
|
|
add('cross_platform', 0.9)
|
|
if category in ('script',) or any(x in p for x in ('release', 'package', 'build', 'cmake', 'preset', 'reproducible')):
|
|
add('build_release', 0.8)
|
|
|
|
if subsystem in ('ecdsa', 'schnorr'):
|
|
add('signing', 0.9)
|
|
add('verification', 0.85)
|
|
if subsystem in ('wallet', 'bip32', 'bip39'):
|
|
add('wallet_flow', 1.0)
|
|
if subsystem in ('musig2', 'frost', 'adaptor'):
|
|
add('protocol_multisig', 1.0)
|
|
if subsystem in ('ecies', 'pedersen'):
|
|
add('privacy_protocol', 0.95)
|
|
if subsystem == 'zk':
|
|
add('zk_proof', 1.0)
|
|
if subsystem == 'ethereum':
|
|
add('ethereum_stack', 1.0)
|
|
if subsystem == 'taproot' or 'bip340' in p or 'taproot' in p:
|
|
add('taproot_stack', 0.95)
|
|
if subsystem == 'hash' or any(x in p for x in ('sha256', 'sha512', 'hash160', 'keccak', 'tagged_hash', 'hash_accel')):
|
|
add('hashing', 1.0)
|
|
|
|
if any(x in p for x in ('parse', 'parser', 'der', 'ffi_round_trip', 'boundary', 'normalize', 'strict')):
|
|
add('parser_boundary', 0.9)
|
|
if any(x in p for x in ('wycheproof', 'differential', 'fiat_crypto', 'equivalence', 'cross_platform_kat', 'vectors')):
|
|
add('differential_testing', 0.9)
|
|
|
|
return tags
|
|
|
|
|
|
def derive_semantic_tags_for_abi(name: str, category: str, layer: str):
|
|
"""Derive semantic tags for a C ABI function."""
|
|
n = name.lower()
|
|
tags = {'ffi_surface': 1.0}
|
|
|
|
def add(tag: str, confidence: float = 1.0):
|
|
if tag in SEMANTIC_TAGS:
|
|
tags[tag] = max(tags.get(tag, 0.0), confidence)
|
|
|
|
if layer == 'ct':
|
|
add('constant_time', 1.0)
|
|
if category in ('ecdsa', 'schnorr'):
|
|
if 'sign' in n:
|
|
add('signing', 1.0)
|
|
if 'verify' in n:
|
|
add('verification', 1.0)
|
|
if category in ('wallet', 'bip32', 'bip39', 'address', 'wif'):
|
|
add('wallet_flow', 1.0)
|
|
if category in ('musig2', 'frost', 'adaptor'):
|
|
add('protocol_multisig', 1.0)
|
|
if category in ('ecies', 'silent_payments', 'pedersen'):
|
|
add('privacy_protocol', 0.95)
|
|
if category == 'zk':
|
|
add('zk_proof', 1.0)
|
|
if category == 'ethereum':
|
|
add('ethereum_stack', 1.0)
|
|
if category == 'taproot':
|
|
add('taproot_stack', 1.0)
|
|
if category == 'hash':
|
|
add('hashing', 1.0)
|
|
if 'parse' in n or 'from_der' in n or 'validate' in n:
|
|
add('parser_boundary', 0.9)
|
|
return tags
|
|
|
|
|
|
def infer_backend_from_path(path: str) -> str:
|
|
p = path.lower()
|
|
if p.startswith('cuda/') or p.startswith('gpu/'):
|
|
return 'cuda'
|
|
if p.startswith('opencl/'):
|
|
return 'opencl'
|
|
if p.startswith('metal/'):
|
|
return 'metal'
|
|
if p.startswith('wasm/'):
|
|
return 'wasm'
|
|
if p.startswith('scripts/'):
|
|
return 'script'
|
|
return 'cpu'
|
|
|
|
|
|
def infer_coordinate_model(symbol_name: str, file_path: str) -> str:
|
|
s = f"{symbol_name} {file_path}".lower()
|
|
if 'jacobian' in s:
|
|
return 'jacobian'
|
|
if 'affine' in s:
|
|
return 'affine'
|
|
if 'point' in s or 'scalar_mul' in s or 'ecmult' in s:
|
|
return 'mixed'
|
|
return 'n/a'
|
|
|
|
|
|
def classify_symbol_category(symbol_name: str, file_path: str):
|
|
s = f"{symbol_name} {file_path}".lower()
|
|
if any(x in s for x in ('field', 'fe52', 'fe_')):
|
|
return 'field_arithmetic', 'field'
|
|
if any(x in s for x in ('scalar', 'safegcd', 'wnaf')):
|
|
return 'scalar_arithmetic', 'scalar'
|
|
if any(x in s for x in ('point', 'ecmult', 'generator_mul', 'scalar_mul', 'multiscalar', 'pippenger', 'shamir')):
|
|
return 'point_arithmetic', 'point'
|
|
if any(x in s for x in ('inverse', 'modinv', 'safegcd')):
|
|
return 'modinv', 'scalar'
|
|
if 'batch_inversion' in s:
|
|
return 'batch_inversion', 'field'
|
|
if 'ecdsa' in s:
|
|
return 'ecdsa', 'protocol'
|
|
if 'schnorr' in s or 'bip340' in s:
|
|
return 'schnorr', 'protocol'
|
|
if 'ecdh' in s:
|
|
return 'ecdh', 'protocol'
|
|
if 'bip352' in s or 'silent_payment' in s:
|
|
return 'bip352', 'protocol'
|
|
if any(x in s for x in ('sha256', 'sha512', 'keccak', 'hash160', 'tagged_hash', 'hash_accel')):
|
|
return 'hashing', 'hash'
|
|
if any(x in s for x in ('kernel', '.cl', '.metal', '.cu')):
|
|
return 'gpu_kernel', 'gpu'
|
|
if any(x in s for x in ('ufsecp_', 'ffi', 'binding')):
|
|
return 'ffi_abi', 'abi'
|
|
if any(x in s for x in ('test_', '/tests/', '/audit/')):
|
|
return 'test', 'tool'
|
|
if '/audit/' in s or 'audit_' in s:
|
|
return 'audit', 'tool'
|
|
if '/fuzz/' in s or 'fuzz' in s:
|
|
return 'fuzz', 'tool'
|
|
if any(x in s for x in ('bip32', 'bip39', 'wallet', 'address', 'coin_', 'wif')):
|
|
return 'wallet', 'wallet'
|
|
return 'general', 'tool'
|
|
|
|
|
|
def derive_symbol_semantics(symbol_name: str, file_path: str):
|
|
category, math_core = classify_symbol_category(symbol_name, file_path)
|
|
s = f"{symbol_name} {file_path}".lower()
|
|
secret_class = 'public_only'
|
|
if any(x in s for x in ('sign', 'nonce', 'seckey', 'ecdh', 'decrypt', 'derive', 'blind', 'adaptor', 'silent_payment', 'frost', 'musig2')):
|
|
secret_class = 'ct_sensitive'
|
|
elif any(x in s for x in ('aggregate', 'combine', 'session', 'tweak', 'commit')):
|
|
secret_class = 'mixed'
|
|
return {
|
|
'category': category,
|
|
'math_core': math_core,
|
|
'backend': infer_backend_from_path(file_path),
|
|
'coordinate_model': infer_coordinate_model(symbol_name, file_path),
|
|
'secret_class': secret_class,
|
|
'abi_surface': 1 if symbol_name.startswith('ufsecp_') or '/ufsecp_' in file_path else 0,
|
|
'generator_path': 1 if any(x in s for x in ('generator_mul', 'gen_mul', '*g', 'output_key')) else 0,
|
|
'varpoint_path': 1 if any(x in s for x in ('scalar_mul', 'varpoint', 'pippenger', 'shamir', 'point_mul')) else 0,
|
|
'bip340_related': 1 if any(x in s for x in ('bip340', 'schnorr', 'taproot')) else 0,
|
|
'bip352_related': 1 if any(x in s for x in ('bip352', 'silent_payment')) else 0,
|
|
}
|
|
|
|
|
|
def derive_symbol_security(symbol_name: str, file_path: str, semantics: dict):
|
|
s = f"{symbol_name} {file_path}".lower()
|
|
uses_secret = 1 if semantics['secret_class'] in ('ct_sensitive', 'mixed') else 0
|
|
must_ct = 1 if semantics['secret_class'] == 'ct_sensitive' or 'ct_' in s or '/ct/' in s else 0
|
|
public_only = 1 if semantics['secret_class'] == 'public_only' else 0
|
|
device_secret = 1 if semantics['backend'] in ('cuda', 'opencl', 'metal') and uses_secret else 0
|
|
zeroize = 1 if any(x in s for x in ('sign', 'nonce', 'ecdh', 'decrypt', 'blind', 'derive', 'seed', 'mnemonic')) else 0
|
|
invalid_input = 1 if any(x in s for x in ('parse', 'verify', 'validate', 'from_der', 'pubkey', 'sig', 'address', 'decode', 'ecies')) else 0
|
|
notes = []
|
|
if device_secret:
|
|
notes.append('secret-bearing symbol reaches GPU/backend memory')
|
|
if invalid_input:
|
|
notes.append('parser or validation boundary')
|
|
if must_ct:
|
|
notes.append('constant-time sensitive')
|
|
return {
|
|
'uses_secret_input': uses_secret,
|
|
'must_be_constant_time': must_ct,
|
|
'public_data_only': public_only,
|
|
'device_secret_upload': device_secret,
|
|
'requires_zeroization': zeroize,
|
|
'invalid_input_sensitive': invalid_input,
|
|
'notes': '; '.join(notes),
|
|
}
|
|
|
|
|
|
def derive_symbol_performance(symbol_name: str, file_path: str, semantics: dict):
|
|
s = f"{symbol_name} {file_path}".lower()
|
|
hotness = 20.0
|
|
if semantics['math_core'] in ('field', 'scalar', 'point'):
|
|
hotness += 25.0
|
|
if any(x in s for x in ('mul', 'verify', 'scan', 'msm', 'pippenger', 'batch', 'kernel')):
|
|
hotness += 20.0
|
|
if any(x in s for x in ('generator_mul', 'scalar_mul', 'ecdh', 'bip352', 'silent_payment')):
|
|
hotness += 15.0
|
|
estimated_cost = min(100.0, hotness + (15.0 if any(x in s for x in ('msm', 'pippenger', 'verify', 'batch')) else 0.0))
|
|
batchable = 1 if any(x in s for x in ('batch', 'msm', 'verify', 'scan', 'aggregate', 'combine')) else 0
|
|
vectorizable = 1 if semantics['math_core'] in ('field', 'scalar', 'point', 'hash') else 0
|
|
gpu_candidate = 1 if semantics['backend'] == 'cpu' and (
|
|
semantics['math_core'] in ('field', 'scalar', 'point', 'hash', 'protocol') and batchable
|
|
or any(x in s for x in ('msm', 'batch', 'verify', 'scan', 'hash160', 'keccak', 'ecdh'))
|
|
) else 0
|
|
memory_bound = 1 if any(x in s for x in ('parse', 'serialize', 'decode', 'encode', 'address', 'wallet', 'seed')) else 0
|
|
compute_bound = 1 if any(x in s for x in ('mul', 'verify', 'msm', 'inverse', 'kernel', 'hash', 'scan')) else 0
|
|
duplicated_backends = 1 if semantics['backend'] in ('cuda', 'opencl', 'metal') or any(x in s for x in ('gpu_', 'opencl', 'metal', 'cuda')) else 0
|
|
return {
|
|
'hotness_score': min(100.0, hotness),
|
|
'estimated_cost': estimated_cost,
|
|
'batchable': batchable,
|
|
'vectorizable': vectorizable,
|
|
'gpu_candidate': gpu_candidate,
|
|
'memory_bound': memory_bound,
|
|
'compute_bound': compute_bound,
|
|
'duplicated_backends': duplicated_backends,
|
|
}
|
|
|
|
|
|
def get_file_history(file_path: str, cache: dict):
|
|
if file_path in cache:
|
|
return cache[file_path]
|
|
try:
|
|
proc = subprocess.run(
|
|
['git', '-C', str(LIB_ROOT), 'log', '--follow', '--format=%H%x09%ct%x09%s', '--', file_path],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
lines = [line for line in proc.stdout.splitlines() if line.strip()]
|
|
except Exception:
|
|
lines = []
|
|
history = {
|
|
'times_modified': len(lines),
|
|
'recently_modified': 0,
|
|
'bug_fix_count': 0,
|
|
'performance_tuning_count': 0,
|
|
'audit_related_changes': 0,
|
|
'last_modified': None,
|
|
}
|
|
if lines:
|
|
now_ts = int(datetime.now(timezone.utc).timestamp())
|
|
first = lines[0].split('\t', 2)
|
|
if len(first) >= 2:
|
|
history['last_modified'] = first[1]
|
|
for entry in lines:
|
|
parts = entry.split('\t', 2)
|
|
if len(parts) < 3:
|
|
continue
|
|
_, ts, subject = parts
|
|
try:
|
|
if now_ts - int(ts) <= 30 * 24 * 3600:
|
|
history['recently_modified'] = 1
|
|
except Exception:
|
|
pass
|
|
lowered = subject.lower()
|
|
if any(x in lowered for x in ('fix', 'bug', 'regression', 'correct', 'repair')):
|
|
history['bug_fix_count'] += 1
|
|
if any(x in lowered for x in ('perf', 'opt', 'speed', 'fast', 'vector', 'batch', 'gpu')):
|
|
history['performance_tuning_count'] += 1
|
|
if any(x in lowered for x in ('audit', 'security', 'ct', 'fuzz', 'wycheproof', 'sanitizer')):
|
|
history['audit_related_changes'] += 1
|
|
cache[file_path] = history
|
|
return history
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# POPULATION FUNCTIONS
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def populate_source_files(cur: sqlite3.Cursor):
|
|
"""Walk the source tree and insert all source files."""
|
|
count = 0
|
|
for root, dirs, files in os.walk(LIB_ROOT):
|
|
# Skip build dirs
|
|
dirs[:] = [d for d in dirs if not should_skip_dir(d)]
|
|
for fname in sorted(files):
|
|
ext = os.path.splitext(fname)[1].lower()
|
|
if ext not in SOURCE_EXTS:
|
|
continue
|
|
full = Path(root) / fname
|
|
rel = str(full.relative_to(LIB_ROOT))
|
|
if any(skip in rel for skip in ['build/', 'CMakeFiles/', 'CMakeCUDACompilerId', 'CMakeCXXCompilerId',
|
|
'x509_crt_bundle', 'sdkconfig.h', 'kernels_embedded']):
|
|
continue
|
|
cat, sub, layer = classify_file(rel)
|
|
lines = count_lines(full)
|
|
sha = file_sha256(full)
|
|
ftype = ext.lstrip('.')
|
|
cur.execute("""INSERT OR IGNORE INTO source_files
|
|
(path, category, subsystem, file_type, lines, sha256, layer)
|
|
VALUES (?,?,?,?,?,?,?)""",
|
|
(rel, cat, sub, ftype, lines, sha, layer))
|
|
count += 1
|
|
return count
|
|
|
|
def populate_abi_functions(cur: sqlite3.Cursor):
|
|
"""Parse ufsecp.h, ufsecp_gpu.h, and ufsecp_version.h for all C ABI function declarations."""
|
|
headers = [
|
|
LIB_ROOT / 'include' / 'ufsecp' / 'ufsecp.h',
|
|
LIB_ROOT / 'include' / 'ufsecp' / 'ufsecp_gpu.h',
|
|
LIB_ROOT / 'include' / 'ufsecp' / 'ufsecp_version.h',
|
|
]
|
|
count = 0
|
|
for header in headers:
|
|
if not header.exists():
|
|
continue
|
|
with open(header, 'r') as f:
|
|
lines = f.readlines()
|
|
|
|
i = 0
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
# Match UFSECP_API function declarations
|
|
m = re.match(r'UFSECP_API\s+(\w[\w\s*]*?)\s+(ufsecp_\w+)\s*\(', line)
|
|
if m:
|
|
ret_type = m.group(1).strip()
|
|
func_name = m.group(2)
|
|
# Collect full signature
|
|
sig = line.strip()
|
|
j = i + 1
|
|
while j < len(lines) and ';' not in sig:
|
|
sig += ' ' + lines[j].strip()
|
|
j += 1
|
|
sig = re.sub(r'\s+', ' ', sig).strip().rstrip(';')
|
|
|
|
cat = categorize_abi_func(func_name)
|
|
layer = abi_layer(func_name)
|
|
|
|
cur.execute("""INSERT OR IGNORE INTO c_abi_functions
|
|
(name, category, signature, line_no, layer)
|
|
VALUES (?,?,?,?,?)""",
|
|
(func_name, cat, sig, i+1, layer))
|
|
count += 1
|
|
i += 1
|
|
return count
|
|
|
|
def populate_include_deps(cur: sqlite3.Cursor):
|
|
"""Extract include dependencies from all .cpp/.cu/.mm files."""
|
|
count = 0
|
|
for root, dirs, files in os.walk(LIB_ROOT):
|
|
dirs[:] = [d for d in dirs if not should_skip_dir(d)]
|
|
for fname in sorted(files):
|
|
ext = os.path.splitext(fname)[1].lower()
|
|
if ext not in ('.cpp', '.cu', '.mm', '.c'):
|
|
continue
|
|
full = Path(root) / fname
|
|
rel = str(full.relative_to(LIB_ROOT))
|
|
if any(skip in rel for skip in ['build/', 'CMakeFiles/', 'CMakeCUDACompilerId',
|
|
'CMakeCXXCompilerId', 'kernels_embedded']):
|
|
continue
|
|
for inc, is_local in extract_includes(full):
|
|
cur.execute("""INSERT OR IGNORE INTO include_deps
|
|
(source_file, included_file, is_local)
|
|
VALUES (?,?,?)""", (rel, inc, is_local))
|
|
count += 1
|
|
return count
|
|
|
|
def populate_test_targets(cur: sqlite3.Cursor):
|
|
"""Insert known CTest targets with metadata."""
|
|
tests = [
|
|
# CPU Core (18)
|
|
('selftest', 'run_selftest', 'cpu_core', 300, '["core"]'),
|
|
('batch_add_affine', 'test_batch_add_affine_standalone', 'cpu_core', 300, '["core"]'),
|
|
('hash_accel', 'test_hash_accel_standalone', 'cpu_core', 300, '["core"]'),
|
|
('field_52', 'test_field_52_standalone', 'cpu_core', 300, '["core"]'),
|
|
('field_26', 'test_field_26_standalone', 'cpu_core', 300, '["core"]'),
|
|
('exhaustive', 'test_exhaustive_standalone', 'cpu_core', 300, '["core"]'),
|
|
('comprehensive', 'test_comprehensive_standalone', 'cpu_core', 300, '["core"]'),
|
|
('bip340_vectors', 'test_bip340_vectors_standalone', 'cpu_core', 300, '["core","bip340"]'),
|
|
('bip340_strict', 'test_bip340_strict_standalone', 'cpu_core', 300, '["core","bip340"]'),
|
|
('bip32_vectors', 'test_bip32_vectors_standalone', 'cpu_core', 300, '["core","bip32"]'),
|
|
('bip39', 'test_bip39_standalone', 'cpu_core', 300, '["core","bip39"]'),
|
|
('rfc6979_vectors', 'test_rfc6979_vectors_standalone', 'cpu_core', 300, '["core","rfc6979"]'),
|
|
('ecc_properties', 'test_ecc_properties_standalone', 'cpu_core', 300, '["core"]'),
|
|
('point_edge_cases', 'test_point_edge_cases_standalone', 'cpu_core', 300, '["core"]'),
|
|
('edge_cases', 'test_edge_cases_standalone', 'cpu_core', 300, '["core"]'),
|
|
('ethereum', 'test_ethereum_standalone', 'cpu_core', 300, '["core","ethereum"]'),
|
|
('zk_proofs', 'test_zk_standalone', 'cpu_core', 300, '["core","zk"]'),
|
|
('wallet', 'test_wallet_standalone', 'cpu_core', 300, '["core","wallet"]'),
|
|
# Audit always-on (20)
|
|
('ct_sidechannel', 'test_ct_sidechannel_standalone', 'audit_always', 600, '["audit","ct"]'),
|
|
('ct_sidechannel_smoke', 'test_ct_sidechannel_standalone', 'audit_always', 120, '["audit","ct"]'),
|
|
('differential', 'differential_test_standalone', 'audit_always', 600, '["audit"]'),
|
|
('ct_equivalence', 'test_ct_equivalence_standalone', 'audit_always', 300, '["audit","ct"]'),
|
|
('fault_injection', 'test_fault_injection_standalone', 'audit_always', 900, '["audit"]'),
|
|
('debug_invariants', 'test_debug_invariants_standalone', 'audit_always', 600, '["audit"]'),
|
|
('fiat_crypto_vectors', 'test_fiat_crypto_vectors_standalone', 'audit_always', 900, '["audit"]'),
|
|
('carry_propagation', 'test_carry_propagation_standalone', 'audit_always', 900, '["audit"]'),
|
|
('wycheproof_ecdsa', 'test_wycheproof_ecdsa_standalone', 'audit_always', 1200, '["audit","ecdsa"]'),
|
|
('wycheproof_ecdh', 'test_wycheproof_ecdh_standalone', 'audit_always', 1200, '["audit","ecdh"]'),
|
|
('batch_randomness', 'test_batch_randomness_standalone', 'audit_always', 600, '["audit"]'),
|
|
('cross_platform_kat', 'test_cross_platform_kat_standalone', 'audit_always', 900, '["audit"]'),
|
|
('abi_gate', 'test_abi_gate', 'audit_always', 30, '["audit","abi"]'),
|
|
('ct_verif_formal', 'test_ct_verif_formal_standalone', 'audit_always', 1200, '["audit","ct"]'),
|
|
('fiat_crypto_linkage', 'test_fiat_crypto_linkage_standalone', 'audit_always', 900, '["audit"]'),
|
|
('audit_fuzz', 'audit_fuzz_standalone', 'audit_always', 600, '["audit","fuzz"]'),
|
|
('adversarial_protocol', 'test_adversarial_protocol_standalone', 'audit_always', 600, '["audit","protocol"]'),
|
|
('ecies_regression', 'test_ecies_regression_standalone', 'audit_always', 120, '["audit","ecies"]'),
|
|
('diag_scalar_mul', 'diag_scalar_mul', 'audit_always', 300, '["audit"]'),
|
|
('unified_audit', 'unified_audit_runner', 'audit_always', 1200, '["audit"]'),
|
|
# Audit conditional (7)
|
|
('cross_libsecp256k1', 'test_cross_libsecp256k1_standalone', 'audit_conditional', 300, '["audit"]'),
|
|
('fuzz_parsers', 'test_fuzz_parsers_standalone', 'audit_conditional', 300, '["audit","fuzz"]'),
|
|
('fuzz_address_bip32_ffi', 'test_fuzz_address_bip32_ffi_standalone', 'audit_conditional', 300, '["audit","fuzz"]'),
|
|
('musig2_frost', 'test_musig2_frost_standalone', 'audit_conditional', 300, '["audit","musig2","frost"]'),
|
|
('musig2_frost_advanced', 'test_musig2_frost_advanced_standalone', 'audit_conditional', 300, '["audit","musig2","frost"]'),
|
|
('frost_kat', 'test_frost_kat_standalone', 'audit_conditional', 300, '["audit","frost"]'),
|
|
('musig2_bip327_vectors', 'test_musig2_bip327_vectors_standalone', 'audit_conditional', 300, '["audit","musig2"]'),
|
|
# GPU (10)
|
|
('cuda_selftest', 'secp256k1_cuda_test', 'gpu', 300, '["gpu","cuda"]'),
|
|
('gpu_audit', 'gpu_audit_runner', 'gpu', 1200, '["gpu","cuda","audit"]'),
|
|
('gpu_ct_smoke', 'test_ct_smoke', 'gpu', 300, '["gpu","cuda","ct"]'),
|
|
('opencl_selftest', 'opencl_test', 'gpu', 300, '["gpu","opencl"]'),
|
|
('opencl_audit', 'opencl_audit_runner', 'gpu', 1200, '["gpu","opencl","audit"]'),
|
|
('metal_host_test', 'metal_host_test', 'gpu', 300, '["gpu","metal"]'),
|
|
('secp256k1_metal_test', 'metal_secp256k1_test', 'gpu', 300, '["gpu","metal"]'),
|
|
('secp256k1_metal_bench', 'metal_secp256k1_bench', 'gpu', 300, '["gpu","metal"]'),
|
|
('secp256k1_metal_bench_full', 'metal_secp256k1_bench_full', 'gpu', 600, '["gpu","metal"]'),
|
|
('secp256k1_metal_audit', 'metal_audit_runner', 'gpu', 1200, '["gpu","metal","audit"]'),
|
|
]
|
|
for name, exe, cat, timeout, labels in tests:
|
|
cur.execute("""INSERT OR IGNORE INTO test_targets
|
|
(name, executable, category, timeout, labels)
|
|
VALUES (?,?,?,?,?)""", (name, exe, cat, timeout, labels))
|
|
return len(tests)
|
|
|
|
def populate_ci_workflows(cur: sqlite3.Cursor):
|
|
"""Insert CI workflow metadata."""
|
|
workflows = [
|
|
('ci.yml', 'CI', '{"push":["main","dev"],"pull_request":["main","dev"]}', 'merge_blocking',
|
|
'["linux-gcc13","linux-clang17","windows","macos"]'),
|
|
('clang-tidy.yml', 'Static Analysis (clang-tidy)', '{"push":["main","dev"],"pull_request":["main","dev"]}', 'merge_blocking',
|
|
'["clang-tidy"]'),
|
|
('cppcheck.yml', 'Static Analysis (Cppcheck)', '{"push":["main","dev"],"pull_request":["main","dev"]}', 'merge_blocking',
|
|
'["cppcheck"]'),
|
|
('ct-verif.yml', 'CT-Verif LLVM', '{"push":["main","dev"],"pull_request":["main","dev"]}', 'merge_blocking',
|
|
'["ct-verif"]'),
|
|
('codeql.yml', 'CodeQL', '{"push":["main","dev"],"pull_request":["main"],"schedule":"weekly"}', 'merge_blocking',
|
|
'["analyze"]'),
|
|
('security-audit.yml', 'Security Audit', '{"push":["main","dev"],"pull_request":["main","dev"],"schedule":"weekly"}', 'merge_blocking',
|
|
'["werror","asan","audit"]'),
|
|
('bench-regression.yml', 'Benchmark Regression', '{"push":["main","dev"]}', 'merge_blocking',
|
|
'["bench-regression"]'),
|
|
('nightly.yml', 'Nightly Extended', '{"schedule":"0 3 * * *"}', 'advisory',
|
|
'["differential-extended","dudect-30min"]'),
|
|
('ct-arm64.yml', 'CT ARM64', '{"push":["main","dev"],"schedule":"0 4 * * *"}', 'advisory',
|
|
'["ct-arm64-dudect"]'),
|
|
('valgrind-ct.yml', 'Valgrind CT', '{"push":["main","dev"]}', 'advisory',
|
|
'["valgrind-taint"]'),
|
|
('cflite.yml', 'ClusterFuzzLite', '{"push":[],"pull_request":[],"schedule":"daily"}', 'advisory',
|
|
'["fuzz"]'),
|
|
('mutation.yml', 'Mutation Testing', '{"schedule":"0 5 * * 0"}', 'advisory',
|
|
'["mutation"]'),
|
|
('sonarcloud.yml', 'SonarCloud', '{"push":["main","dev"],"pull_request":["main","dev"]}', 'advisory',
|
|
'["sonar"]'),
|
|
('scorecard.yml', 'OpenSSF Scorecard', '{"push":["main"],"schedule":"weekly"}', 'advisory',
|
|
'["scorecard"]'),
|
|
('dependency-review.yml', 'Dependency Review', '{"pull_request":[]}', 'advisory',
|
|
'["dependency-review"]'),
|
|
('release.yml', 'Release', '{"tags":["v*"],"workflow_dispatch":{}}', 'release',
|
|
'["build-artifacts"]'),
|
|
('packaging.yml', 'Packaging', '{"tags":["v*"]}', 'release',
|
|
'["deb","rpm"]'),
|
|
('audit-report.yml', 'Audit Report', '{"schedule":"weekly"}', 'release',
|
|
'["unified-audit-artifacts"]'),
|
|
('benchmark.yml', 'Benchmark', '{"push":["main"]}', 'release',
|
|
'["gh-pages-perf"]'),
|
|
('docs.yml', 'Documentation', '{"push":["main"]}', 'release',
|
|
'["doxygen-gh-pages"]'),
|
|
('bindings.yml', 'Bindings', '{"push":["main","dev"],"pull_request":[]}', 'release',
|
|
'["12-language-compile"]'),
|
|
('discord-commits.yml', 'Discord Notify', '{"push":["main","dev"]}', 'release',
|
|
'["webhook"]'),
|
|
]
|
|
for fname, name, triggers, cat, jobs in workflows:
|
|
cur.execute("""INSERT OR IGNORE INTO ci_workflows
|
|
(filename, name, triggers, category, jobs)
|
|
VALUES (?,?,?,?,?)""", (fname, name, triggers, cat, jobs))
|
|
return len(workflows)
|
|
|
|
def populate_error_codes(cur: sqlite3.Cursor):
|
|
codes = [
|
|
(0, 'OK', 'UFSECP_OK', 'Success'),
|
|
(1, 'NULL_ARG', 'UFSECP_ERR_NULL_ARG', 'Null pointer argument'),
|
|
(2, 'BAD_KEY', 'UFSECP_ERR_BAD_KEY', 'Invalid private key (zero or >= n)'),
|
|
(3, 'BAD_PUBKEY', 'UFSECP_ERR_BAD_PUBKEY', 'Invalid or unparseable public key'),
|
|
(4, 'BAD_SIG', 'UFSECP_ERR_BAD_SIG', 'Invalid or unparseable signature'),
|
|
(5, 'BAD_INPUT', 'UFSECP_ERR_BAD_INPUT', 'Generic invalid input'),
|
|
(6, 'VERIFY_FAIL', 'UFSECP_ERR_VERIFY_FAIL', 'Signature verification failed'),
|
|
(7, 'ARITH', 'UFSECP_ERR_ARITH', 'Arithmetic error (point at infinity, etc.)'),
|
|
(8, 'SELFTEST', 'UFSECP_ERR_SELFTEST', 'Self-test failure on init'),
|
|
(9, 'INTERNAL', 'UFSECP_ERR_INTERNAL', 'Internal / unexpected error'),
|
|
(10, 'BUF_TOO_SMALL', 'UFSECP_ERR_BUF_TOO_SMALL', 'Output buffer too small'),
|
|
]
|
|
for code, name, symbol, meaning in codes:
|
|
cur.execute("""INSERT OR IGNORE INTO error_codes
|
|
(code, name, symbol, meaning) VALUES (?,?,?,?)""",
|
|
(code, name, symbol, meaning))
|
|
return len(codes)
|
|
|
|
def populate_constants(cur: sqlite3.Cursor):
|
|
constants = [
|
|
# Curve
|
|
('p', '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 'curve', 'Field prime'),
|
|
('n', '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 'curve', 'Group order'),
|
|
('G.x', '0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 'curve', 'Generator X'),
|
|
('G.y', '0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8', 'curve', 'Generator Y'),
|
|
('h', '1', 'curve', 'Cofactor'),
|
|
('K_MONTGOMERY', '0x1000003D1', 'curve', 'Montgomery reduction constant'),
|
|
# Precomputation
|
|
('WINDOW_G', '15', 'precomp', '8192-entry table for k*G'),
|
|
('WINDOW_P', '5', 'precomp', '8-entry table for k*P'),
|
|
('GLV_WINDOW_DEFAULT_x86', '5', 'precomp', 'GLV window width x86/ARM/RISC-V'),
|
|
('GLV_WINDOW_DEFAULT_ESP32', '4', 'precomp', 'GLV window width ESP32/WASM'),
|
|
('COMB_TEETH', '6', 'precomp', 'CT comb multiplication teeth'),
|
|
('COMB_BLOCKS', '11', 'precomp', 'CT comb multiplication blocks'),
|
|
('CT_SCALAR_MUL_TABLE', '16', 'precomp', 'CT scalar_mul table size'),
|
|
# ABI sizes
|
|
('PRIVKEY_SIZE', '32', 'abi_size', 'Private key bytes'),
|
|
('PUBKEY_COMPRESSED', '33', 'abi_size', 'Compressed public key'),
|
|
('PUBKEY_UNCOMPRESSED', '65', 'abi_size', 'Uncompressed public key'),
|
|
('PUBKEY_XONLY', '32', 'abi_size', 'X-only public key'),
|
|
('SIG_COMPACT', '64', 'abi_size', 'Compact ECDSA/Schnorr signature'),
|
|
('SIG_DER_MAX', '72', 'abi_size', 'Maximum DER-encoded signature'),
|
|
('BIP32_SERIALIZED', '78', 'abi_size', 'BIP-32 extended key serialized'),
|
|
('HASH256_SIZE', '32', 'abi_size', 'SHA-256 / Keccak-256 output'),
|
|
('HASH512_SIZE', '64', 'abi_size', 'SHA-512 output'),
|
|
]
|
|
for name, value, cat, ctx in constants:
|
|
cur.execute("""INSERT OR IGNORE INTO constants
|
|
(name, value, category, context) VALUES (?,?,?,?)""",
|
|
(name, value, cat, ctx))
|
|
return len(constants)
|
|
|
|
def populate_gpu_backends(cur: sqlite3.Cursor):
|
|
backends = [
|
|
('cuda',
|
|
json.dumps(['cuda/src/secp256k1.cu','cuda/src/test_suite.cu','cuda/src/gpu_audit_runner.cu',
|
|
'cuda/src/gpu_bench_unified.cu','cuda/src/bench_compare.cu','cuda/src/bench_bip352.cu',
|
|
'cuda/src/bench_zk.cu','cuda/src/test_ct_smoke.cu','cuda/src/bench_cuda.cu']),
|
|
json.dumps([]), # CUDA uses header-only kernels
|
|
json.dumps(['secp256k1_cuda_lib','secp256k1_cuda_test','gpu_audit_runner','gpu_bench_unified',
|
|
'bench_compare','test_ct_smoke','bench_zk','bench_bip352']),
|
|
json.dumps({'ecdsa_sign':True,'ecdsa_verify':True,'schnorr_sign':True,'schnorr_verify':True,
|
|
'batch_verify':True,'bip32':True,'ecdh':True,'hash160':True,'keccak256':True,
|
|
'pedersen':True,'zk':True,'msm':True,'ct_field':True,'ct_scalar':True,
|
|
'ct_point':True,'ct_sign':True,'ct_zk':True,'bloom':True,'recovery':True})),
|
|
('opencl',
|
|
json.dumps(['opencl/src/opencl_context.cpp','opencl/src/opencl_field.cpp',
|
|
'opencl/src/opencl_point.cpp','opencl/src/opencl_batch.cpp',
|
|
'opencl/src/opencl_selftest.cpp','opencl/src/opencl_audit_runner.cpp']),
|
|
json.dumps(['secp256k1_field.cl','secp256k1_point.cl','secp256k1_batch.cl',
|
|
'secp256k1_affine.cl','secp256k1_extended.cl','secp256k1_hash160.cl',
|
|
'secp256k1_keccak256.cl','secp256k1_msm.cl','secp256k1_pedersen.cl',
|
|
'secp256k1_recovery.cl','secp256k1_zk.cl','secp256k1_bloom.cl',
|
|
'secp256k1_bip32.cl','secp256k1_ecdh.cl','secp256k1_gen_table_w8.cl',
|
|
'secp256k1_bp_gen_table.cl','secp256k1_ct_field.cl','secp256k1_ct_scalar.cl',
|
|
'secp256k1_ct_ops.cl','secp256k1_ct_point.cl','secp256k1_ct_sign.cl',
|
|
'secp256k1_ct_zk.cl']),
|
|
json.dumps(['secp256k1_opencl','opencl_test','opencl_benchmark','opencl_audit_runner']),
|
|
json.dumps({'ecdsa_sign':True,'ecdsa_verify':True,'schnorr_sign':True,'schnorr_verify':True,
|
|
'bip32':True,'ecdh':True,'hash160':True,'keccak256':True,
|
|
'pedersen':True,'zk':True,'msm':True,'ct_field':True,'ct_scalar':True,
|
|
'ct_point':True,'ct_sign':True,'ct_zk':True,'bloom':True,'recovery':True})),
|
|
('metal',
|
|
json.dumps(['metal/src/metal_runtime.mm','metal/src/metal_audit_runner.mm',
|
|
'metal/app/bench_metal.mm','metal/app/metal_test.mm',
|
|
'metal/tests/test_metal_host.cpp','metal/tests/metal_extended_test.mm']),
|
|
json.dumps(['secp256k1_field.h','secp256k1_point.h','secp256k1_affine.h',
|
|
'secp256k1_extended.h','secp256k1_hash160.h','secp256k1_keccak256.h',
|
|
'secp256k1_msm.h','secp256k1_pedersen.h','secp256k1_recovery.h',
|
|
'secp256k1_zk.h','secp256k1_bloom.h','secp256k1_bip32.h',
|
|
'secp256k1_ecdh.h','secp256k1_gen_table_w8.h','secp256k1_bp_gen_table.h',
|
|
'secp256k1_ct_field.h','secp256k1_ct_scalar.h','secp256k1_ct_ops.h',
|
|
'secp256k1_ct_point.h','secp256k1_ct_sign.h','secp256k1_ct_zk.h',
|
|
'secp256k1_kernels.metal']),
|
|
json.dumps(['secp256k1_metal_lib','metal_secp256k1_test','metal_secp256k1_bench_full',
|
|
'metal_audit_runner','metal_host_test']),
|
|
json.dumps({'ecdsa_sign':True,'ecdsa_verify':True,'schnorr_sign':True,'schnorr_verify':True,
|
|
'batch_verify':True,'bip32':True,'ecdh':True,'hash160':True,'keccak256':True,
|
|
'pedersen':True,'zk':True,'msm':True,'ct_field':True,'ct_scalar':True,
|
|
'ct_point':True,'ct_sign':True,'ct_zk':True,'bloom':True,'recovery':True})),
|
|
]
|
|
for backend, sources, kernels, targets, features in backends:
|
|
cur.execute("""INSERT OR IGNORE INTO gpu_backends
|
|
(backend, sources, kernels, targets, features)
|
|
VALUES (?,?,?,?,?)""", (backend, sources, kernels, targets, features))
|
|
return len(backends)
|
|
|
|
def populate_build_configs(cur: sqlite3.Cursor):
|
|
configs = [
|
|
('SECP256K1_BUILD_CPU', 'ON', 'BOOL', 'Build CPU backend'),
|
|
('SECP256K1_BUILD_CUDA', 'OFF', 'BOOL', 'Build CUDA GPU backend'),
|
|
('SECP256K1_BUILD_ROCM', 'OFF', 'BOOL', 'Build AMD ROCm/HIP GPU backend'),
|
|
('SECP256K1_BUILD_OPENCL', 'OFF', 'BOOL', 'Build OpenCL GPU backend'),
|
|
('SECP256K1_BUILD_METAL', 'OFF', 'BOOL', 'Build Apple Metal GPU backend'),
|
|
('SECP256K1_USE_ASM', 'ON', 'BOOL', 'Enable inline assembly (x64/ARM64/RISC-V)'),
|
|
('SECP256K1_BUILD_ETHEREUM', 'ON', 'BOOL', 'Build Ethereum module (Keccak-256, EIP-155)'),
|
|
('SECP256K1_SPEED_FIRST', 'OFF', 'BOOL', '-Ofast, no stack protector'),
|
|
('SECP256K1_GLV_WINDOW_WIDTH', 'platform', 'INTEGER', '5 on x86/ARM/RISC-V, 4 on ESP32/WASM'),
|
|
('UFSECP_BITCOIN_STRICT', 'ON', 'BOOL', 'BIP-340 strict encoding enforcement'),
|
|
('SECP256K1_USE_LTO', 'OFF', 'BOOL', 'Link-time optimization'),
|
|
('SECP256K1_BUILD_FUZZ_TESTS', 'OFF', 'BOOL', 'Build fuzz test binaries'),
|
|
('CMAKE_CUDA_ARCHITECTURES', '89', 'STRING', 'GPU compute capabilities'),
|
|
('CMAKE_BUILD_TYPE', 'Release', 'STRING', 'Build type'),
|
|
]
|
|
for name, default, typ, effect in configs:
|
|
cur.execute("""INSERT OR IGNORE INTO build_configs
|
|
(name, default_value, type, effect) VALUES (?,?,?,?)""",
|
|
(name, default, typ, effect))
|
|
return len(configs)
|
|
|
|
def populate_namespaces(cur: sqlite3.Cursor):
|
|
nss = [
|
|
('secp256k1', 'Top-level namespace'),
|
|
('secp256k1::fast', 'Variable-time maximum-throughput operations'),
|
|
('secp256k1::ct', 'Constant-time side-channel-resistant operations'),
|
|
('secp256k1::fast::debug', 'Debug invariant checking'),
|
|
('secp256k1::detail', 'Internal implementation utilities'),
|
|
('secp256k1::coins', 'Multi-coin wallet layer (BTC, ETH, LTC, etc.)'),
|
|
('secp256k1::hash', 'Accelerated hashing (SHA-NI, ARMv8)'),
|
|
('secp256k1::fast::fe52_constants', 'Field element 5x52 limb constants'),
|
|
]
|
|
for name, purpose in nss:
|
|
cur.execute("""INSERT OR IGNORE INTO namespaces
|
|
(name, purpose) VALUES (?,?)""", (name, purpose))
|
|
return len(nss)
|
|
|
|
def populate_cpp_types(cur: sqlite3.Cursor):
|
|
types = [
|
|
('FieldElement', 'class', 'secp256k1::fast', 'cpu/include/secp256k1/field.hpp', 'Prime field element mod p (4x64 or 5x52 limbs)'),
|
|
('Scalar', 'class', 'secp256k1::fast', 'cpu/include/secp256k1/scalar.hpp', 'Scalar mod n (4x64 limbs)'),
|
|
('Point', 'class', 'secp256k1::fast', 'cpu/include/secp256k1/point.hpp', 'Affine/Jacobian point on secp256k1'),
|
|
('ECDSASignature', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/ecdsa.hpp', 'ECDSA (r,s) signature'),
|
|
('SchnorrSignature', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/schnorr.hpp', 'Schnorr/BIP-340 (R,s) signature'),
|
|
('ExtendedKey', 'class', 'secp256k1::fast', 'cpu/include/secp256k1/bip32.hpp', 'BIP-32 extended key (xpub/xprv)'),
|
|
('RecoverableSignature', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/recovery.hpp', 'ECDSA signature with recovery id'),
|
|
('AdaptorSignature', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/adaptor.hpp', 'Adaptor (pre-)signature'),
|
|
('PedersenCommitment', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/pedersen.hpp', 'Pedersen commitment (point)'),
|
|
('RangeProof', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/zk.hpp', 'Zero-knowledge range proof'),
|
|
('DLEQProof', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/zk.hpp', 'DLEQ proof'),
|
|
('KnowledgeProof', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/zk.hpp', 'ZK knowledge proof'),
|
|
('MuSig2KeyAggContext', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/musig2.hpp', 'MuSig2 key aggregation context'),
|
|
('MuSig2Session', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/musig2.hpp', 'MuSig2 signing session'),
|
|
('FROSTKeygenResult', 'struct', 'secp256k1::fast', 'cpu/include/secp256k1/frost.hpp', 'FROST DKG result'),
|
|
('CoinParams', 'struct', 'secp256k1::coins', 'cpu/include/secp256k1/coins/coin_params.hpp', 'Per-coin address parameters'),
|
|
('FieldElement', 'class', 'secp256k1::ct', 'cpu/include/secp256k1/ct/field.hpp', 'CT field element (constant-time ops)'),
|
|
('Scalar', 'class', 'secp256k1::ct', 'cpu/include/secp256k1/ct/scalar.hpp', 'CT scalar (constant-time ops)'),
|
|
('SHA256', 'class', 'secp256k1::fast', 'cpu/include/secp256k1/sha256.hpp', 'SHA-256 hasher (hardware-accelerated)'),
|
|
('SHA512', 'class', 'secp256k1::fast', 'cpu/include/secp256k1/sha512.hpp', 'SHA-512 hasher'),
|
|
]
|
|
for name, kind, ns, header, desc in types:
|
|
cur.execute("""INSERT OR IGNORE INTO cpp_types
|
|
(name, kind, namespace, header_path, description) VALUES (?,?,?,?,?)""",
|
|
(name, kind, ns, header, desc))
|
|
return len(types)
|
|
|
|
def populate_platform_dispatch(cur: sqlite3.Cursor):
|
|
dispatches = [
|
|
('cpu/src/field.cpp', 'x86_64', 'ifdef', '__SIZEOF_INT128__ -> FE52 5x52 inline mul/sqr'),
|
|
('cpu/src/field.cpp', 'arm64', 'ifdef', '__SIZEOF_INT128__ -> FE52 5x52 inline mul/sqr'),
|
|
('cpu/src/field.cpp', 'riscv64', 'ifdef', '__SIZEOF_INT128__ + optional asm mul/sqr'),
|
|
('cpu/src/field.cpp', 'msvc', 'ifdef', 'No __int128 -> 4x64 Comba fallback'),
|
|
('cpu/src/field.cpp', 'esp32', 'ifdef', 'No __int128 -> 4x64 Comba fallback'),
|
|
('cpu/src/ct_field.cpp', 'x86_64', 'ifdef', 'FE52 constant-time field ops w/ 5x52'),
|
|
('cpu/src/ct_field.cpp', 'msvc', 'ifdef', 'SafeGCD30 25x30 (no __int128)'),
|
|
('cpu/src/ct_scalar.cpp', 'x86_64', 'ifdef', 'SafeGCD 10x59 limbs for inversion'),
|
|
('cpu/src/ct_scalar.cpp', 'msvc', 'ifdef', 'SafeGCD30 25x30 limbs for inversion'),
|
|
('cpu/src/field_asm.cpp', 'x86_64', 'asm_file', 'ADX/MULX inline assembly mul/sqr'),
|
|
('cpu/src/field_asm_arm64.cpp', 'arm64', 'asm_file', 'NEON/MUL inline assembly'),
|
|
('cpu/src/field_asm_riscv64.S', 'riscv64', 'asm_file', 'RV64 assembly field mul/sqr'),
|
|
('cpu/src/field_asm52_riscv64.S', 'riscv64', 'asm_file', 'RV64 5x52 assembly'),
|
|
('cpu/src/field_asm_x64.asm', 'x86_64', 'asm_file', 'MASM x64 assembly (Windows)'),
|
|
('cpu/src/field_asm_x64_gas.S', 'x86_64', 'asm_file', 'GAS x64 assembly (Linux)'),
|
|
('cpu/src/hash_accel.cpp', 'x86_64', 'ifdef', 'SHA-NI intrinsics'),
|
|
('cpu/src/hash_accel.cpp', 'arm64', 'ifdef', 'ARMv8 CE SHA intrinsics'),
|
|
('cpu/src/ct_point.cpp', 'x86_64', 'ifdef', 'SSE2/AVX2 constant-time conditional move'),
|
|
('cpu/src/glv.cpp', 'all', 'constexpr_if', 'GLV endomorphism beta/lambda decomposition'),
|
|
]
|
|
for src, plat, mech, desc in dispatches:
|
|
cur.execute("""INSERT OR IGNORE INTO platform_dispatch
|
|
(source_file, platform, mechanism, description) VALUES (?,?,?,?)""",
|
|
(src, plat, mech, desc))
|
|
return len(dispatches)
|
|
|
|
def populate_docs(cur: sqlite3.Cursor):
|
|
"""Scan docs/ directory for markdown files."""
|
|
docs_dir = LIB_ROOT / 'docs'
|
|
count = 0
|
|
if not docs_dir.exists():
|
|
return 0
|
|
|
|
doc_categories = {
|
|
'ARCHITECTURE': 'architecture', 'CODING_STANDARDS': 'architecture', 'THREAD_SAFETY': 'architecture',
|
|
'API_REFERENCE': 'api', 'USER_GUIDE': 'api', 'ABI_VERSIONING': 'api', 'DEPRECATION': 'api',
|
|
'BUILDING': 'build', 'COMPATIBILITY': 'build', 'CROSS_PLATFORM': 'build', 'ESP32': 'build', 'REPRODUCIBLE': 'build',
|
|
'SECURITY': 'security', 'CT_': 'security', 'SAFE_DEFAULTS': 'security', 'BUG_BOUNTY': 'security',
|
|
'INCIDENT': 'security', 'CRYPTO_INVARIANTS': 'security', 'INVARIANTS': 'security',
|
|
'AUDIT': 'audit', 'FEATURE_ASSURANCE': 'audit', 'INTERNAL_AUDIT': 'audit', 'GPU_VALIDATION': 'audit',
|
|
'DIFFERENTIAL': 'audit', 'TEST_MATRIX': 'testing', 'NORMALIZATION': 'testing', 'ENGINEERING': 'testing',
|
|
'BENCHMARK': 'benchmark', 'PERFORMANCE': 'benchmark', 'ARM64_AUDIT': 'benchmark',
|
|
'BINDINGS': 'binding', 'RELEASE': 'release', 'PRE_RELEASE': 'release', 'LTS': 'release', 'LOCAL_CI': 'release',
|
|
'OPTIMIZATION': 'optimization', 'FROST': 'protocol', 'FAQ': 'general', 'README': 'general',
|
|
}
|
|
|
|
for root, dirs, files in os.walk(docs_dir):
|
|
for fname in sorted(files):
|
|
if not fname.endswith('.md'):
|
|
continue
|
|
rel = str((Path(root) / fname).relative_to(LIB_ROOT))
|
|
title = fname.replace('.md', '').replace('_', ' ')
|
|
|
|
cat = 'general'
|
|
for prefix, c in doc_categories.items():
|
|
if fname.upper().startswith(prefix):
|
|
cat = c
|
|
break
|
|
|
|
cur.execute("""INSERT OR IGNORE INTO docs
|
|
(path, title, category, topics) VALUES (?,?,?,?)""",
|
|
(rel, title, cat, '[]'))
|
|
count += 1
|
|
return count
|
|
|
|
|
|
def populate_semantic_tags(cur: sqlite3.Cursor):
|
|
"""Populate semantic tags and entity mappings for files, ABI functions, and docs."""
|
|
cur.execute("DELETE FROM fts_tags")
|
|
cur.execute("DELETE FROM entity_tags")
|
|
cur.execute("DELETE FROM semantic_tags")
|
|
count = 0
|
|
|
|
for tag, (domain, description) in SEMANTIC_TAGS.items():
|
|
cur.execute("""INSERT OR IGNORE INTO semantic_tags (tag, domain, description)
|
|
VALUES (?,?,?)""", (tag, domain, description))
|
|
count += 1
|
|
|
|
cur.execute("SELECT path, category, subsystem, layer FROM source_files")
|
|
for path, category, subsystem, layer in cur.fetchall():
|
|
for tag, confidence in derive_semantic_tags_for_source(path, category, subsystem, layer).items():
|
|
cur.execute("""INSERT OR IGNORE INTO entity_tags
|
|
(entity_type, entity_id, tag, confidence, origin)
|
|
VALUES (?,?,?,?,?)""",
|
|
('source_file', path, tag, confidence, 'derived'))
|
|
cur.execute("""INSERT INTO fts_tags(entity_type, entity_id, tag, domain, description)
|
|
VALUES (?,?,?,?,?)""",
|
|
('source_file', path, tag, SEMANTIC_TAGS[tag][0], SEMANTIC_TAGS[tag][1]))
|
|
count += 1
|
|
|
|
cur.execute("SELECT name, category, layer FROM c_abi_functions")
|
|
for name, category, layer in cur.fetchall():
|
|
for tag, confidence in derive_semantic_tags_for_abi(name, category, layer).items():
|
|
cur.execute("""INSERT OR IGNORE INTO entity_tags
|
|
(entity_type, entity_id, tag, confidence, origin)
|
|
VALUES (?,?,?,?,?)""",
|
|
('c_abi_function', name, tag, confidence, 'derived'))
|
|
cur.execute("""INSERT INTO fts_tags(entity_type, entity_id, tag, domain, description)
|
|
VALUES (?,?,?,?,?)""",
|
|
('c_abi_function', name, tag, SEMANTIC_TAGS[tag][0], SEMANTIC_TAGS[tag][1]))
|
|
count += 1
|
|
|
|
cur.execute("SELECT path, category FROM docs")
|
|
for path, category in cur.fetchall():
|
|
for tag, confidence in derive_semantic_tags_for_source(path, category, None, 'tool').items():
|
|
cur.execute("""INSERT OR IGNORE INTO entity_tags
|
|
(entity_type, entity_id, tag, confidence, origin)
|
|
VALUES (?,?,?,?,?)""",
|
|
('doc', path, tag, confidence, 'derived'))
|
|
cur.execute("""INSERT INTO fts_tags(entity_type, entity_id, tag, domain, description)
|
|
VALUES (?,?,?,?,?)""",
|
|
('doc', path, tag, SEMANTIC_TAGS[tag][0], SEMANTIC_TAGS[tag][1]))
|
|
count += 1
|
|
|
|
return count
|
|
|
|
def populate_edges(cur: sqlite3.Cursor):
|
|
"""Build cross-reference edges between entities."""
|
|
count = 0
|
|
|
|
# Test -> source file coverage edges
|
|
test_coverage = {
|
|
'field_52': ['cpu/src/field_52.cpp', 'cpu/src/field.cpp'],
|
|
'field_26': ['cpu/src/field_26.cpp'],
|
|
'selftest': ['cpu/src/selftest.cpp', 'cpu/src/precompute.cpp'],
|
|
'comprehensive': [
|
|
'cpu/src/ecdsa.cpp', 'cpu/src/schnorr.cpp', 'cpu/src/point.cpp',
|
|
'cpu/src/glv.cpp', 'cpu/src/ecmult_gen_comb.cpp', 'cpu/src/precompute.cpp',
|
|
],
|
|
'exhaustive': [
|
|
'cpu/src/field.cpp', 'cpu/src/scalar.cpp', 'cpu/src/point.cpp',
|
|
'cpu/src/field_asm.cpp', 'cpu/src/field_asm_arm64.cpp',
|
|
'cpu/src/field_asm_riscv64.cpp',
|
|
],
|
|
'bip340_vectors': ['cpu/src/schnorr.cpp'],
|
|
'bip340_strict': ['cpu/src/schnorr.cpp'],
|
|
'bip32_vectors': ['cpu/src/bip32.cpp'],
|
|
'bip39': ['cpu/src/bip39.cpp'],
|
|
'rfc6979_vectors': ['cpu/src/ecdsa.cpp'],
|
|
'ecc_properties': ['cpu/src/point.cpp', 'cpu/src/scalar.cpp'],
|
|
'ethereum': [
|
|
'cpu/src/ethereum.cpp', 'cpu/src/eth_signing.cpp',
|
|
'cpu/src/keccak256.cpp', 'cpu/src/message_signing.cpp',
|
|
],
|
|
'zk_proofs': ['cpu/src/zk.cpp', 'cpu/src/pedersen.cpp'],
|
|
'wallet': ['cpu/src/wallet.cpp', 'cpu/src/coin_address.cpp', 'cpu/src/coin_hd.cpp', 'cpu/src/address.cpp'],
|
|
'ct_equivalence': ['cpu/src/ct_field.cpp', 'cpu/src/ct_scalar.cpp', 'cpu/src/ct_point.cpp', 'cpu/src/ct_sign.cpp'],
|
|
'ct_sidechannel': ['cpu/src/ct_sign.cpp', 'cpu/src/ct_point.cpp'],
|
|
'adversarial_protocol': [
|
|
'cpu/src/musig2.cpp', 'cpu/src/frost.cpp', 'cpu/src/adaptor.cpp',
|
|
'cpu/src/ecdsa.cpp', 'cpu/src/ecdh.cpp', 'cpu/src/recovery.cpp',
|
|
# H.2: chacha20-poly1305 AEAD
|
|
'cpu/src/chacha20_poly1305.cpp',
|
|
# H.4: ElligatorSwift / EllSwift
|
|
'cpu/src/ellswift.cpp',
|
|
# H.9: BIP-143 sighash
|
|
'cpu/src/bip143.cpp',
|
|
# H.10: BIP-144 txid / wtxid / witness commitment
|
|
'cpu/src/bip144.cpp',
|
|
# H.11: SegWit program helpers
|
|
'cpu/src/segwit.cpp',
|
|
# H.12 + K.4-K.5: Taproot keypath / tapscript sighash
|
|
'cpu/src/taproot.cpp',
|
|
# K.1-K.3: BIP-324 session protocol
|
|
'cpu/src/bip324.cpp',
|
|
# BIP-324 / EllSwift uses HKDF internally
|
|
'cpu/src/hkdf.cpp',
|
|
# I.5: batch verify paths
|
|
'cpu/src/batch_verify.cpp',
|
|
],
|
|
'ecies_regression': ['cpu/src/ecies.cpp'],
|
|
'musig2_frost': ['cpu/src/musig2.cpp', 'cpu/src/frost.cpp'],
|
|
'abi_gate': ['include/ufsecp/ufsecp_impl.cpp'],
|
|
'wycheproof_ecdsa': ['cpu/src/ecdsa.cpp'],
|
|
'wycheproof_ecdh': ['cpu/src/ecdh.cpp'],
|
|
'fault_injection': ['cpu/src/ecdsa.cpp', 'cpu/src/schnorr.cpp'],
|
|
'batch_add_affine': [
|
|
'cpu/src/batch_add_affine.cpp',
|
|
'cpu/src/multiscalar.cpp',
|
|
'cpu/src/pippenger.cpp',
|
|
'cpu/src/batch_verify.cpp',
|
|
],
|
|
'hash_accel': ['cpu/src/hash_accel.cpp'],
|
|
'message_signing': ['cpu/src/message_signing.cpp'],
|
|
}
|
|
for test, files in test_coverage.items():
|
|
for f in files:
|
|
cur.execute("""INSERT OR IGNORE INTO edges
|
|
(src_type, src_id, dst_type, dst_id, relation)
|
|
VALUES (?,?,?,?,?)""",
|
|
('test_target', test, 'source_file', f, 'covers'))
|
|
count += 1
|
|
|
|
# C ABI -> source implementation edges
|
|
abi_impl = {
|
|
'ecdsa': 'cpu/src/ecdsa.cpp', 'schnorr': 'cpu/src/schnorr.cpp',
|
|
'ecdh': 'cpu/src/ecdh.cpp', 'bip32': 'cpu/src/bip32.cpp',
|
|
'bip39': 'cpu/src/bip39.cpp', 'musig2': 'cpu/src/musig2.cpp',
|
|
'frost': 'cpu/src/frost.cpp', 'adaptor': 'cpu/src/adaptor.cpp',
|
|
'taproot': 'cpu/src/taproot.cpp', 'pedersen': 'cpu/src/pedersen.cpp',
|
|
'zk': 'cpu/src/zk.cpp', 'ecies': 'cpu/src/ecies.cpp',
|
|
'ethereum': 'cpu/src/ethereum.cpp', 'hash': 'cpu/src/hash_accel.cpp',
|
|
'wallet': 'cpu/src/wallet.cpp', 'address': 'cpu/src/address.cpp',
|
|
'multiscalar': 'cpu/src/multiscalar.cpp',
|
|
}
|
|
cur.execute("SELECT name, category FROM c_abi_functions")
|
|
for fname, cat in cur.fetchall():
|
|
if cat in abi_impl:
|
|
cur.execute("""INSERT OR IGNORE INTO edges
|
|
(src_type, src_id, dst_type, dst_id, relation)
|
|
VALUES (?,?,?,?,?)""",
|
|
('c_abi_function', fname, 'source_file', abi_impl[cat], 'implements'))
|
|
count += 1
|
|
|
|
# Include dependency edges
|
|
cur.execute("SELECT source_file, included_file FROM include_deps WHERE is_local=1")
|
|
for src, inc in cur.fetchall():
|
|
cur.execute("""INSERT OR IGNORE INTO edges
|
|
(src_type, src_id, dst_type, dst_id, relation)
|
|
VALUES (?,?,?,?,?)""",
|
|
('source_file', src, 'source_file', inc, 'includes'))
|
|
count += 1
|
|
|
|
# ABI routing -> source file edges (routes_through)
|
|
ct_impl_map = {
|
|
'ct::ecdsa_sign': 'cpu/src/ct_sign.cpp',
|
|
'ct::schnorr_sign': 'cpu/src/ct_sign.cpp',
|
|
'ct::generator_mul': 'cpu/src/ct_point.cpp',
|
|
'ct::scalar_mul': 'cpu/src/ct_point.cpp',
|
|
'ct::musig2_partial_sign': 'cpu/src/musig2.cpp',
|
|
'ct::frost_sign': 'cpu/src/frost.cpp',
|
|
'ct::ecdsa_adaptor_sign': 'cpu/src/adaptor.cpp',
|
|
'ct::adaptor_sign': 'cpu/src/adaptor.cpp',
|
|
}
|
|
cur.execute("SELECT abi_function, internal_call, layer FROM abi_routing")
|
|
for abi_fn, internal, layer in cur.fetchall():
|
|
# Link to CT implementation
|
|
for prefix, impl_file in ct_impl_map.items():
|
|
if prefix in (internal or ''):
|
|
cur.execute("""INSERT OR IGNORE INTO edges
|
|
(src_type, src_id, dst_type, dst_id, relation)
|
|
VALUES (?,?,?,?,?)""",
|
|
('abi_routing', abi_fn, 'source_file', impl_file, 'routes_through'))
|
|
count += 1
|
|
break
|
|
|
|
# Binding -> C ABI function edges (wraps)
|
|
cur.execute("SELECT language FROM binding_languages")
|
|
for (lang,) in cur.fetchall():
|
|
cur.execute("SELECT name FROM c_abi_functions")
|
|
for (fn_name,) in cur.fetchall():
|
|
cur.execute("""INSERT OR IGNORE INTO edges
|
|
(src_type, src_id, dst_type, dst_id, relation)
|
|
VALUES (?,?,?,?,?)""",
|
|
('binding', lang, 'c_abi_function', fn_name, 'wraps'))
|
|
count += 1
|
|
|
|
# Security pattern -> source file edges (protects)
|
|
cur.execute("""SELECT DISTINCT source_file FROM security_patterns""")
|
|
for (src_file,) in cur.fetchall():
|
|
cur.execute("""INSERT OR IGNORE INTO edges
|
|
(src_type, src_id, dst_type, dst_id, relation)
|
|
VALUES (?,?,?,?,?)""",
|
|
('security_pattern', 'ct_protection', 'source_file', src_file, 'protects'))
|
|
count += 1
|
|
|
|
return count
|
|
|
|
def populate_audit_modules(cur: sqlite3.Cursor):
|
|
modules = [
|
|
# Section 1: Mathematical Invariants (13)
|
|
('audit_field', 'Field Arithmetic Invariants', 'math_invariants', 1),
|
|
('audit_scalar', 'Scalar Arithmetic Invariants', 'math_invariants', 1),
|
|
('audit_point', 'Point/Group Law Invariants', 'math_invariants', 1),
|
|
('mul', 'Multiplication Correctness', 'math_invariants', 1),
|
|
('arith_correct', 'Arithmetic Correctness', 'math_invariants', 1),
|
|
('scalar_mul', 'Scalar Multiplication', 'math_invariants', 1),
|
|
('exhaustive', 'Exhaustive 6-bit Group', 'math_invariants', 1),
|
|
('comprehensive', 'Comprehensive Operations', 'math_invariants', 1),
|
|
('ecc_properties', 'ECC Group Properties', 'math_invariants', 1),
|
|
('batch_add', 'Batch Addition', 'math_invariants', 1),
|
|
('carry_propagation', 'Carry Propagation', 'math_invariants', 1),
|
|
('field_52', 'Field 5x52 Representation', 'math_invariants', 1),
|
|
('field_26', 'Field 10x26 Representation', 'math_invariants', 1),
|
|
# Section 2: CT Analysis (6)
|
|
('audit_ct', 'CT Layer Coverage', 'ct_analysis', 2),
|
|
('ct', 'CT Equivalence', 'ct_analysis', 2),
|
|
('ct_equivalence', 'CT Fast-vs-CT Parity', 'ct_analysis', 2),
|
|
('ct_sidechannel', 'Dudect Timing Analysis', 'ct_analysis', 2),
|
|
('ct_verif_formal', 'LLVM Formal CT Verification', 'ct_analysis', 2),
|
|
('diag_scalar_mul', 'Scalar Mul Diagnostics', 'ct_analysis', 2),
|
|
# Section 3: Differential (4)
|
|
('differential', 'libsecp256k1 Differential', 'differential', 3),
|
|
('fiat_crypto', 'Fiat-Crypto Vectors', 'differential', 3),
|
|
('fiat_crypto_link', 'Fiat-Crypto Linkage', 'differential', 3),
|
|
('cross_platform_kat', 'Cross-Platform KAT', 'differential', 3),
|
|
# Section 4: Standard Vectors (8)
|
|
('bip340_vectors', 'BIP-340 Official Vectors', 'vectors', 4),
|
|
('bip340_strict', 'BIP-340 Strict Encoding', 'vectors', 4),
|
|
('bip32_vectors', 'BIP-32 HD Key Vectors', 'vectors', 4),
|
|
('rfc6979_vectors', 'RFC-6979 Deterministic k', 'vectors', 4),
|
|
('frost_kat', 'FROST Key/Sign Vectors', 'vectors', 4),
|
|
('musig2_bip327', 'MuSig2 BIP-327 Vectors', 'vectors', 4),
|
|
('wycheproof_ecdsa', 'Wycheproof ECDSA', 'vectors', 4),
|
|
('wycheproof_ecdh', 'Wycheproof ECDH', 'vectors', 4),
|
|
# Section 5: Fuzzing (4+)
|
|
('audit_fuzz', 'Core Fuzzing', 'fuzzing', 5),
|
|
('fuzz_parsers', 'Parser Fuzzing', 'fuzzing', 5),
|
|
('fuzz_addr_bip32', 'Address/BIP32/FFI Fuzz', 'fuzzing', 5),
|
|
('fault_injection', 'Bit-Flip Fault Injection', 'fuzzing', 5),
|
|
# Section 6: Protocol Security (12+)
|
|
('ecdsa_schnorr', 'ECDSA/Schnorr Roundtrip', 'protocol', 6),
|
|
('bip32', 'BIP-32 HD Derivation', 'protocol', 6),
|
|
('bip39', 'BIP-39 Mnemonic', 'protocol', 6),
|
|
('musig2', 'MuSig2 Protocol', 'protocol', 6),
|
|
('ecdh_recovery', 'ECDH + Recovery', 'protocol', 6),
|
|
('v4_features', 'v4 Feature Coverage', 'protocol', 6),
|
|
('coins', 'Multi-Coin Wallet', 'protocol', 6),
|
|
('musig2_frost', 'MuSig2+FROST Combined', 'protocol', 6),
|
|
('musig2_frost_adv', 'MuSig2+FROST Advanced', 'protocol', 6),
|
|
('audit_integration', 'Integration / Adaptor', 'protocol', 6),
|
|
('batch_randomness', 'Batch Randomness Quality', 'protocol', 6),
|
|
('ethereum', 'Ethereum Module', 'protocol', 6),
|
|
# Section 7: Memory Safety (6+)
|
|
('audit_security', 'Security Hardening', 'memory_safety', 7),
|
|
('debug_invariants', 'Debug Invariant Checks', 'memory_safety', 7),
|
|
('abi_gate', 'ABI Gate (symbol check)', 'memory_safety', 7),
|
|
('ffi_round_trip', 'FFI Round-Trip', 'memory_safety', 7),
|
|
('adversarial_proto', 'Adversarial Protocol', 'memory_safety', 7),
|
|
('ecies_regression', 'ECIES Regression', 'memory_safety', 7),
|
|
# Section 8: Performance (4)
|
|
('hash_accel', 'Hash Acceleration', 'performance', 8),
|
|
('edge_cases', 'Edge Case Performance', 'performance', 8),
|
|
('multiscalar', 'Multi-Scalar Ops', 'performance', 8),
|
|
('audit_perf', 'Performance Validation', 'performance', 8),
|
|
]
|
|
for mid, name, section, section_no in modules:
|
|
cur.execute("""INSERT OR IGNORE INTO audit_modules
|
|
(module_id, name, section, section_no) VALUES (?,?,?,?)""",
|
|
(mid, name, section, section_no))
|
|
return len(modules)
|
|
|
|
def populate_cpp_methods(cur: sqlite3.Cursor):
|
|
"""Extract public C++ methods from core headers."""
|
|
methods = [
|
|
# FieldElement (fast)
|
|
('FieldElement', 'zero', 'static FieldElement zero()', 1, 0, 0, 'cpu/include/secp256k1/field.hpp', 35, 'fast'),
|
|
('FieldElement', 'one', 'static FieldElement one()', 1, 0, 0, 'cpu/include/secp256k1/field.hpp', 36, 'fast'),
|
|
('FieldElement', 'from_uint64', 'static FieldElement from_uint64(uint64_t)', 1, 0, 0, 'cpu/include/secp256k1/field.hpp', 37, 'fast'),
|
|
('FieldElement', 'from_limbs', 'static FieldElement from_limbs(const limbs_type&)', 1, 0, 0, 'cpu/include/secp256k1/field.hpp', 38, 'fast'),
|
|
('FieldElement', 'from_bytes', 'static FieldElement from_bytes(const array<uint8_t,32>&)', 1, 0, 0, 'cpu/include/secp256k1/field.hpp', 39, 'fast'),
|
|
('FieldElement', 'parse_bytes_strict', 'static bool parse_bytes_strict(const uint8_t*, FieldElement&) noexcept', 1, 0, 1, 'cpu/include/secp256k1/field.hpp', 45, 'fast'),
|
|
('FieldElement', 'from_mont', 'static FieldElement from_mont(const FieldElement&)', 1, 0, 0, 'cpu/include/secp256k1/field.hpp', 50, 'fast'),
|
|
('FieldElement', 'from_hex', 'static FieldElement from_hex(const string&)', 1, 0, 0, 'cpu/include/secp256k1/field.hpp', 54, 'fast'),
|
|
('FieldElement', 'to_bytes', 'array<uint8_t,32> to_bytes() const', 0, 1, 0, 'cpu/include/secp256k1/field.hpp', 58, 'fast'),
|
|
('FieldElement', 'to_bytes_into', 'void to_bytes_into(uint8_t*) const noexcept', 0, 1, 1, 'cpu/include/secp256k1/field.hpp', 60, 'fast'),
|
|
('FieldElement', 'to_hex', 'string to_hex() const', 0, 1, 0, 'cpu/include/secp256k1/field.hpp', 61, 'fast'),
|
|
('FieldElement', 'limbs', 'const limbs_type& limbs() const noexcept', 0, 1, 1, 'cpu/include/secp256k1/field.hpp', 62, 'fast'),
|
|
('FieldElement', 'limbs_mut', 'limbs_type& limbs_mut() noexcept', 0, 0, 1, 'cpu/include/secp256k1/field.hpp', 66, 'fast'),
|
|
('FieldElement', 'operator+', 'FieldElement operator+(const FieldElement&) const', 0, 1, 0, 'cpu/include/secp256k1/field.hpp', 74, 'fast'),
|
|
('FieldElement', 'operator-', 'FieldElement operator-(const FieldElement&) const', 0, 1, 0, 'cpu/include/secp256k1/field.hpp', 75, 'fast'),
|
|
('FieldElement', 'operator*', 'FieldElement operator*(const FieldElement&) const', 0, 1, 0, 'cpu/include/secp256k1/field.hpp', 76, 'fast'),
|
|
('FieldElement', 'square', 'FieldElement square() const', 0, 1, 0, 'cpu/include/secp256k1/field.hpp', 77, 'fast'),
|
|
('FieldElement', 'inverse', 'FieldElement inverse() const', 0, 1, 0, 'cpu/include/secp256k1/field.hpp', 78, 'fast'),
|
|
('FieldElement', 'sqrt', 'FieldElement sqrt() const', 0, 1, 0, 'cpu/include/secp256k1/field.hpp', 84, 'fast'),
|
|
('FieldElement', 'negate', 'FieldElement negate(unsigned=1) const', 0, 1, 0, 'cpu/include/secp256k1/field.hpp', 95, 'fast'),
|
|
('FieldElement', 'square_inplace', 'void square_inplace()', 0, 0, 0, 'cpu/include/secp256k1/field.hpp', 99, 'fast'),
|
|
('FieldElement', 'inverse_inplace', 'void inverse_inplace()', 0, 0, 0, 'cpu/include/secp256k1/field.hpp', 100, 'fast'),
|
|
('FieldElement', 'operator==', 'bool operator==(const FieldElement&) const noexcept', 0, 1, 1, 'cpu/include/secp256k1/field.hpp', 102, 'fast'),
|
|
# Scalar (fast)
|
|
('Scalar', 'zero', 'static Scalar zero()', 1, 0, 0, 'cpu/include/secp256k1/scalar.hpp', 18, 'fast'),
|
|
('Scalar', 'one', 'static Scalar one()', 1, 0, 0, 'cpu/include/secp256k1/scalar.hpp', 19, 'fast'),
|
|
('Scalar', 'from_uint64', 'static Scalar from_uint64(uint64_t)', 1, 0, 0, 'cpu/include/secp256k1/scalar.hpp', 20, 'fast'),
|
|
('Scalar', 'from_limbs', 'static Scalar from_limbs(const limbs_type&)', 1, 0, 0, 'cpu/include/secp256k1/scalar.hpp', 21, 'fast'),
|
|
('Scalar', 'from_bytes', 'static Scalar from_bytes(const array<uint8_t,32>&)', 1, 0, 0, 'cpu/include/secp256k1/scalar.hpp', 22, 'fast'),
|
|
('Scalar', 'parse_bytes_strict', 'static bool parse_bytes_strict(const uint8_t*, Scalar&) noexcept', 1, 0, 1, 'cpu/include/secp256k1/scalar.hpp', 27, 'fast'),
|
|
('Scalar', 'parse_bytes_strict_nonzero', 'static bool parse_bytes_strict_nonzero(const uint8_t*, Scalar&) noexcept', 1, 0, 1, 'cpu/include/secp256k1/scalar.hpp', 31, 'fast'),
|
|
('Scalar', 'from_hex', 'static Scalar from_hex(const string&)', 1, 0, 0, 'cpu/include/secp256k1/scalar.hpp', 37, 'fast'),
|
|
('Scalar', 'to_bytes', 'array<uint8_t,32> to_bytes() const', 0, 1, 0, 'cpu/include/secp256k1/scalar.hpp', 39, 'fast'),
|
|
('Scalar', 'to_hex', 'string to_hex() const', 0, 1, 0, 'cpu/include/secp256k1/scalar.hpp', 40, 'fast'),
|
|
('Scalar', 'limbs', 'const limbs_type& limbs() const noexcept', 0, 1, 1, 'cpu/include/secp256k1/scalar.hpp', 41, 'fast'),
|
|
('Scalar', 'operator+', 'Scalar operator+(const Scalar&) const', 0, 1, 0, 'cpu/include/secp256k1/scalar.hpp', 43, 'fast'),
|
|
('Scalar', 'operator-', 'Scalar operator-(const Scalar&) const', 0, 1, 0, 'cpu/include/secp256k1/scalar.hpp', 44, 'fast'),
|
|
('Scalar', 'operator*', 'Scalar operator*(const Scalar&) const', 0, 1, 0, 'cpu/include/secp256k1/scalar.hpp', 45, 'fast'),
|
|
('Scalar', 'is_zero', 'bool is_zero() const noexcept', 0, 1, 1, 'cpu/include/secp256k1/scalar.hpp', 51, 'fast'),
|
|
('Scalar', 'operator==', 'bool operator==(const Scalar&) const noexcept', 0, 1, 1, 'cpu/include/secp256k1/scalar.hpp', 52, 'fast'),
|
|
('Scalar', 'inverse', 'Scalar inverse() const', 0, 1, 0, 'cpu/include/secp256k1/scalar.hpp', 56, 'fast'),
|
|
('Scalar', 'negate', 'Scalar negate() const', 0, 1, 0, 'cpu/include/secp256k1/scalar.hpp', 60, 'fast'),
|
|
('Scalar', 'is_even', 'bool is_even() const noexcept', 0, 1, 1, 'cpu/include/secp256k1/scalar.hpp', 63, 'fast'),
|
|
('Scalar', 'bit', 'uint8_t bit(size_t) const', 0, 1, 0, 'cpu/include/secp256k1/scalar.hpp', 76, 'fast'),
|
|
('Scalar', 'to_wnaf', 'vector<int8_t> to_wnaf(unsigned) const', 0, 1, 0, 'cpu/include/secp256k1/scalar.hpp', 95, 'fast'),
|
|
# Point (fast)
|
|
('Point', 'generator', 'static Point generator()', 1, 0, 0, 'cpu/include/secp256k1/point.hpp', 84, 'fast'),
|
|
('Point', 'infinity', 'static Point infinity()', 1, 0, 0, 'cpu/include/secp256k1/point.hpp', 85, 'fast'),
|
|
('Point', 'from_affine', 'static Point from_affine(const FieldElement&, const FieldElement&)', 1, 0, 0, 'cpu/include/secp256k1/point.hpp', 86, 'fast'),
|
|
('Point', 'from_hex', 'static Point from_hex(const string&, const string&)', 1, 0, 0, 'cpu/include/secp256k1/point.hpp', 90, 'fast'),
|
|
('Point', 'x', 'FieldElement x() const', 0, 1, 0, 'cpu/include/secp256k1/point.hpp', 93, 'fast'),
|
|
('Point', 'y', 'FieldElement y() const', 0, 1, 0, 'cpu/include/secp256k1/point.hpp', 94, 'fast'),
|
|
('Point', 'is_infinity', 'bool is_infinity() const noexcept', 0, 1, 1, 'cpu/include/secp256k1/point.hpp', 95, 'fast'),
|
|
('Point', 'add', 'Point add(const Point&) const', 0, 1, 0, 'cpu/include/secp256k1/point.hpp', 120, 'fast'),
|
|
('Point', 'dbl', 'Point dbl() const', 0, 1, 0, 'cpu/include/secp256k1/point.hpp', 121, 'fast'),
|
|
('Point', 'scalar_mul', 'Point scalar_mul(const Scalar&) const', 0, 1, 0, 'cpu/include/secp256k1/point.hpp', 122, 'fast'),
|
|
('Point', 'scalar_mul_precomputed_k', 'Point scalar_mul_precomputed_k(const Scalar&) const', 0, 1, 0, 'cpu/include/secp256k1/point.hpp', 126, 'fast'),
|
|
('Point', 'negate', 'Point negate() const', 0, 1, 0, 'cpu/include/secp256k1/point.hpp', 145, 'fast'),
|
|
('Point', 'next', 'Point next() const', 0, 1, 0, 'cpu/include/secp256k1/point.hpp', 148, 'fast'),
|
|
('Point', 'prev', 'Point prev() const', 0, 1, 0, 'cpu/include/secp256k1/point.hpp', 149, 'fast'),
|
|
('Point', 'add_inplace', 'void add_inplace(const Point&)', 0, 0, 0, 'cpu/include/secp256k1/point.hpp', 154, 'fast'),
|
|
# ECDSASignature
|
|
('ECDSASignature', 'to_der', 'pair<array<uint8_t,72>,size_t> to_der() const', 0, 1, 0, 'cpu/include/secp256k1/ecdsa.hpp', 32, 'fast'),
|
|
('ECDSASignature', 'to_compact', 'array<uint8_t,64> to_compact() const', 0, 1, 0, 'cpu/include/secp256k1/ecdsa.hpp', 35, 'fast'),
|
|
('ECDSASignature', 'from_compact', 'static ECDSASignature from_compact(const uint8_t*)', 1, 0, 0, 'cpu/include/secp256k1/ecdsa.hpp', 38, 'fast'),
|
|
('ECDSASignature', 'parse_compact_strict', 'static bool parse_compact_strict(const uint8_t*, ECDSASignature&) noexcept', 1, 0, 1, 'cpu/include/secp256k1/ecdsa.hpp', 42, 'fast'),
|
|
('ECDSASignature', 'normalize', 'ECDSASignature normalize() const', 0, 1, 0, 'cpu/include/secp256k1/ecdsa.hpp', 46, 'fast'),
|
|
('ECDSASignature', 'is_low_s', 'bool is_low_s() const', 0, 1, 0, 'cpu/include/secp256k1/ecdsa.hpp', 49, 'fast'),
|
|
# Free functions
|
|
('', 'ecdsa_sign', 'ECDSASignature ecdsa_sign(const array<uint8_t,32>&, const fast::Scalar&)', 0, 0, 0, 'cpu/include/secp256k1/ecdsa.hpp', 57, 'fast'),
|
|
('', 'ecdsa_sign_verified', 'ECDSASignature ecdsa_sign_verified(const array<uint8_t,32>&, const fast::Scalar&)', 0, 0, 0, 'cpu/include/secp256k1/ecdsa.hpp', 62, 'fast'),
|
|
('', 'ecdsa_verify', 'bool ecdsa_verify(const uint8_t*, const fast::Point&, const ECDSASignature&)', 0, 0, 0, 'cpu/include/secp256k1/ecdsa.hpp', 81, 'fast'),
|
|
('', 'rfc6979_nonce', 'fast::Scalar rfc6979_nonce(const fast::Scalar&, const array<uint8_t,32>&)', 0, 0, 0, 'cpu/include/secp256k1/ecdsa.hpp', 95, 'fast'),
|
|
('', 'schnorr_sign', 'SchnorrSignature schnorr_sign(const array<uint8_t,32>&, const fast::Scalar&)', 0, 0, 0, 'cpu/include/secp256k1/schnorr.hpp', 77, 'fast'),
|
|
('', 'schnorr_verify', 'bool schnorr_verify(const array<uint8_t,32>&, const array<uint8_t,32>&, const SchnorrSignature&)', 0, 0, 0, 'cpu/include/secp256k1/schnorr.hpp', 82, 'fast'),
|
|
('', 'generate_schnorr_keypair', 'SchnorrKeypair generate_schnorr_keypair(const fast::Scalar&)', 0, 0, 0, 'cpu/include/secp256k1/schnorr.hpp', 63, 'fast'),
|
|
# CT layer
|
|
('', 'ct::ecdsa_sign', 'ECDSASignature ct::ecdsa_sign(const array<uint8_t,32>&, const Scalar&)', 0, 0, 0, 'cpu/include/secp256k1/ct/sign.hpp', 0, 'ct'),
|
|
('', 'ct::schnorr_sign', 'SchnorrSignature ct::schnorr_sign(const array<uint8_t,32>&, const Scalar&)', 0, 0, 0, 'cpu/include/secp256k1/ct/sign.hpp', 0, 'ct'),
|
|
('', 'ct::generator_mul', 'Point ct::generator_mul(const Scalar&)', 0, 0, 0, 'cpu/include/secp256k1/ct/point.hpp', 0, 'ct'),
|
|
('', 'ct::scalar_mul', 'Point ct::scalar_mul(const Point&, const Scalar&)', 0, 0, 0, 'cpu/include/secp256k1/ct/point.hpp', 0, 'ct'),
|
|
# SHA256
|
|
('SHA256', 'update', 'void update(const uint8_t*, size_t)', 0, 0, 0, 'cpu/include/secp256k1/sha256.hpp', 0, 'fast'),
|
|
('SHA256', 'finalize', 'array<uint8_t,32> finalize()', 0, 0, 0, 'cpu/include/secp256k1/sha256.hpp', 0, 'fast'),
|
|
('SHA256', 'digest', 'static array<uint8_t,32> digest(const uint8_t*, size_t)', 1, 0, 0, 'cpu/include/secp256k1/sha256.hpp', 0, 'fast'),
|
|
]
|
|
for cls, meth, sig, is_s, is_c, is_n, hdr, ln, layer in methods:
|
|
cur.execute("""INSERT OR IGNORE INTO cpp_methods
|
|
(class_name, method, signature, is_static, is_const, is_noexcept, header_path, line_no, layer)
|
|
VALUES (?,?,?,?,?,?,?,?,?)""",
|
|
(cls, meth, sig, is_s, is_c, is_n, hdr, ln, layer))
|
|
return len(methods)
|
|
|
|
def populate_security_patterns(cur: sqlite3.Cursor):
|
|
"""Scan source for secure_erase, value_barrier, CLASSIFY/DECLASSIFY."""
|
|
patterns_to_scan = [
|
|
('secure_erase', re.compile(r'secure_erase\s*\(')),
|
|
('value_barrier', re.compile(r'value_barrier\s*\(')),
|
|
('CLASSIFY', re.compile(r'SECP256K1_CLASSIFY\s*\(')),
|
|
('DECLASSIFY', re.compile(r'SECP256K1_DECLASSIFY\s*\(')),
|
|
]
|
|
scan_dirs = ['cpu/src', 'cpu/include', 'include/ufsecp', 'audit']
|
|
count = 0
|
|
for scan_dir in scan_dirs:
|
|
dirpath = LIB_ROOT / scan_dir
|
|
if not dirpath.exists():
|
|
continue
|
|
for root, dirs, files in os.walk(dirpath):
|
|
dirs[:] = [d for d in dirs if not should_skip_dir(d)]
|
|
for fname in files:
|
|
ext = os.path.splitext(fname)[1].lower()
|
|
if ext not in ('.cpp', '.hpp', '.h'):
|
|
continue
|
|
filepath = Path(root) / fname
|
|
rel = str(filepath.relative_to(LIB_ROOT))
|
|
try:
|
|
with open(filepath, 'r', errors='replace') as f:
|
|
for i, line in enumerate(f, 1):
|
|
# Skip #include and comment-only lines
|
|
stripped = line.strip()
|
|
if stripped.startswith('#include') or stripped.startswith('//'):
|
|
# Allow CLASSIFY/DECLASSIFY defines though
|
|
if 'CLASSIFY' not in stripped and 'DECLASSIFY' not in stripped:
|
|
continue
|
|
for pat_name, pat_re in patterns_to_scan:
|
|
if pat_re.search(line):
|
|
ctx = stripped[:120]
|
|
cur.execute("""INSERT INTO security_patterns
|
|
(pattern, source_file, line_no, context)
|
|
VALUES (?,?,?,?)""",
|
|
(pat_name, rel, i, ctx))
|
|
count += 1
|
|
except Exception:
|
|
pass
|
|
return count
|
|
|
|
def populate_abi_routing(cur: sqlite3.Cursor):
|
|
"""Map each ufsecp_* function to the internal call it makes."""
|
|
routing = [
|
|
# Context
|
|
('ufsecp_ctx_create', 'run_selftest + alloc', 'both', 223),
|
|
('ufsecp_ctx_clone', 'memcpy ctx', 'fast', 244),
|
|
('ufsecp_last_error', 'ctx->last_err', 'fast', 262),
|
|
# Seckey (all CT)
|
|
('ufsecp_seckey_verify', 'Scalar::parse_bytes_strict_nonzero', 'ct', 279),
|
|
('ufsecp_seckey_negate', 'Scalar::negate', 'ct', 292),
|
|
('ufsecp_seckey_tweak_add', 'ct scalar add + validate', 'ct', 307),
|
|
('ufsecp_seckey_tweak_mul', 'ct scalar mul + validate', 'ct', 331),
|
|
# Pubkey (CT for create, fast for parse/verify)
|
|
('ufsecp_pubkey_create', 'ct::generator_mul(sk)', 'ct', 375),
|
|
('ufsecp_pubkey_create_uncompressed', 'ct::generator_mul(sk)', 'ct', 387),
|
|
('ufsecp_pubkey_parse', 'point_from_compressed + on_curve', 'fast', 400),
|
|
('ufsecp_pubkey_xonly', 'schnorr_pubkey(sk)', 'ct', 440),
|
|
('ufsecp_pubkey_add', 'Point::add', 'fast', 1232),
|
|
('ufsecp_pubkey_negate', 'Point::negate', 'fast', 1251),
|
|
('ufsecp_pubkey_tweak_add', 'Point::add(gen_mul(tweak))', 'fast', 1264),
|
|
('ufsecp_pubkey_tweak_mul', 'Point::scalar_mul(tweak)', 'fast', 1284),
|
|
('ufsecp_pubkey_combine', 'multi-point add', 'fast', 1303),
|
|
# ECDSA (CT sign, fast verify)
|
|
('ufsecp_ecdsa_sign', 'ct::ecdsa_sign(msg, sk)', 'ct', 461),
|
|
('ufsecp_ecdsa_sign_verified', 'ct::ecdsa_sign + ecdsa_verify', 'ct', 483),
|
|
('ufsecp_ecdsa_verify', 'ecdsa_verify(msg, pubkey, sig)', 'fast', 504),
|
|
('ufsecp_ecdsa_sig_to_der', 'ECDSASignature::to_der', 'fast', 531),
|
|
('ufsecp_ecdsa_sig_from_der', 'DER parse', 'fast', 555),
|
|
('ufsecp_ecdsa_sign_recoverable', 'ct::ecdsa_sign + recovery_id', 'ct', 675),
|
|
('ufsecp_ecdsa_recover', 'ecrecover(msg, sig, v)', 'fast', 704),
|
|
# Schnorr (CT sign, fast verify)
|
|
('ufsecp_schnorr_sign', 'ct::schnorr_sign(msg, sk)', 'ct', 739),
|
|
('ufsecp_schnorr_sign_verified', 'ct::schnorr_sign + schnorr_verify', 'ct', 767),
|
|
('ufsecp_schnorr_verify', 'schnorr_verify(msg, xpub, sig)', 'fast', 795),
|
|
('ufsecp_schnorr_keypair', 'generate_schnorr_keypair(sk)', 'ct', 0),
|
|
# ECDH (CT)
|
|
('ufsecp_ecdh', 'ct::scalar_mul(pubkey, sk)', 'ct', 843),
|
|
('ufsecp_ecdh_xonly', 'ct::scalar_mul + x-only output', 'ct', 859),
|
|
('ufsecp_ecdh_raw', 'ct::scalar_mul + raw output', 'ct', 875),
|
|
# Hash (fast, no secrets)
|
|
('ufsecp_sha256', 'SHA256::digest(data, len)', 'fast', 895),
|
|
('ufsecp_hash160', 'SHA256+RIPEMD160', 'fast', 905),
|
|
('ufsecp_tagged_hash', 'tagged_hash(tag, msg)', 'fast', 913),
|
|
('ufsecp_sha512', 'SHA512::digest(data, len)', 'fast', 1488),
|
|
# Address (fast, public)
|
|
('ufsecp_addr_p2pkh', 'hash160 + base58check', 'fast', 926),
|
|
('ufsecp_addr_p2wpkh', 'hash160 + bech32', 'fast', 947),
|
|
('ufsecp_addr_p2tr', 'taproot_output_key + bech32m', 'fast', 968),
|
|
('ufsecp_wif_encode', 'base58check(privkey)', 'fast', 992),
|
|
('ufsecp_wif_decode', 'base58check_decode + validate', 'fast', 1016),
|
|
# BIP-32 (CT for secret derivation)
|
|
('ufsecp_bip32_master', 'HMAC-SHA512(seed)', 'ct', 1067),
|
|
('ufsecp_bip32_derive', 'CKD_priv or CKD_pub', 'ct', 1088),
|
|
('ufsecp_bip32_derive_path', 'multi-level CKD', 'ct', 1109),
|
|
('ufsecp_bip32_privkey', 'ExtendedKey::privkey()', 'ct', 1130),
|
|
('ufsecp_bip32_pubkey', 'ExtendedKey::pubkey()', 'fast', 1149),
|
|
# Taproot
|
|
('ufsecp_taproot_output_key', 'taproot_output_key(xpub, merkle)', 'fast', 1165),
|
|
('ufsecp_taproot_tweak_seckey', 'taproot_tweak_seckey(sk, merkle)', 'ct', 1185),
|
|
('ufsecp_taproot_verify', 'taproot_verify(xpub, merkle, output)', 'fast', 1209),
|
|
# BIP-39
|
|
('ufsecp_bip39_generate', 'bip39_generate(strength)', 'ct', 1329),
|
|
('ufsecp_bip39_validate', 'bip39_validate(mnemonic)', 'fast', 1349),
|
|
('ufsecp_bip39_to_seed', 'PBKDF2-SHA512(mnemonic, passphrase)', 'ct', 1357),
|
|
('ufsecp_bip39_to_entropy', 'bip39_to_entropy(mnemonic)', 'fast', 1371),
|
|
# Batch (fast)
|
|
('ufsecp_schnorr_batch_verify', 'batch_schnorr_verify(entries)', 'fast', 1391),
|
|
('ufsecp_ecdsa_batch_verify', 'batch_ecdsa_verify(entries)', 'fast', 1414),
|
|
('ufsecp_schnorr_batch_identify_invalid', 'bisection invalid finder', 'fast', 1437),
|
|
('ufsecp_ecdsa_batch_identify_invalid', 'bisection invalid finder', 'fast', 1460),
|
|
# Multi-scalar
|
|
('ufsecp_shamir_trick', 'shamir_trick(a*G + b*P)', 'fast', 1500),
|
|
('ufsecp_multi_scalar_mul', 'pippenger_msm(points, scalars)', 'fast', 1524),
|
|
# MuSig2 (mixed)
|
|
('ufsecp_musig2_key_agg', 'musig2_key_agg(pubkeys)', 'fast', 1552),
|
|
('ufsecp_musig2_nonce_gen', 'musig2_nonce_gen(sk)', 'ct', 1575),
|
|
('ufsecp_musig2_nonce_agg', 'musig2_nonce_agg(nonces)', 'fast', 1607),
|
|
('ufsecp_musig2_partial_sign', 'ct::musig2_partial_sign(sk)', 'ct', 0),
|
|
('ufsecp_musig2_partial_verify', 'musig2_partial_verify', 'fast', 0),
|
|
('ufsecp_musig2_aggregate', 'musig2_aggregate(partials)', 'fast', 0),
|
|
('ufsecp_musig2_start_sign_session', 'musig2_session_init', 'ct', 1628),
|
|
# FROST (mixed)
|
|
('ufsecp_frost_keygen_begin', 'frost_keygen_begin', 'ct', 0),
|
|
('ufsecp_frost_keygen_finalize', 'frost_keygen_finalize', 'fast', 0),
|
|
('ufsecp_frost_sign_nonce_gen', 'frost_sign_nonce_gen', 'ct', 0),
|
|
('ufsecp_frost_sign', 'ct::frost_sign(sk, nonce)', 'ct', 0),
|
|
('ufsecp_frost_verify_partial', 'frost_verify_partial', 'fast', 0),
|
|
('ufsecp_frost_aggregate', 'frost_aggregate(partials)', 'fast', 0),
|
|
# Adaptor
|
|
('ufsecp_schnorr_adaptor_sign', 'ct::adaptor_sign(sk)', 'ct', 0),
|
|
('ufsecp_schnorr_adaptor_verify', 'adaptor_verify', 'fast', 0),
|
|
('ufsecp_schnorr_adaptor_adapt', 'adaptor_adapt(pre_sig, secret)', 'ct', 0),
|
|
('ufsecp_schnorr_adaptor_extract', 'adaptor_extract(sig, pre_sig)', 'fast', 0),
|
|
('ufsecp_ecdsa_adaptor_sign', 'ct::ecdsa_adaptor_sign(sk)', 'ct', 0),
|
|
('ufsecp_ecdsa_adaptor_verify', 'ecdsa_adaptor_verify', 'fast', 0),
|
|
('ufsecp_ecdsa_adaptor_adapt', 'ecdsa_adaptor_adapt', 'ct', 0),
|
|
('ufsecp_ecdsa_adaptor_extract', 'ecdsa_adaptor_extract', 'fast', 0),
|
|
# Pedersen + ZK
|
|
('ufsecp_pedersen_commit', 'pedersen_commit(v, r)', 'fast', 0),
|
|
('ufsecp_pedersen_verify', 'pedersen_verify(C, v, r)', 'fast', 0),
|
|
('ufsecp_pedersen_blind_sum', 'blind factor sum', 'ct', 0),
|
|
('ufsecp_pedersen_verify_tally', 'verify_tally(inputs, outputs)', 'fast', 0),
|
|
('ufsecp_pedersen_switch_commit', 'switch_commit(v, r)', 'fast', 0),
|
|
('ufsecp_zk_range_proof_create', 'create_range_proof', 'ct', 0),
|
|
('ufsecp_zk_range_proof_verify', 'verify_range_proof', 'fast', 0),
|
|
('ufsecp_zk_knowledge_prove', 'prove_knowledge(sk)', 'ct', 0),
|
|
('ufsecp_zk_knowledge_verify', 'verify_knowledge(proof, P)', 'fast', 0),
|
|
('ufsecp_zk_dleq_prove', 'dleq_prove(sk)', 'ct', 0),
|
|
('ufsecp_zk_dleq_verify', 'dleq_verify(proof)', 'fast', 0),
|
|
# Wallet / Coins
|
|
('ufsecp_coin_address', 'coin_address(coin, pubkey)', 'fast', 0),
|
|
('ufsecp_coin_address_validate', 'coin_address_validate(coin, addr)', 'fast', 0),
|
|
('ufsecp_coin_hd_derive', 'coin_hd_derive(coin, xprv, path)', 'ct', 0),
|
|
('ufsecp_btc_message_sign', 'btc_message_sign(msg, sk)', 'ct', 0),
|
|
('ufsecp_btc_message_verify', 'btc_message_verify(msg, sig, addr)', 'fast', 0),
|
|
('ufsecp_coin_params', 'get coin_params(coin_id)', 'fast', 0),
|
|
# Silent Payments
|
|
('ufsecp_silent_payment_create_output', 'silent_payment_create_output', 'ct', 0),
|
|
('ufsecp_silent_payment_scan', 'silent_payment_scan', 'ct', 0),
|
|
('ufsecp_silent_payment_verify_label', 'silent_payment_verify_label', 'fast', 0),
|
|
# ECIES
|
|
('ufsecp_ecies_encrypt', 'ecies_encrypt(pubkey, msg)', 'ct', 0),
|
|
('ufsecp_ecies_decrypt', 'ecies_decrypt(sk, ciphertext)', 'ct', 0),
|
|
# Ethereum
|
|
('ufsecp_keccak256', 'keccak256(data)', 'fast', 0),
|
|
('ufsecp_eth_sign', 'ct::ecdsa_sign(keccak(msg), sk) + v', 'ct', 0),
|
|
('ufsecp_eth_recover', 'ecrecover(keccak(msg), sig, v)', 'fast', 0),
|
|
('ufsecp_eth_address', 'keccak256(pubkey)[12:]', 'fast', 0),
|
|
('ufsecp_eth_eip55_checksum', 'eip55_checksum(addr)', 'fast', 0),
|
|
('ufsecp_eth_typed_data_hash', 'eip712_hash(domain, msg)', 'fast', 0),
|
|
]
|
|
for abi_fn, internal, layer, line in routing:
|
|
cur.execute("""INSERT OR IGNORE INTO abi_routing
|
|
(abi_function, internal_call, layer, impl_line)
|
|
VALUES (?,?,?,?)""",
|
|
(abi_fn, internal, layer, line if line else None))
|
|
return len(routing)
|
|
|
|
def populate_binding_languages(cur: sqlite3.Cursor):
|
|
"""Insert binding language metadata."""
|
|
bindings = [
|
|
('C', 'bindings/c_api', 5, 'stable', 'ufsecp', 'direct'),
|
|
('Python', 'bindings/python', 7, 'active', 'ufsecp', 'cffi'),
|
|
('Rust', 'bindings/rust', 13, 'active', 'ufsecp-sys', 'FFI'),
|
|
('Swift', 'bindings/swift', 8, 'active', 'UltrafastSecp256k1', 'C interop'),
|
|
('Go', 'bindings/go', 5, 'active', 'ufsecp', 'cgo'),
|
|
('Java', 'bindings/java', 12, 'supported', 'com.ultrafast.secp256k1', 'JNI'),
|
|
('C#', 'bindings/csharp', 9, 'supported', 'UltrafastSecp256k1', 'P/Invoke'),
|
|
('Node.js', 'bindings/nodejs', 7, 'supported', '@ultrafast/secp256k1', 'N-API'),
|
|
('Dart', 'bindings/dart', 9, 'supported', 'ufsecp', 'dart:ffi'),
|
|
('PHP', 'bindings/php', 6, 'community', 'ufsecp', 'FFI'),
|
|
('Ruby', 'bindings/ruby', 6, 'community', 'ufsecp', 'FFI'),
|
|
('React Native', 'bindings/react-native', 15, 'experimental', '@ultrafast/react-native-secp256k1', 'JSI'),
|
|
('WASM', 'wasm', 0, 'experimental', 'ufsecp-wasm', 'Emscripten'),
|
|
]
|
|
for lang, directory, fc, status, pkg, ffi in bindings:
|
|
cur.execute("""INSERT OR IGNORE INTO binding_languages
|
|
(language, directory, file_count, status, package_name, ffi_method)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
(lang, directory, fc, status, pkg, ffi))
|
|
return len(bindings)
|
|
|
|
def populate_macros(cur: sqlite3.Cursor):
|
|
"""Insert known compile-time macros and defines."""
|
|
macros = [
|
|
# Platform guards
|
|
('SECP256K1_FAST_52BIT', '1', 'cpu/include/secp256k1/point.hpp', 10, 'platform_guard'),
|
|
('SECP256K1_PLATFORM_X86_64', None, 'cpu/include/secp256k1/config.hpp', 0, 'platform_guard'),
|
|
('__SIZEOF_INT128__', None, 'cpu/src/field.cpp', 0, 'platform_guard'),
|
|
# Size constants
|
|
('UFSECP_PRIVKEY_LEN', '32', 'include/ufsecp/ufsecp.h', 45, 'size_constant'),
|
|
('UFSECP_PUBKEY_COMPRESSED_LEN', '33', 'include/ufsecp/ufsecp.h', 46, 'size_constant'),
|
|
('UFSECP_PUBKEY_UNCOMPRESSED_LEN', '65', 'include/ufsecp/ufsecp.h', 47, 'size_constant'),
|
|
('UFSECP_PUBKEY_XONLY_LEN', '32', 'include/ufsecp/ufsecp.h', 48, 'size_constant'),
|
|
('UFSECP_SIG_COMPACT_LEN', '64', 'include/ufsecp/ufsecp.h', 49, 'size_constant'),
|
|
('UFSECP_SIG_DER_MAX_LEN', '72', 'include/ufsecp/ufsecp.h', 50, 'size_constant'),
|
|
('UFSECP_HASH_LEN', '32', 'include/ufsecp/ufsecp.h', 51, 'size_constant'),
|
|
('UFSECP_HASH160_LEN', '20', 'include/ufsecp/ufsecp.h', 52, 'size_constant'),
|
|
('UFSECP_SHARED_SECRET_LEN', '32', 'include/ufsecp/ufsecp.h', 53, 'size_constant'),
|
|
('UFSECP_BIP32_SERIALIZED_LEN', '78', 'include/ufsecp/ufsecp.h', 54, 'size_constant'),
|
|
('UFSECP_NET_MAINNET', '0', 'include/ufsecp/ufsecp.h', 57, 'size_constant'),
|
|
('UFSECP_NET_TESTNET', '1', 'include/ufsecp/ufsecp.h', 58, 'size_constant'),
|
|
# Feature flags
|
|
('SECP256K1_CT_VALGRIND', '1', 'cpu/include/secp256k1/ct/ops.hpp', 0, 'feature_flag'),
|
|
('SECP256K1_BUILD_ETHEREUM', None, 'CMakeLists.txt', 0, 'feature_flag'),
|
|
('UFSECP_BITCOIN_STRICT', None, 'CMakeLists.txt', 0, 'feature_flag'),
|
|
('SECP256K1_USE_ASM', None, 'CMakeLists.txt', 0, 'feature_flag'),
|
|
# CT markers
|
|
('SECP256K1_CLASSIFY', 'VALGRIND_MAKE_MEM_UNDEFINED', 'cpu/include/secp256k1/ct/ops.hpp', 49, 'ct_marker'),
|
|
('SECP256K1_DECLASSIFY', 'VALGRIND_MAKE_MEM_DEFINED', 'cpu/include/secp256k1/ct/ops.hpp', 50, 'ct_marker'),
|
|
# Debug
|
|
('SCALED', 'ternary(normal,reduced)', 'cpu/include/secp256k1/sanitizer_scale.hpp', 37, 'debug'),
|
|
('SECP256K1_INIT', 'init_macro', 'cpu/include/secp256k1/init.hpp', 42, 'debug'),
|
|
('SECP256K1_INIT_VERBOSE', 'verbose_init_macro', 'cpu/include/secp256k1/init.hpp', 45, 'debug'),
|
|
]
|
|
for name, val, fp, ln, cat in macros:
|
|
cur.execute("""INSERT OR IGNORE INTO macros
|
|
(name, value, file_path, line_no, category)
|
|
VALUES (?,?,?,?,?)""",
|
|
(name, val, fp, ln, cat))
|
|
return len(macros)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# FILE SUMMARIES -- one-line descriptions for token-efficient agent context
|
|
# ---------------------------------------------------------------------------
|
|
def populate_file_summaries(cur):
|
|
"""Hardcoded one-line descriptions for core source files."""
|
|
summaries = [
|
|
# ---- cpu/src core implementations ----
|
|
('cpu/src/field.cpp', 'Fast-layer 4-limb/5-limb field element arithmetic (mul, sqr, inv, add, reduce) mod secp256k1 prime p'),
|
|
('cpu/src/field_52.cpp', 'FE52 5x52-bit field representation: mul/sqr via 128-bit intermediates'),
|
|
('cpu/src/field_26.cpp', 'FE26 10x26-bit field representation for platforms without __int128'),
|
|
('cpu/src/field_asm.cpp', 'x86-64 inline assembly field_mul/field_sqr using mulx/adcx/adox'),
|
|
('cpu/src/field_asm_arm64.cpp', 'ARM64 NEON inline assembly for field arithmetic'),
|
|
('cpu/src/field_asm_riscv64.cpp', 'RISC-V 64-bit inline assembly for field multiplication'),
|
|
('cpu/src/field_asm_x64.asm', 'Standalone x86-64 NASM assembly for field_mul_asm/field_sqr_asm'),
|
|
('cpu/src/scalar.cpp', 'Scalar arithmetic mod group order n: mul, inv (modinv64), add, negate, split_lambda (GLV)'),
|
|
('cpu/src/point.cpp', 'Jacobian point operations: add, double, mul (GLV+comb), to_affine, batch_normalize'),
|
|
('cpu/src/ecdsa.cpp', 'Fast-layer ECDSA: sign (RFC-6979 nonce), verify (Shamir multi-scalar), DER/compact parse'),
|
|
('cpu/src/schnorr.cpp', 'Fast-layer BIP-340 Schnorr: keypair_create, sign, verify, batch_verify'),
|
|
('cpu/src/precompute.cpp', 'Comb table precomputation for k*G: build/serialize 16K-entry generator table'),
|
|
('cpu/src/ecmult_gen_comb.cpp', 'Comb-based ecmult_gen: fixed-base scalar multiplication using precomputed table'),
|
|
('cpu/src/glv.cpp', 'GLV endomorphism: lambda/beta constants, scalar decomposition, w-NAF recoding'),
|
|
('cpu/src/selftest.cpp', 'Runtime self-test suite (21 modules): field, scalar, point, ECDSA, Schnorr, BIP-32/39'),
|
|
('cpu/src/hash_accel.cpp', 'Hardware-accelerated SHA-256: SHA-NI (x86), SHA2 (ARM), Zba (RISC-V) dispatch'),
|
|
('cpu/src/recovery.cpp', 'ECDSA public key recovery from signature + recovery id (ecrecover)'),
|
|
('cpu/src/adaptor.cpp', 'Adaptor signatures: pre-sign, adapt, extract for atomic swaps'),
|
|
('cpu/src/musig2.cpp', 'MuSig2 multi-signature protocol: key aggregation, nonce gen, partial sign, combine'),
|
|
('cpu/src/frost.cpp', 'FROST threshold signatures: key generation, signing shares, aggregation'),
|
|
('cpu/src/taproot.cpp', 'BIP-341 Taproot: tweak_pubkey, check_output, script path spending'),
|
|
('cpu/src/bip32.cpp', 'BIP-32 HD key derivation: master_key_generate, ckd_priv, ckd_pub, path parsing'),
|
|
('cpu/src/bip39.cpp', 'BIP-39 mnemonic: entropy_to_mnemonic, mnemonic_to_seed, wordlist validation'),
|
|
('cpu/src/ecdh.cpp', 'ECDH key exchange: shared_secret = SHA-256(x-only of k*P)'),
|
|
('cpu/src/ecies.cpp', 'ECIES encryption/decryption: ephemeral ECDH + AES-256-GCM'),
|
|
('cpu/src/pedersen.cpp', 'Pedersen commitments: commit(v,r) = v*H + r*G, verify, add/sub homomorphic'),
|
|
('cpu/src/multiscalar.cpp', 'Multi-scalar multiplication: Strauss/Shamir for a*G + b*P and vectorized sums'),
|
|
('cpu/src/pippenger.cpp', 'Pippenger bucket method: multi-scalar multiplication for large input vectors'),
|
|
('cpu/src/zk.cpp', 'Zero-knowledge proofs: range proofs, Schnorr DLEQ, Pedersen opening proofs'),
|
|
('cpu/src/address.cpp', 'Address generation: P2PKH, P2SH, Bech32/Bech32m encoding, RIPEMD-160'),
|
|
('cpu/src/wallet.cpp', 'High-level wallet: derive path, sign tx, serialize privkey/pubkey'),
|
|
('cpu/src/coin_address.cpp', 'Multi-coin address derivation with coin-specific parameters'),
|
|
('cpu/src/coin_hd.cpp', 'Multi-coin HD key derivation with slip44 coin types'),
|
|
('cpu/src/message_signing.cpp', 'Bitcoin message signing/verification (BIP-137 format)'),
|
|
('cpu/src/eth_signing.cpp', 'Ethereum transaction signing: EIP-155 chain ID, v/r/s encoding'),
|
|
('cpu/src/ethereum.cpp', 'Ethereum address derivation: Keccak-256 of uncompressed pubkey'),
|
|
('cpu/src/keccak256.cpp', 'Keccak-256 hash implementation for Ethereum compatibility'),
|
|
('cpu/src/batch_add_affine.cpp', 'Batch affine addition: Montgomery batch inversion for parallel point adds'),
|
|
('cpu/src/batch_verify.cpp', 'Batch ECDSA/Schnorr verification: random-linear-combination method'),
|
|
# ---- CT (constant-time) layer ----
|
|
('cpu/src/ct_field.cpp', 'CT field arithmetic: constant-time mul/sqr/inv with value_barrier, no branching'),
|
|
('cpu/src/ct_scalar.cpp', 'CT scalar arithmetic: constant-time inverse (SafeGCD), negate, cmov operations'),
|
|
('cpu/src/ct_point.cpp', 'CT point operations: constant-time mul, add, double with uniform memory access'),
|
|
('cpu/src/ct_sign.cpp', 'CT ECDSA+Schnorr signing: constant-time nonce gen, signing, secure_erase cleanup'),
|
|
# ---- Headers (key subset) ----
|
|
('cpu/include/secp256k1/field.hpp', 'FieldElement class: 4x64 limb storage, normalize, is_zero, to_bytes, from_bytes'),
|
|
('cpu/include/secp256k1/scalar.hpp', 'Scalar class: mod-n arithmetic, parse/serialize, is_zero, negate, inverse'),
|
|
('cpu/include/secp256k1/point.hpp', 'Point/JacobianPoint classes: affine/jacobian coords, infinity, compress/decompress'),
|
|
('cpu/include/secp256k1/ecdsa.hpp', 'ECDSA public API: sign, verify, parse/serialize DER and compact signatures'),
|
|
('cpu/include/secp256k1/schnorr.hpp', 'BIP-340 Schnorr API: keypair, sign, verify, batch_verify declarations'),
|
|
('cpu/include/secp256k1/precompute.hpp', 'Comb precomputation table layout, serialize/deserialize declarations'),
|
|
('cpu/include/secp256k1/ct/ops.hpp', 'CT operations: ct_select, ct_equal, value_barrier, CLASSIFY/DECLASSIFY macros'),
|
|
('cpu/include/secp256k1/ct/sign.hpp', 'CT signing API: ct_ecdsa_sign, ct_schnorr_sign, ct_nonce_function declarations'),
|
|
('cpu/include/secp256k1/ct/field.hpp', 'CT FieldElement class: same API as fast::FieldElement, constant-time guarantees'),
|
|
('cpu/include/secp256k1/ct/scalar.hpp', 'CT Scalar class: constant-time inverse, negate, cmov declarations'),
|
|
('cpu/include/secp256k1/ct/point.hpp', 'CT Point class: constant-time scalar multiplication declarations'),
|
|
('cpu/include/secp256k1/ct_utils.hpp', 'CT utility functions: secure_erase, ct_memcmp, ct_cswap, timing annotations'),
|
|
('cpu/include/secp256k1/glv.hpp', 'GLV decomposition header: split_scalar, beta constant, window width config'),
|
|
('cpu/include/secp256k1/sha256.hpp', 'SHA-256 class: init, update, finalize, HMAC-SHA256, double-SHA256'),
|
|
('cpu/include/secp256k1/sha512.hpp', 'SHA-512 class: init, update, finalize, HMAC-SHA512 for PBKDF2'),
|
|
('cpu/include/secp256k1/hash_accel.hpp', 'Hash acceleration dispatch: SHA-NI, ARM SHA2, software fallback detection'),
|
|
('cpu/include/secp256k1/bip32.hpp', 'BIP-32 HD derivation API: ExtendedKey struct, ckd_priv, ckd_pub, from_seed'),
|
|
('cpu/include/secp256k1/bip39.hpp', 'BIP-39 mnemonic API: generate, validate, to_seed declarations'),
|
|
('cpu/include/secp256k1/musig2.hpp', 'MuSig2 API: key aggregation, nonce commitment, partial sign, aggregate'),
|
|
('cpu/include/secp256k1/frost.hpp', 'FROST threshold API: keygen, sign_share, aggregate, verify_share'),
|
|
('cpu/include/secp256k1/taproot.hpp', 'Taproot API: tweak_pubkey, check_output, control_block verification'),
|
|
('cpu/include/secp256k1/recovery.hpp', 'ECDSA recovery API: recover_pubkey from signature + recid'),
|
|
('cpu/include/secp256k1/pedersen.hpp', 'Pedersen commitment API: commit, verify, add, sub declarations'),
|
|
('cpu/include/secp256k1/zk.hpp', 'ZK proof API: range_proof, dleq_prove, dleq_verify, opening_proof'),
|
|
('cpu/include/secp256k1/ecdh.hpp', 'ECDH API: shared_secret computation declaration'),
|
|
('cpu/include/secp256k1/ecies.hpp', 'ECIES API: encrypt, decrypt with ephemeral ECDH + AES-GCM'),
|
|
('cpu/include/secp256k1/config.hpp', 'Compile-time platform detection: __int128, ASM, SIMD, cache sizes'),
|
|
('cpu/include/secp256k1/context.hpp', 'Library context: init, destroy, precomp table pointer, allocator config'),
|
|
('cpu/include/secp256k1/field_52.hpp', 'FE52 representation header: 5x52 limb layout, mul/sqr/inv declarations'),
|
|
('cpu/include/secp256k1/field_26.hpp', 'FE26 representation header: 10x26 limb layout for 32-bit platforms'),
|
|
# ---- C ABI ----
|
|
('include/ufsecp/ufsecp.h', 'Stable C ABI header: all ufsecp_* function declarations, opaque context, error codes'),
|
|
('include/ufsecp/ufsecp_impl.cpp', 'C ABI implementation: routes 108 ufsecp_* functions to fast/CT internal calls'),
|
|
('include/ufsecp/ufsecp_error.h', 'Error code enum: ufsecp_error_t with 11 error codes and string conversion'),
|
|
('include/ufsecp/ufsecp_version.h', 'Library version macros: UFSECP_VERSION_MAJOR/MINOR/PATCH, version string'),
|
|
]
|
|
for path, summary in summaries:
|
|
cur.execute("INSERT OR IGNORE INTO file_summaries (path, summary) VALUES (?,?)",
|
|
(path, summary))
|
|
return len(summaries)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# FUNCTION INDEX -- exact line ranges for token-efficient file reading
|
|
# ---------------------------------------------------------------------------
|
|
def populate_function_index(cur):
|
|
"""Scan source files for function/method definitions with line ranges."""
|
|
import re
|
|
|
|
count = 0
|
|
# Patterns for C/C++ function definitions (not declarations)
|
|
# Match: ReturnType [Class::]func_name( ... ) [const] [noexcept] {
|
|
fn_pattern = re.compile(
|
|
r'^(?:static\s+)?(?:inline\s+)?(?:constexpr\s+)?'
|
|
r'(?:[\w:*&<>, ]+?)\s+' # return type
|
|
r'((?:\w+::)*(\w+))\s*\(', # [Class::]function_name(
|
|
re.MULTILINE
|
|
)
|
|
|
|
# Files to scan (core .cpp files where agents most often need line ranges)
|
|
scan_paths = [
|
|
'cpu/src/field.cpp', 'cpu/src/scalar.cpp', 'cpu/src/point.cpp',
|
|
'cpu/src/ecdsa.cpp', 'cpu/src/schnorr.cpp', 'cpu/src/precompute.cpp',
|
|
'cpu/src/ct_field.cpp', 'cpu/src/ct_scalar.cpp', 'cpu/src/ct_point.cpp',
|
|
'cpu/src/ct_sign.cpp', 'cpu/src/glv.cpp', 'cpu/src/selftest.cpp',
|
|
'cpu/src/hash_accel.cpp', 'cpu/src/recovery.cpp', 'cpu/src/musig2.cpp',
|
|
'cpu/src/frost.cpp', 'cpu/src/taproot.cpp', 'cpu/src/bip32.cpp',
|
|
'cpu/src/bip39.cpp', 'cpu/src/ecdh.cpp', 'cpu/src/ecies.cpp',
|
|
'cpu/src/pedersen.cpp', 'cpu/src/batch_add_affine.cpp',
|
|
'cpu/src/batch_verify.cpp', 'cpu/src/zk.cpp', 'cpu/src/adaptor.cpp',
|
|
'cpu/src/address.cpp', 'cpu/src/wallet.cpp',
|
|
'cpu/src/multiscalar.cpp', 'cpu/src/pippenger.cpp',
|
|
'cpu/src/ecmult_gen_comb.cpp',
|
|
'cpu/src/eth_signing.cpp', 'cpu/src/ethereum.cpp',
|
|
'cpu/src/keccak256.cpp', 'cpu/src/message_signing.cpp',
|
|
'include/ufsecp/ufsecp_impl.cpp',
|
|
]
|
|
|
|
for rel_path in scan_paths:
|
|
full_path = LIB_ROOT / rel_path
|
|
if not full_path.exists():
|
|
continue
|
|
|
|
try:
|
|
lines = full_path.read_text(encoding='utf-8', errors='replace').split('\n')
|
|
except Exception:
|
|
continue
|
|
|
|
# Find function definitions by looking for lines that:
|
|
# 1) Start a function (has a return type and opening paren)
|
|
# 2) Are followed eventually by an opening brace at appropriate nesting
|
|
func_starts = []
|
|
for i, line in enumerate(lines):
|
|
stripped = line.strip()
|
|
# Skip comments, preprocessor, blank lines
|
|
if not stripped or stripped.startswith('//') or stripped.startswith('#') or stripped.startswith('/*'):
|
|
continue
|
|
# Skip lines that are clearly not function defs
|
|
if stripped.startswith('return') or stripped.startswith('if') or stripped.startswith('for'):
|
|
continue
|
|
if stripped.startswith('else') or stripped.startswith('while') or stripped.startswith('switch'):
|
|
continue
|
|
if stripped.startswith('case') or stripped.startswith('using') or stripped.startswith('typedef'):
|
|
continue
|
|
|
|
m = fn_pattern.match(stripped)
|
|
if m:
|
|
qualified = m.group(1) # e.g., FieldElement::mul or ecdsa_sign
|
|
func_name = m.group(2) # e.g., mul or ecdsa_sign
|
|
|
|
# Skip common false positives
|
|
if func_name in ('if', 'for', 'while', 'switch', 'return', 'sizeof',
|
|
'static_assert', 'alignas', 'alignof', 'throw',
|
|
'catch', 'try', 'delete', 'new'):
|
|
continue
|
|
|
|
# Determine class name
|
|
class_name = None
|
|
if '::' in qualified:
|
|
class_name = qualified.rsplit('::', 1)[0]
|
|
|
|
# Determine kind
|
|
kind = 'method' if class_name else 'function'
|
|
|
|
func_starts.append((i, func_name, class_name, kind))
|
|
|
|
# Compute end lines: each function ends at the line before the next function starts,
|
|
# or at EOF for the last function. Then refine by finding the matching closing brace.
|
|
for idx, (start_line, func_name, class_name, kind) in enumerate(func_starts):
|
|
# End boundary: next function start or EOF
|
|
if idx + 1 < len(func_starts):
|
|
boundary = func_starts[idx + 1][0]
|
|
else:
|
|
boundary = len(lines)
|
|
|
|
# Find the actual end by tracking brace depth
|
|
depth = 0
|
|
found_open = False
|
|
end_line = boundary - 1
|
|
for j in range(start_line, boundary):
|
|
for ch in lines[j]:
|
|
if ch == '{':
|
|
depth += 1
|
|
found_open = True
|
|
elif ch == '}':
|
|
depth -= 1
|
|
if found_open and depth == 0:
|
|
end_line = j
|
|
break
|
|
if found_open and depth == 0:
|
|
break
|
|
|
|
cur.execute("""INSERT OR IGNORE INTO function_index
|
|
(file_path, name, start_line, end_line, kind, class_name)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
(rel_path, func_name, start_line + 1, end_line + 1, kind, class_name))
|
|
count += 1
|
|
|
|
return count
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PHASE 4 BUILDERS
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def populate_call_edges(cur: sqlite3.Cursor):
|
|
"""Build function-level call graph by scanning .cpp source files."""
|
|
# Load all known function names -> canonical file mappings
|
|
known: dict = {}
|
|
cur.execute("SELECT file_path, name FROM function_index")
|
|
for fpath, name in cur.fetchall():
|
|
if name not in known:
|
|
known[name] = fpath
|
|
cur.execute("SELECT name FROM c_abi_functions")
|
|
for (name,) in cur.fetchall():
|
|
if name not in known:
|
|
known[name] = 'include/ufsecp/ufsecp_impl.cpp'
|
|
|
|
# Build file -> sorted list of (start_line, end_line, func_name)
|
|
ranges: dict = {}
|
|
cur.execute("SELECT file_path, name, start_line, end_line FROM function_index ORDER BY file_path, start_line")
|
|
for fpath, name, sl, el in cur.fetchall():
|
|
ranges.setdefault(fpath, []).append((sl, el, name))
|
|
|
|
SKIP_WORDS = frozenset([
|
|
'if', 'for', 'while', 'switch', 'return', 'sizeof', 'static_assert',
|
|
'alignas', 'alignof', 'throw', 'catch', 'try', 'delete', 'new',
|
|
'else', 'case', 'do', 'goto', 'break', 'continue', 'decltype',
|
|
'static_cast', 'reinterpret_cast', 'const_cast', 'dynamic_cast',
|
|
'assert', 'ASSERT', 'CHECK', 'memset', 'memcpy', 'memmove', 'memcmp',
|
|
'printf', 'fprintf', 'puts', 'fopen', 'fclose', 'fread', 'strlen',
|
|
'std', 'secp256k1', 'ct', 'fast', 'detail', 'hash', 'coins',
|
|
])
|
|
|
|
call_re = re.compile(r'\b([a-zA-Z_]\w*)\s*\(')
|
|
count = 0
|
|
|
|
for rel_path, func_list in sorted(ranges.items()):
|
|
full = LIB_ROOT / rel_path
|
|
if not full.exists():
|
|
continue
|
|
try:
|
|
lines = full.read_text(encoding='utf-8', errors='replace').split('\n')
|
|
except Exception:
|
|
continue
|
|
|
|
for lineno, line in enumerate(lines, 1):
|
|
s = line.strip()
|
|
if not s or s.startswith('//') or s.startswith('#') or \
|
|
s.startswith('/*') or s.startswith('*'):
|
|
continue
|
|
|
|
# Find which function this line belongs to
|
|
caller = None
|
|
for sl, el, fname in func_list:
|
|
if sl <= lineno <= el:
|
|
caller = fname
|
|
break
|
|
if not caller:
|
|
continue
|
|
|
|
for m in call_re.finditer(line):
|
|
callee = m.group(1)
|
|
if callee in SKIP_WORDS or callee == caller:
|
|
continue
|
|
if callee not in known:
|
|
continue
|
|
callee_file = known[callee]
|
|
try:
|
|
cur.execute("""INSERT OR IGNORE INTO call_edges
|
|
(caller_file, caller_func, callee_func, callee_file, call_line)
|
|
VALUES (?,?,?,?,?)""",
|
|
(rel_path, caller, callee, callee_file, lineno))
|
|
count += 1
|
|
except Exception:
|
|
pass
|
|
|
|
return count
|
|
|
|
|
|
def populate_config_bindings(cur: sqlite3.Cursor):
|
|
"""Map CMake options and project constants to the code symbols that implement them."""
|
|
count = 0
|
|
|
|
# 1. Scan CMakeLists.txt for option() declarations
|
|
cmake_files = [LIB_ROOT / 'CMakeLists.txt']
|
|
opt_re = re.compile(r'option\s*\(\s*(\w+)\s+"([^"]*)"')
|
|
for cmake_f in cmake_files:
|
|
if not cmake_f.exists():
|
|
continue
|
|
text = cmake_f.read_text(errors='replace')
|
|
for m in opt_re.finditer(text):
|
|
opt_name, opt_desc = m.group(1), m.group(2)
|
|
cur.execute("""INSERT OR IGNORE INTO config_bindings
|
|
(config_file, config_key, code_symbol, code_file, binding_type, description)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
('CMakeLists.txt', opt_name, opt_name, 'CMakeLists.txt',
|
|
'cmake_option', opt_desc))
|
|
count += 1
|
|
# Find which source files reference this option symbol
|
|
for root, dirs, files in os.walk(LIB_ROOT / 'cpu'):
|
|
dirs[:] = [d for d in dirs if not should_skip_dir(d)]
|
|
for fname in files:
|
|
if os.path.splitext(fname)[1].lower() not in ('.cpp', '.hpp', '.h'):
|
|
continue
|
|
fpath = Path(root) / fname
|
|
try:
|
|
if opt_name in fpath.read_text(errors='replace'):
|
|
rel = str(fpath.relative_to(LIB_ROOT))
|
|
cur.execute("""INSERT OR IGNORE INTO config_bindings
|
|
(config_file, config_key, code_symbol, code_file, binding_type, description)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
('CMakeLists.txt', opt_name, opt_name, rel,
|
|
'build_flag', f'Referenced in {rel}'))
|
|
count += 1
|
|
except Exception:
|
|
pass
|
|
|
|
# 2. Insert project constants as config bindings
|
|
cur.execute("SELECT name, value, category, context FROM constants")
|
|
for cname, cval, ccat, cctx in cur.fetchall():
|
|
cur.execute("""INSERT OR IGNORE INTO config_bindings
|
|
(config_file, config_key, code_symbol, code_file, binding_type, description)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
('constants', cname, cname, None, 'project_constant', cctx or ''))
|
|
count += 1
|
|
|
|
# 3. Map error codes as config bindings (value -> symbol)
|
|
cur.execute("SELECT code, symbol, meaning FROM error_codes")
|
|
for code, symbol, meaning in cur.fetchall():
|
|
cur.execute("""INSERT OR IGNORE INTO config_bindings
|
|
(config_file, config_key, code_symbol, code_file, binding_type, description)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
('error_codes', str(code), symbol, 'include/ufsecp/ufsecp_error.h',
|
|
'error_code', meaning or ''))
|
|
count += 1
|
|
|
|
return count
|
|
|
|
|
|
def populate_symbol_aliases(cur: sqlite3.Cursor):
|
|
"""Find similar symbol names (variant/typo detection) using string similarity."""
|
|
from difflib import SequenceMatcher
|
|
from collections import defaultdict
|
|
|
|
# Collect all function names from function_index + c_abi_functions
|
|
names: set = set()
|
|
cur.execute("SELECT DISTINCT name FROM function_index WHERE kind IN ('function','method')")
|
|
for (n,) in cur.fetchall():
|
|
if len(n) > 4: # skip very short identifiers
|
|
names.add(n)
|
|
cur.execute("SELECT name FROM c_abi_functions")
|
|
for (n,) in cur.fetchall():
|
|
names.add(n)
|
|
|
|
# Group by leading prefix (first 2 underscore segments) for scalability
|
|
groups: dict = defaultdict(list)
|
|
for n in sorted(names):
|
|
parts = n.split('_')
|
|
key = '_'.join(parts[:2]) if len(parts) >= 2 else parts[0]
|
|
groups[key].append(n)
|
|
|
|
count = 0
|
|
for key, group in groups.items():
|
|
if len(group) < 2:
|
|
continue
|
|
for i in range(len(group)):
|
|
for j in range(i + 1, len(group)):
|
|
a, b = group[i], group[j]
|
|
ratio = SequenceMatcher(None, a, b).ratio()
|
|
if 0.72 <= ratio < 1.0:
|
|
kind = 'typo' if ratio >= 0.90 else 'variant' if ratio >= 0.80 else 'abbreviation'
|
|
# canonical = shorter name (assumed more primitive)
|
|
canonical, alias = (a, b) if len(a) <= len(b) else (b, a)
|
|
try:
|
|
cur.execute("""INSERT OR IGNORE INTO symbol_aliases
|
|
(canonical, alias, similarity, kind)
|
|
VALUES (?,?,?,?)""",
|
|
(canonical, alias, round(ratio, 3), kind))
|
|
count += 1
|
|
except Exception:
|
|
pass
|
|
|
|
return count
|
|
|
|
|
|
def populate_hotspot_scores(cur: sqlite3.Cursor):
|
|
"""Compute per-file hotspot risk scores from existing graph data."""
|
|
cur.execute("""SELECT path, lines, layer, subsystem FROM source_files
|
|
WHERE category IN ('cpu_core', 'abi', 'audit')
|
|
AND file_type IN ('cpp', 'source')
|
|
AND lines > 0 ORDER BY path""")
|
|
files = cur.fetchall()
|
|
|
|
count = 0
|
|
for fpath, lines, layer, subsystem in files:
|
|
# Coupling: fan-in (rdeps) + fan-out (deps)
|
|
cur.execute("SELECT COUNT(*) FROM include_deps WHERE source_file=?", (fpath,))
|
|
fan_out = cur.fetchone()[0]
|
|
fname_only = os.path.basename(fpath)
|
|
cur.execute("SELECT COUNT(*) FROM include_deps WHERE included_file LIKE ?",
|
|
(f'%{fname_only}%',))
|
|
fan_in = cur.fetchone()[0]
|
|
coupling = (fan_in + fan_out) / 10.0
|
|
|
|
# Security density: patterns per 100 lines
|
|
cur.execute("SELECT COUNT(*) FROM security_patterns WHERE source_file=?", (fpath,))
|
|
sec_count = cur.fetchone()[0]
|
|
security_density = (sec_count / lines) * 100.0
|
|
|
|
# Test coverage gap: 0 = covered, 1 = not covered
|
|
cur.execute("""SELECT COUNT(*) FROM edges
|
|
WHERE dst_type='source_file' AND dst_id=? AND relation='covers'""",
|
|
(fpath,))
|
|
test_gap = 0.0 if cur.fetchone()[0] > 0 else 1.0
|
|
|
|
# Pointer/null risk: scan source for risky patterns
|
|
null_risk = 0.0
|
|
full_path = LIB_ROOT / fpath
|
|
if full_path.exists():
|
|
try:
|
|
text = full_path.read_text(errors='replace')
|
|
null_count = len(re.findall(r'\*\s*\w+\s*(?:==|!=)\s*nullptr', text))
|
|
null_count += len(re.findall(r'reinterpret_cast<', text))
|
|
null_count += len(re.findall(r'\bfree\s*\(', text))
|
|
null_risk = min(null_count / 10.0, 1.0)
|
|
except Exception:
|
|
pass
|
|
|
|
# CT layer is more critical
|
|
ct_mult = 1.5 if layer == 'ct' else 1.0
|
|
score = ct_mult * (coupling * 0.25 + security_density * 4.0 +
|
|
test_gap * 3.0 + null_risk * 0.5)
|
|
score = min(score, 10.0)
|
|
|
|
reasons = []
|
|
if fan_in + fan_out > 20:
|
|
reasons.append(f'high_coupling:{fan_in}in+{fan_out}out')
|
|
if sec_count > 5:
|
|
reasons.append(f'security_critical:{sec_count}_patterns')
|
|
if test_gap > 0:
|
|
reasons.append('no_test_coverage')
|
|
if null_risk > 0.3:
|
|
reasons.append('pointer_risk')
|
|
if layer == 'ct':
|
|
reasons.append('ct_layer')
|
|
|
|
cur.execute("""INSERT OR REPLACE INTO hotspot_scores
|
|
(file_path, coupling_score, security_density, null_risk_score,
|
|
test_coverage_gap, hotspot_score, reasons)
|
|
VALUES (?,?,?,?,?,?,?)""",
|
|
(fpath, round(coupling, 3), round(security_density, 3),
|
|
round(null_risk, 3), test_gap, round(score, 3),
|
|
json.dumps(reasons)))
|
|
count += 1
|
|
|
|
return count
|
|
|
|
|
|
def populate_reachability(cur: sqlite3.Cursor):
|
|
"""Mark functions as reachable/dead using BFS from ABI entry points."""
|
|
# Seed: all public ABI entry points
|
|
reachable: set = set()
|
|
cur.execute("SELECT name FROM c_abi_functions")
|
|
for (name,) in cur.fetchall():
|
|
reachable.add(name)
|
|
|
|
# Also seed from test executables (they independently exercise code)
|
|
cur.execute("SELECT name, executable FROM test_targets")
|
|
for name, exe in cur.fetchall():
|
|
reachable.add(name)
|
|
if exe:
|
|
reachable.add(exe)
|
|
|
|
# BFS through call_edges
|
|
queue = list(reachable)
|
|
visited = set(reachable)
|
|
reach_via: dict = {}
|
|
|
|
while queue:
|
|
caller = queue.pop(0)
|
|
cur.execute("SELECT callee_func FROM call_edges WHERE caller_func=?", (caller,))
|
|
for (callee,) in cur.fetchall():
|
|
if callee not in visited:
|
|
visited.add(callee)
|
|
reachable.add(callee)
|
|
reach_via[callee] = caller
|
|
queue.append(callee)
|
|
|
|
# Insert reachability for all indexed functions
|
|
cur.execute("SELECT file_path, name FROM function_index")
|
|
all_funcs = cur.fetchall()
|
|
|
|
count = 0
|
|
for fpath, name in all_funcs:
|
|
is_r = 1 if name in reachable else 0
|
|
via = reach_via.get(name)
|
|
dead_reason = None if is_r else 'no_caller_in_call_graph'
|
|
try:
|
|
cur.execute("""INSERT OR IGNORE INTO reachability
|
|
(symbol, file_path, is_reachable, reach_via, dead_reason)
|
|
VALUES (?,?,?,?,?)""",
|
|
(name, fpath, is_r, via, dead_reason))
|
|
count += 1
|
|
except Exception:
|
|
pass
|
|
|
|
return count
|
|
|
|
|
|
def populate_runtime_entrypoints(cur: sqlite3.Cursor):
|
|
"""Find main() entrypoints and config-loading patterns in app source files."""
|
|
count = 0
|
|
app_dirs = ['apps', 'cpu/tests', 'cpu/bench', 'audit', 'examples']
|
|
main_re = re.compile(r'\bint\s+main\s*\(')
|
|
config_re = re.compile(r'"config\.json"|"config_path"|"database"|fopen\s*\(')
|
|
|
|
for app_dir in app_dirs:
|
|
dirpath = LIB_ROOT / app_dir
|
|
if not dirpath.exists():
|
|
continue
|
|
for root, dirs, files in os.walk(dirpath):
|
|
dirs[:] = [d for d in dirs if not should_skip_dir(d)]
|
|
for fname in sorted(files):
|
|
if os.path.splitext(fname)[1].lower() not in ('.cpp', '.c', '.cu', '.mm'):
|
|
continue
|
|
fpath = Path(root) / fname
|
|
rel = str(fpath.relative_to(LIB_ROOT))
|
|
binary = os.path.splitext(fname)[0]
|
|
try:
|
|
lines = fpath.read_text(errors='replace').split('\n')
|
|
except Exception:
|
|
continue
|
|
for i, line in enumerate(lines, 1):
|
|
if main_re.search(line):
|
|
cur.execute("""INSERT OR IGNORE INTO runtime_entrypoints
|
|
(binary, entrypoint_func, loads_file, load_mechanism, source_file, line_no)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
(binary, 'main', None, 'compiled-in', rel, i))
|
|
count += 1
|
|
elif config_re.search(line):
|
|
loads = 'config.json' if 'config.json' in line else None
|
|
mech = 'fopen' if 'fopen' in line else 'string_ref'
|
|
cur.execute("""INSERT OR IGNORE INTO runtime_entrypoints
|
|
(binary, entrypoint_func, loads_file, load_mechanism, source_file, line_no)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
(binary, 'config_load', loads, mech, rel, i))
|
|
count += 1
|
|
|
|
# ufsecp_ctx_create always runs selftest on startup
|
|
cur.execute("""INSERT OR IGNORE INTO runtime_entrypoints
|
|
(binary, entrypoint_func, loads_file, load_mechanism, source_file, line_no)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
('libufsecp', 'ufsecp_ctx_create', None, 'compiled-in',
|
|
'include/ufsecp/ufsecp_impl.cpp', 223))
|
|
count += 1
|
|
|
|
return count
|
|
|
|
|
|
def populate_function_test_map(cur: sqlite3.Cursor):
|
|
"""Map functions to test targets at function level (derived from test-covers-file edges)."""
|
|
count = 0
|
|
|
|
# Derive from existing test -> source_file edges
|
|
cur.execute("""SELECT src_id, dst_id FROM edges
|
|
WHERE src_type='test_target' AND dst_type='source_file'
|
|
AND relation='covers'""")
|
|
pairs = cur.fetchall()
|
|
|
|
for test_name, source_file in pairs:
|
|
cur.execute("SELECT name FROM function_index WHERE file_path=?", (source_file,))
|
|
for (func_name,) in cur.fetchall():
|
|
try:
|
|
cur.execute("""INSERT OR IGNORE INTO function_test_map
|
|
(function_name, function_file, test_target, coverage_type)
|
|
VALUES (?,?,?,?)""",
|
|
(func_name, source_file, test_name, 'indirect'))
|
|
count += 1
|
|
except Exception:
|
|
pass
|
|
|
|
# Additional KAT linkage: test -> abi_routing -> impl file -> functions
|
|
cur.execute("""SELECT name, category FROM test_targets
|
|
WHERE category IN ('cpu_core', 'audit_always')""")
|
|
for test_name, cat in cur.fetchall():
|
|
parts = test_name.split('_')
|
|
pattern = f'%{parts[0]}%' if parts else '%'
|
|
cur.execute("SELECT abi_function FROM abi_routing WHERE abi_function LIKE ?",
|
|
(pattern,))
|
|
for (abi_fn,) in cur.fetchall():
|
|
impl = cur.execute("""SELECT dst_id FROM edges WHERE src_id=?
|
|
AND relation='implements'""", (abi_fn,)).fetchone()
|
|
if not impl:
|
|
continue
|
|
impl_file = impl[0]
|
|
cur.execute("SELECT name FROM function_index WHERE file_path=?", (impl_file,))
|
|
for (func_name,) in cur.fetchall():
|
|
try:
|
|
cur.execute("""INSERT OR IGNORE INTO function_test_map
|
|
(function_name, function_file, test_target, coverage_type)
|
|
VALUES (?,?,?,?)""",
|
|
(func_name, impl_file, test_name, 'kat'))
|
|
count += 1
|
|
except Exception:
|
|
pass
|
|
|
|
return count
|
|
|
|
|
|
def populate_symbol_reasoning(cur: sqlite3.Cursor):
|
|
"""Populate symbol-level semantic, security, performance, audit, history, and score layers."""
|
|
for table in (
|
|
'symbol_semantics',
|
|
'symbol_security',
|
|
'symbol_performance',
|
|
'symbol_audit_coverage',
|
|
'symbol_history',
|
|
'symbol_scores',
|
|
):
|
|
cur.execute(f"DELETE FROM {table}")
|
|
|
|
history_cache = {}
|
|
count = 0
|
|
|
|
rows = cur.execute("""
|
|
SELECT DISTINCT file_path, name
|
|
FROM function_index
|
|
ORDER BY file_path, name
|
|
""").fetchall()
|
|
|
|
for file_path, symbol_name in rows:
|
|
semantics = derive_symbol_semantics(symbol_name, file_path)
|
|
security = derive_symbol_security(symbol_name, file_path, semantics)
|
|
performance = derive_symbol_performance(symbol_name, file_path, semantics)
|
|
history = get_file_history(file_path, history_cache)
|
|
|
|
test_rows = cur.execute("""
|
|
SELECT DISTINCT test_target, coverage_type
|
|
FROM function_test_map
|
|
WHERE function_name=? AND function_file=?
|
|
""", (symbol_name, file_path)).fetchall()
|
|
test_names = [r[0] for r in test_rows]
|
|
test_blob = ' '.join(test_names).lower()
|
|
coverage = {
|
|
'covered_by_unit_test': 1 if test_names else 0,
|
|
'covered_by_fuzz': 1 if any('fuzz' in t for t in test_names) else 0,
|
|
'covered_by_invalid_vectors': 1 if any(x in test_blob for x in ('wycheproof', 'vectors', 'strict', 'bip340', 'frost_kat', 'bip327')) else 0,
|
|
'covered_by_ct_test': 1 if any(x in test_blob for x in ('ct_', 'ct-', 'ctsidechannel', 'ct_sidechannel', 'ct_equivalence', 'ct_verif')) else 0,
|
|
'covered_by_cross_impl_diff': 1 if any(x in test_blob for x in ('differential', 'cross_', 'fiat_crypto', 'equivalence')) else 0,
|
|
'covered_by_gpu_equivalence': 1 if any(x in test_blob for x in ('gpu_', 'opencl_', 'metal_')) else 0,
|
|
'covered_by_regression_test': 1 if any(x in test_blob for x in ('regression', 'fault_injection', 'adversarial')) else 0,
|
|
'last_audit_result': 'covered' if test_names else 'uncovered',
|
|
'times_failed_historically': history['bug_fix_count'],
|
|
'known_fragile': 1 if any(x in test_blob for x in ('fault_injection', 'regression', 'adversarial')) or history['bug_fix_count'] >= 3 else 0,
|
|
}
|
|
|
|
low_audit = 1 if (coverage['covered_by_unit_test'] + coverage['covered_by_fuzz'] + coverage['covered_by_ct_test']) <= 1 else 0
|
|
risk_reasons = []
|
|
gain_reasons = []
|
|
risk = 0.0
|
|
gain = 0.0
|
|
|
|
if security['must_be_constant_time']:
|
|
risk += 5.0
|
|
risk_reasons.append('ct_sensitive')
|
|
if security['invalid_input_sensitive']:
|
|
risk += 4.0
|
|
risk_reasons.append('invalid_input_sensitive')
|
|
if low_audit:
|
|
risk += 3.0
|
|
risk_reasons.append('low_audit_coverage')
|
|
if history['recently_modified']:
|
|
risk += 2.0
|
|
risk_reasons.append('recently_modified')
|
|
if coverage['times_failed_historically']:
|
|
risk += min(12.0, coverage['times_failed_historically'] * 1.5)
|
|
risk_reasons.append('historical_failures')
|
|
|
|
gain += performance['hotness_score'] * 0.4
|
|
if performance['batchable']:
|
|
gain += 12.0
|
|
gain_reasons.append('batchable')
|
|
if performance['gpu_candidate']:
|
|
gain += 18.0
|
|
gain_reasons.append('gpu_candidate')
|
|
if performance['duplicated_backends']:
|
|
gain += 8.0
|
|
gain_reasons.append('duplicated_backends')
|
|
if performance['compute_bound']:
|
|
gain += 6.0
|
|
gain_reasons.append('compute_bound')
|
|
|
|
optimization_priority = max(0.0, min(100.0, gain - (risk * 0.35)))
|
|
|
|
cur.execute("""
|
|
INSERT INTO symbol_semantics
|
|
(symbol_name, file_path, category, math_core, backend, coordinate_model,
|
|
secret_class, abi_surface, generator_path, varpoint_path, bip340_related, bip352_related)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
|
""", (
|
|
symbol_name, file_path, semantics['category'], semantics['math_core'], semantics['backend'],
|
|
semantics['coordinate_model'], semantics['secret_class'], semantics['abi_surface'],
|
|
semantics['generator_path'], semantics['varpoint_path'], semantics['bip340_related'],
|
|
semantics['bip352_related'],
|
|
))
|
|
|
|
cur.execute("""
|
|
INSERT INTO symbol_security
|
|
(symbol_name, file_path, uses_secret_input, must_be_constant_time, public_data_only,
|
|
device_secret_upload, requires_zeroization, invalid_input_sensitive, notes)
|
|
VALUES (?,?,?,?,?,?,?,?,?)
|
|
""", (
|
|
symbol_name, file_path, security['uses_secret_input'], security['must_be_constant_time'],
|
|
security['public_data_only'], security['device_secret_upload'], security['requires_zeroization'],
|
|
security['invalid_input_sensitive'], security['notes'],
|
|
))
|
|
|
|
cur.execute("""
|
|
INSERT INTO symbol_performance
|
|
(symbol_name, file_path, hotness_score, estimated_cost, batchable, vectorizable,
|
|
gpu_candidate, memory_bound, compute_bound, duplicated_backends)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?)
|
|
""", (
|
|
symbol_name, file_path, performance['hotness_score'], performance['estimated_cost'],
|
|
performance['batchable'], performance['vectorizable'], performance['gpu_candidate'],
|
|
performance['memory_bound'], performance['compute_bound'], performance['duplicated_backends'],
|
|
))
|
|
|
|
cur.execute("""
|
|
INSERT INTO symbol_audit_coverage
|
|
(symbol_name, file_path, covered_by_unit_test, covered_by_fuzz, covered_by_invalid_vectors,
|
|
covered_by_ct_test, covered_by_cross_impl_diff, covered_by_gpu_equivalence,
|
|
covered_by_regression_test, last_audit_result, times_failed_historically, known_fragile)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
|
""", (
|
|
symbol_name, file_path, coverage['covered_by_unit_test'], coverage['covered_by_fuzz'],
|
|
coverage['covered_by_invalid_vectors'], coverage['covered_by_ct_test'],
|
|
coverage['covered_by_cross_impl_diff'], coverage['covered_by_gpu_equivalence'],
|
|
coverage['covered_by_regression_test'], coverage['last_audit_result'],
|
|
coverage['times_failed_historically'], coverage['known_fragile'],
|
|
))
|
|
|
|
cur.execute("""
|
|
INSERT INTO symbol_history
|
|
(symbol_name, file_path, times_modified, recently_modified, bug_fix_count,
|
|
performance_tuning_count, audit_related_changes, last_modified)
|
|
VALUES (?,?,?,?,?,?,?,?)
|
|
""", (
|
|
symbol_name, file_path, history['times_modified'], history['recently_modified'],
|
|
history['bug_fix_count'], history['performance_tuning_count'],
|
|
history['audit_related_changes'], history['last_modified'],
|
|
))
|
|
|
|
cur.execute("""
|
|
INSERT INTO symbol_scores
|
|
(symbol_name, file_path, risk_score, gain_score, optimization_priority, risk_reasons, gain_reasons)
|
|
VALUES (?,?,?,?,?,?,?)
|
|
""", (
|
|
symbol_name, file_path, round(min(100.0, risk * 5.0), 2), round(min(100.0, gain), 2),
|
|
round(optimization_priority, 2), json.dumps(risk_reasons), json.dumps(gain_reasons),
|
|
))
|
|
count += 1
|
|
|
|
return count
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# MAIN
|
|
# ---------------------------------------------------------------------------
|
|
def build_graph(rebuild=False):
|
|
if rebuild and DB_PATH.exists():
|
|
DB_PATH.unlink()
|
|
|
|
conn = sqlite3.connect(str(DB_PATH))
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
conn.execute("PRAGMA synchronous=NORMAL")
|
|
cur = conn.cursor()
|
|
|
|
# Create schema
|
|
cur.executescript(SCHEMA_SQL)
|
|
|
|
# Populate
|
|
now = datetime.now(timezone.utc).isoformat()
|
|
cur.execute("INSERT OR REPLACE INTO meta VALUES ('built_at', ?)", (now,))
|
|
cur.execute("INSERT OR REPLACE INTO meta VALUES ('lib_root', ?)", (str(LIB_ROOT),))
|
|
cur.execute("INSERT OR REPLACE INTO meta VALUES ('version', '4.29.0')", ())
|
|
cur.execute("INSERT OR REPLACE INTO meta VALUES ('schema_version', '4')", ())
|
|
|
|
stats = {}
|
|
stats['source_files'] = populate_source_files(cur)
|
|
stats['namespaces'] = populate_namespaces(cur)
|
|
stats['cpp_types'] = populate_cpp_types(cur)
|
|
stats['c_abi_functions'] = populate_abi_functions(cur)
|
|
stats['include_deps'] = populate_include_deps(cur)
|
|
stats['test_targets'] = populate_test_targets(cur)
|
|
stats['ci_workflows'] = populate_ci_workflows(cur)
|
|
stats['audit_modules'] = populate_audit_modules(cur)
|
|
stats['error_codes'] = populate_error_codes(cur)
|
|
stats['constants'] = populate_constants(cur)
|
|
stats['gpu_backends'] = populate_gpu_backends(cur)
|
|
stats['build_configs'] = populate_build_configs(cur)
|
|
stats['platform_dispatch'] = populate_platform_dispatch(cur)
|
|
stats['docs'] = populate_docs(cur)
|
|
stats['semantic_tags'] = populate_semantic_tags(cur)
|
|
stats['cpp_methods'] = populate_cpp_methods(cur)
|
|
stats['security_patterns'] = populate_security_patterns(cur)
|
|
stats['abi_routing'] = populate_abi_routing(cur)
|
|
stats['binding_languages'] = populate_binding_languages(cur)
|
|
stats['macros'] = populate_macros(cur)
|
|
stats['file_summaries'] = populate_file_summaries(cur)
|
|
stats['function_index'] = populate_function_index(cur)
|
|
stats['edges'] = populate_edges(cur)
|
|
# Phase 4: new intelligence layers
|
|
stats['call_edges'] = populate_call_edges(cur)
|
|
stats['config_bindings'] = populate_config_bindings(cur)
|
|
stats['symbol_aliases'] = populate_symbol_aliases(cur)
|
|
stats['hotspot_scores'] = populate_hotspot_scores(cur)
|
|
stats['reachability'] = populate_reachability(cur)
|
|
stats['runtime_entrypoints'] = populate_runtime_entrypoints(cur)
|
|
stats['function_test_map'] = populate_function_test_map(cur)
|
|
stats['symbol_reasoning'] = populate_symbol_reasoning(cur)
|
|
|
|
cur.execute("INSERT OR REPLACE INTO meta VALUES ('stats', ?)", (json.dumps(stats),))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return stats
|
|
|
|
if __name__ == '__main__':
|
|
rebuild = '--rebuild' in sys.argv
|
|
print(f"Building project graph: {DB_PATH}")
|
|
stats = build_graph(rebuild=rebuild)
|
|
total = sum(stats.values())
|
|
print(f"\nPopulated {total} records across {len(stats)} tables:")
|
|
for table, count in sorted(stats.items()):
|
|
print(f" {table:25s} {count:5d}")
|
|
print(f"\nDatabase: {DB_PATH} ({DB_PATH.stat().st_size / 1024:.0f} KB)")
|