SamiAhmed7777 9a50ab3b2e Expand script.cpp test coverage: libFuzzer harness + EvalScript stress tests + UBSan fix (#27)
* simd: fix UBSan signed-shift UB in fft64 INNER macro

The INNER macro at src/simd.c:379 combines the low and high halves of
two FFT values with a multiplier:

    ((u32)((l) * (mm)) & 0xFFFFU) + ((u32)((h) * (mm)) << 16)

When (h)*(mm) is a negative s32, the (u32) cast recovers the bit
pattern (large positive number), but then << 16 operates on the
integer-promoted value (typically int on x86_64). UBSan flags this as
signed-shift of negative.

Fix: explicit (u32) cast inside the shift expression forces the shift
operand to unsigned (well-defined per C++20). Outer (u32) cast keeps
the result type consistent. Bit-equivalent at runtime; type-safe for
UBSan.

Mirrors the pattern Krystie applied in b9d06d5 for the same issue in
FFT8/FFT16 macros. Could not reproduce the trip in a standalone
100-trial test (Hash9's specific call pattern from CBlock::GetHash
may not be reproducible in isolation), but the macro is the same UB
class as already-fixed sites — fix by inspection per the
triangles-test-suite-audit skill.

Build verified: ninja test_triangles clean, all 234 test cases +
21720 assertions pass under sanitizers. Tests exercise Hash9 via
TestingSetup, so the FFT path is covered.

* test: add libFuzzer harness + EvalScript stress tests + CI fuzz job

Three pieces, one goal: expand coverage of script.cpp (the
consensus-critical opcode interpreter) beyond what Boost unit tests
catch.

1. libFuzzer harness (src/test/fuzz/)

   Builds against the existing daemon object files (init.cpp.o,
   wallet.cpp.o, noui.cpp.o) so we get the full CWallet vtable
   without writing 100+ lines of fragile method stubs. Link line
   reuses the sanitizer-friendly flags from test-linux-sanitizers.
   Corpus seeded from src/test/data/script_{valid,invalid}.json
   (1055 real Triangles scripts).

   BUILD_FUZZ is OFF by default — gcc default build doesn't have
   libFuzzer, so the flag gates the custom clang++ build cleanly.

2. Stress tests (src/test/script_stress_tests.cpp)

   Six regression-guard tests for EvalScript's hard limits. Anyone
   who removes a bound will get a test failure:
   - deep_dup_stack_hits_opcount_limit   — 250 OP_DUP rejected <1s
   - max_keys_multisig_20_of_20          — 20-of-20 terminates <2s
   - multisig_rejects_21_keys            — nKeysCount > 20 rejected
   - pushdata_over_520_rejected          — MAX_SCRIPT_ELEMENT_SIZE
   - script_size_over_10000_rejected     — MAX_SCRIPT_SIZE
   - disabled_opcodes_rejected           — all 15 disabled opcodes

   EvalScript contract caveat (captured in the test comments): on
   false return, the stack is left dirty — inputs pushed before
   rejection are still there. Tests assert stack.size() <= N for
   N = number of inputs pushed, not the post-opcode expectation.

3. CI fuzz job (.github/workflows/build-all.yml)

   test-fuzz-smoke job, sibling of test-linux-sanitizers. Reuses
   the same runner + apt-get + RocksDB-from-source + Tor-from-source
   steps so CI runtime doesn't double. Builds with clang-15,
   ASan+UBSan+libFuzzer, seeds corpus, runs 5 minutes, fails only
   on crash artifact (not on find_new_units=0 — libFuzzer always
   writes .tmp churn during normal operation).

Verified:
- ninja test_triangles clean
- 234/234 test cases + 21720/21720 assertions pass
- 4/4 ctest suites pass (triangles_unit + chaindb_equivalence +
  snapshotnet + chaindb_runtime)
- Local fuzz run: 8354 corpus files, ~5400 exec/sec, zero crashes
  after several hours (-jobs=2 -workers=2)

* build: explicit <cassert> in allocators.h

clang's stricter include resolution surfaces the missing include even
though gcc tolerates it via some other transitive path. Without this,
PR #27 build with clang fails on assert() in LockedPageManager.

* fix(ci,fuzz): unbreak test-fuzz-smoke workflow + CMake fuzz link deps

Three bugs in PR #27's fuzz smoke integration that CI caught on first run:

1. CMAKE_EXE_LINKER_FLAGS pulled in '-fsanitize=fuzzer $SAN_FLAGS'. CMake's
   compiler-probe linker test (used to verify the toolchain) doesn't define
   LLVMFuzzerTestOneInput, so adding -fsanitize=fuzzer pulls in
   libclang_rt.fuzzer's main() and trips 'multiple definition of `main`'.
   Remove -fsanitize=fuzzer from global flags; fuzz_script already adds it
   per-target via FUZZ_COMMON_FLAGS in src/CMakeLists.txt.

2. Workflow said --target script_fuzz / ./build-fuzz/bin/script_fuzz, but
   the CMake target is fuzz_script (add_custom_target(fuzz_script ...)).
   CI failed with 'unknown target'. Fixed in workflow + comment.

3. fuzz_script link references static libs at ${CMAKE_BINARY_DIR}/lib/
   (libhash9_crypto.a, libleveldb_lib.a, libleveldb_memenv.a, libsecp256k1.a)
   but didn't declare them as DEPENDS. First clean build races the link
   step and fails with 'no such file or directory'. Added the four static
   library targets to DEPENDS so ninja builds them first.

Verified locally: cmake configure clean, fuzz_script link succeeds,
binary runs (./bin/fuzz_script prints libFuzzer banner and reads corpus).

Tested with the same flag set CI uses (SAN_FLAGS with fuzzer-no-link,
BUILD_FUZZ=ON, clang-14).

* fix(ci,fuzz): make test-fuzz-smoke work end-to-end on clean builds

Three more bugs in PR #27's fuzz integration, caught on local repro
after commit 3239425 fixed the easy ones:

1. clang vs gcc warning mismatch (cmake/AddCompilerFlags.cmake):
   gcc treats -Wreserved-user-defined-literal as a warning. clang-15+
   in C++20 mode promotes it to an error and trips on hundreds of
   Bitcoin-derived sites like strprintf("%"PRId64...) in util.cpp /
   kernel.cpp. Conditional -Wno-reserved-user-defined-literal scoped
   to clang only — gcc builds keep the original diagnostic.

2. secp256k1 ASM strictness (.github/workflows/build-all.yml):
   Add -DSECP256K1_ASM=OFF to the fuzz configure. Clang-15+'s
   register allocator is sometimes stricter than clang-14 about the
   x86_64 inline asm in scalar_4x64_impl.h and fails with 'inline
   assembly requires more registers than available'. The fuzz target
   only needs ECC at the C-fallback level — slower but correct.

3. Empty .o glob at link time (src/CMakeLists.txt):
   The fuzz link line referenced CMakeFiles/triangles_common.dir/*.o
   and CMakeFiles/trianglesd.dir/*.o via file(GLOB), which evaluates
   at cmake CONFIGURE time. On a fresh build dir, no .o files exist
   yet → the link line was always empty → undefined references for
   CKey::GetPubKey, typeinfo for CKeyStore, etc.

   Replace the GLOB with a generated bash wrapper script
   (fuzz_objs/link.sh) that does the find at link time, using bash
   arrays to safely handle paths with spaces. The script is invoked
   via ninja with the original link line as its argv; it prepends
   the discovered .o files (excluding script.cpp.o — we have our
   own clang-instrumented copy in fuzz_objs/) and exec's clang++.

Verified locally:
  - cmake configures cleanly under clang-18 with the same flag set CI uses
  - ninja fuzz_script links end-to-end (132 MB ELF, debug info, all
    sanitizer coverage instrumentation intact)
  - ./bin/fuzz_script runs and discovers coverage: 'INITED cov: 3
    ft: 3 corp: 1/1b' from libFuzzer banner
  - 4/4 ctest suites still pass on the gcc build (master chaindb /
    snapshotnet / unit / equivalence)

This should make test-fuzz-smoke pass on the next CI run.

* fix(ci,fuzz): stabilize fuzz smoke job

* fix(ci,fuzz): portable fuzz build, exclude daemon main, stub globals

- Build daemon (init/wallet/noui) as an OBJECT library under BUILD_FUZZ
  so the fuzz job does not pay the daemon executable link cost.
- Exclude init.cpp.o from the fuzz link wrapper (it defines the daemon's
  main(), conflicting with libFuzzer's own).
- Replace hardcoded libboost/librocksdb filenames with -l flags and
  Boost target paths resolved at configure time.
- Add -lubsan to the fuzz link line so libstdc++'s ubsan hooks resolve.
- Add a generated fuzz_stubs.cpp that defines pwalletMain, uiInterface,
  CheckpointsMode, nNodeLifespan, etc. — every global that init.cpp
  used to provide.
- Keep the CI workflow's libtor build step (fuzz links libtor.a).

Local verify: clang-15 + clang-18 + gcc all build fuzz_script; 15s
fuzz run completes 1913 execs with no crash artifacts; gcc test build
passes 4/4 ctest suites unchanged.

* fix(ci,fuzz): drop libi2pd*.a paths from fuzz link line

The fuzz job in build-all.yml runs libtor.sh but NOT libi2pd.sh, so
src/i2p/i2pd-src/libi2pd{,client,lang}.a don't exist on the CI runner.
The previous commit (720d711) hardcoded them into the fuzz link line;
the link failed with
  clang: error: no such file or directory: '.../i2p/i2pd-src/libi2pd*.a'

i2p_embedded.cpp is already compiled into triangles_common, and with
USE_I2P_EMBEDDED=OFF (the CI default) the only i2p surface is the
no-op stub in triangles_common. So the .a references were both wrong
AND redundant.

Keep libtor.a: src/tor/build-libtor.sh IS run in the fuzz job, so the
file exists when the link wrapper invokes clang++.

Verified locally with clang-15 against the same cmake flags the
workflow uses: clean link, 12s fuzz run did 1239 executions, no crash
artifacts, libFuzzer reporting normal coverage growth.

* fix(ci,fuzz): install libgflags-dev for fuzz smoke job

CI failure on PR #27 test-fuzz-smoke: link step aborted with
`/usr/bin/ld: cannot find -lgflags`. The fuzz link line in
src/CMakeLists.txt references -lgflags (transitive dep of RocksDB),
and CI's ubuntu-22.04 runner does NOT ship libgflags-dev.

DNS2 ships libgflags-dev as an automatic dep of build-essential,
which is why local dry-runs didn't catch this.

Verified locally on DNS2:
- Cloned the exact CI cmake invocation (build-fuzz-verify dir)
- cmake -B + cmake --build --target fuzz_script: clean build
- 30s fuzz pass: 1058 inputs, 1935 features covered, no crashes

The libtor build step also depends on gflags transitively; making
it explicit in the apt-get list future-proofs both paths.

* fix(ci,fuzz): link -lrocksdb unconditionally in fuzz target

CI's test-fuzz-smoke job was failing with a torrent of
`undefined reference to rocksdb::Status::ToString[abi:cxx11]()`
errors after the libgflags fix landed. The fuzz target's RocksDB
link arg was:

    $<IF:$<TARGET_EXISTS:RocksDB::rocksdb>,-lrocksdb,${ROCKSDB_LIBRARY}>

That generator expression was wrong on CI's exact code path:

  1. CMake's `find_package(RocksDB CONFIG)` does NOT find the .cmake
     config RocksDB 8.9.1 ships — only the .pc file.
  2. `pkg_check_modules(RocksDB IMPORTED_TARGET)` therefore exposes
     `PkgConfig::RocksDB` (NOT `RocksDB::rocksdb`), so
     $<TARGET_EXISTS:RocksDB::rocksdb> is FALSE.
  3. The fallback ${ROCKSDB_LIBRARY} is set ONLY inside the manual
     `find_library()` probe at top-level CMakeLists.txt:170-190, which
     is skipped when EITHER `RocksDB::rocksdb` or `PkgConfig::RocksDB`
     already exists.

Result on CI: an empty string landed in the link line, so the link
step saw no `-lrocksdb` arg and every RocksDB symbol the fuzz
binary referenced became undefined.

Fix: just use a bare `-lrocksdb` and let the library search path
do the work. `build-rocksdb.sh` installs to /usr/local/lib (CI);
`librocksdb-dev` (apt) installs to /usr/lib/x86_64-linux-gnu (DNS2).
Both paths are in the default search path.

Verified locally on DNS2 with the exact CI cmake invocation + flags:
- build-fuzz-verify2: clean build, exit 0
- 20s fuzz pass: 1548 inputs, 2024 features covered, no crashes

Co-located with the libgflags fix on feat/script-fuzz-and-stress-tests
because both fixes are required for test-fuzz-smoke to turn green.

---------

Co-authored-by: Sami Ahmed <sami@sami-ahmed.net>
2026-07-11 16:02:35 -07:00
2026-07-07 21:10:41 -07:00
2026-07-08 17:57:44 -07:00
2015-07-22 23:00:36 +02:00
2026-07-08 17:57:44 -07:00
2014-10-05 23:37:30 +02:00
2014-10-05 23:37:30 +02:00

Cryptographic Triangles (TRI)

Triangles is a privacy-focused cryptocurrency featuring Proof-of-Stake consensus, Tor v3 onion routing, and built-in encrypted messaging. Originally launched in July 2014, the chain was revived in March 2026 after being frozen since December 2022.

Key Features

  • Proof-of-Stake - Energy-efficient block production with 33% annual staking rewards (coin-age based)
  • Hash9 Algorithm - Unique 13-step hash cascade (Fugue, Hamsi, Groestl, Blake, BMW, Skein, Keccak, Shavite, JH, Luffa, Cubehash, Echo, SIMD)
  • Encrypted Messaging - Send and receive encrypted messages directly through the wallet
  • Tor v3 Integration - Connect and transact over the Tor network with v3 onion hidden services
  • 120-second Block Time - Fast confirmations with 2-minute target spacing

Specifications

Property Value
Algorithm Hash9 (PoW blocks 0-9000), PoS from block 9001
Block Time ~120 seconds
Max Supply 2,222,222 TRI
PoS Reward 33% annual, coin-age based
P2P Port 24112
RPC Port 19112
Protocol 70205

Network Status

The Triangles network operates exclusively over Tor for privacy:

Tor v3 Seeds:

  • jbpfhe7zw3qm67wy3j2ayysp3mnrjobopthnko3b3sgahqtecblwqmid.onion:24112
  • uddaxjbo3lh2zskg7w6gwln4ty5cel7q4c5jbx7fdtv6zf2j47gdlyad.onion:24112
  • el5sirhhleecuctpeeprelzubpqmoqivvra3rzlwbjttinxa4fq3wnid.onion:24112
  • sj5dhybnlp3v4y5niyc5unrnd6s43lyx5ibup7rolyosjbi2u2hsbvyd.onion:24112
  • i3kr5meha7se4ns3wss3h7v46m6uksfzv4wrohdqxpj6n35wyo2bvlid.onion:24112

HTTP Seed List:

  • seeds.cryptographic-triangles.org/seeds.txt - Dynamically updated list of active onion peers

Building from Source

Triangles uses CMake. All platforms follow the same build pattern.

Dependencies

Dependency Minimum Version
CMake 3.16+
C++ compiler C++17 support
OpenSSL 3.x
Boost 1.90+
SQLite 3.x (default wallet database backend)
Berkeley DB 5.3 with C++ bindings (legacy wallet backend, used for migration)
libevent 2.x
RocksDB 7.4+ (default chain database backend)
LevelDB bundled (legacy chain DB backend, used for migration)

Linux (Ubuntu 24.04 / Debian 12+)

Install dependencies:

sudo apt-get install -y build-essential cmake ninja-build \
    libboost-all-dev libssl-dev libdb5.3++-dev libevent-dev \
    zlib1g-dev libminiupnpc-dev

For the Qt wallet, also install:

sudo apt-get install -y qtbase5-dev qt5-qmake libqrencode-dev

Build:

cmake -B build -G Ninja -DBUILD_QT=ON
cmake --build build

Linux (AlmaLinux 9 / RHEL 9)

Install dependencies:

sudo dnf install -y gcc-c++ cmake ninja-build boost-devel openssl-devel \
    libevent-devel zlib-devel miniupnpc-devel

BDB 5.3 C++ bindings must be built from source on RHEL-based systems (the libdb-devel package does not include C++ headers). Download BDB 5.3.28 from Oracle and build with --enable-cxx.

Then build as above.

Windows (MSYS2 MinGW64)

Open an MSYS2 MinGW64 shell and install:

pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja \
    mingw-w64-x86_64-boost mingw-w64-x86_64-openssl \
    mingw-w64-x86_64-db mingw-w64-x86_64-miniupnpc \
    mingw-w64-x86_64-qt5-base mingw-w64-x86_64-qrencode \
    mingw-w64-x86_64-libevent

Build:

cmake -B build -G Ninja -DBUILD_QT=ON
cmake --build build

Build Options

Option Default Description
BUILD_QT ON Build the Qt GUI wallet
BUILD_DAEMON ON Build the headless daemon
BUILD_TESTS OFF Build unit tests

Running

First Run

mkdir -p ~/.triangles
cat > ~/.triangles/triangles.conf << 'EOF'
port=24112
rpcport=19112
rpcuser=trianglesrpc
rpcpassword=<generate-a-strong-password>
rpcallowip=127.0.0.1
staking=1
txindex=1
listen=1
server=1
daemon=1
proxy=127.0.0.1:9050
EOF

trianglesd

The node will connect to seed nodes over Tor and sync the blockchain automatically.

Chain Database (RocksDB)

The chain database (block index, transaction index, UTXO set, address index) uses RocksDB by default. RocksDB gives faster sync and lookups than the legacy LevelDB backend through parallel compaction, bloom filters, and a larger write buffer and block cache (tunable with -dbcache=<MB>).

If you are upgrading a node that already has a LevelDB chain database (txleveldb/ in your data directory), it is migrated automatically on first launch: the chain state is copied into a new rocksdb/ directory and verified (record count, UTXO count and value, best-chain hash, and DB format must all match) before use. The original txleveldb/ directory is left untouched as a fallback and is never modified.

To select a backend explicitly:

trianglesd -chaindb=rocksdb   # default
trianglesd -chaindb=leveldb   # legacy backend (retained for fallback/migration)

Migration can also be triggered or forced manually:

trianglesd -migratechaindb        # migrate txleveldb -> rocksdb if not already done
trianglesd -migratechaindbforce   # re-migrate, replacing any existing rocksdb/

Existing Wallet Holders

If you have a wallet.dat from the original Triangles network:

  1. Place your wallet.dat in ~/.triangles/ (Linux) or %APPDATA%\triangles\ (Windows)
  2. Start the wallet - it will sync the blockchain and your balance will appear automatically
  3. No migration or special action is needed - all keys and balances are preserved

Staking

To stake, your wallet must be:

  • Running with staking=1 in the config
  • Connected to at least one peer
  • Containing coins with sufficient coin-age (mature inputs)

Check staking status:

trianglesd getstakinginfo

Trusted Snapshot Publisher (UTXO Snapshots)

The daemon verifies that any UTXO snapshot it loads was signed by a trusted publisher. Starting with v6.1.8, the trusted publisher can be rotated at runtime via RPC — no rebuild required. The compiled-in fallback (TG8f76yktTxDrT7JJymY3wVAusXiD3fVvX, Sami's legacy key) remains in effect if no runtime override is set.

# Rotate to a new publisher
trianglesd settrustedv2snapshotpublisher TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH

# Check current publisher
trianglesd gettrustedv2snapshotpublisher

# Revert to the compiled-in fallback
trianglesd unsettrustedv2snapshotpublisher

The model is single-slot: calling settrustedv2snapshotpublisher atomically drops the previous publisher. See docs/snapshot-publisher.md for the full operator guide.

Encrypted Messaging

Send and receive encrypted messages between wallet addresses:

# Enable messaging
trianglesd smsgenable

# Send a message
trianglesd smsgsend <your-address> <recipient-address> "Hello from Triangles!"

# Check inbox
trianglesd smsginbox all

# Send anonymous message
trianglesd smsgsendanon <recipient-address> "Anonymous message"

Messages are encrypted end-to-end using AES and distributed through the peer network in time-bucketed batches.

Tor Support

Triangles is designed as a Tor-only network. Install the Tor daemon and configure your proxy:

# triangles.conf
proxy=127.0.0.1:9050

To run your own hidden service, add to /etc/tor/torrc:

HiddenServiceDir /var/lib/tor/triangles/
HiddenServiceVersion 3
HiddenServicePort 24112 127.0.0.1:24112

Then set externalip=<your-onion-address> in triangles.conf.

RPC Commands

General

  • getinfo - Node status, balance, block height, connections
  • getpeerinfo - Connected peer details
  • getstakinginfo - Staking status and weight

Wallet

  • getbalance - Current balance
  • listunspent - Unspent transaction outputs
  • sendtoaddress <addr> <amount> - Send TRI
  • getnewaddress - Generate new receiving address

Messaging

  • smsgenable / smsgdisable - Toggle secure messaging
  • smsgsend <from> <to> <message> - Send encrypted message
  • smsgsendanon <to> <message> - Send anonymous message
  • smsginbox [all|unread|clear] - View received messages
  • smsgoutbox [all|clear] - View sent messages
  • smsglocalkeys - List messaging-enabled addresses
  • smsgscanchain - Scan blockchain for public keys

Trusted Snapshot Publisher (v6.1.8+)

  • settrustedv2snapshotpublisher <address> - Atomically replace the trusted snapshot publisher (previous one dropped immediately). Persists to <datadir>/snapshot-publisher.json.
  • gettrustedv2snapshotpublisher - Returns the currently active runtime publisher and whether a runtime override is in effect.
  • unsettrustedv2snapshotpublisher - Clear the runtime override and revert to the compiled-in fallback list.

See docs/snapshot-publisher.md for the full operator guide.

Chain History

  • July 16, 2014 - Genesis block
  • Block 0-9000 - Proof-of-Work mining phase (Hash9)
  • Block 9001+ - Proof-of-Stake only
  • Block 17,651 - V5 hard fork (removed Tor v2, disabled checkpoint master key)
  • December 8, 2022 - Chain frozen (all nodes offline)
  • March 11, 2026 - Chain revived, staking resumed

Project Structure

src/
  main.cpp          - Core blockchain logic, block/tx validation, message routing
  miner.cpp         - Staking miner thread
  net.cpp           - P2P networking
  init.cpp          - Daemon initialization
  wallet.cpp        - Wallet management
  smessage.cpp/h    - Encrypted messaging system
  kernel.cpp        - PoS kernel (stake validation)
  checkpoints.cpp   - Hardcoded checkpoints
  net_bootstrap.h   - DNS/IP seed configuration
  onionseed.h       - Tor v3 onion seed addresses
  tor/
    onion_v3.cpp/h  - Tor v3 hidden service management
    tor_crypto_compat.h - Ed25519/SHA3 crypto compatibility

License

Distributed under the MIT/X11 software license. See COPYING for details.

S
Description
No description provided
Readme 162 MiB
Languages
C++ 39.9%
C 25.9%
Makefile 23.3%
Shell 8.6%
CMake 0.7%
Other 1.4%