Shekyl Changelog
[Unreleased]
Changed
-
Logging output format (breaking change, all binaries). Chore #2 of the
easylogging++retirement completes the migration started in V3.1 alpha.4:shekyld,shekyl-wallet-rpc,shekyl-cli, and every other in-tree binary now emit through the same Rusttracing-subscriberstack. The default formatter istracing_subscriber::fmt::layer, and its line shape is not byte-compatible with the vendoredeasylogging++layout it replaces:# Before (easylogging++ default format string): 2026-04-19 14:23:11.042 INFO global src/daemon/main.cpp:322 Shekyl 'Codename' (v3.1.0-alpha.3-release) # After (tracing-subscriber fmt::layer default): 2026-04-19T14:23:11.042123Z INFO global: Shekyl 'Codename' (v3.1.0-alpha.3-release)Timestamps are RFC 3339 UTC (not local time with microseconds), level tokens are full words (
ERROR/WARN/INFO/DEBUG/TRACE, not theE/W/I/D/Vsingle letters), the target appears as a structuredtarget:field, and source location (file:line) is elided by default. Log-scraping tooling that parsed the prior format byte-for-byte must be updated;docs/USER_GUIDE.mdยง"Logging" documents the new shape for operators. -
MONERO_LOGSโSHEKYL_LOG(env-var rename). Every in-tree consumer ofMONERO_LOGSnow readsSHEKYL_LOGinstead. This closes the C++-side half of the per-.cursor/rules/93-legacy- symbol-migration.mdcrename โ Chore #1 (V3.1 alpha.4) already migrated the Rust binaries.SHEKYL_LOGaccepts the sametracing-subscriber-compatible directive grammar as Chore #1 (bare levels, per-target overrides, module-qualified targets) plus the legacy easylogging++ category grammar (net.p2p:DEBUG,wallet.wallet2:INFO, numeric0..=4presets,+/-modifiers) routed through the Rust-side translator. The legacy grammar is preserved on purpose: the ~1,345MINFO/MDEBUG/ etc. call sites insrc/andcontrib/ship category strings in that grammar, and operator runbooks doingSHEKYL_LOG='*:DEBUG,net.p2p:TRACE'must keep working with no downstream edits.Operator action required before upgrading past V3.x alpha.0: scripts, systemd units, Docker/Podman compose files, or launch plists that set
MONERO_LOGS=...will silently become no-ops. Add aSHEKYL_LOG=...line alongside eachMONERO_LOGS=...line before cutting over (both can coexist on pre-Chore-#2 builds so the rollover is safe). -
Log target separator normalized to
::. Targets that used to render in the easylogging++ output asnet.p2p/daemon.rpcnow appear asnet::p2p/daemon::rpcin everytracing-subscriber-rendered line. The FFI boundary (shekyl_log_emit/shekyl_log_level_enabledinrust/shekyl-logging/src/ffi.rs) rewrites dot-separated category names into Rust-idiomatic module-path form before handing the event to the dispatcher, matching the form the legacy-grammar translator emits into EnvFilter directives (net::p2p=trace). Without this, every category-scoped emit from the C++ shim (MCINFO("net.p2p", โฆ),MCLOG(level, "daemon.rpc", โฆ), โฆ) would silently fall through to the bare default clause because EnvFilter compares target strings byte-for-byte. Operator-suppliedSHEKYL_LOGdirectives continue to accept both spellings โ the legacy-grammar translator rewrites.to::on the way in, soSHEKYL_LOG='*:WARNING,net.p2p:TRACE'andSHEKYL_LOG='warn,net::p2p=trace'behave identically. Only the rendered output changes. Log-scraping pipelines that grep fortarget=net\.p2pneed to grep fortarget=net::p2p(or, per the format-break entry above,net::p2p:at the front of the fields block) instead. -
shekylddefault log sink moved to~/.shekyl/logs/. Underchore/cxx-logging-consolidation, the daemon's default--log-filepath changed from<data_dir>/shekyld.log(next to the blockchain database) to~/.shekyl/logs/shekyld.log, resolved through the Rust FFI'sshekyl_log_default_path. Testnet/stagenet/regtest runs use the suffixed base namesshekyld-testnet.log/shekyld-stagenet.log/shekyld-regtest.logso the three networks can run side-by-side without clobbering each other's log. Rotation defaults to ~100 MB ร 50 archives, and the live file plus every rotated archive are forced to POSIX mode0600on Unix โ operator-tunable permissions are not a supported knob. Operators who want to keep the legacy next-to-data-dir layout can pass--log-fileexplicitly; the override path is unchanged.
Removed
-
MONERO_LOG_FORMATenv var (no replacement). The custom format string thatMONERO_LOG_FORMATused to seed on the easylogging++ tree is no longer a tunable. Formatting is owned by the Rust subscriber's layer stack (fmt::layer, optionally stacked withtracing-subscriberfeature flags at build time), not by an operator env var. There is no V3.x alpha.0 replacement and no intent to re-add one โ if you have a log-format requirement that RFC 3339 UTC does not satisfy, file an issue rather than patching the format string. -
Vendored
external/easylogging++/tree. Deleted inded9875b6. All call sites that reachedel::Logger/el::Configurations/el::base::Writeretc. directly have been rewritten to route through theshekyl_log_emit/shekyl_log_level_enabledFFI insrc/shekyl/shekyl_log.h. Theel::namespace survives only as a thin typedef-only compatibility shim incontrib/epee/include/misc_log_ex.h(el::Level,el::Color,el::base::DispatchAction) so the existingMINFO/MDEBUG/MWARNING/MCINFOmacros expand without touching the ~1,345 call sites. Closes theSTRUCTURAL_TODO.mdยง"Replace easylogging++ with a maintained logger" item (both chores); swept narrative indocs/audit_trail/RESOLVED_260419.md. -
src/rpc/rpc_version_str.{h,cpp}and its unit test (tests/unit_tests/rpc_version_str.cpp), inherited from Monero. The daemon constructs its own version string deterministically incmake/GitVersion.cmakefrom the annotated tag on HEAD, then emitsSHEKYL_VERSION_FULLover RPC as an opaque value. The validator regex was a Monero-era sanity check that parsed that string back against a hardcoded pattern โ "protecting" consumers from a failure mode that the CMake construction logic already makes impossible.Exposed on the
v3.1.0-alpha.3tag-push CI run (#394,test-ubuntumatrix): on a tagged build,SHEKYL_VERSION_FULLresolves to3.1.0-alpha.3-release, and the regex (adapted from Monero but never taught SemVer 2.0.0 ยง9 dotted pre-release identifiers) rejects the dot in-alpha.3. Every tagged release using-alpha.N/-beta.N/-rc.Nnumbering would trip the same assertion โ so every tagged release with this file in tree is inherently broken, which is enough of a tell that the file is wrong to have on disk.Per
.cursor/rules/60-no-monero-legacy.mdc"ask why is this here?" โ this is an inherited assertion against a Shekyl-owned invariant. The invariant is enforced bycmake/GitVersion.cmake; the daemon should not re-parse its own output to re-check it.rpc_command_executor.cppkeeps the empty-string guard (if (res.version.empty())) so the CLI still reports "version not available" when the RPC response lacks a version, but no longer attempts to format-validate the string it receives.
Fixed
- Tagged-release
ci/gh-actions/clijobs ontest-ubuntumatrix. Follows from therpc_version_strremoval above.v3.1.0-alpha.3shipped with the daemon, wallet, and source archive built cleanly, but its tag-push CI ran red on this single unit test;v3.1.0-alpha.4will be the first alpha whose tag-push CI is green end-to-end.
Known regressions
MLOG_SET_THREAD_NAME(label)no longer reaches the log stream. The macro still compiles and still evaluates its argument (so-Wunused-valuestays quiet at the call sites), but the label ([SRV_MAIN]fromabstract_tcp_server2.inl,[miner N]fromminer.cpp,DLNfromdownload.cpp) does not appear in emitted events. easylogging++ used this hook to stamp a semantic label into every subsequent log line; the Rusttracing-subscriberformatter reads the OS-level thread name instead (via the platformpthread_getname_np/GetThreadDescriptionpath), and those names are not being populated in Chore #2. Restoring semantic thread labels โ either by teaching the C++ shim to callpthread_setname_np+ Windows equivalents, or by routing the label through the Rust subscriber as aspanfield โ is tracked as a V3.2 follow-up indocs/FOLLOWUPS.md. The impact is diagnostic only: thread-scoped log lines now show a generic thread ID instead of the human-readable label the prior format carried.
[3.1.0-alpha.3] - 2026-04-19
Added
- Release signing policy and maintainer keys (
docs/SIGNING.md). New document establishing that every release tag fromv3.1.0-alpha.3onward is a signed annotated tag created withgit tag -a -s. It records the initial maintainer signing key (Rick Dawson, ed25519FEFEC7EF9952D40C, ASCII-armored public key embedded in the doc so downstream verifiers can import it from the repo without trusting a keyserver lookup), and documents verification withgit verify-tag, the reproducible-build cross-check that tag verification does not subsume, procedures for adding new maintainer keys, rotation, retirement, revocation, key hygiene expectations (passphrase, offline revocation certificate, hardware token or encrypted storage, GitHub registration), and the rationale for GPG over SSH signing or Sigstore at this stage. Earlier alpha tags (v3.1.0-alpha.1,v3.1.0-alpha.2) predate this policy and are not signed; their authenticity is established by branch topology and reproducible Guix builds.
Changed
-
Branch policy mandates signed annotated release tags and non-fast-forward merges from
devtomain..cursor/rules/06-branching.mdcwas updated to require thatmainadvance only via a merge commit (git merge --no-ff dev, GitHub "Create a merge commit") with a signed annotated tag placed on the resulting merge commit. Fast-forward, rebase-and-merge, squash-and-merge, and force-push tomainare now explicitly forbidden. The rule cross-links todocs/SIGNING.mdat both the Hard rule 1 mention and the Release flow step 4 mention so a maintainer reading the policy lands on the signing doc. A new "Rationale (why merge commit, not fast-forward)" section was added to capture the reasoning so the decision is not re-litigated each cycle. -
docs/FOLLOWUPS.mdtracks Shekyl Foundation institutional signing key as V3.1.x+ item. Records the V3.1 decision: release signing uses maintainer keys, not an institutional Foundation key, until the Foundation has multi-maintainer operational structure (two or more active release maintainers). Cross-referenced fromdocs/SIGNING.mdยง"Future: Foundation institutional signing key".
Security
-
Bump
cryptographyfrom44.0.2to46.0.6intools/reference/requirements.txtto clear two Dependabot advisories indexed 2026-04-13:- GHSA-r6ph-v2qm-q3c2 (high): missing subgroup validation for SECT curves could allow a small-subgroup attack during ECDH.
- GHSA-m959-cc7f-wv43 (low): incomplete DNS name constraint enforcement on peer names.
Not exploitable against Shekyl users.
cryptographyis pulled in only bytools/reference/derive_output_secrets.py, a developer-only HKDF test-vector generator that never ships in any binary and is not on a consensus path at runtime. Inspection shows thecryptography.hazmat.primitives.{hashes,kdf.hkdf}imports in that script are unused โ all HKDF logic is hand-rolled with stdlibhmac/hashlibโ so the bump cannot change its output. Verified by regeneratingdocs/test_vectors/PQC_OUTPUT_SECRETS.jsonunder the new version in a clean venv; SHA-256 matches byte-for-byte (1159cb6de2ce3fa4af5d7a8f88eac71ed35c8f00ebf297a4d9259439b6477163). -
Accept seven
rand 0.8.5Dependabot alerts as risk-tolerated. GHSA-cq8v-f236-94qc ("Rand is unsound with a custom logger using rand::rng()") indexes against the five workspace crates that pinrand = "0.8"plus twoCargo.lockfiles. CVSS is 0 on all seven; the actual exploit requires callingrand::rng()(a 0.9+ thread-local RNG API that does not exist in 0.8) while a customlog::Logimplementation is installed. Shekyl usesrand::rngs::OsRngdirectly andrand_chacha::ChaCha20Rng::from_seedfor deterministic derivation, and the daemon installs no customlog::Log, so no Shekyl code path reaches the vulnerable code. Migrating torand = "0.9"cascades into bumpingcurve25519-dalek4 โ 5 plus several other crypto crates; per.cursor/rules/20-rust-vs-cpp-policy.mdcthat is a planning activity with its own design doc and review cycle, tracked indocs/FOLLOWUPS.mdยง"rand 0.9 migration and curve25519-dalek 5 cascade" with target V3.1.x. Alerts #3 through #9 dismissed on GitHub with reason "risk tolerated" and a link to the follow-up.
Changed
-
wallet2_ffino longer carries wallet-directory state. Removedwallet2_ffi_set_wallet_dirand thewallet_dirfield onwallet2_handle. The four wallet-file FFI entry points (wallet2_ffi_create_wallet,wallet2_ffi_open_wallet,wallet2_ffi_restore_deterministic_wallet,wallet2_ffi_generate_from_keys) now take a fullwallet_pathparameter in place of the barefilenamethat was joined withwallet_dirusing a hardcoded"/"separator. Path construction was inherited Monerowallet_rpc_serverscaffolding and produced mixed-separator paths on Windows (C:\Users\x\...\...//My Wallet.keys). Callers now join paths in Rust viaPathBuf::join, which is platform-correct on every target. The legacy C++wallet_rpc_server.cppkeeps its ownwallet_dirstate and is unaffected โ it does not go through the FFI. Theshekyl-cliWalletContextnow holds the directory and joins filenames before each call; theshekyl-wallet-rpcRust shim keepsServerConfig.wallet_dirfor the V3.2 cutover when its handlers will own wallet-file creation.validate_filenamewas narrowed and renamed tovalidate_wallet_path(empty-path check only) โ path-component validation is the caller's responsibility now that the caller also owns the directory. -
Nightly
proptest-exhaustivejob tuned and extended todev. DroppedPROPTEST_CASESfrom1_000_000to200_000โ the old value could not finish inside the 30-minute runner cap onubuntu-latest(ML-KEM-768 keygen per case dominates wall time, the run was being cancelled not failed). Raisedtimeout-minutesto180so the job has real headroom, and added a branch matrix[main, dev]with per-branch cache keys so nightly coverage tracks both active histories instead of only the default branch. Actual elapsed time is surfaced via the job's::notice::annotation so the 200k / 180m bracket can be tightened once we have real data. See.github/workflows/nightly.yml.
[3.1.0-alpha.2] - 2026-04-17
Retroactive CHANGELOG entry. The v3.1.0-alpha.2 tag was created without promoting
[Unreleased]first; the bullets below were subsequently split out from[Unreleased]during the alpha.3 release cycle. The split is based on the commit rangev3.1.0-alpha.1..v3.1.0-alpha.2; content is verbatim from the original[Unreleased]copy and has not been edited retrospectively.
Removed
- Daemonizer layer. Deleted
src/daemonizer/(POSIXfork()detach, Windows Service Control Manager registration, console-control glue) and the four thin wrapper classes insrc/daemon/(t_core,t_protocol,t_p2p,t_rpc) plus the executor shim. Background execution is now delegated to systemd (Linux), launchd (macOS), Task Scheduler (Windows), or the Tauri sidecar (GUI wallet); in-process forking and Windows service registration were untested code paths touching privilege boundaries and file-descriptor lifetimes, so their removal is a security improvement in addition to an audit-surface reduction. The removal also breaks the circular include chain wheredaemon/command_line_args.htransitively pulledwindows.hinto most of the codebase. Closes FOLLOWUPS.md ยง"windows-daemonizer-cleanup" and STRUCTURAL_TODO.md ยง"Daemonizer removal". - Daemonizer CLI flags:
--detach,--pidfile,--install-service,--uninstall-service,--start-service,--stop-service,--run-as-service. Bothshekyldandshekyl-wallet-rpcaccept these only long enough to print a migration message pointing at platform service managers (seesrc/common/removed_flags.{h,cpp}, markedTODO(v3.2)for deletion alongside theshekyl-wallet-rpcRust cutover).--non-interactiveis preserved in both binaries.
Changed
- Daemon orchestration class renamed.
daemonize::t_daemonis nowdaemonize::Daemoninshekyld, andshekyl-wallet-rpc's unrelated inline class is nowWalletRpcDaemon. The two binaries no longer share a type name, clarifying audit scope and the V3.2 Rust cutover plan. - Default data directory resolution moved to
src/common/. The admin-vs-userCSIDL_*branching formerly indaemonizernow lives incommon/daemon_default_data_dir.{h,cpp}, preserving the exact pathshekyldresolved before V3.1. Pinned by a newdaemon_default_data_dirunit test so a future refactor cannot silently point operators at an empty data directory. - MSVC CI job now builds
--target daemon walletinstead of just--target wallet, matching what the GUI wallet release workflow actually compiles. Future MSVC regressions in daemon code will be caught in shekyl-core CI rather than surfacing in the GUI wallet release after an hour of compilation.
Fixed
- Fixed probabilistic flake in
shekyl-crypto-pq::multisig_receiving::tests::scan_wrong_participant_ciphertext_fails. The view tag hint is a single byte by design (fast scanner pre-filter), so a wrong-ciphertext decapsulation had ~1/256 chance of producing a hint that collided with the published one, causing the test's rejection assertion to fail. Test now retries keypair generation (bounded to 64 attempts) until the wrong-ciphertext hint actually differs, so the rejection path is exercised deterministically. No protocol or code change; scan semantics are unchanged. - Made all
src/daemon/headers self-contained for MSVC portability:protocol.h(6 missing includes),p2p.h(2),daemon.h(2),rpc.h(2). These headers relied on include ordering from their callers, which GCC/Clang tolerated but MSVC rejects. - Fixed
#ifdefinsideMERROR()macro argument incore_rpc_server.cpp(undefined behavior, C2059 on MSVC). Replaced with literal function name. - Explicitly captured
handshakein lambda inabstract_tcp_server2.inl(C3493 on MSVC). - Explicitly captured
credits_per_hash_thresholdin lambda incore_rpc_server.cpp(C3493 on MSVC). - SFINAE-constrained
network_addresstemplate constructor innet_utils_base.hto prevent MSVC eager instantiation (C2039).
[3.1.0-alpha.1] - 2026-04-15
First public alpha release. First green CI in repository history.
This release establishes the Shekyl versioning scheme: software versions
follow SemVer independently per repo; the protocol version is a separate
integer (protocol_version = 3). See docs/VERSIONING.md for the full
scheme. The version jump from prior tags (v3.0.x-RC series) to 3.1.0
reflects the addition of FROST-style multisig to the feature set.
Highlights
-
FCMP++ end-to-end test suite passing. The full prove-sign-verify pipeline works across C++ and Rust via FFI, validated by 10-iteration randomized round-trip tests and C++ unit tests on Ubuntu 22.04/24.04, Arch Linux, macOS, and Windows.
-
Five FCMP++ integration bugs fixed. Root causes documented in
docs/FOLLOWUPS.mdaudit trail: FFI depth/layers off-by-one, branch extraction loop bound, missing point-to-scalar conversion, leaf count off-by-one, key image y-normalization breaking batch verification. Additionally, a sixth bug (FFI depth-to-layers convention ambiguity) was found and fixed during CI stabilization. -
V3.1 multisig protocol specified and implemented. FROST-style coordinator-less multisig with hybrid PQC signing, specified in
docs/PQC_MULTISIG.mdand wire format indocs/SHEKYL_MULTISIG_WIRE_FORMAT.md. 93 unit tests, 19 integration tests, 11 fuzz harnesses. -
Versioning scheme established.
docs/VERSIONING.mddefines SemVer for software versions and a separate integer protocol version.SHEKYL_PROTOCOL_VERSIONconstant added tocryptonote_config.h, exposed via--versionoutput and/get_infoRPC.
Unreleased
โจ Added
-
PQC Multisig V3.1: equal-participants protocol implementation. Full implementation of the coordinator-less multisig protocol as specified in
PQC_MULTISIG.md. Key components:MultisigKeyContainerv1.1 withspend_auth_versionfield andmultisig_group_idv1.1 (includes version byte)rotating_prover_index: cryptographic hash-based prover assignment- 8 HKDF-derived key/nonce labels for domain-separated derivation
construct_multisig_output_for_sender,scan_for_multisig_output,validate_multisig_output_i7for output lifecycleGriefingTracker: per-output cost bounding for invalid outputsshekyl1mBech32m address format with file-based handling and 3-representation fingerprintSpendIntent: 14-check validation pipeline (structural, temporal, chain state, balance)ProverOutput,SignatureShare,ProverReceipt: prover and signing flow types with equivocation detection- Honest-signer invariants I1โI7 enforcement
MultisigEnvelopewith 11 message types and AEAD encryption (ChaCha20-Poly1305 with HKDF-derived keys)- Per-intent state machine (8 states: Proposed โ Broadcast + terminal)
HeartbeatTracker: liveness, censorship, and sync anomaly detectionCounterProof: 8-rule chain evidence verification for counter recovery- C++
tx_extratags 0x08, 0x09, 0x0A for multisig metadata - FFI:
shekyl_pqc_verify_with_group_idfor defense-in-depth - Consensus: scheme_id consistency enforcement across transaction inputs
-
PQC Multisig V3.1: GUI components (shekyl-gui-wallet). 7 React components for the multisig UX:
FingerprintBadge: grouped hex fingerprint with copy and metadataProverView: per-participant prover assignment breakdownLossAcknowledgment: mandatory 1/N loss checkboxAddressProvenance: fingerprint history with change detectionRelayConfig: multi-relay management with operator diversityViolationAlert: I1โI7 violation display with auto-abortSigningDashboard: real-time intent state with sign/veto actions
-
PQC Multisig V3.1: test infrastructure.
- 93 unit tests across all V3.1 modules
- 19 integration tests (functional, adversarial, determinism)
- 4 cross-platform determinism canaries with pinned byte prefixes
- 11 fuzz harnesses (wallet-core) covering serialization, encryption, state machine, validation, and verification
- Criterion benchmarks for intent_hash, encryption, serialization, fingerprint computation, and assembly consensus
-
docs/MULTISIG_OPERATIONS.md: end-user operations guide covering group setup, receiving, spending, recovery, relay configuration, and security considerations. -
docs/AUDIT_SCOPE.md: expanded to include V3.1 multisig attack surface (KDF, prover assignment, invariants, AEAD, CounterProof, griefing defense). -
docs/SHEKYL_MULTISIG_WIRE_FORMAT.md: standalone portable wire format spec for the V3.1 multisig protocol. Covers MultisigEnvelope binary layout, SpendIntent canonical serialization, 11 message type discriminants, AEAD parameters (ChaCha20-Poly1305 with HKDF-SHA256), DecryptedPayload encoding, chain state fingerprint computation, file transport conventions, and conformance requirements. Enables third-party wallet implementations without reading the full spec. -
GroupDescriptor: canonical JSON backup file format for multisig groups. One file contains everything needed to restore a group from seeds (group_id, threshold, pubkeys, relays, fingerprint). Rust type in
shekyl-wallet-core, Tauri export/import commands, and GUI component inshekyl-gui-wallet. -
Failure-mode UX: Multisig page restructured with 6 failure-mode alert banners (unresponsive co-signer, counter divergence, relay disconnect, fingerprint change, stuck intent, CounterProof failure). All Phase 3 components (SigningDashboard, ViolationAlert, ProverView, FingerprintBadge, LossAcknowledgment, AddressProvenance, RelayConfig) wired into the Multisig page.
-
File-based transport: promoted from placeholder to first-class GUI option with Tauri file I/O commands and functional import/sign/export workflow. Equal prominence with relay transport.
-
Fee impact analysis: added to MULTISIG_OPERATIONS.md with tx size comparison, per-input/per-output overhead, Bitcoin comparison, and economic viability analysis for small transactions.
-
Address format discipline: cursor rule
65-address-format-discipline.mdccodifying thatshekyl1mis the sole multisig HRP for V3.x, with version bytes as the extension mechanism.
๐ Documentation
-
docs/MULTISIG_OPERATIONS.md: expanded from 222-line protocol reference to ~500-line comprehensive operations guide with decision framework, 3 operational playbooks, 6 failure recovery guides, threat model worksheet, and honest limitations section. -
docs/FOLLOWUPS.md: added hardware wallet constraints (ML-DSA-65 computation cost on Cortex-M, screen constraints, vendor outreach) and headless co-signer service reference implementation, both targeting V3.2. -
GUI wallet cursor rules: added
81-no-protocol-knowledge.mdc(users never see FCMP++, KEM, HKDF in the UI) and82-failure-mode-ux.mdc(every feature must enumerate failure modes before implementation, failure states get dedicated UI).
๐ Security
-
Zeroize ephemeral multisig signing seeds.
ed_seedandml_seedstack copies inconstruct_multisig_output_for_senderare now wrapped inZeroizing<[u8; 32]>, ensuring automatic zeroing on drop. Closes a theoretical side-channel surface from FOLLOWUPS.md V3.1 audit response. -
PersistedMultisigOutputDebug redaction. TheDebugderive onPersistedMultisigOutputwas replaced with a manual implementation that redactsmy_shared_secret(64-byte KEM-derived material). Prevents accidental secret exposure throughdbg!or structured logging. -
validate_balancechecked arithmetic.SpendIntent::validate_balancenow useschecked_addfor input sums, output sums, and fee addition. Previously used wrappingsum()โ crafted u64 values could wrap both sides to the same value and pass the equality check. -
HKDF derivations return
Result.derive_multisig_kem_seedandderive_participant_kem_randomnessnow returnResult<..., CryptoError>instead of panicking via.expect()on the transaction construction path. -
eprintln!removed fromshekyl_fcmp_verifyFFI. Two diagnosticeprintln!calls in the FCMP verification FFI path have been removed. The C++ caller already logs verification failures; the Rust-side stderr output was redundant and failed the CI lint.
๐ Fixed
-
FCMP++ FFI: move depth-to-layers conversion to C++ callers.
shekyl_fcmp_proveandshekyl_fcmp_verifypreviously converted LMDB depth to upstreamlayersinternally (layers = depth + 1). This created an ambiguous contract where the sametree_depthparameter meant different things in different FFI functions. Now both functions accept the upstreamlayerscount directly; C++ callers (blockchain.cpp,rctSigs.cpp) performdepth + 1before calling.shekyl_sign_fcmp_transactionstill accepts LMDB depth and converts internally (wallet callers pass LMDB depth). Added diagnostic tracing toproof::verifyforFcmpPlusPlus::readand key image decompression failures. Fixedvalidate.rsc1/c2 alternation comment (the formula was correct but had been transiently swapped during refactoring). Tests simplified to single-layer Selene root (layers=1) to match the Rust unit test convention. -
CI: fix
cargo auditfailure from RUSTSEC-2026-0098/0099. Bumpedrustls-webpki0.103.10 -> 0.103.12 andrand0.9.2 -> 0.9.4 inCargo.lock. Addedrust/audit.tomlto acknowledgerand0.8.5 (RUSTSEC-2026-0097, not applicable: Shekyl usesOsRng, notrand::rng()with a custom logger). -
Remove dead
verify_transaction_pqc_authone-arg overload. The no-argument overload intx_pqc_verify.cpphad zero callers โ the sole production caller (blockchain.cpp) uses the two-arg form withexpected_scheme_id. Replaced with a default parameter. Per15-deletion-and-debt.mdc: dead code goes. -
Fix stale
shekyl_ffi.hshekyl_pqc_verify_debugcomment. The error code documentation (0-4) did not match the RustPqcVerifyErrorenum (0-11). Updated to reflect the actualrepr(u8)discriminants. -
Reconcile FOLLOWUPS.md and STRUCTURAL_TODO.md. Marked 5 items in STRUCTURAL_TODO as resolved (code already fixed). Corrected the
expected_scheme_idFOLLOWUPS entry (parameter is actively used byblockchain.cpp, contrary to the prior note). Markedrpasswordaudit as covered by CI.
๐ Changed
-
FFI: verification functions return typed
u8error codes instead ofbool.shekyl_pqc_verify,shekyl_pqc_verify_with_group_id, andshekyl_fcmp_verifynow return 0 on success and a nonzero error discriminant on failure. PQC verify usesPqcVerifyErrorcodes 1-11; FCMP verify usesVerifyErrorcodes 1-7. Error codes are available in all build modes, eliminating the debug-only double-call pattern. C++ callers (tx_pqc_verify.cpp,blockchain.cpp) updated to log error codes unconditionally. Per30-ffi-discipline.mdc. -
Clippy lint rename:
unchecked_duration_subtractionโunchecked_time_subtraction. Updated in workspaceCargo.tomlto track the upstream rename.
๐๏ธ Removed
shekyl_pqc_verify_debugdeleted. Now that productionshekyl_pqc_verifyreturns typed error codes, the debug-only variant is redundant. All call sites and the#ifndef NDEBUGC header guard removed.
๐ Fixed (continued)
-
All Rust clippy warnings resolved in
shekyl-crypto-pq. Fixed 1 error (missing_fields_in_debuginPersistedMultisigOutput) and 13 warnings:op_ref(11 sites inkem.rs,montgomery.rs,output.rs),needless_range_loopandunnecessary_map_or(inmultisig_receiving.rs),uninlined_format_args(inoutput.rstests). Also rancargo fmtacross workspace. -
FCMP++ proof verification: five integration bugs fixed, first green CI. The FCMP++ core tests (
gen_fcmp_tx_valid,gen_fcmp_tx_double_spend,gen_fcmp_tx_reference_block_too_old,gen_fcmp_tx_reference_block_too_recent,gen_fcmp_tx_timestamp_unlock_rejected) have never passed since integration. Root causes identified and fixed:- FFI depth/layers off-by-one. LMDB stores 0-indexed
tree_depth; the upstream library expects 1-indexedlayerscount. Fix:layers = tree_depth + 1at the FFI boundary. - C++ branch extraction loop was
< depthinstead of<= depth. BothgenRctFcmpPlusPlusandassemble_tree_path_for_outputskipped the root layer's branch data. Fix:layer <= tree_depthin both. - Point-to-scalar conversion missing in witness construction.
Raw LMDB point hashes were passed as branch siblings without converting
to cycle scalars. Fix:
selene_to_helios_scalar/helios_to_selene_scalarapplied duringgenRctFcmpPlusPlusbranch assembly. compute_leaf_count_at_heightoff-by-one. Maturity comparison used<= target_height + 1while LMDB'sdrain_pending_tree_leavesuses<= current_height. Fix: removed the+ 1to match LMDB semantics.key_image_y_normalizebroke Ed25519 batch verification. The normalization (clearing byte 31 sign bit) modified the key image away from the truex * Hp(O)used by the Rust prover. Fix: deletedkey_image_y_normalizeentirely โ FCMP++ key images are not y-normalized.- PQC signing payload computed before all public keys were derived.
get_transaction_signed_payloadhashes all inputs'hybrid_public_keyvalues, but the single-loop approach signed early inputs before later keys existed. Fix: two-phase PQC signing (derive all keys, then sign all inputs). All 5 FCMP++ core tests, 4 staking tests, 28 FCMP unit tests, and 45 Rustshekyl-fcmptests now pass. This is the first green CI in the repository's history.
- FFI depth/layers off-by-one. LMDB stores 0-indexed
-
Consensus-critical: curve tree leaf ordering bug (DB v6 โ v7).
pending_tree_leavesusedMDB_DUPSORTon 128-byte leaf data, causing outputs with the same maturity height to drain into the curve tree in byte-sorted order rather thanglobal_output_indexorder. This broke the implicitglobal_output_index == tree_leaf_indexassumption that every caller ofget_curve_tree_leaf()relied on. Replaced with 16-byte composite keysBE(maturity) || BE(output_index)enforcing canonical drain order. Same restructuring applied topending_tree_drain. Added explicit bidirectional mapping tables (output_to_leaf,leaf_to_output) and ablock_pending_additionsjournal for robustpop_blockreversal. DB schema bumped to v7 (incompatible with v6 โ requires resync). -
get_curve_tree_leaf()parameter was silently misnamed. The function acceptedglobal_output_indexin its signature but actually looked up by tree position. Renamed toget_curve_tree_leaf_by_tree_position()and addedget_curve_tree_leaf_by_output_index()(double lookup via mapping table). All callers updated โ compile errors catch any missed sites. -
check_stake_claim_inputnow recomputes and verifies the stored leaf. Previously the stake claim gate only checked bounds (staked_output_index < leaf_count). Now the stored leaf is retrieved via the outputโleaf mapping and bytewise-compared to a leaf recomputed from the output's(output_key, commitment, h_pqc). This binds the claim to the actual output data in the tree.
โจ Added
-
src/blockchain_db/shekyl_types.h: Strongly-typed identifiers (TreePosition,OutputIndex,MaturityHeight,BlockHeight) and LMDB key/value encoders (PendingLeafKey,DrainKey,DrainValue,BlockPendingKey,BlockPendingValue) for curve-tree state. Designed for 1:1 translation to Rust newtypes and heedBytesEncode/BytesDecode. -
4 new regression tests in
deferred_insertion.cpp: same-maturity drain order by output_index, block_pending_additions journal round-trip, outputโleaf mapping round-trip, pop_block journal-driven reversal simulation.
๐ Protocol
-
X25519 public key derived from Ed25519 view key. The X25519 public key used in the hybrid KEM classical component is the EdwardsโMontgomery image of the Ed25519 view public key:
x25519_pub = (1 + y) / (1 - y) mod p. It is not carried in the address or generated independently. The Bech32m address PQC segments carry ML-KEM material exclusively. SeePOST_QUANTUM_CRYPTOGRAPHY.mdยงX25519 Binding to View Key. -
Unclamped Montgomery DH (not RFC 7748 X25519). The classical KEM component performs
Scalar * MontgomeryPointwith the Ed25519 view scalar as the private input. RFC 7748 scalar clamping is not applied because the view scalar is already reduced modโ; clamping would mutate it and desynchronize sender/receiver derivation. SeePOST_QUANTUM_CRYPTOGRAPHY.mdยงDH Semantics. -
Low-order Montgomery point rejection (validation rule). Recipients MUST reject low-order Montgomery points on
kem_ct_x25519before performing DH:if (8 * point).is_identity() โ reject. This replaces RFC 7748 clamping's cofactor-clearing role. Sender-side check on the derived recipient X25519 pub is defense-in-depth. SeePOST_QUANTUM_CRYPTOGRAPHY.mdยงDH Semantics. -
m_pqc_public_keylayout invariant: 1216 bytes.X25519_pub[0..32] || ML-KEM-768_ek[32..1216]whereX25519_pubis derived (never transmitted). Canonical assemblers:get_account_address_from_str,generate_pqc_key_material. Runtime checks enforce exact size at every split site. -
Wallet key consistency invariant.
m_pqc_secret_key[0..32] == m_view_secret_key. Wallet refuses to open on mismatch. -
X25519 derivation test vectors published.
docs/test_vectors/PQC_TEST_VECTOR_005_X25519_DERIVATION.jsonpins the Ed25519โX25519 derivation, unclamped DH shared secrets, low-order rejection inputs, and Edwards rejection inputs for third-party implementers.
โจ Added
montgomery.rs: EdwardsโMontgomery conversion, unclamped scalar interpretation, low-order point detection. (shekyl-crypto-pq)shekyl_view_pub_to_x25519_pubFFI export for C++ callers. (shekyl-ffi)- Genesis reproducibility artifacts:
verify_genesis.pyscript andGENESIS_BUILD_INFO.txt. (shekyl-dev/tools/genesis_builder/)
๐ Changed
genesis_builderprint_usage updated to Bech32m. Usage example now shows<bech32m>addresses instead of<base58>.
๐ Fixed
-
Fixed
core_testsFCMP++ proof verification failures.gen_fcmp_tx_valid,gen_fcmp_tx_double_spend, andgen_staking_lifecycleall failed with "FCMP++ proof verification failed" because test-chain block headers carried a placeholdercurve_tree_root(selene_hash_init) while witness paths were assembled from the real LMDB tree. Added per-height curve tree root storage (m_curve_tree_rootsLMDB table) so both the prover and verifier read the correct historical root for any reference block height. Also alignedcompute_leaf_count_at_heightinchaingen.cppwith productioncollect_outputslogic (output-type filtering andoutPkbounds checks). -
Reverted
vcpkg.jsonmanifest that broke MSVC CI. Commit397817bintroduced avcpkg.jsonwith"builtin-baseline": null, which caused the MSVC CI job to fail (vcpkg auto-detected the manifest and rejected the null baseline). The CI workflow already manages vcpkg dependencies via explicit CLI invocation. Deleted the manifest to restore the working state. -
Restored and upgraded
JsonSerialization.FcmpPlusPlusTransactiontest. Replaced ring-stylemake_transactionwithmake_fcmp_transaction()that constructs a real v3 FCMP++ transaction via the full Rust FFI signing pipeline: KEM keypair generation, output construction, scan-and-recover, curve tree leaf/root building, FCMP++ proof signing and verification, and PQC auth signing. The test now exercises real cryptographic operations (not stubs) before round-tripping through JSON serialization. Deprecatedwallet_tools::gen_tx_srcwith migration note pointing to the FCMP++ pipeline inchaingen.cpp. -
Fixed
rctSigJSON serializer missingmessageandreferenceBlock. The JSON round-trip forrct::rctSigdid not serialize themessagefield (tx prefix hash) or thereferenceBlockfield (forRCTTypeFcmpPlusPlusPqc). Both are part of the binary wire format inrctTypes.hbut were silently lost during JSON serialization. Addedmessageto all rctSig JSON output andreferenceBlockfor FCMP++ transactions. Discovered by theFcmpPlusPlusTransactionJSON round-trip test. -
on_get_curve_tree_pathRPC consistency fix. The RPC handler readleaf_countfrom tip state but returned areference_blockseveral blocks behind tip. If the tree grew in between, the returned leaf data and layer hashes did not match the reference block'scurve_tree_root. Fixed by computingref_leaf_countatreference_heightvia drain journal, capping all reads to that count, and applying boundary-chunk hash trimming for sibling chunks that changed since the reference block. Mirrors the fix already applied to the test harness inchaingen.cpp. -
MSVC portability batch. Expanded
src/common/compat.hwith centralized platform-conditional includes forunistd.h/io.h,dlfcn.h, andsys/mman.h. AddedAND NOT MSVCguards tomonero_enable_coverage(GCC-only--coverageflags) andenable_stack_trace(GNUld-Wl,--wrap=__cxa_throw). Fixedbootstrap_file.cpplongtypes tostd::streamoff/uint64_tfor LLP64 correctness. Fixed unsigned negation inwallet2.cpp:772(std::advance(left, -N)where N issize_t) withstatic_cast<ptrdiff_t>. Created rootvcpkg.jsonmanifest for deterministic dependency management. -
FCMP++ test harness: tree state mismatch.
assemble_tree_path_for_outputandconstruct_fcmp_txintests/core_tests/chaingen.cppread the current (tip) curve tree state but the verifier checks against the reference block's historical tree root. Fixed by computingref_leaf_countat the reference block height and capping all leaf/layer reads to that count, with boundary chunk hash trimming viashekyl_curve_tree_hash_trim_selenefor siblings that changed since the reference block. Also fixed a layer offset bug where sibling hashes were read fromlayerinstead oflayer - 1. -
FCMP++ test harness: staking tests missing FCMP++ pipeline.
gen_staking_lifecycleandgen_stake_all_tiersusedconstruct_staked_txwhich produced stub RCT signatures without FCMP++ proofs or PQC auth. Rewritten to use callback-based testing (likegen_fcmp_tx_valid) with a newconstruct_fcmp_staked_txthat routes through the full FCMP++ proving and PQC signing pipeline viaapply_fcmp_pipeline.
๐ Changed
-
Unified constant-time comparison for all 32-byte crypto types.
public_key,key_image, andhashnow usecrypto_verify_32viaCRYPTO_MAKE_HASHABLE_CONSTANT_TIMEinstead ofmemcmp-basedCRYPTO_MAKE_HASHABLE. Eliminates the footgun of a developer choosing the non-constant-time macro for a new secret-bearing 32-byte type. -
Added
ct_signaturestype alias.using ct_signatures = rct::rctSig;added incryptonote_basic.has the starting point for migrating away from the Monero-erarct_signaturesname. Full caller migration andrct::namespace rename deferred to V4. -
Documented alternative tokens decision. Keeping
/FIiso646.hworkaround for MSVC; mechanical replacement ofnot/and/oris high-effort, low-value. Recorded in STRUCTURAL_TODO.md. -
Workspace-wide clippy cleanup. Resolved all
cargo clippy --all-targets --no-deps -- -D warningserrors across the Rust workspace (14 crates, 52 files). Key changes: replacedas u128casts withu128::from(), added#[allow]for intentional truncation in economics/FFI code, marked FFIextern "C"functionsunsafewith# Safetydocs, replaced redundant closures with method references, usedlet...else, switchedfrom_slicetoGenericArray::from()in chacha20poly1305, changed&Vec<T>to&[T]in public APIs. No behavioral changes.
โจ Added
-
Fuzz target for
derive_output_secrets. Newfuzz_derive_output_secretscargo-fuzz harness inrust/shekyl-crypto-pq/fuzz/. Exercises arbitrarycombined_ssinputs (up to 1200 bytes) and output indices; asserts determinism, non-zero ho/y scalars, and absence of panics on truncated/oversized input. Closes FOLLOWUPS.md fuzz-derivation item. -
Witness header round-trip test. New
witness_header_build_then_parse_roundtriptest inrust/shekyl-ffi/with locked vectors indocs/test_vectors/WITNESS_HEADER.json. Provesshekyl_fcmp_build_witness_header(writer) andparse_prove_witness(reader) agree byte-for-byte on all 8 header fields[O:32][I:32][C:32][h_pqc:32][x:32][y:32][z:32][a:32]. Closes FOLLOWUPS.md witness-roundtrip item.
๐ Documentation
-
y=0 consensus check resolved as infeasible. Documented that a consensus-level rejection of outputs with
y=0T-component cannot be implemented: the verifier does not knowy(it is a KEM-derived secret) and testing whetherOlies in the G-only subgroup requires knowing the DL between G and T. Defense is structural viaderive_output_secretshard-assert and fuzz coverage. Closes FOLLOWUPS.md y=0-consensus item. -
scheme_id binding analysis corrected in
PQC_MULTISIG.md. Theexpected_scheme_idparameter inverify_transaction_pqc_authis unused because FCMP++ hides which output is being spent. Scheme downgrade protection is provided by theh_pqccurve tree leaf commitment โ the FCMP++ proof bindsH(hybrid_public_key)to the leaf, making a downgrade require a Blake2b-512 collision. Updated Attack 1 mitigation description andPOST_QUANTUM_CRYPTOGRAPHY.mdaccordingly. -
FOLLOWUPS.md and STRUCTURAL_TODO.md audit and cleanup. Marked 5 stale items as resolved (2 in FOLLOWUPS, 3 in STRUCTURAL_TODO):
signing_round_trip.rsnow exercises FFI,AUDIT_SCOPE.mdexists, C++20-isms audit complete, easylogging++ MSVC fully fixed,wallet2.h:2324bool/char pattern removed by wallet refactoring. Updated 2 stale references:simplewallet.cppdeleted (removed fromlongtype sites andmemcmpresolution list),wallet2.cpp:782shifted to line 772. Updated testmemcmpcount from 84 to ~90. Annotatedexpected_scheme_idremoval as deferred to PQC multisig PR.
๐ Documentation
- Cross-repo documentation audit. Comprehensive review across all five
Shekyl repos fixing stale references, Monero-era branding, completed-but-
unchecked items, and broken cross-references. Key changes:
README.md: Removed Monero CI badges (Coverity, OSS Fuzz, Coveralls), stale distribution packages (apt install monero, etc.), Raspberry Pi Jessie instructions, 2022-era pruning sizes,monerod.confreferences. Fixed research section cross-references to shekyl-dev repo.proxies.md: Renamed "Monero ecosystem" to "Shekyl ecosystem".DOCUMENTATION_TODOS_AND_PQC.md: Fixed FCMP++ "Phase 8" references (doc exists), CryptoNight reference (Shekyl uses RandomX from genesis),CURVE_TREE_OPERATIONS.mdreference (covered inFCMP_PLUS_PLUS.md), v2.0 tx references (should be v3).INSTALLATION_GUIDE.md:FCMP_PLUS_PLUS.mdexists, not "planned."V4_DESIGN_NOTES.md: Checked boxes for items done in V3.RELEASE_CHECKLIST.md: Marked wallet/exchange/pool entries as placeholders for Shekyl-specific partners.FOLLOWUPS.md: Added items for fuzz harness onderive_output_secrets, witness header round-trip test, y=0 consensus check, andAUDIT_SCOPE.mdcreation.- KEM plan: Updated 18 todo items from
pendingtocompletedmatching actual codebase state.
๐๏ธ Removed
-
tests/unit_tests/address_from_url.cppdeleted. The test referencedMONERO_DONATION_ADDR(removed constant) and tested Monero OpenAlias DNS resolution againstdonate.getmonero.org. Both the constant and the DNS endpoint are irrelevant to Shekyl; the test broke the macOS CI build. -
simplewallet(shekyl-wallet-cli) deleted. The 9,126-line C++ interactive wallet REPL has been removed. Its replacement,shekyl-cli(Rust), was already at full parity for all actively-used commands. Removedsrc/simplewallet/directory, CMake target, CI artifact references, and Windows installer entries. Thetranslations/directory retains simplewallet-era.tsstrings as dead entries within shared i18n files. -
wallet/api/C++ wrapper layer deleted. The 3,909-line Monero-era C++ wrapper (wallet2_api.hand 10 implementation files) had no production consumer -- the GUI useswallet2_ffiviashekyl-wallet-rpc(Rust). Removedsrc/wallet/api/directory,tests/libwallet_api_tests/, and theadd_subdirectory(api)entry fromsrc/wallet/CMakeLists.txt. Cleaned up stale#include "wallet/api/*.h"references inobject_sizes.cppandaddress_from_url.cpp.
๐ Fixed
-
19
core_testsfailures and SEGFAULT from v3 transaction incompatibility. The test framework'sconstruct_miner_tx_manuallywas hardcoded to produce v2 transactions without PQC output construction, causing 16 block validation tests to fail during generation and a SEGFAULT intx_validationtests. Rewrote the function to perform genuine v3 output construction viashekyl_construct_outputFFI. Addedappend_v3_output_to_miner_txhelper for tests that add outputs to coinbase. Fixedfill_tx_sourcesto populateho/v3_ho_validon source entries viatry_v3_scan_output. Removed stale classical key derivation from view tag tests. Fixed serialization consistency in tests that modifyvout/vinwithout updatingrct_signaturesfields. -
Non-exhaustive
TxBuilderErrormatch in FFI error-code mapping. Commitaff9f777addedTreeDepthTooLarge(u8)toTxBuilderErrorbut did not add the corresponding arm totx_builder_error_code()inshekyl-ffi, breaking CI compilation on all platforms. AddedTreeDepthTooLarge(_) => -27.
[core-v3.1.0] - 2026-04-13
๐ Changed
- Dev merged into main. 128 commits from
devpromoted tomainincluding: FCMP++ curve-tree integration, hybrid PQC KEM scanning, shekyl-cli full parity, shekyl-address Bech32m encoding, native Rust transaction signing, staking enhancements, wallet/api removal, and ZeroMQ cleanup. Tagged ascore-v3.1.0for GUI wallet CI pinning.
โจ Added
-
shekyl-clifull parity with simplewallet (40 of 81 commands). Therust/shekyl-cli/crate now covers all actively-used simplewallet functionality. Key additions since the initial scaffold:- Security-hardened UX:
display.rsfor secret display with TTY checks, multiplexer warnings, best-effort scrollback clear, and honest residual-scrollback warning.errors.rsfor JSON-RPC error sanitization (strips paths/hex;--debugroutes raw errors to stderr or 0600 log file, never stdout). Context-specificconfirm_dangerous()tokens for destructive operations (sweep amount, address prefix, acknowledgment phrase). - Stateless account model:
ReplSessionholds session-default account on REPL stack;ResolvedCommandenum resolves--account Nat parse time. No wallet-level current-account state.--subaddr-index/--subaddr-indicesfor subaddress selection. - Independent daemon client:
daemon.rsusing ureq (rustls backend, pinned) forchain_health. SOCKS stream isolation via distinct auth username.--daemon-ca-certand--proxyCLI flags. Differentiated error reporting (5 failure modes). - Staking:
stake,unstake,claim,staking_info,chain_health. - Keys:
viewkey,spendkeywith terminal safety;export_key_images(0600 permissions,--since-height,--all);import_key_imageswith format validation. - Proofs:
get_tx_key,check_tx_key,get_tx_proof,check_tx_proof,get_reserve_proof,check_reserve_proof. - Wallet ops:
password(old-first with fast-fail validation),rescan(confirm_dangerous),sweep_all(privacy warning),show_transfer. - Offline signing:
describe_transfer,sign_transfer,submit_transfer;--do-not-relayontransfer. - Signing:
sign,verify(domain separation documented),version,wallet_info(no filename). - Input validation:
validate.rswith hex, txid, address, and input-length validators. - Fuzz tests:
proptestdev-dependency with 14 property tests for amount parsing, hex validation, address validation, and argument parsing. - Parity matrix:
docs/CLI_PARITY_MATRIX.mdmaps all 81 simplewallet commands to shekyl-cli equivalents or explicit out-of-scope with reasons. Phase 3 deletion gate defined. - Categorized help with per-command usage docs and domain-separation note on sign/verify.
- Security-hardened UX:
-
CI gate:
dalek-ff-groupversion isolation. Added a workflow step that assertsshekyl-ffi's normal dependency tree never pulls indalek-ff-groupv0.4. The 0.4 version is allowed transitively insideciphersuiteinternals but must never be used directly by Shekyl code. -
CI lint: no debug macros in production Rust. Added a workflow step that rejects
eprintln!,dbg!, andprintln!in production Rust code (excluding test modules, build scripts, binary entry points, and the economics simulator). Prevents accidental debug logging from reaching production builds. -
CI lint: BOOST_FOREACH guard. Added a workflow step that fails if any
BOOST_FOREACHusage is reintroduced via upstream cherry-picks. All 31 prior instances were replaced with range-based for loops.
๐ Changed
- CI lint: exclude
shekyl-clifrom debug-macro ban. The interactive CLI REPL legitimately usesprintln!/eprintln!for terminal output. The lint now skipsrust/shekyl-cli/to avoid false positives on binary crate I/O.
๐ Fixed
-
[CONSENSUS] Genesis TX blobs upgraded to v3 wire format. The hardcoded
GENESIS_TXhex incryptonote_config.h(mainnet, testnet, stagenet) was still in the legacy v2 format, missing theenc_amountsandoutPkarrays required by the currentserialize_rctsig_base. Updated all three blobs to v3 (tx.version = 3) with zero-filledenc_amounts/outPkforRCTTypeNullcoinbase. This was the root cause ofcore_testsSEGFAULT,block_weightfailure, and wallet init failures in CI. -
JSON serialization now includes
enc_amounts/commitmentsforRCTTypeNullcoinbase. ThetoJsonValue/fromJsonValueforrct::rctSigpreviously skipped these fields forRCTTypeNull, but the binary wire format serializes them for all RCT types since the v3 format change. This caused JSON round-trip failures for coinbase transactions. -
HTTP_Client_Auth.MD5_authtest used hardcoded empty cnonce. The test computed the expected MD5 digest withcnonce=""while the productionhttp_auth.cppgenerates a random cnonce. Fixed to extract the actual cnonce from the parsed auth response.
๐๏ธ Deprecated
-
test::make_transactionring-style helper. The helper constructs Monero-era ring-signature source entries incompatible with v3/FCMP++ transaction construction.BulletproofPlusTransactionisGTEST_SKIP'd pending FCMP++ test infrastructure. -
[CONSENSUS-ADJACENT] Branch layer depth validation off-by-one in
shekyl-tx-builder. The rulec1 + c2 == depthwas corrected toc1 + c2 + 1 == depth(layer 0 is the leaf hash and has no branch entry). The previous rule incorrectly rejected valid witnesses at depth=1 and accepted structurally wrong branch counts at all other depths. Discovered by the FFI signing round-trip test introduced in this release. Verifier side verified: uses proof-structure-implicit depth enforcement (no explicit c1/c2 check needed). Additionally, validation now enforces the spec-correct C1/C2 alternation split (c1 == c2orc1 == c2 + 1), the error.rs doc was corrected (previously stated the relationship backwards), andMAX_TREE_DEPTH=24was added as a named constant inshekyl-fcmpwith enforcement in both prover and verifier. See FOLLOWUPS.md for the full audit trail.
โ Testing
- FFI signing round-trip test rewritten to use
shekyl_sign_fcmp_transaction.rust/shekyl-ffi/tests/signing_round_trip.rsnow exercises the full C-ABI FFI boundary: KEM keypair generation, output construction, output scanning, curve tree leaf/root computation, JSON serialization ofFcmpSignInput+OutputInfo, signing viashekyl_sign_fcmp_transaction, and verification viashekyl_fcmp_verify. Runs 10 iterations with different random seeds. Previously calledproof::provedirectly, bypassing FFI JSON parsing, key derivation, and buffer management.
๐ Documentation
- FFI header upgraded to
///doc comments (Phase 6 completion). Converted all//function and struct documentation comments insrc/shekyl/shekyl_ffi.hto///Doxygen-style. Covers all ~70 FFI exports: output construction/scanning, key image computation, FCMP++ prove/verify, wallet proofs, cache encryption, KEM operations, Bech32m encoding, curve tree hashing, seed derivation, and daemon RPC. Rewrote theSHEKYL_PROVE_WITNESS_HEADER_BYTEScomment fromDEPRECATED/TODOlanguage to document its role as test infrastructure forgenRctFcmpPlusPlusincore_tests.
๐ Changed
-
simplewalletmarked deprecated. Added a yellow deprecation banner tosimplewallet.cppstartup: "shekyl-wallet-cli is deprecated and will be removed. Use shekyl-cli instead." No new features will be added; the binary will be deleted onceshekyl-clireaches parity. -
Axum RPC binds to standard port. When
--no-rust-rpcis not set, the Axum daemon RPC server now binds to the standard RPC port (11029/12029/13029) and the epee HTTP listener is skipped. Falls back to epee on Axum startup failure. Previously Axum bound toepee_port + 10000. -
Production
eprintln!removed from Rust FFI. Replaced 6eprintln!calls inshekyl-ffi/src/lib.rserror handlers with silent error suppression (the C++ caller checks the bool return). Converted 1eprintln!inshekyl-daemon-rpc/src/ffi_exports.rstotracing::error!. -
Test code migrated to remove all calls to deleted crypto/device functions. Updated 14 test files across
tests/crypto/,tests/unit_tests/,tests/core_tests/,tests/performance_tests/,tests/trezor/, andtests/benchmark.cppto remove references toderive_public_key,derive_secret_key,derivation_to_scalar,derive_subaddress_public_key,derive_view_tag,is_out_to_acc,lookup_acc_outs,ecdhDecode,ecdhHash,genCommitmentMask,generate_key_image_helper, andgenerate_output_ephemeral_keys. Where inline key derivation was needed (block/miner-tx construction tests), local helpers using Ed25519 primitives (hash_to_scalar,ge_scalarmult_base,sc_add) replace the deleted functions. Legacy output scanning inchaingen.cppandchain_switch_1.cppfalls through to the v3 scan path. Alladditional_tx_keysparameters removed fromconstruct_tx_and_get_tx_keycall sites. Benchmark harnesses forderive_subaddress_public_keyand per-tx scanning removed.
๐๏ธ Removed
-
Complete ZMQ removal. Deleted the entire ZeroMQ subsystem: ZMQ pub/sub (
zmq_pub.cpp), ZMQ RPC server (zmq_server.cpp,daemon_handler.cpp,daemon_messages.cpp), low-level ZMQ helpers (net/zmq.cpp), message schema (message.cpp,daemon_rpc_version.h,rpc/fwd.h), and therpc_pub,daemon_rpc_server,daemon_messagesCMake targets. Removedlibzmqbuild dependency from root CMakeLists,contrib/depends, and all link targets. Deleted 3 test files (zmq_rpc.cpp,txpool.py,python-rpc/framework/zmq.py) and thezeromq.mkdepends recipe with its patches. Removed--zmq-rpc-bind-ip,--zmq-rpc-bind-port,--zmq-pub,--no-zmqCLI arguments. ZMQ was a duplicate, unauthenticated RPC surface inherited from an abandoned Monero "migrate RPC to ZMQ" effort. It had zero first-party consumers, leakeddo_not_relaytransactions, and its tests had been broken for 82+ consecutive CI runs, polluting the test signal during the FCMP++ migration. Ports 11025/12025/13025 are now reserved. Re-audit follow-up: removed stale#include "rpc/daemon_messages.h"and two ZMQ-schema-dependent tests (DaemonInfo,HandlerFromJson) fromjson_serialization.cpp, and fixed daemon link order (rpcafter${SHEKYL_DAEMON_RPC_LINK_LIBS}) to resolve circular FFI back-references previously satisfied transitively throughdaemon_rpc_server. -
wallet/api/C++ wrapper layer deleted (~3,900 lines). Thesrc/wallet/api/directory (22 files) wrappedwallet2for GUI consumption. With the Tauri GUI usingwallet2_ffivia Rust, no production consumer remained. Removed the directory,add_subdirectory(api)from wallet CMakeLists,wallet/apiincludes and sizeof reporters fromobject_sizes.cpp, broken includes insubaddress.cppand trezor tests,wallet_apilink target from trezor CMakeLists, and CI--target wallet_apibuild steps. -
libwallet_api_tests/test suite deleted (~1,300 lines). Removed thetests/libwallet_api_tests/directory and its CMake entry. Cleaned up the Makefile'slibwallet_api_testsctest exclusions (originally disabled for Issue #895, now fully removed). Also removed thewallet_api_testsclass and implementation from trezor tests. -
load_deprecated_formats/is_deprecateddead code excised (Phase 6 completion). Removed theis_deprecated()method,is_old_file_formatmember,m_load_deprecated_formatsmember and its getter/setter fromwallet2.h. Deleted theis_deprecated()definition, JSON save/load ofload_deprecated_formats, the non-JSON wallet keys file fallback (now a hard error), and the boostportable_binary_iarchiveversion\003/\004branches inparse_unsigned_tx_from_strandparse_tx_from_strfromwallet2.cpp. Removed theset_load_deprecated_formatscommand, itsCHECK_SIMPLE_VARIABLEentry, settings display line, and theis_deprecated()upgrade flow fromsimplewallet.cpp/.h. Shekyl is v3-from-genesis; there are no legacy non-JSON wallet files or boost-serialized transaction blobs to load. -
additional_tx_keys/additional_tx_pub_keysinfrastructure fully removed. Deleted member variables, struct fields, serialization entries, and function parameters referencing additional transaction keys fromwallet2.h,wallet2.cpp,cryptonote_tx_utils.h/.cpp,cryptonote_format_utils.h,device.hpp,device_default.hpp/.cpp, anddevice_ledger.hpp/.cpp. Inwallet2.cpp, removed alladditional_tx_pub_keys/additional_tx_keyslocal variables, derivation computation loops,m_additional_tx_keysmap operations,etd.m_additional_tx_keysexport/import paths, and updated function definitions (get_tx_key_cached,get_tx_key,set_tx_key,check_tx_key,get_tx_proof) to match the simplified header signatures. Theconceal_derivationdevice method implementations were updated to match the simplified signatures (no additional keys/derivations parameters). TheABPkeysstruct no longer carriesadditional_key. Cleaned up all remaining call sites acrosswallet2_ffi.cpp,wallet/api/wallet.cpp,simplewallet.cpp,wallet_rpc_server.cpp, andtrezor/protocol.cppโ removing additional-key parsing loops, serialization, and pass-through parameters.get_additional_tx_pub_keys_from_extrais now an inline stub returning an empty vector. In V3, per-output KEM ciphertexts replace additional tx keys; there is only one tx pubkey per transaction. -
derive_public_key,derive_secret_key, andderivation_to_scalarremoved from the device interface chain. Deleted the pure virtual declarations fromdevice.hppand all override implementations fromdevice_defaultanddevice_ledger. Also deletedderive_public_keyandderive_secret_keyfromcrypto.cpp/crypto.h(keptderivation_to_scalarin crypto, still needed byderive_subaddress_public_key). Removed associated performance test files. These Keccak-based one-component key derivation helpers are superseded by the V3 HKDF two-component output key derivation incryptonote_tx_utils. -
out_can_be_to_acc,is_out_to_acc_precomp, andderive_view_tagdead code removed. Deleted the Keccak-basedout_can_be_to_accandis_out_to_acc_precompfunctions fromcryptonote_format_utils, thederive_view_tagfunction fromcrypto, and thederive_view_tagvirtual method from the device interface chain (device.hpp,device_default,device_ledger). Removed associated performance tests. These functions were superseded by the X25519/HKDF view-tag derivation path in the V3 transaction format. -
ecdhHashandgenCommitmentMaskdead code removed. Deleted theecdhHashandgenCommitmentMaskfunction definitions fromrctOps.cpp, their declarations fromrctOps.h, thegenCommitmentMaskvirtual method from the device interface chain (device.hpp,device_default,device_ledger), and theecdhDecodeunit test that depended on them. These Keccak-based helpers were superseded by HKDF-derived amount encryption in V3. -
Ring signature / decoy infrastructure removed from wallet2. Removed
fake_outs_countparameters fromcreate_transactions_2,create_transactions_all,create_transactions_single, andcreate_transactions_from. Removedtransfer_selected_rct'sfake_outputs_countandoutsparameters. Deletedget_output_relatedness,outs_unique,m_print_ring_members, andm_ringsbookkeeping. FCMP++ eliminates ring signatures, making decoy selection and output relatedness scoring dead code.
๐ Security
-
m_combined_shared_secretchanged toscrubbed_arr<uint8_t, 64>(Phase 6, Gate 3). Replacedstd::vector<uint8_t>withtools::scrubbed_arr<uint8_t, 64>in bothtransfer_detailsandexported_transfer_details. This ensures zero-on-drop semantics consistent withm_yandm_mask. A booleanm_combined_shared_secret_setflag replaces size-based emptiness checks. All serialization (epee and Boost) updated with safe vector round-trip conversion. -
WalletState invariant enforcement (Phase 6, Gate 5b). Added
check_invariants()toWalletStateverifying 8 structural properties (balance consistency, spendable/spent partition, key image correspondence, etc.).debug_assert!fires after every mutation in debug builds. Property test (Gate 5c) exercises random operation sequences against invariant checks.
โจ Added
-
PQC output round-trip property tests (Phase 6, Gate 1).
prop_round_trip.rsexercisesconstruct_outputโscan_output_recoverโderive_proof_secretsโcompute_key_imagewith random keys and amounts viaproptest. Asserts determinism (same inputs โ identical outputs) and non-zero secrets (ho,y,z,k_amount,key_image). Includes boundary cases foramount=0andamount=u64::MAX. Runs with--releasein CI. -
Wallet cache AEAD tests (Phase 6, Gate 2).
cache_crypto.rscovers encrypt/decrypt round-trip, version mismatch detection (returns -1 before AEAD decryption attempt), wrong-key auth failure, empty ciphertext, and truncated ciphertext. Sub-case A2 proves version check ordering by corrupting ciphertext and asserting version mismatch fires first. -
100-iteration signing round-trip stress test (Phase 6, Gate 4).
test_gate4_signing_round_trip_100inproof_round_trip.rsruns full outbound prove+verify cycle 100 times with unique randomness per iteration. -
unmark_spentunit tests (Phase 6, Gate 5a). Five tests covering: reversal to spendable pool, unknown key image noop, idempotent on already-unspent, partial set behavior, and invariant preservation after unmark. -
Random-sequence invariant property test (Phase 6, Gate 5c).
proptestdrives random sequences ofAddOutputs,MarkSpent,UnmarkSpent,Freeze,Thaw, andReorgoperations, assertingcheck_invariants()after each step. -
Sync bookkeeping tests (Phase 6, Gate 7). Mock-block-driven tests for
WalletStatemutations: progress monotonicity, spend detection, reorg state restoration, empty block height advancement, and spend/unmark round-trip. Explicitly documented as bookkeeping-only (not integration against a real daemon). -
CI grep gates (Phase 6). Seven blocking grep gates in
build.yml:shekyl_yabsence,derivation_to_y_scalarabsence, legacy RCT type absence, v1/v2 tx version branch absence,HASH_KEY_TXPROOFabsence,combined_shared_secretconfinement to wallet boundary,ecdhEncode/ecdhDecodeconfinement to Ledger gate. All run withoutcontinue-on-error. -
FFI header documentation (Phase 6).
shekyl_ffi.hnow has Doxygen-style file-level documentation covering the memory model, secret handling conventions, and error reporting contract.
๐๏ธ Removed
-
derivation_to_y_scalardeleted (Phase 6). Removed the function body fromcrypto.cpp, declarations fromcrypto.h, and all call sites inderive_public_keyandderive_subaddress_public_key. The"shekyl_y"salt no longer appears in the binary. -
Test stubs 9-10 deleted (Phase 6). Removed
#[ignore]placeholder teststest_09_watch_only_outbound_proof_errorandtest_10_restored_wallet_outbound_proof_errorfromproof_round_trip.rs. Future implementations tracked inWALLET_STATE_MIGRATION.md. -
Dead v1/v2 transaction branches in consensus (Phase 5).
check_tx_outputsnow rejectstx.version < 3instead of< 2. Removed redundantif (tx.version >= 2)zero-amount guard (now unconditional). Tightened coinbase version check from>= 2to>= 3. Removed deadtx.version < 3early return incheck_commitment_mask_valid. Commitment mask checks are now unconditional (version is always >= 3). -
Dead legacy code excision (Phase 6 completion). Deleted
decodeRctSimpleand its overload fromrctSigs.cpp/.h. Deletedtools::decodeRctwrapper and all callers inwallet2.cpp. Deletedgenerate_output_ephemeral_keysdeclaration fromcryptonote_tx_utils.h. Deletedtx_proof.cppunit test (referenced removedcrypto::generate_tx_proof_v1). Deletedis_out_to_acc.hperformance test and its registrations. -
generate_key_image_helper/generate_key_image_helper_precompfully removed. Migrated remaining production callers inwallet2.cpp(export_key_images, twoimport_outputsoverloads) to the v3 HKDF path viashekyl_derive_proof_secretsFFI. Replaced deadelsebranch incryptonote_tx_utils.cpp::construct_tx_with_tx_keywith a hard error. Replacedscan_output'sgenerate_key_image_helper_precompcall with a v3-only assertion (function is dead for v3 scanning). Deleted both function definitions fromcryptonote_format_utils.cpp/.h, thecompute_key_imagevirtual method fromdevice.hppand its Trezor override indevice_trezor.hpp/.cpp. Updated test callers inchaingen.cppandtx_validation.cppto use v3sc_add(ho, b)derivation.
๐ Security (Phase 5 Audit Notes)
-
Consensus hardening: commitment mask validation verified (Phase 5). Audited
check_commitment_mask_validinblockchain.cpp: confirms rejection of identity commitment (mask=0, amount=0), generator-point commitment (mask=1, amount=0), and coinbasezeroCommit(amount)form (mask=1, any amount). Called unconditionally for both miner transactions and regular transactions. -
y=0 defense-in-depth verified (Phase 5). Confirmed construction-time
assert!(y != [0u8; 32])andassert!(ho != [0u8; 32])inderive_output_secrets(Rust, release-mode assert). Both sender (construct_output) and receiver (scan_output_recover) hit the same assert. Documented inPOST_QUANTUM_CRYPTOGRAPHY.mdwith full defense stack analysis.
โจ Added
-
GUI wallet native-sign activation (Phase 4a). Added
native-signfeature to the GUI wallet'sshekyl-wallet-rpcdependency. The transfer path is now: C++ prepare โ Rust sign โ C++ finalize. -
Scanner keys FFI export (Phase 4b). Added
wallet2_ffi_get_scanner_keysto the wallet2 FFI layer, returning all keys needed by the Rust scanner (spend/view secrets, X25519 SK, ML-KEM DK) as JSON. Addedget_scanner_keyswrapper method toWallet2. -
Hybrid PQC KEM scanner (Phase 3a).
shekyl-scannernow scans blocks using the V3 two-component key derivation: X25519 + ML-KEM-768 hybrid KEM. TheInternalScanner::scan_transactionpipeline parsesTX_EXTRA_TAG_PQC_KEM_CIPHERTEXT(0x06), applies X25519 view-tag pre-filtering (~99.6% rejection), and callsscan_output_recoverfor full KEM decapsulation, HKDF secret derivation, amount decryption, and B' recovery. Key images are computed natively in Rust viahash_to_point+compute_output_key_image. Legacy ECDH scan path removed. -
RecoveredWalletOutputstruct. New scan result type carrying all KEM-derived secrets (ho,y,z,k_amount,combined_shared_secret), the computedkey_image, and decryptedamountalongside the baseWalletOutput. ImplementsZeroizeOnDropโ secrets are wiped when the struct leaves scope. -
TransferDetailsPQC fields andeligible_height. Extended withho,y,z,k_amount,combined_shared_secret(allZeroizing) andeligible_height: u64(block_height + SPENDABLE_AGE). Outputs beloweligible_heightare immature (no curve-tree path) and cannot be spent.is_spendable()enforces this gate. -
WalletStateKEM-aware processing.process_scanned_outputsnow populates all PQC fields fromRecoveredWalletOutput, sets key images at scan time, and performs duplicate output key detection (burning bug).spendable_outputsfilters oneligible_height. -
unmark_spentfor rollback.WalletState::unmark_spentreverses spent marks on outputs whose signing round succeeded but whose finalize step failed (daemon rejection, relay timeout). Prevents phantom-spent balance loss. -
Background sync loop (Phase 3b).
shekyl-scanner::sync::run_sync_looppolls the daemon RPC for new blocks, feeds them through the hybrid KEM scanner, detects spent outputs via key-image matching against block inputs, and emitsSyncProgressevents after each block. Cancellation-safe viatokio_util::CancellationToken. Configurable flush interval: every 100 blocks on desktop, every block on mobile (OS can kill without warning). -
BalanceSummaryuseseligible_height. Timelock categorization now readstd.eligible_heightdirectly instead of recomputing fromblock_height + DEFAULT_LOCK_WINDOW. -
ViewPairextended with KEM keys. Addedx25519_skandml_kem_dkfields toViewPairfor hybrid KEM decapsulation. The scanner requires both the X25519 secret and ML-KEM decapsulation key.
๐ Fixed
-
Stale
fake_outs_countarguments in wallet transaction creation. Removed vestigial0(decoy count) from 9 call sites acrosswallet2_ffi.cpp,wallet_rpc_server.cpp, andwallet/api/wallet.cppthat no longer matchcreate_transactions_2,create_transactions_all, andcreate_transactions_singlesignatures after ring removal. -
Test compilation:
wallet_tools.cppandtransactions_flow_test.cpp. Replaced removedtd.is_rct()calls withtrue(all Shekyl outputs are RCT), changedtools::wallet2::get_outs_entryto the local typedef fromchaingen.h, and removed stalemix_in_factorargument in the functional test. -
PQC doc label error. Fixed incorrect HKDF label reference in
POST_QUANTUM_CRYPTOGRAPHY.md: the output-key check useshowith labelshekyl-output-x, notshekyl-pqc-output(which is the ML-DSA seed label). -
Test compilation:
json_serialization.cppaggregate init. Replaced brace-enclosed initializer list fortx_source_entrywith explicit member assignment. The struct is no longer an aggregate (user-declared destructor forhowiping) and the old initializer also referenced a removedreal_out_additional_tx_keysfield. -
Multi-output scan bug. Removed erroneous
breakinInternalScanner::scan_transactionthat exited the output iteration loop after finding the first matching output. Transactions with multiple wallet outputs (e.g., payment + change) now detect all of them. -
Reorg handling in
handle_reorg. RewroteWalletState::handle_reorgto use(height, hash)pairs instead of treating height as a direct vector index. Correctly handles non-genesis-aligned and sparse sync histories.synced_heightis now derived from the last remaining block entry. -
Reorg detection in sync loop.
run_sync_loopnow compares each incoming block'sheader.previoushash against the wallet's stored hash for the prior height. On mismatch, walks backwards to find the fork point and callshandle_reorgbefore resuming. -
Block fetch retry with backoff. Per-block
get_scannable_block_by_numbercalls now retry up to 5 times with exponential backoff (500ms initial, capped at 30s) instead of immediately aborting the sync loop on transient failures. -
Secure memory wiping.
TransferDetailsnow implements bothZeroize(covering all fields includingkey,commitment, andfcmp_precomputed_path) andDrop(callszeroize()on drop).WalletStateimplementsDropto wipe all transfers, key images, pub keys, and block hashes. Removed unsafe#[derive(Clone, Debug)]fromTransferDetails;Debugis now manual and redacts secret fields. -
Misleading payment ID comment. Corrected comment in
scan.rsthat incorrectly described ECDH-based XOR decryption for payment IDs; V3 transactions do not use encrypted payment IDs. -
Always-true pattern in sync loop. Removed
if let Some(tx_hashes) = Some(&scannable.block.transactions)which was a no-op guard. Block transactions are now iterated directly.
๐ Changed
-
EncryptedAmountwire format fix. The RustEncryptedAmountstruct (inshekyl-oxide::fcmp) now correctly includes bothamount: [u8; 8]andamount_tag: u8, matching the C++ 9-byte wire format. Previously only the 8-byte amount was read, causing silent data misalignment. -
Scanner::newsignature. Now requires the wallet'sspend_secret(Zeroizing<[u8; 32]>) for native key image computation at scan time. BothScanner::newandGuaranteedScanner::newupdated. -
Deterministic KEM encapsulation from
tx_key_secret.construct_outputnow derives X25519 ephemeral keys and ML-KEM ciphertexts deterministically via HKDF-SHA-512 (derive_kem_seed), eliminating the need to cache per-output shared secrets. The sender can re-derivecombined_ssat proof time fromtx_key_secretand public data. -
Proof pipeline helpers in
shekyl-crypto-pq. Seven new functions:rederive_combined_ss,derive_proof_secrets,derive_output_key,recover_recipient_spend_pubkey,decrypt_amount,compute_output_key_image, andcompute_output_key_image_from_ho. These support the V3 tx_proof / reserve_proof / key-image protocols. The narrowProofSecrets(ho, y, z, k_amount)projection ensurescombined_ssnever crosses the FFI boundary. -
ProofSecretswidened to includez. The Pedersen commitment mask is now part of the proof secrets projection, enabling directC = z*G + amount*Hverification in TX proofs.derive_proof_secretspasseszthrough instead of discarding it. -
shekyl-proofscrate: full Phase 1a implementation. Three modules:dleq.rs: Two-base Schnorr DLEQ proof with domain separatorshekyl-reserve-proof-dleq-v1and full base binding in the challenge hash (G,Hp(O),R1,R2,P,I,msg). 6 unit tests.tx_proof.rs: Outbound (101+128N bytes) and inbound (69+128N bytes) proof generation and verification. Domain-separated Schnorr signatures (shekyl-outbound-tx-proof-v1,shekyl-inbound-tx-proof-v1). Per-outputho,y,z,k_amountwith algebraic output key and commitment checks.reserve_proof.rs: Reserve proof (69+192N bytes) with per-output DLEQ key image binding.enc_amountsourced from blockchain, not from proof.- Version assertion (v1) before any cryptographic work. 4-byte output_count (u32 LE) supporting up to 2ยณยฒโ1 outputs per proof.
- 10-point round-trip test skeleton (exit criterion for Phase 5,
#[ignore]).
-
FCMP_PLUS_PLUS.md section 21: Wallet Proof Structure. Genesis-native proof design rationale. Documents the Schnorr/KEM decomposition, reserve proof DLEQ requirement, HKDF binding argument for z-omission in reserve proofs, and the
enc_amount-from-chain invariant. -
Phase 1b FFI exports (PR-wallet). New exports in
shekyl_ffi.h:shekyl_scan_and_recover: Merged scan + key image in one call. All secret outputs write directly intotransfer_detailsfields (no intermediate scratch buffers).persist_combined_ssflag controls whethercombined_ssis returned or wiped internally (hot vs cold).shekyl_compute_output_key_image/_from_ho: Key image computation for the 2 remaining sites (stake claim, tx_source_entry).shekyl_sign_fcmp_transaction: Collapsed signing. C++ passes wallet master spend keyb+ per-input{combined_ss, output_index, ...}. Rust derivesx = ho + bandyinternally via HKDF. C++ never touchesx.shekyl_derive_proof_secrets: Helper writingho,y,z,k_amountdirectly to caller-provided destination addresses.shekyl_encrypt_wallet_cache/shekyl_decrypt_wallet_cache: AEAD encryption with AAD binding oncache_format_version. Distinct error codes for version mismatch (-1), auth failure (-2), and format error (-3).- 6 proof FFI exports:
shekyl_generate_tx_proof_outbound,shekyl_verify_tx_proof_outbound,shekyl_generate_tx_proof_inbound,shekyl_verify_tx_proof_inbound,shekyl_generate_reserve_proof,shekyl_verify_reserve_proof. Signatures stabilized; wiring toshekyl-proofsinternals deferred to Phase 2e.
-
shekyl-chachaAEAD extension. Addedchacha20poly1305(v0.10) support:encrypt_with_aadanddecrypt_with_aadwrapping XChaCha20-Poly1305. No hand-rolled AEAD โ nonce handling, constant-time tag comparison, and AD framing delegated to audited crate. 6 new tests. -
RecoveredOutputnow includescombined_ss. The scan result carries the 64-byte combined shared secret so the merged scan FFI can optionally persist it without re-doing KEM decapsulation. Wiped byZeroizeOnDrop. -
ML-KEM shared secret
Zeroizingwrap (W5 fix). All 4 production sites whereml_ss.into_bytes()produces a bare stack-local now wrap the result inZeroizing<[u8; 32]>, ensuring the ML-KEM shared secret bytes are zeroed on scope exit. Closes the W5 correlation leak. -
Fixed stale
shekyl_construct_outputC header. Added missingtx_key_secretparameter to match the Rust implementation. -
KEM derivation KAT vectors.
docs/test_vectors/KEM_DERIVE_V1_KAT.jsonwith 8 pinned vectors forderive_kem_seed. Serves as tripwire against silent behavior changes fromfips203orcurve25519-dalekupgrades. -
fips203exact version pin. Pinned to=0.4.3with audit comment explaining theDummyRng::fill_bytes = unimplemented!()risk. -
Fuzz target for
derive_output_key. Exercisesderive_output_keyandrecover_recipient_spend_pubkeyround-trip with fuzzer-supplied inputs. -
Ledger V3 hard gate.
device_ledger.cppnow has a#errorthat fires whenWITH_DEVICE_LEDGERis defined, preventing silently broken builds. The Ledger APDU protocol has not been updated for V3 two-component keys. -
Fuzz target for malformed KEM ciphertexts on scan. New
fuzz_scan_malformed_ctexercises corrupted, truncated, and random ML-KEM ciphertexts throughscan_output_recoverwith a valid wallet KEM secret. Validates ML-KEM implicit rejection + downstream algebraic checks fail closed without panics or timing leaks.
๐ Documentation
-
Security properties of the derivation section in
docs/POST_QUANTUM_CRYPTOGRAPHY.md. Documents the y==0 defense-in-depth stack (construction assert + probabilistic impossibility + fuzz coverage), explains why a wire-level y==0 check is impossible, documents malformed KEM ciphertext handling through ML-KEM implicit rejection, view-tag pre-filter behavior on adversarial match grinding, and the wallet cache version gate requirement for PR-wallet. -
Tightened malformed KEM ciphertext framing. Reframed
amount_tagas a ~99.6% cheap pre-filter (performance optimization), not a security gate. Commitment algebraic checkC == z*G + amount*His the soundness barrier. Documented structural independence of the two algebraic checks (different HKDF labels, different scalar families). -
Wallet cache version gate hardened. Added mandatory AAD binding (include
cache_format_versionin XChaCha20-Poly1305 AAD to prevent version-confusion attacks) and hard no-migration policy (delete and resync from seed, never in-place migration).
๐๏ธ Removed
-
ecdhTuple/ecdhEncode/ecdhDecoderemoval. Deleted the Monero-era ECDH amount-masking struct and encode/decode functions fromrctTypes.h,rctOps.h/.cpp,device.hpp,device_default.hpp/.cpp,device_ledger.hpp/.cpp, and the Trezor protocol files. Theenc_amount_to_ecdh_compatshim is deleted. -
check_tx_key_helper/is_out_to_accdeletion. Both overloads ofwallet2::check_tx_key_helperandwallet2::is_out_to_accremoved. These usedderive_public_key(Keccak Category 1) and the old ecdhDecode path. Replaced by KEM-based proof FFI round-trip incheck_tx_key. -
crypto::generate_tx_proof/generate_tx_proof_v1/check_tx_proofdeletion. Monero-era DH-based Schnorr proof functions removed fromcrypto.cpp,crypto.h,device_default.cpp,device_ledger.cpp,device.hpp, and derived device headers.HASH_KEY_TXPROOF_V2removed fromcryptonote_config.h. -
ecdh.rsmodule stub cleanup. Removed orphanedmod ecdhdeclaration and associated test functions fromshekyl-tx-builder(module file was previously deleted, declaration left behind). -
V3-from-genesis Boost serialization purge (
wallet2.h). Deleted allif (ver < N)migration branches from Boostserializefunctions fortransfer_details,unconfirmed_transfer_details,confirmed_transfer_details,payment_details,address_book_row,unsigned_tx_set,signed_tx_set,tx_construction_data, andpending_tx. Deleted theinitialize_transfer_detailshelper (both saving and loading overloads). Reset allBOOST_CLASS_VERSIONmacros to 1 (genesis version). Addedassert(ver == 1)guards. Epee cache envelopeif (version < N)branches also removed, replaced withassert(version == 2). Staking fields (m_staked,m_stake_tier,m_stake_lock_until,m_last_claimed_height) and new Phase 2b field (m_k_amount) added to thetransfer_detailsBoost serializer. Legacym_rctfield no longer serialized (previously removed from struct).
๐ Changed
-
Phase 2e: Proof functions collapsed to Rust FFI (PR-wallet). All six wallet proof functions (
get_tx_proof,check_tx_proof,get_reserve_proof,check_reserve_proof) now delegate to theshekyl-proofsRust crate via the FFI bridge.check_tx_keyalso uses the FFI round-trip (generate outbound proof + verify with on-chain data). The intermediate C++ helperscheck_tx_key_helper(both overloads) andis_out_to_acchave been deleted. Newgather_on_chain_proof_datahelper extracts output keys, commitments, encrypted amounts, and KEM ciphertexts from transactions for proof verification. Reserve proof wire format now includes output locators (txid + index_in_tx) as a header so the verifier can independently fetch on-chain data from the daemon. -
Phase 2f: Category 1 Keccak deletions (PR-wallet). Deleted Monero-era DH-based proof functions from the crypto layer:
crypto::generate_tx_proof,crypto::generate_tx_proof_v1,crypto::check_tx_proof, along with their device implementations (device_default, device_ledger) and virtual interface declarations. RemovedHASH_KEY_TXPROOF_V2fromcryptonote_config.h. Removed orphanedecdh.rsmodule declaration and tests fromshekyl-tx-builder. Remaining Category 1 functions (derive_public_key,derivation_to_scalar,derive_subaddress_public_key,decodeRctSimple) still have live callers in scan/sign paths and are deferred to Phase 3 migration.ecdhHashandgenCommitmentMaskhave been removed. -
Phase 2d: Collapsed signing via
shekyl_sign_fcmp_transaction(PR-wallet). The CLI wallet'stransfer_selected_rctnow calls the Rust collapsed signing FFI instead of C++genRctFcmpPlusPlus. C++ builds JSON arrays ofFcmpSignInput(per-inputcombined_ss,output_index, tree layers) andOutputInfo(per-outputcommitment_mask,enc_amount), then unpacks the returnedSignedProofs(BP+ blob, FCMP++ proof, pseudo-outs, commitments, enc_amounts) intotx.rct_signatures. Rust owns all witness assembly โ C++ never touches the ephemeral spend secretx.genRctFcmpPlusPlusis deprecated (retained only forchaingen.cpptest infrastructure). -
Rust
sign_transactionupdated for v3 HKDF semantics (PR-wallet).OutputInfonow carriescommitment_mask: [u8; 32]andenc_amount: [u8; 9](pre-derived byconstruct_output), replacing the oldamount_keyfield.SignedProofs.enc_amountswidened from 8 to 9 bytes. The signing pipeline uses pre-derived HKDF masks for BP+ instead of generating random ones, and uses pre-encrypted amounts instead of Keccak-based ECDH encoding. -
wallet2_ffi.cppenc_amountsfield name fix. The native-sign finalize path now readsenc_amountsfrom RustSignedProofsJSON (was incorrectly readingecdh_amounts). -
enc_amountsfield comment updated inrctTypes.h. Clarifies that byte [8] is the HKDF-derivedamount_tagAAD, documents the Rust scanner validation behavior (reject on mismatch), and removes the staleRESERVED_AMOUNT_TAG_PLACEHOLDERreference. -
Comprehensive CLI User Guide (
docs/USER_GUIDE.md). Covers all shipped executables, daemon operation (flags, config file, console commands), wallet CLI (create, restore, send, receive, proofs), staking (tiers, unstake, claim, accrual rules), mining, PQC multisig (file-based workflow, size table), anonymity networks (Tor/I2P), wallet RPC, blockchain utilities, security/backup, and troubleshooting. Mirrors the GUI wallet guide structure for easy cross-referencing. -
C++/Rust cross-validation test for
total_weighted_stake. New test instaking.cppconstructs the same staker set via both the C++ 128-bit cache accumulation and the Rust FFI, then asserts byte-equality of the results. Prevents spec/impl drift regression. -
u128saturation test. Demonstrates that the u128 weighted stake does NOT saturate where u64 would (100M stakers at 100 SKL, tier 2), and verifies reward computation remains correct with the large denominator. -
LMDB write atomicity audit. Comprehensive audit of all
BlockchainLMDBwrite paths (block connect, block pop, txpool, alt blocks, staking, FCMP++ curve tree). Documented indocs/LMDB_WRITE_ATOMICITY_AUDIT.md. Found and fixed a missinglock.commit()inget_relayable_transactions(Dandelion++ timestamp rollback bug) and added a defensivedb_wtxn_guardaround the staker accrual reversal inpop_block_from_blockchain. -
LMDB schema reference (
docs/LMDB_SCHEMA.md). Complete documentation of all 28 sub-databases: LMDB names, open flags, custom comparators, key/value byte layouts with struct field offsets, read/write access patterns, and hard fork version introduction. Standalone audit value and prerequisite for the eventual heed migration. -
Vendored dependency tracking (
docs/VENDORED_DEPENDENCIES.md). Documents the vendored LMDB version (0.9.70, based on OpenLDAPmdb.masterbranch), applied upstream patches (ITS#9385, ITS#9496, ITS#9500, etc.), CVE review (CVE-2026-22185 does not affect us), and themdb.mastervsmdb.master3branch distinction relevant to future heed migration. -
V4 design notes (
docs/V4_DESIGN_NOTES.md). Records the heed LMDB migration deferral with detailed reasoning (shared-write risk, schema drift, map resize race conditions) and the recommended approach for V4 (single Rust-owned Env, no split write ownership, full BlockchainLMDB unit cutover). -
Additional C++ conservation-invariant tests. Six new tests in
tests/unit_tests/staking.cpp: weighted denominator >= raw sum invariant, tier-0 weight equality, higher-tier strict inequality, zero-staker burn path, single-staker full capture, dust staker conservation, multi-block claim range conservation, and MAX_CLAIM_RANGE boundary validation. -
shekyl-wallet-corecrate. New Rust crate providing transaction builder plans for stake, unstake, and claim operations. IncludesClaimTxBuilderfor constructing claim transaction plans with automatic MAX_CLAIM_RANGE splitting, andClaimAndUnstakePlanfor the two-step drain-then-unstake workflow. -
Coin selection module (
shekyl-scanner/coin_select.rs). Min-relatedness output selection algorithm that prefers combining outputs with fewer shared metadata fingerprints (tx hash, block height, subaddress, tier) for improved on-chain privacy. Supports dust separation and configurable selection criteria. -
Output freezing and coin control.
WalletStatenow supports freeze/thaw of individual outputs by index or key image, with frozen outputs excluded from spendable candidate lists. Newspendable_outputs()method with optional account, subaddress, and minimum amount filters. -
Staker pool tracking in Rust (
shekyl-scanner/staker_pool.rs). Wallet-sideStakerPoolStatemirrors per-block accrual records from the daemon, enabling local reward estimation without RPC round-trips. Supports reorg handling and conservation invariant checking. -
Claim watermark tracking.
TransferDetailsnow carrieslast_claimed_heightfor monotonic claim watermark management.WalletStateexposesupdate_claim_watermark(),claimable_outputs(), andclaimable_rewards_summary()methods. NewClaimableInfostruct provides per-output claim state including accrual frozen status. -
New RPC methods.
get_claimable_stakes,get_unstakeable_outputs,freeze, andthawadded to the Rust scanner-backed RPC handler. All four are routed through the Rust scanner whenrust-scannerfeature is active. -
GUI wallet staking bridge.
wallet_bridge.rsextended withget_scanner_claimable_stakes,get_scanner_unstakeable_outputs,scanner_freeze, andscanner_thawfor Tauri frontend integration. -
Staking transaction types in
shekyl-oxide.Input::StakeClaimvariant (binary tag 0x03) andOutput::staking: Option<StakingMeta>(binary tag 0x04) added with full binary serialization/deserialization.StakingMetacarries thelock_tierfield (lock_untilis computed dynamically). -
Property-based staking tests. 11 new property tests in
shekyl-staking: conservation across uniform/mixed/stress scenarios, proportionality, floor division safety, weight function validation, multi-block accumulation bounds, and adversarial edge cases. -
shekyl-chachacrate. New Rust crate providing XChaCha20 (192-bit nonce) stream cipher for wallet and cache file encryption. Wraps the NCC-audited RustCryptochacha20crate. Exported via FFI asxchacha20(), replacing the C implementation inchacha.c. -
KEM-derived output secrets (
OutputSecrets). New Rust infrastructure inshekyl-crypto-pq/src/derivation.rsderives per-output secrets (ho,y,z,k_amount,view_tag_combined,amount_tag,ml_dsa_seed) from the combined X25519 + ML-KEM shared secret via HKDF-SHA-512 with distinct info labels. Includesderive_view_tag_x25519for fast wallet scan pre-filtering without ML-KEM decapsulation. FFI exports:shekyl_derive_output_secrets,shekyl_derive_view_tag_x25519. -
Cross-language HKDF test vectors. Python reference implementation (
tools/reference/derive_output_secrets.py) generates locked JSON test vectors (docs/test_vectors/PQC_OUTPUT_SECRETS.json). Rust unit tests validate byte-for-byte against these vectors. -
Witness header constant.
SHEKYL_PROVE_WITNESS_HEADER_BYTES = 256defined in bothshekyl_ffi.handshekyl-ffi/src/lib.rs, replacing all magic literal 256 values. -
Consensus
mask=1placeholder.check_commitment_mask_valid()wired intocheck_tx_outputsfor all v3 transactions. Returns accept-all now; PR-construct will flip to rejectzeroCommitform for non-coinbase. -
HKDF label registry.
docs/POST_QUANTUM_CRYPTOGRAPHY.mdnow documents all HKDF salt/info pairs for the per-output derivation stream and the separate X25519-only view tag derivation. -
Unified Rust output construction (
construct_output). Newshekyl-crypto-pq/src/output.rsimplementsconstruct_output(KEM encapsulation + HKDF โ two-component keyO = ho*G + B + y*T, Pedersen commitmentC = z*G + amount*H, encrypted amount, view tag, PQC leaf hash) andscan_output_recover(KEM decapsulation + HKDF โ recovered spend keyB' = O - ho*G - y*Tfor subaddress lookup, plus all per-output secrets). FFI exports:shekyl_construct_output,shekyl_scan_output_recover. -
PQC signing in Rust (
sign_pqc_auth). ML-DSA-65 keypair is derived, used, and wiped entirely within Rust. The secret key never crosses the FFI boundary. FFI export:shekyl_sign_pqc_auth. -
FCMP++ witness header assembly in Rust. The 256-byte witness header (
[O:32][I:32][C:32][h_pqc:32][x:32][y:32][z:32][a:32]) is now assembled viashekyl_fcmp_build_witness_headerwith a typedProveInputFieldsstruct, replacing 8 rawmemcpycalls in C++. -
construct_miner_txandconstruct_tx_with_tx_keyrewired to Rust. Both v3 output construction paths now callshekyl_construct_outputper output in a unified loop. KEM ciphertexts and PQC leaf hashes are written totx_extra. The legacyderivation_to_y_scalarpath is retired on all construction paths. -
Wallet scanner uses
scan_output_recover.wallet2::process_new_transactionhas a v3-specific scanning path that callsshekyl_scan_output_recoverfor KEM decapsulation, HKDF derivation, amount recovery, and subaddress lookup. Key images are computed as(ho + b_spend) * Hp(O). -
X25519-derived view tag. Per-output view tags are now derived from the X25519 shared secret only (no ML-KEM needed), enabling fast wallet scan pre-filtering. Written during construction, checked first during scanning.
-
additional_tx_keysremoved for v3.need_additional_txkeysis false fortx.version >= 3. Theadditional_tx_public_keysfield is no longer populated or consumed in v3 construction or scanning. -
Real Pedersen commitments for coinbase (
RCTTypeNull).outPkandenc_amountsare now serialized forRCTTypeNulltransactions.blockchain_db.cppuses the on-chainoutPk[i].maskfor v3+ coinbase instead of computingzeroCommit(amount). -
check_commitment_mask_validenforced. Rejects trivial commitment masks (z = 0orz = 1) for all non-coinbase v3 outputs. Called from bothcheck_tx_outputsandprevalidate_miner_transaction. -
PQC salt consolidation. All per-output PQC key derivation now uses the unified
OutputSecrets.ml_dsa_seedfrom salt B (shekyl-output-derive-v1). The legacyHKDF_SALT_PQC_DERIVEsalt A is deleted. Testnet reset required โ invalidates all existingh_pqc. -
Chaingen test infrastructure updated for v3.
init_output_indices,fill_tx_sources,init_spent_output_indices, andconstruct_fcmp_txnow useshekyl_scan_output_recoverfor HKDF-based output ownership detection, mask recovery, and key image computation. -
genRctFcmpPlusPlususes HKDF commitment masks. The function now accepts pre-computed HKDFzscalars (commitment_masks) and pre-computed encrypted amounts (enc_amounts_precomputed) instead of re-deriving them internally via Keccak. This fixes a critical mismatch where BP+ proofs used Keccak-derived masks whilescan_outputexpected HKDF-derived values. The oldamount_keysparameter is removed. Testnet reset required โ on-chain commitments and encrypted amounts are now HKDF-derived, incompatible with prior Keccak format. -
Stake claim outputs use
shekyl_construct_output. The wallet'screate_stake_claim_txnow constructs outputs via the unified Rust HKDF path, producing correct output keys, view tags, KEM ciphertexts, leaf hashes, andenc_amountswithamount_tag. BP+ blinding factors remain constrained by thezeroCommitpseudo-out balance equation (sum to N). -
Chaingen PQC signing via
shekyl_sign_pqc_auth. Core testconstruct_fcmp_txnow uses the high-level FFI that derives, signs, and wipes the ML-DSA secret key entirely inside Rust. The rawshekyl_pqc_signcall (which accepted the secret key as a C++ byte pointer) is replaced. -
zeroCommitdead code removed from DB layer.blockchain_db.cppanddb_lmdb.cppno longer fall back tozeroCommit(amount)for output commitments. All outputs (including coinbase) use on-chainoutPk[i].mask. Thepre_rct_outkeybranch in LMDB now throws foramount != 0(Shekyl has no pre-RCT outputs). -
RCTTypeNull round-trip serialization test. New test in
tests/unit_tests/serialization.cppverifies thatRCTTypeNulltransactions with populatedoutPkandenc_amounts(8-byte amount + 1-byteamount_tag) survive binary serialize/deserialize round-trip. -
libFuzzer harness for
construct_output. New fuzz targetfuzz_construct_outputinrust/shekyl-crypto-pq/fuzz/exercisesconstruct_output+scan_outputround-trip with arbitrary spend keys, amounts, corruptedenc_amount, and wrongamount_tag. -
libFuzzer harness for malformed KEM keys. New fuzz target
fuzz_construct_output_malformed_kemfeeds arbitrary bytes as X25519 and ML-KEM-768 encapsulation keys toconstruct_output. Exercises wrong-length, oversized, and garbage KEM public key inputs to ensure the function returnsErr, never panics. -
PQC leaf hash known-answer test. New JSON fixture
docs/test_vectors/PQC_LEAF_HASH_KAT.json(8 vectors) pins the output ofderive_pqc_leaf_hash(combined_ss, output_index). Rust KAT test validates byte-for-byte against the fixture. -
Coinbase
check_commitment_mask_validhardened. ForRCTTypeNull(coinbase) outputs, the consensus check now rejects commitments that equalzeroCommit(public_amount)(i.e.C = G + amount*H), preventing miners from constructing trivial-mask coinbases that leak amount to observers. Non-coinbase defense-in-depth checks (identity and G) are retained. -
Dead Keccak y-scalar fallback removed from wallet scanner. The
else if (tx.vout[o].amount == 0)andelse if (miner_tx)branches that fell back toderivation_to_y_scalarare removed. Shekyl is v3 from genesis; all matched outputs must succeed the HKDF scan path. A hardwallet_internal_erroris thrown ifv3_hkdf_scannedis false, preventing silent domain fallback that would produce unspendable outputs. -
Legacy coinbase construction path removed.
construct_miner_txnow asserts PQC key presence with a clear error message (CHECK_AND_ASSERT_MES) before entering the output construction loop, instead of falling back to legacy Keccakderive_public_key/derive_view_tagwhich would produce an invalid (unscannable, missingoutPk/enc_amounts) coinbase. All Shekyl addresses carry PQC keys from genesis. -
Genesis coinbase builder uses
shekyl_construct_output.build_genesis_coinbase_from_destinationsnow constructs outputs via the Rust HKDF path, producing correct HKDF-derived output keys, view tags, commitments, encrypted amounts withamount_tag, KEM ciphertexts, and PQC leaf hashes. The legacy Keccak derivation path is removed. -
Legacy
additional_tx_public_keysdead code removed. Theneed_additional_txkeyslogic,additional_tx_public_keysvector, and pre-v3 output derivation loop inconstruct_tx_with_tx_keyare deleted. V3 replaces per-output additional tx keys with KEM ciphertext (tag 0x06).
๐ Changed
-
transfer_details::m_masktype changed.rct::keyโcrypto::secret_keyfor automatic zeroization on drop. All RCT call sites use explicitrct::sk2rct()/rct::rct2sk()conversion. Binary-compatible (same 32-byte layout). -
ecdhInforeplaced byenc_amounts. The per-output encrypted amount format changes fromecdhTuple(64 bytes: 32 mask + 32 amount) tostd::array<uint8_t, 9>(8 bytes XOR-encrypted amount + 1 byte amount tag). AffectsrctSigBase, all serialization paths (binary, boost, JSON), and transaction construction (genRctFcmpPlusPlus,fill_construct_tx_rct_stub, wallet claim construction). -
ecdhEncoderemoved. The ECDH encoding function is deleted fromrctOps,device.hpp, anddevice_default. Transaction construction now writesenc_amountsdirectly via Rust HKDF-based output construction.ecdhDecodeis retained as a scanner shim until the wallet migrates to Rustscan_output.ecdhHashandgenCommitmentMaskhave been fully removed fromrctOps, the device interface chain, and tests. -
FROST SAL deferred to V4. Per-output HKDF-derived
yis incompatible with DKG group-sharedy. FROST SAL section indocs/PQC_MULTISIG.mdmarked as deferred with V4 resolution path (Carrot-style address scheme).
๐ Fixed
-
sc_check()signed left-shift undefined behavior.signum(...) << konint64_tincrypto-ops.cis UB when the result is negative. Introducedsigned_lshift()helper that uses multiplication on non-GCC compilers. Ported from monero@c5be4dd. -
wallet2::verify_password()logic inversion. Background wallet detection usedHasParseError() && IsObject()instead of!HasParseError() && IsObject(), causing background wallets to fail password verification. Added the missing!. Ported from monero@b19cd82. -
HTTP digest auth missing client nonce (
cnonce). The epee HTTP client sent an emptycnoncewithqop=auth, weakening the digest exchange against replay attacks. Now generates a random 16-byte cnonce viaRAND_bytesand includes it in the response hash and Authorization header. Ported from monero@3d6b9fb. -
Critical: SAL
y/ commitment maskzconflation in FCMP++ prover.wallet2.cpppassedtd.m_mask(Pedersen commitment mask) asspend_key_yto the FCMP++ prover, but SAL requiresysuch thatO = xG + yT. Since legacy outputs hady = 0andz != 0,OpenedInputTuple::openalways failed. Fixed by migrating to two-component output keys (O = xG + yT) wherey = Hs_y(derivation || i), and passingzas a separatecommitment_maskfield. Affects every spend on the chain โ this was the root cause of all FCMP++ proof generation failures. -
Coinbase commitment mask in test harness.
fill_tx_sourcesinchaingen.cppsetts.mask = rct::zero()for coinbase, butzeroCommit(amount) = G + amount*Hhas mask = scalar 1. Fixed torct::identity(). -
Critical: u64 saturation in
total_weighted_stake(Bug 7). The in-memory cache and LMDBstaker_accrual_recorduseduint64_tfor the tier-weighted stake denominator. With 12-decimal atomic units and tier multipliers > 1.0, this saturates at ~18.4M SHEKYL of weighted stake โ well below moderate adoption. Reward computation collapses to a meaningless ceiling once saturated. Fixed by widening to u128 end-to-end: in-memory cache uses lo/hi u64 pairs with proper carry arithmetic, LMDB record gainstotal_weighted_stake_hifield (32โ40 bytes), FFIshekyl_calc_per_block_staker_rewardaccepts lo/hi parameters, and RustAccrualRecord/StakeRegistry::total_weighted_stake()return u128. -
Critical: back-dating exploit on first claim (Bug 3).
check_stake_claim_inputonly enforcedfrom_height == watermarkwhen watermark > 0. For the first claim (no watermark),from_heightwas unconstrained. An attacker could stake at block N, then submit a claim withfrom_height = 0, walking 10,000 historical blocks and collecting rewards against denominators that never included the attacker's output. Fixed by looking up the staked output's creation height and requiringfrom_height >= creation_heightwhen no watermark exists. -
Critical: inter-tx pool sufficiency race within a block (Bug 4). The per-tx pool balance check in
check_tx_inputsreads the pre-block pool balance, so five claim txs each claiming 1000 against a pool of 3000 all individually pass. The silent-skip path inadd_transaction_datathen lets over-claimed txs through without decrementing the pool. Fixed with two changes: a block-level aggregate pool check inhandle_block_to_main_chainthat sums all claim amounts across ALL txs and rejects the block if the total exceeds the pool, plus converting the silent-skip path inadd_transaction_datato a hard throw (dead code if validation is correct, fatal if not). -
Reorg watermark restoration loses data (Bug 5).
remove_transactionusedfrom_height == 0as the signal for "first claim, remove watermark." Butfrom_heightfor a first claim is typically the creation height (non-zero). Fixed by looking up the staked output's creation height to distinguish first claims from subsequent claims. -
Reorg pool reversal direction wrong for no-staker blocks (Bug 6).
pop_block_from_blockchainunconditionally subtracted accrued inflow frompool_balance, but for no-staker blocks the inflow was burned (not added to pool). Popping such a block caused a spurious pool underflow. Fixed by reading the accrual record'stotal_weighted_stake: if zero, subtract fromtotal_burnedinstead ofpool_balance. -
Empty-staker-set accrual audit trail. The
actually_destroyedfield in the persisted accrual record did not reflect the no-staker burn because the record was written before the burn decision. Fixed by movingadd_staker_accrualto after the no-staker burn path, so the record captures the fullactually_destroyedvalue. -
Dandelion++ relay timestamp rollback.
get_relayable_transactionsintx_pool.cppwas missinglock.commit(), causing all stem/forward timestamp updates to be silently rolled back by theLockedTXNdestructor. Transactions in Dandelion++ stem/forward states could be re-relayed with stale timing data, degrading transaction-origin privacy. Fixed by adding the missing commit. -
Staker accrual reversal without write transaction guard. The staker pool balance and burn total reversal in
pop_block_from_blockchainrelied on the caller's batch context for a write transaction but had no defensive guard. While all current production callers maintain a batch, a future caller without one would crash or produce undefined behavior. Fixed by wrapping the reversal block indb_wtxn_guard. -
Critical: weighted denominator bug in staker reward accrual. The per-block
total_weighted_stakewas computed from raw staked amounts instead of tier-weighted amounts, causing proportional over-distribution (up to +100% when all stakers use the Long tier). Fixed by introducing separate caches for raw and tier-weighted stake amounts inblockchain.h/blockchain.cpp. -
Claim timing: lock conflated with claimability.
check_stake_claim_inputincorrectly rejected claims whenlock_until > current_height, making rewards unclaimable during the lock period. Fixed by removing the lock-based rejection and addingto_height <= min(current_height, lock_until)enforcement. Wallet filters updated to include both locked and matured-but-unspent outputs. -
Zero-staker blocks: unclaimed pool accumulation. When no stakers existed, staker emission and fee pool amounts accumulated in
staker_pool_balanceindefinitely. Fixed to burn these amounts whentotal_weighted_stake == 0. -
Staked outputs incorrectly spendable.
is_spendable()allowed spending staked outputs after maturity. Fixed: staked outputs are never directly spendable -- they must go through the unstake path. -
Claim watermark not persisted. Added
m_last_claimed_heighttotransfer_details(C++ wallet) andTransferDetails(Rust scanner) with serialization. FFI layer now callsstage_claim_watermarks()after broadcasting claim transactions. -
Critical: stake tx only mineable in exact creation block (Bug 13).
handle_block_to_main_chainvalidated staked outputs with strict equalitystaked.lock_until == blockchain_height + lock_blocks. Since the wallet signedlock_until = current_height + lock_blocks, any mempool latency made every honest stake tx permanently unminable. Fixed by removinglock_untilfrom the on-chaintxout_to_staked_keystruct entirely. The effective lock expiry is now computed dynamically ascreation_height + tier_lock_blocksat every check site. Removes ~8 bytes per staked output and eliminates the signing-time/mining-time mismatch bug class. -
High: mempool admits unminable stake txs (Bug 12). Pool admission checked tier validity and non-zero
lock_untilbut not the strict equality that block validation enforced. Honest and malicious stake txs passed admission but were rejected at block-add time, causing miners to waste work on blocks that would be rejected. Resolved by the Bug 13 fix: with no on-chainlock_until, the entire validation path is removed. -
Medium: off-by-one at upper lock boundary (Bug 11). The accrual scan excluded an output at block
lock_until(<= eval_height), but claim validation acceptedto_height <= lock_until. A staker could claim a one-block reward atlock_untilagainst a denominator that didn't include their weight. Fixed by changing the accrual scan toeffective_lock_until < eval_height(inclusive upper bound) and scheduling unlock subtraction ateffective_lock_until + 1.lock_blocks = Nnow means exactly N blocks of accrual. -
Medium: unstake forfeits unclaimed rewards (Bug 8).
create_unstake_transactionjumped straight tocreate_transactions_fromwithout checking for unclaimed reward backlog. A user who staked for the long tier and never claimed would silently forfeit all accrued rewards. Fixed: the wallet now refuses to unstake if any target output hasm_last_claimed_height < min(current_height, effective_lock_until)and instructs the user to claim first. -
Minor: local claim watermark advanced on broadcast, not confirmation.
update_claim_watermarks(nowstage_claim_watermarks) committed the watermark immediately after broadcast. If the tx was dropped or never confirmed, the local watermark diverged from consensus. Fixed with an in-flight tracking system: claims are staged inm_pending_claim_watermarksat broadcast, committed byconfirm_claim_watermarkswhen the tx appears in a confirmed block during scan, and expired byexpire_pending_claim_watermarksafter 100 unconfirmed blocks.
๐ Changed
-
Wallet encryption upgraded from ChaCha20 (64-bit nonce) to XChaCha20 (192-bit nonce). The 24-byte nonce eliminates collision risk for randomly-generated nonces. Implementation moved from C (
chacha.c) to Rust (shekyl-chachacrate) using the NCC-audited RustCryptochacha20crate.CHACHA_IV_SIZEincreased from 8 to 24 bytes. Wallet keys files and cache files now use XChaCha20 exclusively. -
Two-component output keys (
O = xG + yT). All output public keys now include a domain-separatedycomponent along generatorT, satisfying the FCMP++ SAL proof'sOpenedInputTuple::openconstraint. Previously, outputs were single-component (O = xG + 0ยทT) and the wallet incorrectly passed the Pedersen commitment maskzas the SALy, causing proof generation to fail. The y-scalar uses the"shekyl_y"domain separator incrypto.cpp. The commitment maskzis now passed separately in the 256-byte witness header at offset 192.transfer_detailsstoresm_y(boost serial v14). Two regression tests inproof.rsverify that the old bug (y=mask) fails and the correct path (y=real) succeeds. -
MAX_TX_EXTRA_SIZE(24576 bytes). The previous Monero-era cap (1060) was too small for FCMP++tx_extrapayloads (hybrid KEM ciphertexts ~1120 B per output, PQC leaf hashes, pubkey/nonce). Construction of v3 spends failed once PQC fields were appended; the pool andconstruct_txchecks now allow the larger bound. -
construct_txRCT/PQC stubs. v3 spends require|pqc_auths| == |vin|for binary serialization, andRCTTypeFcmpPlusPlusPqcneeds BP+, ECDH, and pseudo-out vectors sized to inputs/outputs.construct_txnow assigns stubpqc_authenticationentries and callsrct::fill_construct_tx_rct_stub()(dummy Bulletproofs+, ECDH encoding, Pedersen pseudo-outs) soget_transaction_hashand JSON/blob round-trips succeed before the wallet replaces the RCT payload withgenRctFcmpPlusPlus().
๐๏ธ Removed
-
shekyl_fcmp_derive_pqc_keypairFFI function. Deleted the Rust FFI function and its C declaration. This function returned the ML-DSA secret key to C++, violating the security invariant that PQC secrets stay in Rust. Replaced byshekyl_derive_pqc_leaf_hash(returns only h_pqc) andshekyl_derive_pqc_public_key(returns only the public key). -
derive_pqc_keypair,derive_hybrid_pqc_keypair,DerivedPqcKeypair,DOMAIN_PQC_OUTPUTfromshekyl-crypto-pq. These legacy derivation functions used the old salt A (shekyl-pqc-derive-v1) and returned secret key material. All callers now usederive_output_secrets(salt B) +keygen_from_seedor the higher-levelsign_pqc_auth_for_output. -
derived_pqc_secret_keys,derived_pqc_public_keys,claim_signing_sksvectors inwallet2.cpp. These C++ vectors held PQC secret keys in wallet memory. All 4 call sites migrated toshekyl_derive_pqc_leaf_hash+shekyl_sign_pqc_auth, which derive and zeroize internally in Rust. -
pqc_secret_keysfromnative_sign_state(wallet2.h). The deferred native-signing path no longer stores PQC secret keys. The Rust tx-builder receivescombined_ss+output_indexand derives keys internally. -
SpendInput::pqc_secret_keyfromshekyl-tx-builder. Replaced withcombined_ss: Vec<u8>(64 bytes) andoutput_index: u64. The Rustsign_pqc_authsfunction now callssign_pqc_auth_for_outputinternally. -
4 legacy Monero fixture tests in
serialization.cpp. Removedportability_wallet,portability_outputs,portability_unsigned_tx,portability_signed_tx. These tested Monero-era wallet/tx formats that Shekyl does not support (no backward compatibility). -
10 Monero-specific long-term block weight tests. Removed all tests from
long_term_block_weight.cpp(empty_shortthroughcache_matches_true_value). Monero-specific weight baselines do not apply to Shekyl economics. -
chacha.c(C ChaCha implementation). Replaced by the Rustshekyl-chachacrate via FFI. The C implementation had a strict aliasing violation in itsU8TO32_LITTLE/U32TO8_LITTLEmacros (pointer cast touint32_t*). -
ChaCha8 dead code. All
crypto::chacha8()call sites inwallet2.cppwere Monero backward-compatibility fallbacks for reading pre-2018 wallet files. Shekyl has no legacy wallets; these paths were unreachable.
๐ Security
-
ML-DSA secret keys never cross the FFI boundary. All wallet PQC signing paths now use
shekyl_sign_pqc_auth(Rust FFI) orsign_pqc_auth_for_output(Rust tx-builder), which derive the keypair fromcombined_ss+output_index, sign, and zeroize the secret key โ all within Rust. No ML-DSA secret key bytes exist in C++ memory at any point. This eliminates the largest PQC secret key exposure surface (~4064 bytes per input) from the wallet process. -
XChaCha20 192-bit nonces for wallet encryption. Upgraded from the DJB ChaCha20 64-bit nonce to XChaCha20 192-bit nonce, eliminating nonce collision risk for randomly-generated nonces. The previous 64-bit nonce was safe for Shekyl's usage pattern but the larger nonce provides a wider safety margin.
-
Secure memory hardening (project-wide). Systematic implementation of the
secure-memory.mdcrule across Rust and C++ codebases:shekyl_buffer_freenow useszeroizecrate instead ofstd::ptr::write_bytes, preventing the compiler from optimizing away the secret-wiping write.native_sign_state::clear()inwallet2.hnowmemwipes all secret fields (spend_key_x,spend_key_y,h_pqc,amount_key,pqc_secret_keys) before clearing vectors.- Added
prctl(PR_SET_DUMPABLE, 0)to daemon (main.cpp), simplewallet, andwallet2_ffi_create()to prevent core dumps containing key material on Linux. - Passwords, seeds, spend keys, and view keys in
wallet2_ffi.cppJSON-RPC dispatch now usememwipescope guards to wipe temporarystd::stringbuffers after use. - New
shekyl_madvise_dontdumpFFI function (MADV_DONTDUMPon Linux, no-op elsewhere) declared inshekyl_secure_mem.h. - PQC long-lived secret keys (
m_pqc_secret_key) are nowmlocked andmadvise(MADV_DONTDUMP)ed after generation and decryption, andmemwiped +munlocked onforget_spend_key().
-
Dev branch audit: Tier 1-6 security and code hardening. Comprehensive re-audit of the dev branch with 22 findings addressed:
- PQC secret key lifecycle (Tier 1). Added
~account_keys()destructor that wipes all secret keys (classical + PQC) and munlocks PQC material. Fixedcreate_from_keysandset_nullto wipe+unlock PQC secrets before clearing. Prevents secrets from lingering in freed heap memory. - Debug trait on secret key types (Tier 1). Removed
#[derive(Debug)]fromHybridSecretKey,HybridKemSecretKey, andSharedSecret. All now implement manualDebugprinting[REDACTED]to prevent log leakage. - Proof generation panic removal (Tier 1). Replaced 12
ScalarDecomposition::new(...).unwrap()calls inproof.rswith?-propagatedProveError::ScalarDecompositionFailed. Zero-scalar blinding factors now return a clean error instead of panicking the wallet. - RELEASE-BLOCKER resolution (Tier 1). Evaluated and downgraded all 6 RELEASE-BLOCKER comments in shekyl-oxide to TODO with documented justifications. None were correctness or security blockers.
- FROST multisig feature-gated (Tier 1). All FROST SAL and DKG FFI
functions gated behind
#[cfg(feature = "multisig")]. Production builds exclude multisig code unless the feature is enabled. C++#ifdef SHEKYL_MULTISIGblocks have been removed fromshekyl_ffi.h,wallet2.h/cpp, andwallet2_ffi.cppโ FROST multisig is now consumed exclusively through the Rust wallet crates. - CString unwrap removal (Tier 2). Replaced all
CString::new().unwrap()inshekyl-wallet-rpcwithto_cstring()helper returningWalletError. FixedMutex::lock().unwrap()in server.rs to return JSON-RPC error on lock poisoning. - Sign function zeroization (Tier 2).
HybridEd25519MlDsa::sign()now wraps temporary secret arrays inZeroizing<[u8; N]>for automatic cleanup. - hex_to_key temp buffer wiped (Tier 2). Added
memwipescope guard tohex_to_keyinwallet2_ffi.cpp. - PQC verify debug gated (Tier 2).
shekyl_pqc_verify_debugnow only compiled withdebug_assertionsordebug-verifyfeature to prevent use as a signature oracle in production. - Free-string wipe (Tier 2).
wallet2_ffi_free_stringnow wipes the buffer before freeing, protecting against secret-bearing JSON residue. - Buffer free contract documented (Tier 2).
shekyl_buffer_freelen safety contract documented in both Rust doc-comment and C header. - Claim builder silent wrong index (Tier 2).
position(...).unwrap_or(0)replaced with explicitTransferNotFounderror inclaim_builder.rs. - deny(unsafe_code) added (Tier 3). Added to 5 pure-Rust crates:
shekyl-consensus,shekyl-economics,shekyl-staking,shekyl-crypto-hash,shekyl-crypto-pq. - Workspace lints inherited (Tier 3).
[lints] workspace = trueadded to 11 Shekyl-first crates for consistent Clippy enforcement. - Legacy naming cleanup (Tier 4). Renamed
MONERO_DEFAULT_LOG_CATEGORYtoSHEKYL_DEFAULT_LOG_CATEGORYacross 128 files. - FCMP++ edge-case tests (Tier 5). Added 9 parametrized tests covering boundary input counts, missing tree paths, empty proof data, count mismatches, zero tree depth, and wrong signable_tx_hash.
- CI improvements (Tier 6). Added
.envto.gitignore, created explicit CodeQL workflow targeting bothdevandmainbranches, addedpermissions: contents: readtobuild.yml.
- PQC secret key lifecycle (Tier 1). Added
-
Base58 overflow and non-canonical encoding fix (monero-oxide fork).
shekyl-base58::decode()now useschecked_addto prevent integer overflow during character accumulation, and rejects non-canonical encodings where unused high bytes of the decoded sum are non-zero. Defense-in-depth measure; Shekyl production addresses use Bech32m. -
Cargo profile hardening (both Rust workspaces). All profiles (dev, release, test, bench) now enforce
overflow-checks = truein both the monero-oxide forkCargo.tomland the Shekylrust/Cargo.toml. Dev and release profiles additionally setpanic = "abort". -
HKDF domain-separated salts for PQC key derivation. All HKDF-SHA-512 calls in
shekyl-crypto-pqnow use explicit fixed salts (shekyl-pqc-derive-v1,shekyl-master-derive-v1) instead ofNone. Strengthens domain separation and prevents cross-protocol seed reuse if the same combined shared secret appears in other contexts. -
FrostSalSessionsecret deduplication. Removed the redundantx(spend secret scalar) fromFrostSalSessionstruct fields. Previously the secret was stored both in the struct and insideSalAlgorithm, with only the struct copy explicitly zeroized on drop. Now the secret lives solely inside the algorithm, eliminating the unprotected duplicate. -
Levin double-compression guard.
try_compress_messagenow checksLEVIN_PACKET_COMPRESSEDin the input header before compressing. Prevents double-compression of already-compressed messages in future refactors. -
Divisor degree underflow assertions.
Divisor::divnow asserts thatself.a.degree >= rhs.degreeandself.b.degree >= rhs.degreebeforeusizesubtraction, converting silent wraparound into a clear panic with diagnostic context. -
Interpolator allocation bounds fix.
Interpolator::interpolatenow allocates the output coefficient vector using the domain size (self.lagrange_polys.len()) instead ofevals.len(), preventing trailing zeros from inflating the vector when callers provide excess evaluations. -
member_of_listwitness construction hardened. Replacednext_eval.unwrap()withcarry_eval.zip(next_eval)in the FCMP++ circuit gadget, eliminating a potential panic if evaluation invariants change.
โจ Added
-
shekyl-tx-buildercrate. New Rust crate (rust/shekyl-tx-builder/) consolidating Bulletproofs+ range proofs, FCMP++ full-chain membership proof construction, ECDH amount encoding, and PQC (ML-DSA-65) signing into a single native Rust call path. Replaces the prior C++ โ Rust โ C++ โ Rust FFI round-trip for proof generation. Includes 19 unit tests covering validation edge cases (0 inputs, overflow amounts, empty trees, wrong-length PQC keys) and ECDH encoding round-trips. All secret key material is wrapped inzeroize::Zeroizingand wiped on drop. -
shekyl_sign_transactionFFI export. New C ABI function inshekyl-ffiwrappingshekyl-tx-builder::sign_transaction(). Accepts JSON-serialized inputs/outputs, returns aShekylSignResultwith either JSON proofs or a structured error code and message. Declared inshekyl_ffi.h. -
Wallet RPC
native-signfeature.shekyl-wallet-rpcgains an optionalnative-signCargo feature that enablestransfer_native()โ a pure-Rust transfer path usingshekyl-tx-builderdirectly, eliminating C++ proof FFI round-trips. The split pipeline useswallet2_ffi_prepare_transfer(C++ โ JSON) โshekyl-tx-builder::sign_transaction(pure Rust) โwallet2_ffi_finalize_transfer(JSON โ C++). -
wallet2_ffi_prepare_transfer/wallet2_ffi_finalize_transferimplemented. Full C++ implementation of the split transfer pipeline.prepare_transferactivates native-sign mode intransfer_selected_rct(skipping C++ proof generation), gathers per-input signing data (secret keys, tree paths parsed into c1/c2 branch layers, leaf chunks, PQC key material), per-output data (dest keys, amount keys), tree context (reference block, curve tree root, depth), and serializes everything as hex-encoded JSON matching the RustSpendInput/OutputInfo/TreeContexttypes.finalize_transferreceives the Rust-generatedSignedProofsJSON, manually reconstructs the BP+ struct from the Rust blob (handling the V-field format difference), inserts all proofs intotx.rct_signatures, performs PQC signing using stored secret keys, and commits/broadcasts the transaction. Fee estimation usesshekyl_fcmp_proof_len()to pad the stub FCMP++ proof to the correct estimated size. -
Native-sign mode in
wallet2::transfer_selected_rct. Newm_native_sign_modeflag andnative_sign_statestruct onwallet2. When enabled,transfer_selected_rctskipsgenRctFcmpPlusPlusand PQC signing, instead storing all signing data for the Rust path. Tree path blobs are parsed into structured c1/c2 branch layers. Padded stub proofs provide accurate fee estimation. -
Hex serde for
shekyl-tx-buildertypes. All[u8; 32],Vec<u8>, andVec<[u8; 32]>fields onSpendInput,OutputInfo,TreeContext,SignedProofs,LeafEntry, andPqcAuthnow serialize/deserialize as hex strings via custom serde modules. This enables clean JSON interop with the C++ FFI layer which produces hex-encoded cryptographic keys and blobs. -
Secure memory Cursor rule. Added
.cursor/rules/secure-memory.mdccodifying project-wide conventions for cryptographic secret zeroization in both Rust (Zeroizing<T>,ZeroizeOnDrop) and C++ (memwipe, scope guards,wipeable_string), FFI boundary ownership, and OS-level protections (mlock,prctl(PR_SET_DUMPABLE, 0),MADV_DONTDUMP). -
Vendored monero-oxide protocol crates. Completed the vendored crate set in
rust/shekyl-oxide/: addedshekyl-primitives(Keccak-256, Pedersen commitments),shekyl-bulletproofs(BP+ range proofs), the rootshekyl-oxidecrate (transaction/block types, FCMP module),shekyl-rpc(daemon RPC trait,ScannableBlock), andshekyl-simple-request-rpc(HTTP transport). Resolved theshekyl-addressnaming collision by removing the oxide base58 address dependency from the vendored RPC crate (Shekyl uses Bech32m exclusively). Added crypto-heavy crate optimizations to[profile.dev.package]and workspace-level clippy lints for the oxide crates. -
shekyl-scannercrate. New Rust crate (rust/shekyl-scanner/) providing a native transaction scanner with Shekyl-specific extensions. Ported the core scanning pipeline from monero-oxide (SharedKeyDerivations, Extra parsing, ViewPair, per-block/per-tx/per-output ECDH scan loop) and extended it with:- PQC KEM ciphertext parsing (tx_extra tag 0x06) and leaf hash parsing (0x07)
- Staking output detection and balance categorization (matured/locked tiers)
TransferDetailsstruct with FCMP++ path precompute, combined PQC shared secret, and spend tracking fieldsWalletStatefor in-memory transfer management with key image dedup, spend detection, and reorg handlingBalanceSummarywith staking-aware breakdown (total, unlocked, timelocked, staked matured/locked, frozen)
-
Split RPC routing (
rust-scannerfeature).shekyl-wallet-rpcnow supports arust-scannerfeature flag that routes scanner-backed read-only methods (get_balance, get_transfers, incoming_transfers, get_height, get_staked_outputs, get_staked_balance) to native Rust handlers viashekyl-scanner, while all mutation methods continue through the C++ FFI. AddedScannerState,dispatch_with_scanner(), and typed scanner handlers. -
GUI wallet scanner integration. Updated
wallet_bridge.rsinshekyl-gui-walletto include aScannerStatealongside the FFIWallet2handle. Addedget_scanner_balance(),get_scanner_staked_outputs(), andget_scanner_height()bridge methods for future scanner-backed queries. -
shekyl-encodingcrate. New standalone Rust crate (rust/shekyl-encoding/) for general-purpose Bech32m blob encoding and decoding with arbitrary HRPs. Defines HRP constants for wallet proofs (shekylspendproof,shekyltxproof,shekylreserveproof,shekylsig,shekylmultisig,shekylsigner). -
shekyl-addresscrate. New standalone Rust crate (rust/shekyl-address/) for network-aware segmented Bech32m address encoding. DefinesNetworkenum (Mainnet, Testnet, Stagenet) with HRP lookup tables for classical (shekyl,tshekyl,sshekyl) and PQC (skpq/skpq2,tskpq/tskpq2,sskpq/sskpq2) segments.ShekylAddresssupportsencode(),decode(), anddecode_for_network(). -
Generic Bech32m blob FFI.
shekyl_encode_blob()andshekyl_decode_blob()FFI functions allow C++ to encode/decode arbitrary binary data with purpose-specific HRPs, replacing all direct Base58 calls in wallet proofs. -
Network-aware address FFI.
shekyl_address_encode()andshekyl_address_decode()now accept/return anetworkparameter (0=mainnet, 1=testnet, 2=stagenet) for HRP-based network discrimination. -
Shekyl-first development rule. Added
.cursor/rules/shekyl-first-development.mdccodifying that Shekyl core is the authoritative codebase and the monero-oxide fork is a disposable downstream consumer. -
FROST SAL threshold signing for FCMP++ multisig. New
frost_salmodule inshekyl-fcmpwraps upstreamSalAlgorithm<Ed25519T>for threshold Spend-Auth-and-Linkability proofs.FrostSalSessionmanages per-input FROST state;prove_with_sal()constructs FCMP++ proofs from pre-aggregated SAL pairs. FFI functions (shekyl_frost_sal_session_new,_get_rerand,_aggregate_and_prove,_session_free) expose the session lifecycle to C++. Themultisigfeature flag enables FROST dependencies (modular-frost,transcript,rand_chacha). -
FROST DKG key management. New
frost_dkgmodule inshekyl-fcmpprovidesSerializedThresholdKeysforThresholdKeys<Ed25519T>serialization/deserialization, group key extraction, and parameter validation. FFI functions (shekyl_frost_keys_import,_export,_group_key,_validate,_free) manage threshold keys from C++. -
Variable-length FCMP++ witness wire format.
shekyl_fcmp_proveFFI now accepts a singlewitness_ptr/witness_lenblob containing per-input fixed headers, leaf chunk Ed25519 output data, and Helios/Selene branch layers.genRctFcmpPlusPlusinrctSigs.cppserializes the full witness. -
Daemon RPC
chunk_outputs_blob.get_curve_tree_pathresponse now includes per-chunk compressed Ed25519 output data (O, I=Hp(O), C, H(pqc_pk)) enabling the wallet to pass full output points to the prover. -
C++ wallet FROST multisig integration (removed). Previously added C++ FROST integration in
wallet2.cpp(prepare_multisig_fcmp_proof,export_multisig_signing_request,import_multisig_signatures, threshold key import/export). This C++ code has been replaced by the Rust-native wallet crates and all#ifdef SHEKYL_MULTISIGblocks have been removed fromwallet2.h/cpp,wallet2_ffi.cpp, andshekyl_ffi.h. -
FrostSigningCoordinatorfor multi-input nonce aggregation. New coordinator inshekyl-fcmp/src/frost_sal.rsmanages per-input preprocess collection, nonce sum computation, share collection, and final aggregation intoSpendAuthAndLinkabilitypairs forprove_with_sal(). -
Full FROST DKG ceremony via
MultisigDkgSession. New wallet-level wrapper inshekyl-wallet-core/src/multisig/dkg.rsdrives thedkg-pedpopKeyGenMachinestate machine through all three rounds with type-safe transitions:generate_coefficientsโgenerate_secret_sharesโcalculate_shareโcomplete. DKG messages are exchanged as byte buffers (file-based, air-gap compatible). -
MultisigSigningSessionfor wallet-level FROST orchestration. New session inshekyl-wallet-core/src/multisig/signing.rswraps per-inputFrostSalSessioninstances and aFrostSigningCoordinator, providing hex-encoded preprocess/share exchange for transport-agnostic signing. -
MultisigGroupwith PQC keypair management. New type inshekyl-wallet-core/src/multisig/group.rsstores threshold keys, group metadata, and PQC hybrid keypairs with automatic zeroization on drop. Supports serialization/deserialization for wallet storage. -
FROST multisig RPC endpoints. 9 new JSON-RPC methods in
shekyl-wallet-rpc/src/multisig_handlers.rsfor FROST signing coordination:multisig_register_group,multisig_list_groups,multisig_create_signing,multisig_sign_preprocess,multisig_sign_add_preprocess,multisig_sign_nonce_sums,multisig_sign_own,multisig_sign_add_shares,multisig_sign_aggregate. All byte fields hex-encoded. DKG is intentionally excluded from RPC (file-based only). -
SalLegacyAlgorithmandlegacy_multisigremoved from shekyl-oxide. Deleted the legacy Monero multisig SAL algorithm and test module from the vendoredshekyl-oxide/fcmp/fcmp++crate. Only the modernSalAlgorithm(used byFrostSalSession) is retained. -
16+ new Rust tests for FROST. 4
frost_salunit tests (session creation, pseudo-out distinctness, identity rejection, field roundtrip), 6FrostSigningCoordinatortests (wrong preprocess count, shares before nonces, duplicate shares, nonce sums timing, point addition, bytes roundtrip), 2FrostSalSessionnegative tests, 4frost_dkgunit tests (serialization roundtrip, group key extraction, parameter validation, byte-level roundtrip), 8 FFI lifecycle tests (null safety, invalid data rejection, session handle management), 5shekyl-wallet-coremultisig tests (DKG 2-of-3 and 3-of-5 roundtrips, DKG state machine errors, group serialization, threshold keys roundtrip). -
FCMP++ prove/verify round-trip test.
prove_verify_roundtrip()inrust/shekyl-fcmp/src/proof.rsexercises the full stack: random key generation, single-leaf tree root computation,prove(),verify(), and negative tests (tampered key image, wrong tree root).
๐ Fixed
-
Suppressed vendored crate warnings. Fixed
dead_codewarning forInconsistentWitnessvariant ingeneralized-bulletproofs(only constructed underdebug_assertions) with#[cfg_attr(not(debug_assertions), allow(dead_code))]. Fixed deprecatedGenericArray::as_slice()inhelioseleneciphersuite by replacing withas_ref(). -
Stake-claim vs
verRctSemanticsSimpleconflict. Stake-claim transactions useRCTTypeFcmpPlusPlusPqcbut have no FCMP++ membership proof (they prove ownership via PQC auth on public amounts).ver_non_input_consensusnow excludes stake-claim-only transactions from the RCT semantics batch that rejects emptyfcmp_pp_proof. -
genRctFcmpPlusPlushard-fail on proof failure. Previously logged and returned anrctSigwith an empty proof whenshekyl_fcmp_provefailed; now throwsCHECK_AND_ASSERT_THROW_MESso the wallet catches the error immediately rather than producing an invalid transaction. -
PQC leaf scalar now uses proper Selene field reduction.
PqcLeafScalar::from_pqc_public_keyandhash_pqc_public_keypreviously truncated Blake2b-512 to 32 bytes and cleared bit 255, which could produce non-canonical values exceeding the Selene base field modulus. Now usesHelioseleneField::wide_reduceon the full 64-byte hash for unbiased, canonical field elements. -
Deterministic PQC keygen stability. Replaced
rand::rngs::StdRngwithrand_chacha::ChaCha20Rngfor ML-DSA-65 keypair derivation.StdRng's underlying algorithm is not a stability guarantee acrossrandversions, which could break wallet-restore-from-seed. -
Bech32m variant enforcement.
decode_blobnow strictly enforces the Bech32m checksum variant instead of accepting both Bech32 and Bech32m. Removed unusedEncodingError::EmptyDatavariant.
๐ Security
-
FrostSalSession spend secret zeroized on drop. The FROST SAL session's spend secret scalar is zeroized when the session is dropped, per the project-wide secure memory rule. After the
FrostSalSessionsecret deduplication (see Changed), the secret lives solely inside theSalAlgorithmand is zeroized through itsDropimpl. -
RELEASE-BLOCKER resolved in circuit gadgets. The
incomplete_add_pubfunction in the FCMP++ circuit already receives parameters typed asOnCurve, which guarantees the on-curve constraint. Replaced theRELEASE-BLOCKER(shekyl)comment with documentation explaining why no additional constraint is needed. -
Pruning watermark hardening.
BlockchainLMDB::prune_tx_data()now fails the current batch on missing transaction rows (TX_DNE) instead of logging and continuing, sotx_prune_next_blockcannot advance on partial pruning. -
FCMP++ compile-path compatibility fixes. Updated wallet/core-test FCMP++ construction callsites for the current
genRctFcmpPlusPlusleaf-chunk API, and added explicit cached-chunk torct::fcmp_chunk_entryconversion in wallet construction to keep GCC 14 builds green. -
CI portability and fuzz gate hardening. Replaced GNU-only
xargs -rusage in Cargo absolute-path guard with a portable shell loop, and added a required fuzz-harness inventory smoke gate in Rust CI. -
Stale fuzz targets updated.
fuzz_fcmp_proof_deserializeandfuzz_tx_deserialize_fcmp_type7now pass the requiredsignable_tx_hash7th argument toverify().fuzz_block_header_tree_rootrewritten for the currentProveInputstruct and 4-argprove()signature. -
prune_tx_dataminer output lookup. When storing output-pruning metadata, RCT coinbase outputs are keyed under amount0in LMDB (same asadd_transaction); pruning now uses that amount forget_output_keyinstead of the plaintextvout.amount, avoidingOUTPUT_DNEduring prune for miner transactions.
๐๏ธ Removed
-
RingCT-era dead code excision (C++ wallet). Comprehensive removal of ring-signature infrastructure that is structurally unreachable on an FCMP++ chain. Deleted:
gamma_pickerclass andGAMMA_SHAPE/GAMMA_SCALEconstants,transfer_selected(non-RCT overload),wallet2::get_outsdecoy-fetching overloads (~700 lines),tx_add_fake_output,select_available_mixable_outputs,select_available_outputs_from_histogram,get_spend_proof/check_spend_proof(ring-sig-dependent proofs),get_min_ring_size/get_max_ring_size,m_confirm_non_default_ring_sizepreference, the entireringdb.h/ringdb.cppsubsystem (LMDB ring database), ring commands in simplewallet, spend proof RPC endpoints and FFI dispatch,boroSigstruct fromrctTypes.h, unreachablehf_version < HF_VERSION_FCMP_PLUS_PLUS_PQCbranch incryptonote_tx_utils.cpp,blockchain_blackballutility, andoutput_selection.cppunit test. Removed LMDB link dependency from wallet CMake target. -
Decoy and ring_size removal from Rust RPC. Removed
ring_size: u32parameter fromshekyl-wallet-rpctransfer API (types.rs,wallet.rs,ffi.rs), from the C++ FFI boundary (wallet2_ffi.h/.cpp), and from the C++ wallet RPCestimate_tx_size_and_weightcommand definition. DeletedDecoysstruct,MAX_RING_SIZEconstant,DecoyRpctrait and blanket implementation,OutputInformationstruct,rpc_pointhelper, andtest_decoy_rpctest fromshekyl-oxide. Removed/get_output_distribution.binroute fromshekyl-daemon-rpc. -
Bulletproof v1 ("Original") deletion. Deleted the entire
original/module tree and its tests fromshekyl-bulletproofs. RemovedBulletproof::Originalenum variant, v1prove()/read()functions, v1 match arms inverify/batch_verify/write_core, and the standaloneBulletproofsBatchVerifierstruct. Cleaned up deadinner_productandmul_vecmethods that were only used by v1 code. -
Light wallet support removed. Deleted all
m_light_walletstate,set_light_wallet,light_wallet_login,light_wallet_get_outs,import_outputs,get_unspent_outs,submit_raw_tx, and allif (m_light_wallet)branches fromwallet2.cpp/.h. Deletedwallet_light_rpc.hentirely. Removed light wallet API fromwallet2_api.h/wallet.h/wallet.cpp. Fundamentally incompatible with FCMP++ privacy model (sends view keys to remote server).
๐ Changed
-
MLSAG naming debt resolved. Renamed
get_pre_mlsag_hashtoget_tx_prehash,mlsag_prehash/mlsag_prepare/mlsag_hash/mlsag_signtotx_prehash/tx_prepare/tx_hash/tx_signacross the device interface hierarchy (device.hpp,device_default.hpp/.cpp,device_ledger.hpp/.cpp),rctSigs.cpp/.h, andprotocol.cpp. Renamed LedgerINS_MLSAGconstant toINS_TX_SIGN. These functions are live code repurposed for FCMP++ transaction hashing; the names now reflect their actual role. -
Base58 encoding removed entirely. Deleted
src/common/base58.{h,cpp},tests/unit_tests/base58.cpp,tests/fuzz/base58.cpp, and all CMake references. RemovedCRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX,CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX, andCRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIXconstants from all network namespaces andconfig_t. No code path accepts or produces Base58 strings. -
Legacy address structs removed.
integrated_address,legacy_account_public_address, andlegacy_integrated_addressstructs removed fromcryptonote_basic_impl.cpp. Subaddress and integrated address logic removed from address encoding/decoding chokepoints.
๐ Changed
-
Rust naming convention cleanup. Fixed phantom FFI function reference in
shekyl_pqc_verifydoc comment (referenced non-existentshekyl_pqc_verify_multisig_with_group_id, now points toshekyl_pqc_multisig_group_id). Renamed WindowsSystemInfo.dw_page_sizetopage_size(drop Hungarian notation). Renamedshekyl-wallet-rpc-rsbinary toshekyl-wallet-rpc(drop-rssuffix per Rust API Guidelines). -
Address encoding migrated to Bech32m.
get_account_address_as_str()andget_account_address_from_str()now call Rust FFI (shekyl_address_encode,shekyl_address_decode) for network-aware Bech32m encoding. Thesubaddressparameter is retained for API compatibility but ignored.address_parse_infofieldsis_subaddressandhas_payment_idare always false. -
Wallet proofs use Bech32m blob encoding. Spend proofs, tx proofs (in/out), reserve proofs, message signatures, multisig signatures, and signer keys are now encoded with purpose-specific HRPs via
shekyl_encode_blob/shekyl_decode_blobFFI. Version headers (SpendProofV1,InProofV2, etc.) removed; the HRP now serves as the type discriminator. -
shekyl-crypto-pqre-exportsshekyl-address. Theaddressmodule inshekyl-crypto-pqis now a re-export of the standaloneshekyl-addresscrate. The oldshekyl-crypto-pq/src/address.rshas been deleted. -
Tx-data prune watermark.
prune_tx_datanow storestx_prune_next_block(exclusive next height) instead of ambiguouslast_pruned_tx_data_heightvalues; legacy keys migrate on read/write. LMDB unit tests live intests/unit_tests/tx_data_pruning_lmdb.cpp(minimal block builder only; does not linktests/core_tests/chaingen.cppintounit_tests, avoiding duplicate object code and macOS linker unwind/diagnostic issues in CI). -
FCMP++ Rust dependency source moved in-repo.
shekyl-fcmpnow consumes vendoredshekyl-oxidecrates via path dependencies underrust/shekyl-oxide/instead of git dependencies plus local absolute-path[patch]overrides. This removes host-specific Cargo path failures in CI and keeps builds fully repo-local. -
Upstream sync and portability guardrails. Added vendored snapshot metadata at
rust/shekyl-oxide/UPSTREAM_MONERO_OXIDE_COMMIT, a divergence workflow (.github/workflows/shekyl-oxide-divergence.yml), and build workflow checks that fail on absolute local paths in Cargo manifests/config.
โจ Added
-
--prune-blockchaintransaction-data pruning. LMDB v6 addstxs_pqc_auths(split fromtxs_prunedatpqc_auths_offset), implementsprune_tx_data(batch 256 blocks, output metadata, watermark, TOCTOU height check), default depthCRYPTONOTE_TX_PRUNE_DEPTH(5000),pop_blockguard when verification data is gone, continuous pruning viaupdate_blockchain_pruning, RPCget_transactions.prunedandget_info.tx_prune_height. -
Staking FFI and config-driven tier parameters.
shekyl-stakingnow generates tier lock durations, yield multipliers, and max stake-claim range fromconfig/economics_params.jsonat build time (aligned withshekyl-economics). New FFI:shekyl_calc_per_block_staker_reward(128-bit division with optional overflow flag),shekyl_stake_tier_count,shekyl_stake_tier_name,shekyl_stake_max_claim_range. C++ uses these inblockchain.cpp,core_rpc_server.cpp, andsimplewalletinstead of duplicating tier strings or inlinemul128/div128_64reward math. -
FCMP++ transaction construction helper (
construct_fcmp_tx). New chaingen helper intests/core_tests/chaingen.cppthat builds fully valid FCMP++ transactions during core test replay: tree path assembly from the live LMDB curve tree,genRctFcmpPlusPlusproof generation, KEM decapsulation for per-input PQC keypair derivation, and PQC auth signing. This unblocks 30+ disabled core tests that relied on the oldconstruct_tx_rctstub. -
FCMP++ core test generators (Phase 7). Five new tests in
tests/core_tests/fcmp_tests.cpp:gen_fcmp_tx_valid: end-to-end FCMP++ transaction construction and pool acceptance during replaygen_fcmp_tx_double_spend: second FCMP++ spend of the same output rejectedgen_fcmp_tx_reference_block_too_old: stale referenceBlock rejectedgen_fcmp_tx_reference_block_too_recent: too-recent referenceBlock rejectedgen_fcmp_tx_timestamp_unlock_rejected: timestamp-basedunlock_timerejected
-
Verification caching unit tests. Six new GTest cases in
tests/unit_tests/fcmp.cppvalidatingcompute_fcmp_verification_hashdeterminism, sensitivity to proof/referenceBlock/key-image changes, null return for non-FCMP++ types, and multi-input handling. -
Deferred insertion boundary tests. New
tests/unit_tests/deferred_insertion.cppwith tests for: outputs not drainable before maturity, coinbase maturity window (60 blocks), regular tx maturity window (10 blocks), drain journal atomicity round-trip, and insertion ordering determinism across two DB instances. -
Pending tree add/pop stress test. New
tests/unit_tests/pending_tree_fuzz.cppwith randomized stress test (100 random leaves, multi-height draining), add/remove round-trip, drain journal CRUD, and leaf removal correctness. -
fuzz_tx_deserialize_fcmp_type7Rust fuzz target. New cargo-fuzz target inrust/shekyl-fcmp/fuzz/that exercises FCMP++ proof verification with transaction-structured random inputs: pseudoOuts, proof blobs, PQC hashes, corrupted type bytes, empty proofs, and mismatched input counts. -
Comprehensive staking test suite. New test coverage across C++ and Rust:
tests/unit_tests/staking.cpp: 20+ GTest unit tests coveringtxin_stake_claimandtxout_to_staked_keyserialization round-trips, reward integer math (includingmul128/div128_64vsdoubledivergence at large values), helper function coverage (get_inputs_money_amount,check_inputs_overflow,check_inputs_types_supported,get_output_staking_info,set_staked_tx_out), stake weight/tier FFI validation, and variant type handling.tests/core_tests/staking.cpp+staking.h: 18 chaingen core tests covering staking lifecycle (stake output creation), invalid claim rejection (inverted range, oversized range, future height, wrong watermark, wrong amount, non-staked output, output not in tree), lock period enforcement (invalid tier), rollback correctness (pool balance, watermark), txpool handling, sorted-input enforcement, and multi-tier staking.rust/shekyl-staking/src/tiers.rs: 10 edge-case tests including exhaustive invalid tier ID rejection, ordering invariants for yield multiplier and lock blocks, contiguous ID verification, and positive parameter assertions.rust/shekyl-staking/fuzz/fuzz_targets/fuzz_claim_reward.rs: cargo-fuzz target that generates random accrual records and verifies reward computation invariants (no overflow, reward <= pool, weight monotonicity, cumulative bounds).
๐ Changed
-
Universal deferred curve-tree insertion (Decision 15). All outputs (coinbase, regular, staked) now enter the
pending_tree_leavestable at creation and drain into the curve tree only after their type-specific maturity height (coinbase: +60, regular: +10, staked: max(effective_lock_until, +10)). Thepending_staked_*identifiers were renamed topending_tree_*across all database interfaces. The drain journal (pending_tree_drain) now stores full 136-byte entries (maturity_height + leaf_data) for exactpop_blockreversal instead of just a drain count.pop_blockrestores drained leaves to pending and removes the popped block's own pending entries. -
FCMP_REFERENCE_BLOCK_MIN_AGE reduced to 5 (Decision 14). With maturity enforced by deferred tree insertion, MIN_AGE now serves only as a reorg safety margin (5 blocks โ 10 minutes). The old static_asserts tying MIN_AGE to unlock windows have been removed.
-
Timestamp-based
unlock_timerejected (Decision 13). Transactions withunlock_time >= CRYPTONOTE_MAX_BLOCK_HEIGHT_SENTINEL(500M) are now rejected incheck_tx_outputs. Only height-based lock times are accepted. -
prune_tx_datastatus clarification. The output-metadata pruning loop indb_lmdb.cppis a plumbing-only stub (TODO(phase6f)). Thestore_output_metadata,get_output_metadata, andis_output_prunedinterfaces are live, but the block-iteration pruning loop does not execute.
๐๏ธ Removed
-
Vestigial hard fork constants. Removed
HF_VERSION_CLSAGandHF_VERSION_MIN_V2_COINBASE_TXfromcryptonote_config.h. All test references replaced with literal1. -
Legacy tests incompatible with FCMP++ consensus. Disabled 30+ core and unit tests that relied on Monero-era transaction construction (
RCTTypeBulletproofPlus, CLSAG ring signatures, v1/v2 transactions):tests/core_tests/chaingen_main.cpp: Disabledgen_simple_chain_001,gen_simple_chain_split_1,gen_chain_switch_1,gen_ring_signature_1,gen_ring_signature_2, alltxpool_*tests, allgen_double_spend_*tests,gen_block_reward, allgen_bpp_*Bulletproofs+ tests, and severalgen_tx_*tests whose setup required valid user transactions. These tests construct transactions viaMAKE_TX/construct_tx_rctwhich produceRCTTypeFcmpPlusPlusPqcstubs with emptypqc_auths, rejected bycheck_tx_inputseven in FAKECHAIN mode.tests/unit_tests/bulletproofs.cpp: All three weight tests (weight_equal,weight_more,weight_pruned) prefixed withDISABLED_and hex blobs removed. Shekyl'srctSigBaseserialization rejects any type other thanRCTTypeFcmpPlusPlusPqc(type 7), so oldRCTTypeBulletproofPlus(type 6) blobs fail to deserialize.- Re-enabling requires a chaingen FCMP++ transaction generator that produces valid PQC auth signatures and curve-tree membership proofs.
๐ Changed
-
Upstream monero-oxide dependencies renamed to shekyl-oxide. Updated
shekyl-fcmp/Cargo.tomland all Rust source files to use the renamed packages from the monero-oxide fork (monero-fcmp-plus-plusโshekyl-fcmp-plus-plus,monero-generatorsโshekyl-generators).Cargo.lockadvanced from pin92af05eto416d8d1which includes the completemonero-oxide/โshekyl-oxide/directory and package rename. -
shekyl-fcmpcrate cleanup. Removed unusedsha2andshekyl-crypto-pqdependencies fromrust/shekyl-fcmp/Cargo.toml. Renamed the misleadingProveError::InputCountMismatchvariant toProveError::PqcHashMismatchwith a clearinput_indexfield indicating which input has a mismatched leafh_pqcvspqc_authcommitment.
๐ Fixed
-
Private member access in pending tree unit tests. Fixed 18 compile errors in
pending_tree_fuzz.cppand 4 indeferred_insertion.cppon macOS CI where calls toadd_pending_tree_leaf,drain_pending_tree_leaves,add_pending_tree_drain_entry,get_pending_tree_drain_entries,remove_pending_tree_drain_entries, andremove_pending_tree_leafwere calling private overrides onBlockchainLMDB. Changed all test methods to useBlockchainDB&references, accessing the public base class interface. -
CI compile errors across all platforms. Fixed compilation failures in the new staking and FCMP++ test suites:
tests/core_tests/staking.cpp: Added missingfill_tx_sourcesdeclaration tochaingen.hand movedBlockchain::check_stake_claim_inputfrom the private section to the public API so core tests can call it withoutIN_UNIT_TESTS.tests/unit_tests/fcmp.cpp: Fixed serialization calls to usedo_serialize(ar, v)instead of non-existentv.serialize(ar)member; replacedbinary_archive<false>(istringstream&)with the correctbinary_archive<false>(span<const uint8_t>)constructor; fixedshekyl_pqc_verifycall to include the requiredscheme_idfirst argument and corrected parameter order.tests/unit_tests/staking.cpp: Samebinary_archive<false>constructor fix โ replacedistringstreamwithepee::span<const uint8_t>in all four serialization round-trip tests.- macOS CI: Added
zstdto Homebrew dependencies and fixed CMake to usePkgConfig::ZSTDimported target instead of bare library name, resolvingld: library 'zstd' not foundon macOS Homebrew where the library lives in a non-standard path (/opt/homebrew/lib).
-
RPC estimate_claim_reward floating-point precision bug. The
on_estimate_claim_rewardRPC handler useddouble-precision arithmetic for reward estimation, which diverges from the consensusmul128/div128_64path whentotal_weighted_stake > 2^53. Fixed to use identical 128-bit integer math, ensuring wallet reward estimates always match consensus.
๐ Fixed
-
FCMP++ wallet precompute metadata and input consistency checks.
transfer_selected_rctand multisig proof prep now read tree depth from RPC metadata (tree_depth) instead ofpath_blob[0], enforce that all selected inputs share the same reference block/depth snapshot, and reject empty precomputed paths. This fixes silent spend-construction failures. -
Stake-claim input routing in consensus verification.
Blockchain::check_tx_inputsnow routes puretxin_stake_claimtransactions through the claim-specific input checks before generic FCMP++txin_to_keyvalidation, preventing incorrect rejection of valid stake-claim transactions that useRCTTypeFcmpPlusPlusPqc. -
Stake-claim reward math overflow defense. Added a defensive
q_hi != 0check afterdiv128_64in claim reward computation, rejecting impossible overflow states instead of silently truncating. -
Claim transaction PQC signing correctness/performance. Removed wallet master-key fallback for claim input signing and now require per-output shared-secret rederivation for all claim inputs. Claim signing keypairs are derived once per input and reused for both
pqc_authspublic key and signature generation. -
Curve-tree path RPC returns spendable reference block.
get_curve_tree_pathnow returns areference_blockat leastFCMP_REFERENCE_BLOCK_MIN_AGE + 1behind tip, avoiding immediate mempool rejection of freshly built transactions that used a too-recent tip anchor. -
PQC derivation index correctness and duplicate derivation overhead. Spend-path and multisig PQC key derivation now use
m_internal_output_index(matching KEM encapsulation/decapsulation) and derive each per-input keypair once per transaction, reusing it for bothH(pqc_pk)and signing. -
Staked-output FCMP++ path precompute filtering. Wallet precompute/incremental updates now skip still-locked staked outputs (
m_stake_lock_until > current_height) to avoid daemon path lookup errors. -
Stake-claim rollback completeness.
BlockchainDB::remove_transactionnow fully reversestxin_stake_claimstate on reorg: watermark is restored to its pre-claim value (or removed for first-time claims) and the claimed amount is credited back into the staker reward pool. Previously only the spent key was removed, leaving claim-progress accounting permanently advanced after a reorg. -
Txpool key-image handling for stake claims. All six txpool functions that walk transaction inputs (
insert_key_images,remove_transaction_keyimages,have_tx_keyimges_as_spent,have_key_images,append_key_images,mark_double_spend) now handletxin_stake_claiminputs alongsidetxin_to_key. Previously they usedCHECKED_GET_SPECIFIC_VARIANT(..., txin_to_key, ...)which caused immediate false-return on any stake-claim input, breaking mempool bookkeeping for claim transactions. -
remove_transaction_keyimagesno longer returns early on error. The function now continues removing remaining key images instead of aborting at the first mismatch, eliminating the partial-cleanup semantics noted by the long-standing FIXME. -
Core helper support for
txin_stake_claim.get_inputs_money_amountandcheck_inputs_overflownow handle bothtxin_to_keyandtxin_stake_claiminput variants instead of failing on the latter. These are called unconditionally for all transactions (viacheck_money_overflow), so the old hard-cast totxin_to_keywould reject any transaction containing a stake claim.
๐ Security
-
FFI buffer zeroization before free.
shekyl_buffer_freenow wipes buffer contents prior to deallocation, reducing secret-key residue risk in allocator-managed memory. -
Wallet KEM key management fix.
generate_pqc_key_material()now generatesHybridX25519MlKemKEM keypairs viashekyl_kem_keypair_generate()instead ofHybridEd25519MlDsasigning keypairs. The wallet-level PQC keys (m_pqc_public_key/m_pqc_secret_key) are encapsulation/decapsulation keys; per-output ML-DSA-65 signing keys are always derived from the KEM shared secret at spend time. -
Full hybrid ciphertext storage in tx_extra tag 0x06. All KEM encapsulation sites (coinbase, claim, regular transfers) now store the complete 1120-byte hybrid ciphertext (
x25519_ephemeral_pk[32] || ml_kem_ct[1088]) instead of only the ML-KEM portion. This enables correct hybrid decapsulation during wallet scanning and seed restore.
โจ Added
-
FCMP++ wallet transaction construction (Phase 5).
transfer_selected_rctnow builds transactions using full-chain membership proofs instead of ring signatures:- Inputs contain only the real output (no decoy selection).
genRctFcmpPlusPlusgenerates the combined Bulletproofs+ and FCMP++ membership proof.- Per-input PQC auth signatures use ML-DSA-65 keypairs derived from the KEM shared secret and output index.
construct_tx_with_tx_keyadds KEM encapsulation (tag 0x06) andH(pqc_pk)leaf hashes (tag 0x07) for each output, and skips wallet-level PQC signing.
-
KEM decapsulation during wallet scanning.
process_new_transactionnow extracts hybrid KEM ciphertexts fromtx_extratag 0x06, callsshekyl_kem_decapsulatewith the wallet's KEM secret keys, and stores the resulting 64-byte combined shared secret intransfer_details::m_combined_shared_secret. This enables per-output PQC key derivation at spend time. -
FCMP++ fee estimation.
estimate_rct_tx_sizenow accounts for the FCMP++ membership proof size (shekyl_fcmp_proof_len), per-input PQC auth envelopes (~5400 bytes each), and per-output KEM ciphertexts and leaf hashes. -
GUI wallet QR code. Receive page now renders a real QR code encoding the full FCMP++ Bech32m address via
qrcode.react. -
GUI wallet fee preview. Send page shows an estimated transaction fee before submission, debounced as the user types.
๐๏ธ Removed
-
CLSAG device interface methods. Removed
clsag_prepare,clsag_hash, andclsag_signvirtual methods fromdevice.hppand all implementations (device_default.cpp,device_ledger.cpp). Shekyl never supported CLSAG; the device interface now only exposes FCMP++ methods. -
get_outs/get_outs.binRPC endpoints. Removed the ring member fetching endpoints from the C++ daemon (core_rpc_server), the FFI dispatch tables (core_rpc_ffi.cpp), and the Rust daemon RPC (shekyl-daemon-rpc). FCMP++ uses full-chain membership proofs; there is no decoy selection. -
Dead hard fork constants. Removed
HF_VERSION_MIN_MIXIN_4/6/10/15,HF_VERSION_SAME_MIXIN,HF_VERSION_ENFORCE_MIN_AGE,HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY,HF_VERSION_REJECT_SIGS_IN_COINBASE,HF_VERSION_ENFORCE_RCT,HF_VERSION_DETERMINISTIC_UNLOCK_TIMEfromcryptonote_config.h. These were defined but never referenced in production code.HF_VERSION_CLSAGandHF_VERSION_MIN_V2_COINBASE_TXare retained for test compilation until Phase 7 rewrites the legacy tests.
โจ Added
- Zstd compression for Levin P2P relay (Phase 6e). P2P payloads above
256 bytes are transparently compressed with zstd (level 1) before relay.
A new
LEVIN_PACKET_COMPRESSEDflag (0x10) in the Levin header marks compressed frames. Peers negotiate compression viaP2P_SUPPORT_FLAG_ZSTD_COMPRESSION(0x02) in the handshake support flags. Reduces relay bandwidth by ~10-20% for FCMP++ transactions, especially important for Tor/I2P connections. Compression is optional at compile time (requires libzstd); decompression always succeeds if the flag is set.
๐ Documentation
- Updated
DAEMON_RPC_RUST.md. Fixed stale references toget_outs.binandget_curve_tree_root; corrected endpoint counts and cutover test steps.
๐ Fixed
-
rct::keymissingoperator!=. Addedoperator!=to thekeystruct inrctTypes.h. The operator was present for cross-type comparisons (rct::keyvscrypto::public_key) but not forrct::keyvsrct::key, causing compilation failures on all platforms when comparing pseudo-outs to expected zero-commitments in the stake claim verification path. -
MSVC
binary_archiveconstructor mismatch. Fixedwallet2.cppto useepee::strspan<std::uint8_t>instead ofstd::istringstreamfor constructingbinary_archive<false>, which MSVC could not resolve. -
Memory leak on exception in PQC auth signing. Added RAII scope guard for
ShekylPqcKeypairbuffers intransfer_selected_rctPhase C, ensuring Rust-allocated key material is freed even ifTHROW_WALLET_EXCEPTION_IFthrows mid-loop. -
Secret key material not wiped on KEM decapsulation failure. The stack buffer in
process_new_transactionKEM decapsulation is now wiped unconditionally (success or failure), preventing partial key material from lingering on the stack. -
Shadowed
tx_extra_fieldsvariable in KEM decapsulation. Removed redundant innertx_extra_fieldsreference that shadowed the outer one inprocess_new_transaction, using the already-resolved outer reference instead.
๐ Changed
-
Decoy selection functions are dead code.
get_outs,tx_add_fake_output, andlight_wallet_get_outsinwallet2.cppare no longer called from the active transfer path. They remain in the codebase for reference and will be removed in a follow-up cleanup. -
Claim transaction indistinguishability (Phase 4 โ CRITICAL). Rewrote
wallet2::create_claim_transaction()to produce privacy-preserving claim transactions that blend into the anonymity set:- Uses
RCTTypeFcmpPlusPlusPqcwith Bulletproofs+ range proofs instead ofRCTTypeNullwith plaintext amounts. - Adds a dummy change output (amount = 0) to match the standard 2-output transaction structure, preventing structural fingerprinting.
- Performs hybrid KEM derivation (X25519 + ML-KEM-768) via
shekyl_fcmp_derive_pqc_keypair()for per-output PQC keys instead of reusing the wallet master PQC key. - Embeds ML-KEM ciphertexts in
tx_extraunder tag0x06andH(pqc_pk)leaf hashes under new tag0x07. - Signs with per-output KEM-derived PQC keys, not the wallet-level key.
- Sets deterministic pseudo-outs (
zeroCommit(claim_amount)) for each stake claim input to satisfy the Bulletproofs+ balance check.
- Uses
-
Consensus rejects
RCTTypeNullfor non-coinbase v3 transactions.check_tx_inputsnow enforces that only coinbase (txin_gen) may useRCTTypeNull. All other v3 transactions (including stake claims) must useRCTTypeFcmpPlusPlusPqcwith confidential amounts. Claim transactions are validated within the FCMP++ handler with their own sub-path that verifies pseudo-out determinism, PQC ownership, and pool balance while skipping the membership proof (which is not applicable totxin_stake_claiminputs).
โจ Added
-
TX_EXTRA_TAG_PQC_LEAF_HASHES(0x07). Newtx_extrafield (tx_extra_pqc_leaf_hashes) stores per-outputH(pqc_pk)values โ the 32-byte Blake2b-512 hashes of each output's derived ML-DSA-65 public key. Used by curve tree insertion to commit the correct PQC ownership hash to each leaf instead of a zero placeholder. -
Curve tree leaves use actual
H(pqc_pk)fromtx_extra. Thecollect_outputs/make_leafpath inblockchain_db.cppnow extractsH(pqc_pk)values from the0x07tag, replacing the zero placeholder that was previously committed to the 4th leaf scalar. This enables the PQC ownership cross-check for stake claim verification. -
Coinbase transactions emit
H(pqc_pk)leaf hashes.construct_miner_txnow derives per-output PQC keypairs via KEM shared secrets and includes theirH(pqc_pk)values in the0x07tx_extrafield alongside the existing KEM ciphertexts in0x06.
๐ Security
-
Integer-only stake reward computation. Replaced floating-point arithmetic (
(double)total_reward * weight / total_weighted_stake) with 128-bit integer math (mul128/div128_64) incheck_stake_claim_inputto eliminate rounding errors that could cause determinism mismatches across platforms. -
Batch pool balance validation for stake claims. Moved the staker pool balance check from per-claim (
check_stake_claim_input) to a batch check incheck_tx_inputsthat sums all claim amounts first. Prevents multiple claims in the same block from independently passing the balance check and overdrawing the pool. -
PQC ownership cross-check on stake claims. Each
txin_stake_claimnow verifies that theH(pqc_pk)stored in the curve tree leaf (bytes 96โ128) matchesshekyl_fcmp_pqc_leaf_hash(pqc_auths[i].hybrid_public_key), preventing reward claims for outputs the claimer does not own the PQC key for.
๐ Fixed
- Stake claim key image cleanup on reorg.
remove_transactioninblockchain_db.cppnow handlestxin_stake_claimkey images in addition totxin_to_key, preventing stale key images from persisting after block pops.
๐ Changed
-
Sorted input enforcement extended to stake claims. The sorted-inputs check in
check_tx_inputsnow covers bothtxin_to_keyandtxin_stake_claimkey images, ensuring consistent ordering rules across all input types. -
Third-party headers treated as SYSTEM includes.
external/,external/rapidjson,external/easylogging++, andexternal/supercopare now-isystemin CMake, suppressing-Wsuggest-overrideand other warnings from third-party code while keeping strict warnings for first-party code.
๐๏ธ Removed
-
Dead
check_ring_signaturefunction. Removed unused ring signature verification fromblockchain.cppand its declaration fromblockchain.h. Shekyl uses FCMP++ from genesis; ring signatures are never validated. -
Dead
expand_transaction_2function. Removed the no-op transaction expansion function fromblockchain.cppand its declaration fromblockchain.h. FCMP++ does not use mixRing expansion. -
Dropped
serde_jsondev-dependency fromshekyl-fcmp. Replaced the JSON round-trip test with a byte-level serialization check, reducing the dev-dep surface.
๐ Documentation
- Synced
docs/FCMP_PLUS_PLUS.mdcurve-tree text with consensus: outputs are indexed at creation; maturity is enforced viareferenceBlockand other rules, not by delaying leaf insertion. - Clarified
docs/POST_QUANTUM_CRYPTOGRAPHY.mdto usepqc_auths(per-input) terminology consistently. - Documented mempool FCMP verification-cache id:
compute_fcmp_verification_hashbinds proof +referenceBlock+ key images (comment inblockchain.cpp). - Noted the monero-oxide commit pin in
rust/shekyl-fcmp/Cargo.tomlcomments (lockfile remains authoritative). - Updated
docs/STAKER_REWARD_DISBURSEMENT.mdwith integer arithmetic, batch pool check, PQC cross-check, and sorted input consensus rules.
โจ Added
-
Block-inclusion FCMP++ cache fast path. When a transaction was previously verified in the mempool and arrives in a block,
check_tx_inputsskips the expensiveshekyl_fcmp_verifyFFI call (~35ms/input) while still running all structural checks (referenceBlock, depth, key images, PQC auth). -
construct_leafnow accepts PQC key hash parameter. The Rust FFI functionshekyl_construct_curve_tree_leaftakes a 4thh_pqc_ptrargument (32 bytes) to set the 4th leaf scalar. Callers pass zero bytes until per-output PQC commitments are wired in Phase 3. -
Deferred staked leaf insertion infrastructure. Added
pending_staked_leaves(LMDB DUPSORT/DUPFIXED table keyed bylock_until_heightwith 128-byte leaf values) andpending_staked_drain(block_height โ drain count) tables to the blockchain database layer. Five new methods onBlockchainDB:add_pending_staked_leaf,drain_pending_staked_leaves,set_pending_staked_drain_count,get_pending_staked_drain_count, andremove_pending_staked_drain_count. This enables staked outputs whoseeffective_lock_until > block_heightto be parked in a pending table and batch-inserted into the curve tree when they mature. -
Comprehensive FCMP++ test suite and fuzz targets (Phase 7). Added 6
cargo-fuzztargets acrossrust/shekyl-fcmp/fuzz/(proof deserialization, curve tree leaf hashing, block header tree root mismatch) andrust/shekyl-crypto-pq/fuzz/(Bech32m address decoding, KEM decapsulation with corrupted ciphertexts). Extended Rust unit tests inproof.rs,tree.rs,leaf.rs,kem.rs,address.rs, andderivation.rscovering prove/verify round-trips, hash grow/trim inverse properties, boundary values, and cross-crate consistency. Extended C++ unit tests intests/unit_tests/fcmp.cppwith RCTTypeFcmpPlusPlusPqc serialization round-trip, key image y-normalization, referenceBlock staleness constants, and empty proof rejection. Added PQC rederivation criterion benchmark (rust/shekyl-crypto-pq/benches/pqc_rederivation.rs) targeting < 100ms per output for the full ML-KEM-768 decapsulation + HKDF-SHA-512 + ML-DSA-65 keygen pipeline. -
Stressnet tooling for FCMP++ pre-audit gate (Phase 7.7). Added
tests/stressnet/with configuration, load generator, and monitoring scripts for a 4-week sustained-load testnet. The stressnet exercises curve tree growth, verification caching, wallet restore correctness, pruned vs. full node storage, staking lifecycle, and block validation latency under near-block-weight-limit load. Includesconfig.yamlwith load profiles,load_generator.pyfor synthetic transaction submission, andmonitor.pyfor real-time metric collection, consensus checking, and daily report generation. -
Security audit scope document (Phase 9). Added
docs/AUDIT_SCOPE.mddefining the scope for a third-party security review of the 4-scalar leaf circuit modification. Covers soundness, zero-knowledge, and completeness verification for theH(pqc_pk)extension, Shekyl fork modifications to monero-fcmp-plus-plus, PQC commitment binding, and the FFI verification boundary. Includes materials list, auditor guidance questions, success criteria, and timeline. -
Mainnet gate: stressnet and audit prerequisites in release checklist. Updated
docs/RELEASE_CHECKLIST.mdwith "Stressnet stable for 4 consecutive weeks" and "4-scalar leaf circuit audit completed" as hard prerequisites for mainnet launch.
๐ Changed
-
Renamed
src/ringct/tosrc/fcmp/for naming consistency. Shekyl does not use ring signatures; the directory now reflects the actual FCMP++ confidential transaction system. CMake targets renamed fromringct/ringct_basictofcmp/fcmp_basic. All#include "ringct/..."paths updated across 44 source and test files. Log categories, user-facing strings ("RingCT" โ "FCMP"), JSON keys, and documentation updated. Therct::namespace is preserved for now as a separate future rename. -
Unified coinbase transaction version to v3.
construct_miner_txandbuild_genesis_coinbase_from_destinationsnow emittx.version = 3, matching regular FCMP++ transactions. Allminer_tx && tx.version == 2checks have been widened to>= 2acrossblockchain_db,blockchain,wallet2, and test infrastructure. Thepqc_authsserialization gate (!txin_gen) already excluded coinbase, so v3 coinbase serializes identically to v2 minus the version byte.
๐ Fixed
-
Fixed wallet API compilation errors after ring-signature removal.
wallet/api/wallet.cppstill referenced the undefinedfake_outs_countvariable and calledestimate_feewith the old 12-argument signature. Replacedfake_outs_countwith0(FCMP++ has no decoys) and updatedestimateTransactionFeeto use the simplified 8-argumentestimate_feesignature with hardcodeduse_per_byte_fee=true,use_rct=true,use_view_tags=true. -
Fixed CI build failure from removed legacy RCT types in test files. Stripped all references to removed
rct::Bulletproof,rct::RCTConfig,rct::RangeProofType,rct::RCTTypeBulletproofPlus,rct::clsag,rct::proveRctCLSAGSimple/verRctCLSAGSimple, andrct::genRctSimplefrom:chaingen.h/.cpp,bulletproof_plus.cpp/.h,chain_switch_1.cpp,wallet_tools.h/.cpp,bulletproofs.cpp(unit),ringct.cpp(unit),serialization.cpp(unit),ver_rct_non_semantics_simple_cached.cpp,json_serialization.cpp,fuzz/bulletproof.cpp, and all performance test headers. Removed legacy-only test cases; updated shared test helpers to dropRangeProofType/bp_versionparameters.
๐๏ธ Removed
- Dead verification cache code (
verRctNonSemanticsSimple,ver_rct_non_semantics_simple_cached). Removed the stubverRctNonSemanticsSimplefromrctSigs.cpp/.h(returnedtrueunconditionally), thever_rct_non_semantics_simple_cachedwrapper and itsver_rct_non_semhelper fromtx_verification_utils.cpp/.h, the unusedrct_ver_cache_ttype alias andm_rct_ver_cachemember fromBlockchain, and the deadRCT_CACHE_TYPEconstant fromcheck_tx_inputs. Real FCMP++ verification lives incheck_tx_inputs(blockchain.cpp) and the mempool usescompute_fcmp_verification_hashfor caching.
๐ Security
-
CRITICAL: PQC signed payload now binds to prunable FCMP++ data (Phase 4c).
get_transaction_signed_payloadnow includesH(serialize(RctSigPrunable))in the signed payload, binding PQC signatures to the FCMP++ proof, pseudoOuts, curve_trees_tree_depth, and Bulletproofs+. Without this, an attacker could substitute different prunable data without invalidating PQC signatures, breaking the dual-layer security model. -
CRITICAL: Wired stake claim validation in
check_tx_inputs(Phase 4e audit fix). The non-FAKECHAIN gate incheck_tx_inputsrejected allRCTTypeNulltransactions, which includes pure stake-claim txs. The gate now allowsRCTTypeNulltransactions through when all inputs aretxin_stake_claim. Additionally, theRCTTypeNullswitch case now callscheck_stake_claim_inputfor each claim input and checks key image double-spend โ previously itbreaked without any validation. -
HIGH: Bound all inputs' H(pqc_pk) hashes into PQC signed payload.
get_transaction_signed_payloadnow appendsH(pqc_pk_0) || ... || H(pqc_pk_{N-1})after the per-input header blob, preventing key-substitution attacks where an attacker replaces one input's PQC key without invalidating other signatures. -
MEDIUM: Stake claim curve tree leaf verification (Phase 4e).
check_stake_claim_inputnow verifies the staked output's leaf is present in the curve tree by checkingstaked_output_index < get_curve_tree_leaf_count()and reading the leaf withget_curve_tree_leaf(). Previously, only the lock period check was performed, which didn't guarantee the leaf had been inserted into the tree. -
MEDIUM: PQC
auth_versionandflagsconsensus enforcement.verify_transaction_pqc_authnow rejectsauth_version != 1andflags != 0, enforcing spec steps 6a/6c. Previously these fields were serialized and signed over but never validated. -
LOW: Single-signer
hybrid_public_keysize enforcement.verify_transaction_pqc_authnow verifies single-signer key blobs are exactlyHYBRID_SINGLE_KEY_LEN(1996 bytes). Previously only multisig keys had size bounds checks; single-signer keys relied solely on the FFI call to reject malformed keys. -
LOW: Added deserialization size bounds for
pqc_authenticationblobs.hybrid_public_keyandhybrid_signaturevectors are now rejected during deserialization if they exceedPQC_MAX_PUBLIC_KEY_BLOBorPQC_MAX_SIGNATURE_BLOB, preventing memory-exhaustion attacks via oversized PQC fields.
๐ Fixed
-
HIGH: Fixed
pop_block()off-by-one for staked-output curve tree removal. The height used for staked-output eligibility checking was captured afterremove_block(), using the post-pop height instead of the removed block's height. This caused a mismatch withadd_block()'s logic: outputs added at the exact lock boundary were inserted during add but not removed during pop, leaving orphaned leaves in the curve tree. -
HIGH: Fixed
pseudoOutsserialization mismatch in genericrctSigBase. The genericBEGIN_SERIALIZE_OBJECT()path inrctSigBaseunconditionally includedpseudoOuts, even forRCTTypeFcmpPlusPlusPqcwhere pseudo-outs live in the prunable section. Now gated withif (type != RCTTypeFcmpPlusPlusPqc)to match the custom serializer. -
MEDIUM:
get_curve_tree_pathRPC now fails on missing layer hashes. Previously, a failedget_curve_tree_layer_hash()silently inserted zeros into the proof path, potentially generating invalid proofs from inconsistent DB state. Now returnsCORE_RPC_ERROR_CODE_INTERNAL_ERROR. -
CRITICAL: Fixed incorrect existing_child in internal layer hash propagation (
grow_curve_tree). When updating an existing child chunk's hash, the parent's Pedersen commitment was computed withexisting_child = 0instead of the previous cycle-scalar. This produced wrong chunk hashes for any block that updated (rather than created) a child chunk. The fix tracks both old and new hashes throughupdated_chunk_tand passes the previous cycle-scalar tohash_grow. -
CRITICAL: Replaced O(N)
trim_curve_treewith incrementalhash_trim. Reorgs previously read all remaining leaves, cleared the tree, and rebuilt from scratch โ a liveness risk at scale. The new implementation useshash_trim_selene/hash_trim_heliosFFI to surgically update only the affected chunks, then propagates the oldโnew deltas up through internal layers. Complexity is now O(removed ร log N). -
CRITICAL: Enforced output maturity via
FCMP_REFERENCE_BLOCK_MIN_AGE. Outputs enter the curve tree at creation time (maximising the anonymity set). Maturity is enforced at spending time by requiring the reference block to be at leastCRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW(60) blocks behind the tip. Addedstatic_asserts incryptonote_config.hto prevent regression. -
HIGH: Validated meta reads in
save_curve_tree_checkpoint. The function now checks that root, depth, and leaf_count were all successfully read from meta before storing a checkpoint. If any value is missing or leaf_count is 0, the checkpoint is skipped with a log warning instead of storing a corrupt zero-valued checkpoint.
๐ Changed
-
Consensus:
curve_trees_tree_depthvalidation now accepts<= current. The referenceBlock's tree may have fewer layers than the current tip (depth is monotonically non-decreasing). The strict!=check was replaced with a range check(0, current_depth], and the FCMP++ proof verifier provides the authoritative depth validation. -
Consensus: Removed ring-based validation path from
check_tx_inputs. Shekyl starts at genesis with FCMP++; the legacy ring-signature per-input validation is unreachable dead code. Theelsebranch now immediately rejects non-FCMP++ transactions with a clear error message. -
Coinbase KEM: Added warning when miner address lacks PQC public key. If a miner's address has no PQC key at the FCMP++ hard fork, a warning is logged noting that the output will have
H(pqc_pk) = 0in the curve tree โ a distinguishable pattern. -
RPC: Replaced hardcoded chunk widths with FFI calls.
get_curve_tree_pathnow callsshekyl_curve_tree_selene_chunk_width()andshekyl_curve_tree_helios_chunk_width()instead of using static constants. -
RPC: Added
reference_heightandleaf_counttoget_curve_tree_pathresponse. Wallets can now verify response freshness and detect stale paths without parsing the reference block hash. -
RPC: Added
MAX_OUTPUTS_PER_RPC_REQUEST(64) rate limit toget_curve_tree_pathto prevent abuse from unbounded requests.
โจ Added
-
RPC:
get_curve_tree_infoendpoint returns root hash, depth, leaf count, and chain height for the current curve tree state. -
RPC:
get_curve_tree_checkpointendpoint retrieves a stored checkpoint (root, depth, leaf_count) at a given block height, needed for fast-sync.
๐ Documentation
- Documented
verRctNonSemanticsSimplestub status: the FCMP++ membership proof is verified in the main consensus path (check_tx_inputs), not in the verification-caching path. Added TODO for Phase 5 unification. Documented coinbaseโ superseded: coinbase is now version 3, unified with regular transactions.tx.version = 2rationale- Documented LMDB post-delete cursor contract (
MDB_GET_CURRENTaftermdb_cursor_delreturns the next item) in pruning and GC loops. - Added
ct_layer_chunk_keybit-layout comment explaining the 8-bit layer / 56-bit chunk index encoding for LMDB integer keys. - Documented
construct_leafzero 4th scalar (H(pqc_pk)) and the tree rebuild requirement when PQC per-output keys are activated. - Documented depth tracking semantics (root layer index, not layer count) and
root detection invariant in
grow_curve_tree. - Added TODO for async/batched checkpoint+pruning in
add_block. - Documented
get_curve_tree_rootempty-tree return semantics (returnshash_init, callers should checkleaf_count).
๐๏ธ Removed
- Legacy RCT and mixin references stripped from wallet layer. Completed
the wallet-side refactor removing all references to legacy ring sizes,
adjust_mixin,default_mixin,m_default_mixin,RCTConfig, and mixin-count parameters:wallet2.h: Removedestimate_feemixin/bulletproof/clsag params,adjust_mixin(),default_mixin()getter/setter,m_default_mixinmember,rct_configfrompending_txandtransfer_selected_rct.wallet2.cpp: Removed mixin fromestimate_rct_tx_size,estimate_tx_size,estimate_tx_weight,estimate_feesignatures and all call sites. Removedadjust_mixin()definition, JSON serialization ofdefault_mixin, constructor initialization. Removedconst bool clsag/bulletproof/bulletproof_plus = truepatterns.wallet_errors.h: Removedmixin_countfield fromnot_enough_outs_to_mixerror struct.wallet2_ffi.cpp: Replacedadjust_mixincalls with constant0.wallet_rpc_server.cpp: Replacedadjust_mixincalls with constant0.wallet2_api.h,wallet.h,wallet.cpp: Removedmixin_countparameter fromcreateTransactionandcreateTransactionMultDest.unsigned_transaction.cpp: Simplifiedmixin()andminMixinCount()to always return 0 (FCMP++ has no explicit mixin).simplewallet.cpp: Removed ring-size parsing,adjust_mixincalls, anddefault_mixindisplay. All fake_outs_count set to 0.
- Legacy RCT references stripped from all src/ files. Removed all
remaining references to CLSAG, legacy RCT types,
RCTConfig,mixRing, andlow_mixinfrom device drivers, Trezor protocol, RPC handlers, blockchain verification, transaction utilities, wallet, and serialization:device_ledger.cpp: RemovedINS_CLSAGdefine, legacy type branches inmlsag_prehash, replacedclsag_prepare/clsag_hash/clsag_signwith FCMP++ TODO stubs.protocol.cpp/protocol.hpp(Trezor): Removedrct::Bulletproofvariant,is_simple()/is_req_bulletproof()/is_bulletproof()/is_clsag()helpers,mixRingresize, CLSAG deserialization instep_final_ack. Addedis_fcmp_pp()helper.core_rpc_server.cpp/core_rpc_server_commands_defs.h: Removedlow_mixinfield and its assignment from send_raw_tx response.daemon_handler.cpp: Removedm_low_mixinerror branch.verification_context.h: Removedm_low_mixinfromtx_verification_context.blockchain.cpp: Replaced legacy mixin-checking branch with a reject gate for non-FCMP++ transactions (Shekyl only supports FCMP++).cryptonote_tx_utils.h/.cpp: Removedrct::RCTConfigparameter fromconstruct_tx_with_tx_keyandconstruct_tx_and_get_tx_key. ReplacedgenRctSimplecall with FCMP++ proof generation stub. RemovedmixRingconstruction.cryptonote_format_utils.cpp: Removedis_rct_bulletproof/is_rct_clsagcalls, simplified BP+ weight calculations.cryptonote_boost_serialization.h: Removed serialization functions forrct::rangeSig,rct::Bulletproof,rct::mgSig,rct::clsag,rct::RCTConfig,rct::boroSig. SimplifiedrctSigBaseandrctSigPrunableserialization to only handle FCMP++.tx_verification_utils.h/.cpp: Removedmix_ringparameter fromver_rct_non_semantics_simple_cached. Removedexpand_tx_and_ver_rct_non_sem,calc_tx_mixring_hash, andis_canonical_bulletproof_layout.json_object.h/.cpp: Removed JSON serialization forrct::rangeSig,rct::Bulletproof,rct::boroSig,rct::mgSig,rct::clsag. Removed legacy prunable fields fromrctSigJSON output.wallet2.h: Removedrct_configfield fromtx_construction_dataserialization and the version-gatedRangeProofPaddedBulletproofdefaults in Boost serialization.wallet2.cpp: Fixedconstruct_tx_and_get_tx_keycall site that still passed{}where the removedrct_configparameter was.bulletproofs.h/.cc: Gutted non-plus Bulletproof PROVE/VERIFY functions โ therct::Bulletproofstruct was already removed fromrctTypes.h, making these 1000+ lines of dead code.
- Legacy RCT types stripped from core. Removed
RCTTypeFull(1),RCTTypeSimple(2),RCTTypeBulletproof(3),RCTTypeBulletproof2(4),RCTTypeCLSAG(5), andRCTTypeBulletproofPlus(6) from the enum. OnlyRCTTypeNull(0) andRCTTypeFcmpPlusPlusPqc(7) remain. - Deleted structs:
mgSig,clsag,rangeSig,Bulletproof(non-plus),RangeProofTypeenum, andRCTConfig. - Removed
mixRingmember fromrctSigBaseandmixinparameter fromserialize_rctsig_prunable. - Removed from
rctSigPrunable:rangeSigs,bulletproofs(non-plus),MGs,CLSAGsvectors and their serialization blocks. - Removed functions:
CLSAG_Gen,proveRctCLSAGSimple,verRctCLSAGSimple,genRctSimple(both overloads),populateFromBlockchainSimple,getKeyFromBlockchain,is_rct_simple,is_rct_bulletproof,is_rct_borromean,is_rct_clsag,proveRangeBulletproof,verBulletproof,make_dummy_bulletproof,make_dummy_clsag. - Removed
HASH_KEY_CLSAG_ROUND,HASH_KEY_CLSAG_AGG_0,HASH_KEY_CLSAG_AGG_1, andHASH_KEY_TXHASH_AND_MIXRINGfromcryptonote_config.h. - Removed VARIANT_TAG entries for
mgSig,rangeSig,Bulletproof, andclsag. - Simplified
get_pre_mlsag_hashto only handleRCTTypeFcmpPlusPlusPqc. - Simplified
verRctSemanticsSimpleandverRctNonSemanticsSimpleto only accept FCMP++ transactions (no CLSAG/ring verification path).
๐ Changed
- FCMP++ Phase 3: Per-input PQC authorization vector. Replaced
std::optional<pqc_authentication> pqc_authwithstd::vector<pqc_authentication> pqc_authsoncryptonote::transaction(onepqc_authenticationper input). Updated binary, Boost, and JSON serialization, transaction hash (cn_fast_hashof serializedpqc_auths), per-input PQC verification, and wallet/RPC signing paths.
โจ Added
-
FCMP++ (Full-Chain Membership Proofs): complete implementation across Phases 1โ6. Shekyl replaces ring signatures (CLSAG) with FCMP++ from genesis. Every spend proves membership in the entire UTXO set via a Helios/Selene curve tree, giving every transaction full-chain anonymity instead of 16-decoy ring ambiguity. Combined with hybrid post-quantum spend authorization (Ed25519 + ML-DSA-65), this makes Shekyl the first cryptocurrency to offer full-UTXO-set anonymity with quantum-resistant ownership.
Key components delivered:
- Rust foundation (Phase 1):
shekyl-fcmpcrate wrapping upstreammonero-fcmp-plus-pluswith 4-scalar leaf type{O.x, I.x, C.x, H(pqc_pk)}. Hybrid X25519 + ML-KEM-768 KEM with HKDF-SHA-512. Bech32m segmented address encoding. Per-output PQC key derivation. 15 FFI exports. Security audit (zero vulnerabilities, zero unsafe in first-party code). Reproducible builds with pinned Cargo.lock. - Transaction format (Phase 3):
RCTTypeFcmpPlusPlusPqc = 7withreferenceBlock,curve_trees_tree_depth, andfcmp_pp_prooffields.curve_tree_rootcommitment in every block header. - Consensus verification (Phase 4): 7-step verification order in
check_tx_inputsโ referenceBlock age, tree depth, key image y-normalization, FCMP++ proof via Rust FFI, PQC signature verification, BP+ range proofs. Mempool verification caching (fcmp_verification_hashintxpool_tx_meta_t). Staked output curve-tree leaves. - Curve tree database (Phase 2): Full
get_curve_tree_pathRPC implementation assembling real Merkle paths (leaf scalars + per-layer sibling hashes with position encoding). Selective pruning of intermediate tree layers between checkpoints, wired intoadd_blockaftersave_curve_tree_checkpoint. Old checkpoint garbage collection. - Wallet integration (Phase 5):
genRctFcmpPlusPlus()proof construction.get_curve_tree_pathRPC. Tree-path precomputation and incremental update in wallet refresh loop. PQC key rederivation from stored shared secret. Restore-from-seed PQC rederivation. - Infrastructure (Phase 6): Hardware device FCMP++ stubs. CI pipeline
for Rust workspace build, FCMP crate, determinism check, Bech32m tests.
output_pruning_metadata_tandm_output_metadataLMDB table for transaction pruning. LMDB curve tree schema (leaves, layers, meta, checkpoints). Checkpoint every 10,000 blocks for fast-sync resumption.
See
docs/FCMP_PLUS_PLUS.mdfor the full specification. - Rust foundation (Phase 1):
-
FCMP++ Phase 3: KEM ciphertext
tx_extraand coinbase self-encapsulation.tx_extra_pqc_kem_ciphertextwith tagTX_EXTRA_TAG_PQC_KEM_CIPHERTEXT(0x06): payloadblobis the concatenation of N ML-KEM-768 ciphertexts (1088 bytes each), one per output in order.- Coinbase: When the miner address has a PQC key and the hard-fork
version is at least
HF_VERSION_FCMP_PLUS_PLUS_PQC,construct_miner_txperforms KEM self-encapsulation to the minerโs own address per coinbase output (same tag and derivation semantics as normal transfers), then wipes the shared secret after use.
-
FCMP++ Phase 5e: Wallet precomputation of curve tree paths.
- Added
fcmp_precomputed_pathstruct towallet2.hcaching per-output tree path, root hash at precompute time, and precompute height. - Added
m_fcmp_precomputed_pathsruntime cache (not serialized) andm_fcmp_last_precompute_heightwatermark towallet2. precompute_fcmp_paths()fetches tree paths for all unspent outputs via theget_curve_tree_pathdaemon RPC endpoint.update_fcmp_paths_incremental(new_height)extends existing paths and adds newly discovered outputs, pruning paths for spent outputs.- Incremental path update is hooked into the wallet refresh loop, triggering after sync catches up if blocks were fetched.
- Progress callbacks (
on_fcmp_path_precompute_progress) fire during both initial and incremental precomputation.
- Added
-
FCMP++ Phase 5.5: Wallet sync and restore-from-seed PQC support.
transfer_details::m_combined_shared_secret(64 bytes) stores the hybrid KEM shared secret needed to rederive per-output PQC keys.rederive_pqc_keys_for_output(td)callsshekyl_fcmp_derive_pqc_keypairvia FFI to validate keypair derivation from stored shared secret.rederive_all_pqc_keys()iterates all transfers with stored shared secrets and rederives PQC keys, with progress callbackon_pqc_rederivation_progress.- Restore-from-seed triggers full PQC key rederivation on first refresh after sync completes.
๐ Fixed
-
Curve tree pop_block over-trim:
pop_blockpreviously counted alltx.voutentries when computing how many leaves to trim, butadd_blockskips outputs that fail type checks (unknown target types), locked staked outputs, and outputs whose FFI leaf construction fails. The trim count now mirrors the same filtering logic used in the grow path, preventing tree desynchronization during reorgs. -
Curve tree pruning correctness:
prune_curve_tree_intermediate_layerswas deleting all intermediate layer entries instead of selectively pruning only chunks fully below the previous checkpoint boundary. Fixed to compute the chunk boundary from the previous checkpoint'sleaf_countand only remove sealed entries. Also added garbage collection of stale checkpoint records (only the two most recent are kept). -
LMDB output metadata: removed undefined behavior in cursor macros.
store_output_metadatanow usesmdb_putdirectly withm_write_txninstead of theCURSOR()macro which requiredm_cursorsto be in scope.get_output_metadataandprune_tx_datanow usem_txn(fromTXN_PREFIX_RDONLY) instead oftxn_ptr(fromTXN_PREFIX).- Removed unused
m_txc_output_metadatacursor field andm_cur_output_metadatamacro fromdb_lmdb.h.
-
Wallet FCMP++ path precomputation: fixed undefined behavior.
- Replaced
reinterpret_cast<std::string&>onstd::vector<uint8_t>with a proper intermediatestd::stringcopy in bothprecompute_fcmp_pathsandupdate_fcmp_paths_incremental.
- Replaced
-
FCMP++ Phase 6c: CI pipeline updates.
- Added x86_64 architecture verification step to the
rust-audit-and-testCI job in.github/workflows/build.yml. - Added explicit
cargo build --locked -p shekyl-fcmpstep to verify the FCMP++ crate builds as part of the Rust workspace. - Added dedicated Bech32m address encoding test step that runs
shekyl-crypto-pqaddress tests with visible CI output. - The monero-oxide git dependency is cached via
~/.cargo/gitin the existing Cargo cache key (rust-${{ hashFiles('rust/Cargo.lock') }}). - Determinism check (build twice, diff
libshekyl_ffi.ahashes) andcargo auditremain in place.
- Added x86_64 architecture verification step to the
-
FCMP++ Phase 6f: Transaction pruning mode (skeleton).
- Added
output_pruning_metadata_tpacked struct toblockchain_db.hstoring per-output scan data (pubkey, commitment, unlock_time, height, pruned flag) for wallet scanning after transaction pruning. - Added abstract interface in
BlockchainDB:store_output_metadata(),get_output_metadata(),is_output_pruned(),prune_tx_data(). - Added
m_output_metadataLMDB table (keyed byglobal_output_index) indb_lmdb.handdb_lmdb.cppwith cursor, rflag, and DBI member. - LMDB implementation:
store_output_metadataandget_output_metadataare fully wired;is_output_pruneddelegates toget_output_metadata;prune_tx_datavalidates depth againstCRYPTONOTE_DEFAULT_TX_SPENDABLE_AGEand reads/writes alast_pruned_tx_data_heightwatermark in the properties table to skip already-processed blocks on subsequent runs. The block-iteration pruning loop is documented as a TODO skeleton. --prune-blockchainCLI flag now also triggersprune_tx_data()incryptonote_core.cpp, running output-metadata pruning alongside Monero's existing stripe-based pruning.- Test DB (
testdb.h) updated with no-op stubs for all four new methods.
- Added
-
FCMP++ Phase 4b: Mempool verification caching.
- Added
fcmp_verification_hash(32-bytecrypto::hash) andfcmp_verified(1-bit flag) totxpool_tx_meta_tinsrc/blockchain_db/blockchain_db.h, carved from the existing 76-byte padding (now 44 bytes). Struct stays 192 bytes. - New
Blockchain::compute_fcmp_verification_hash()computes a deterministic cache key fromhash(proof || referenceBlock || key_images). tx_memory_pool::add_txstores the cache hash on successful FCMP++ verification.tx_memory_pool::is_transaction_ready_to_gochecks the cached hash viais_fcmp_verification_cached()and seedsm_input_cacheto skip re-runningshekyl_fcmp_verify()for previously-verified mempool transactions.- Added
static_assertguards at thememcmpsite ontxpool_tx_meta_t(tx_pool.cpp line 1656) enforcing trivially-copyable layout and 192-byte struct size. - All padding and new fields are zero-initialized at every meta construction site.
- Added
-
FCMP++ Phase 4e: Staking consensus rules for FCMP++.
collect_outputsinblockchain_db.cpp::add_blocknow handlestxout_to_staked_keyoutputs using the same 4-scalar leaf format{O.x, I.x, C.x, H(pqc_pk)}.- Deferred insertion: staked outputs only enter the curve tree when
block_height >= effective_lock_until. Outputs still within their lock period are stored in thepending_staked_leavesDB table and inserted into the curve tree when they mature (see deferred staked leaf insertion entry below). check_stake_claim_inputvalidates claims against the staked output'seffective_lock_until(creation_height + tier_lock_blocks) and enforcesto_height <= min(current_height, effective_lock_until).
-
FCMP++ Phase 5: Wallet transaction construction skeleton.
- Added
rct::genRctFcmpPlusPlus()insrc/fcmp/rctSigs.cppโ builds an FCMP++rctSigwithRCTTypeFcmpPlusPlusPqc, Bulletproofs+ range proofs, balanced pseudo-outputs, and invokesshekyl_fcmp_prove()via FFI to generate the membership proof. - Declared the new function in
src/fcmp/rctSigs.h. - Added
COMMAND_RPC_GET_CURVE_TREE_PATHRPC command insrc/rpc/core_rpc_server_commands_defs.hโ accepts output indices and returns Merkle paths from the curve tree (stub handler for now). - Wired
get_curve_tree_pathJSON-RPC endpoint insrc/rpc/core_rpc_server.handsrc/rpc/core_rpc_server.cpp. - Added TODO scaffolding in
src/wallet/wallet2.cppat the decoy selection (get_outs), transaction construction (construct_tx_and_get_tx_key), and fee estimation (estimate_tx_weight) sites, documenting how FCMP++ replaces ring signatures in the wallet transfer flow.
- Added
-
FCMP++ Phase 6a: Hardware device stubs.
- Added
fcmp_prepare,fcmp_proof_start, andfcmp_proof_add_inputvirtual methods tohw::device(base class) with defaultreturn falseimplementations for unsupported devices. - Software device (
device_default) returnstrue(scaffolding for Rust FFI delegation). - Ledger device (
device_ledger) logs an informative error and returnsfalse, guiding users to software wallets until Ledger firmware gains FCMP++ support. - Trezor inherits the base-class defaults (unsupported) without code changes.
- Updated
RELEASE_CHECKLIST.mdto document hardware wallet readiness status.
- Added
-
FCMP++ Phase 4a: Verification in
check_tx_inputs.- Added
RCTTypeFcmpPlusPlusPqcverification path inBlockchain::check_tx_inputs(src/cryptonote_core/blockchain.cpp). referenceBlockage validation: confirmed within[tip - MAX_AGE, tip - MIN_AGE]using DB block lookup.curve_trees_tree_depthvalidated against the current tree state.- Key offsets verified empty for all FCMP++ inputs.
- Key image y-normalization enforced (sign bit of byte 31 cleared).
- Input count bounded by
FCMP_MAX_INPUTS_PER_TX. shekyl_fcmp_verify()FFI call wired up with key images, pseudo outputs, and proof blob.- Per-input
pqc_authsverification left as documented TODO pending the per-input auth field migration.
- Added
-
FCMP++ Phase 4a-pre: PQC auth binding specification.
- New
docs/FCMP_PLUS_PLUS.mdformally documents the dual-layer binding model, per-input signed payload layout, and 7-step consensus verification order forRCTTypeFcmpPlusPlusPqctransactions.
- New
-
FCMP++ Phase 3.5: Curve tree root in block header (consensus-critical).
- Added
curve_tree_root(crypto::hash) field toblock_headerinsrc/cryptonote_basic/cryptonote_basic.h, initialized tonull_hash. - Field is always serialized (genesis-native, no version gating) in both
the binary archive (
BEGIN_SERIALIZE) and Boost serialization. - Block template creation (
Blockchain::create_block_template) snapshots the current DB curve tree root into the header. - Block validation (
Blockchain::handle_block_to_main_chain) verifiescurve_tree_rootmatches the locally-computed tree root afteradd_blockgrows the tree; rejects the block on mismatch. - RPC
block_header_responsenow includescurve_tree_roothex string. - Test generator (
chaingen.cpp) setscurve_tree_roottonull_hashinconstruct_blockandconstruct_block_manually.
- Added
-
FCMP++ Phase 3: Transaction format for FCMP++ PQC.
- Added
RCTTypeFcmpPlusPlusPqc = 7to the RCT type enum insrc/fcmp/rctTypes.hโ Shekyl's only non-coinbase transaction type. - Added
referenceBlock(block hash anchoring the curve tree snapshot) torctSigBase, serialized only for the new type. - Added
curve_trees_tree_depthandfcmp_pp_proof(opaque FCMP++ proof blob) torctSigPrunable, replacing CLSAG ring signatures for the new type. - Added
TX_EXTRA_TAG_PQC_KEM_CIPHERTEXT(0x06) totx_extra.hfor per-output ML-KEM-768 ciphertexts. - Added
key_image_y_normalize()tocrypto.h/crypto.cppโ clears the sign bit of a key image's y-coordinate as required by FCMP++. - Added
is_rct_fcmp_pp_pqc()helper torctTypes.h/rctTypes.cpp. - Updated serialization helpers (
serialize_rctsig_base,serialize_rctsig_prunable) and type classifier functions (is_rct_simple,is_rct_bulletproof_plus) to handle the new type.
- Added
-
FCMP++ Phase 2e: Curve tree checkpoint strategy.
- New
BlockchainDBvirtual methods:save_curve_tree_checkpoint,get_curve_tree_checkpoint,get_latest_curve_tree_checkpoint_height,prune_curve_tree_intermediate_layers. - LMDB implementation with
curve_tree_checkpointstable (MDB_INTEGERKEY), storing root[32] + depth[1] + leaf_count[8] per checkpoint. - Automatic checkpoint every
FCMP_CURVE_TREE_CHECKPOINT_INTERVAL(10 000) blocks duringadd_block, enabling fast-sync resumption. - Configurable interval via
cryptonote_config.hconstant.
- New
-
FCMP++ Phase 2f: Curve tree pruning strategy.
prune_curve_tree_intermediate_layersremoves recomputable internal hash layers between checkpoints, preserving leaves and the root layer to reduce storage overhead.
-
FCMP++ Phase 1: Rust foundation crates.
- New
rust/shekyl-fcmp/crate wrapping upstreammonero-fcmp-plus-plus(fromShekyl-Foundation/monero-oxidefork,fcmp++branch) with 4-scalar curve tree leaf type{O.x, I.x, C.x, H(pqc_pk)}. - Implemented
HybridX25519MlKem(X25519 + ML-KEM-768 FIPS 203) inshekyl-crypto-pq/src/kem.rswith HKDF-SHA-512 shared-secret combination and master-seed key derivation. - Implemented Bech32m segmented address encoding
(
shekyl1<classical>/skpq1<pqc_a>/skpq21<pqc_b>) inshekyl-crypto-pq/src/address.rs, keeping each segment within Bech32m's proven checksum range. - Implemented per-output PQC keypair derivation (HKDF-Expand โ ML-DSA-65
deterministic keygen) in
shekyl-crypto-pq/src/derivation.rs. - Added 15 new FFI exports to
shekyl-ffifor FCMP++ proofs, KEM operations, address encoding, and seed derivation. - Added FCMP++ consensus constants to
cryptonote_config.h:HF_VERSION_FCMP_PLUS_PLUS_PQC,FCMP_REFERENCE_BLOCK_MAX_AGE(100),FCMP_REFERENCE_BLOCK_MIN_AGE(2),FCMP_MAX_INPUTS_PER_TX(8). - Updated
BuildRust.cmakewith--lockedflag for reproducible builds.
- New
-
FCMP++ Phase 1a.1: Security review of forked monero-oxide crates.
cargo audit: 226 crate dependencies scanned, zero vulnerabilities found.unsafeblock audit: zerounsafein first-party monero-oxide workspace code (helioselene, ec-divisors, generalized-bulletproofs, fcmps, monero-oxide). Only 4unsafeblocks exist in helioselene benchmarks (_rdtsc()for cycle counting, not in library code).dalek-ff-group(crates.io dependency) also has zerounsafeblocks.- Veridise audit status: FCMPs circuit audited by Veridise (June 2025);
Generalized Bulletproofs security proofs by Cypher Stack; Divisor proofs
reviewed by both Veridise and Cypher Stack. Pinned commit
92af05e0is post-audit. Helioselene and ec-divisors are not yet independently audited. Multi-phase integration audit (seraphis-migration/monero#294) is in planning.
-
FCMP++ Phase 1a.2: Rust reproducible builds.
Cargo.lockpins all git dependencies to exact commit hash92af05e0.- Double-build determinism verified:
libshekyl_ffi.ahash identical across consecutive builds on x86_64. - Added CI job
rust-audit-and-testto.github/workflows/build.ymlwith cargo audit, workspace tests, and determinism check (build twice, diff). - Documented x86_64-only build requirement and Guix integration status in
docs/COMPILING_DEBUGGING_TESTING.md.
๐ Changed
- P2P reorg functional test uses deadline-based polling. Replaced three
fixed-sleep polling sites in
test_p2p_reorg()(time.sleep(10)x2,loops = 100counter) with 240 s deadline + 0.25 s interval polling, matching the pattern already used intest_p2p_tx_propagation(). Adapted from upstream Monero #9795.
โจ Added
- Extra compiler warnings and hardening flags. Added
-Wredundant-decls,-Wdate-time,-Wimplicit-fallthrough,-Wunreachable-code(common);-Woverloaded-virtual,-Wsuggest-override(C++ only);-Wgnu,-Wshadow-field,-Wthread-safety,-Wloop-analysis,-Wconditional-uninitialized,-Wdocumentation,-Wself-assign(Clang);-Wduplicated-branches(GCC). Added security protections:-fno-extended-identifiers,-fstack-reuse=none, and ARM64 branch protection (-mbranch-protection=btion macOS,standardelsewhere). Adapted from upstream Monero #9858. - Linker dead-code stripping. Added
-ffunction-sections -fdata-sectionsto compile flags and-Wl,--gc-sections(Linux) /-Wl,-dead_strip(macOS) to linker flags, enabling the linker to strip unreferenced functions and data. Inspired by upstream Monero #9898 author's findings (~14 MiB reduction in Docker images).
๐ Documentation
- Upstream Monero PR triage. Replaced the stale "To be done (and merged)"
section in
COMPILING_DEBUGGING_TESTING.mdwith a structured triage table covering applied PRs (#6937, #9762, #9795, #9858, #9898) and tracked-for- future-work PRs (#10157, #10084, #9801) with STRUCTURAL_TODO.md cross-refs. - FCMP++ documentation rework (Phase 0.5a). Reworked all core documentation to reflect FCMP++ as the membership proof system from genesis. Replaced CLSAG and ring signature references with FCMP++ full-chain membership proof language. Updated PQC spec for per-input pqc_auths, per-output KEM derivation, Bech32m addresses, and curve tower architecture. Retired V4 lattice ring signature roadmap. Updated V3_ROLLOUT.md size estimates for ~23 KB typical transactions. Added FCMP++ items to RELEASE_CHECKLIST.md.
๐ Fixed
-
Re-enabled
gen_block_rewardcore test with Shekyl economics. Rewrotecheck_block_rewards()inblock_reward.cppto verify miner outputs against Shekyl's four-component economics formula (release multiplier + emission split + fee burn) instead of legacy Monero fixed expectations. Updatedconstruct_miner_tx_by_weightto pass explicit economics parameters. Fixedconstruct_blockandconstruct_block_manuallyinchaingen.cppto passcirculating_supply=already_generated_coinstoconstruct_miner_tx, preventing parameter mismatch between test generator and validator. 80 core_tests now pass (was 79). -
MSVC C4334: 23
1 << nsites widened to1ULL << nin consensus code. Fixed potential undefined behavior (signed 32-bit overflow if shift amount ever reaches 32) incryptonote_format_utils.cpp(3),bulletproofs.cc(6),bulletproofs_plus.cc(6),rctTypes.cpp(5),rctSigs.cpp(2), andmultiexp.cc(2). -
MSVC C4333 right-shift warning in UTF-8 helpers. Changed
wint_t cptouint32_t cpinsrc/common/util.cppget_string_prefix_by_width(), and added an explicitstatic_cast<uint32_t>on the transform result insrc/common/utf8.hutf8canonical(). On MSVC,wint_tis 16-bitunsigned short, socp >> 18shifted by more than the type's width. -
Remaining HF17 references corrected to HF1. Fixed stale Monero-era
HF17/HF_VERSION_SHEKYL_NG = 17references inPOST_QUANTUM_CRYPTOGRAPHY.md(scheme registry, rollout notes, V4 roadmap),PQC_MULTISIG.md(V3 heading, V4 scheme table, activation target),V3_ROLLOUT.md(title, consensus gate, node checklist), andSTAKER_REWARD_DISBURSEMENT.md. Also correctedHF18references toHF2in multisig V4 rollout tables. The source code constantHF_VERSION_SHEKYL_NGwas already correctly defined as1incryptonote_config.h; only documentation was affected. -
CMake Boost detection on CMake 3.30+: The built-in
FindBoost.cmakemodule was removed in CMake 3.30. Restructured Boost detection to try CONFIG mode first (findingBoostConfig.cmakeinstalled by b2), falling back to MODULE on older CMake. Fixescontrib/dependsbuilds on Ubuntu 24.04 runners with CMake โฅ 3.30.
๐๏ธ Removed
- Classical multisig wallet RPC commands. Removed all 9 Monero-inherited
multisig RPC endpoints (
is_multisig,prepare_multisig,make_multisig,export_multisig_info,import_multisig_info,finalize_multisig,exchange_multisig_keys,sign_multisig,submit_multisig) from the wallet RPC server. Removedmultisig_txsetfields from transfer and sweep response structs. Removed theCHECK_MULTISIG_ENABLEDmacro andmultisig/multisig.hdependency. Classical secret-splitting multisig is replaced by PQC-only authorization (scheme_id = 2); seedocs/PQC_MULTISIG.md. - Classical multisig simplewallet CLI commands. Removed all multisig and
MMS (Multisig Messaging System) commands from
simplewallet:prepare_multisig,make_multisig,exchange_multisig_keys,export_multisig_info,import_multisig_info,sign_multisig,submit_multisig,export_raw_multisig_tx, and allmmssubcommands. Removed--generate-from-multisig-keysand--restore-multisig-walletCLI flags. Removedenable-multisig-experimentalwallet setting. Removedwallet/message_store.hdependency. Thetransfer_main/called_by_mmsindirection was collapsed into a singletransfermethod. - Classical multisig test and device_trezor remnants. Removed stale
multisig references from test infrastructure:
m_multisig*wallet resets inwallet_tools.cpp,multisig_sigs.clear()in Trezor tests,multisig_txsetassertion incold_signing.py, and deletedtests/functional_tests/multisig.py. Removedmultisigfrom the functional test default list. Cleaned up device_trezor protocol: removedtranslate_klrki,MoneroMultisigKLRkialias,m_multisigmember, and multisig cout decryption inSigner::step_final_ack. Removedmms_error,no_connection_to_bitmessage, andbitmessage_api_errorerror classes fromwallet_errors.h. - Classical multisig wallet API layer. Removed all classical multisig
code from the public wallet API:
MultisigStatestruct, virtual multisig declarations (multisig,getMultisigInfo,makeMultisig,exchangeMultisigKeys,exportMultisigImages,importMultisigImages,hasMultisigPartialKeyImages,restoreMultisigTransaction,publicMultisigSignerKey,signMultisigParticipant,multisigSignData,signMultisigTx). Removed multisig helper functions and multisig threshold check from PendingTransaction commit path. Removed multisig guard from the background-sync validation macro. - Classical multisig wallet core (
wallet2.cpp). Removed all classical multisig code from the wallet core:#include "multisig/..."headers,MULTISIG_UNSIGNED_TX_PREFIX/MULTISIG_EXPORT_FILE_MAGIC/MULTISIG_SIGNATURE_MAGICconstants,m_multisig/m_multisig_threshold/m_multisig_rounds_passed/m_enable_multisig/m_message_store/m_mms_filemember initializations,num_priv_multisig_keys_post_setup,get_multisig_seed, multisig restore path ingenerate(),make_multisig,exchange_multisig_keys,get_multisig_first_kex_msg,multisig(),has_multisig_partial_key_images,frozen(multisig_tx_set), allsave/parse/load/sign_multisig_txoverloads, the multisig transaction builder path intransfer_selected_rct,export_multisig,import_multisig,update_multisig_rescan_info,get_multisig_signer_public_key,get_multisig_signing_public_key,get_multisig_k,get_multisig_kLRki,get_multisig_composite_kLRki,get_multisig_composite_key_image,get_multisig_wallet_state,sign_multisig_participant, JSON serialization/deserialization of multisig fields, MMS file handling, and all scatteredm_multisigguard branches. - Classical multisig
m_key_image_partialremnants. Removed them_key_image_partialbitfield fromexported_transfer_detailsand all code references inwallet2.cppandsimplewallet.cpp. Since classical multisig was removed, partial key images can never exist; all guard conditions (!known || partial,known && !partial, standalone partial checks) were simplified to reference onlym_key_image_known. Removed the deadold_mms_filecleanup block fromwallet2::store_to.
โจ Added
- Daemon RPC migrated to Rust/Axum (Phase 1). The daemon HTTP RPC transport
is now served by the
shekyl-daemon-rpcRust crate using Axum, replacingepee::http_server_impl_base. All 90 endpoints (33 JSON REST, 9 binary, 48 JSON-RPC 2.0) are routed through Axum with PQC-ready 10 MiB body limits, CORS, and restricted-mode enforcement. The C++core_rpc_serverhandler logic is unchanged and accessed via acore_rpc_ffiC ABI facade. Enabled by default;--no-rust-rpcfalls back to the legacy epee HTTP server. JSON REST endpoints accept both GET and POST (matching epee). Binary endpoints return 400 on parse failure (matching epee's MAP_URI_AUTO_BIN2). Validated on live testnet: 23/25 pass, 2 expected diffs (rpc_connections_count), 2 binary skips (empty-POST โ 400 on both). Validation harness attests/rpc_comparison/compare_rpc.sh; test data inshekyl-dev/data/rpc_comparison/. - PQC multisig core (scheme_id=2). Implemented M-of-N hybrid Ed25519 +
ML-DSA-65 multisig in Rust. Includes
MultisigKeyContainer,MultisigSigContainer,multisig_group_id, and a 10-check adversarial verification pipeline. Maximum 7 participants (consensus constant). Domain separator:shekyl-multisig-group-v1. - PQC multisig FFI bridge. Extended
shekyl_pqc_verifyto acceptscheme_idand dispatch between single-signer (1) and multisig (2) paths. Addedshekyl_pqc_verify_debugfor diagnostic error codes andshekyl_pqc_multisig_group_idfor group identity computation. - Scheme downgrade protection. New
tx_extra_pqc_ownershiptag (0x05) records the expected PQC scheme and group ID for each output, preventing attackers from spending multisig-protected outputs with single-signer transactions. - Wallet multisig coordination. New wallet2 methods for PQC multisig:
create_pqc_multisig_group,export_multisig_signing_request,sign_multisig_partial,import_multisig_signatures. File-based JSON signing protocol. Wallet serialization version bumped to 32. - Cargo-fuzz harnesses. 4 fuzz targets for multisig deserialization and
verification (
fuzz_multisig_key_blob,fuzz_multisig_sig_blob,fuzz_multisig_verify,fuzz_group_id), each validated at 10M iterations with zero panics. - PQC multisig subset-signing test. Added
valid_subset_signing_3_of_5test toshekyl-crypto-pqverifying that any valid 3-of-5 signer subset produces a valid multisig through the full 10-check verification pipeline. - PQC multisig test vectors. Published
docs/PQC_TEST_VECTOR_002_MULTISIG.jsonwith canonical encoding sizes, wire-format sizes, verification pipeline checks, the 10-check pipeline, size regression data, and adversarial test cases forscheme_id = 2. - MSVC wallet-core build path:
BuildRust.cmakenow selects thex86_64-pc-windows-msvcRust target when CMake is driven by MSVC, enabling the Tauri GUI wallet to link against shekyl-core on Windows. The existing MinGW cross-compilation path for headless binaries is unchanged. - CI: Windows MSVC wallet-core job (
build-windows-msvc): New CI lane builds the wallet-core static libraries with Visual Studio / MSVC via vcpkg, validating the MSVC portability patches on every push. - Unified Gitian release pipeline. The
gitianworkflow is now the sole release pipeline, replacing the separaterelease-taggedworkflow. Gitian builds produce reproducible binaries; a newpackage-and-publishjob creates.deb/.rpmpackages, a Windows NSIS installer, source archive, andSHA256SUMS, then publishes the GitHub Release. Eliminates duplicate cross-compilation and host-toolchain issues. - Source archive in GitHub Releases. The packaging job produces
shekyl-vX.Y.Z-source.tar.gzcontaining the full source tree with all submodules, attached to each release alongside the binaries.
๐ Changed
shekyl_pqc_verifyFFI signature change. Now requiresscheme_idas first parameter for scheme dispatch.depends.ymldemoted to PR-only. The cross-compilation CI workflow now runs only on pull requests (and manual dispatch), not on every push. Saves significant CI minutes; Gitian catches cross-platform issues at release time.release-tagged.ymldisabled. The Gitian pipeline now handles all release artifacts. The old workflow is preserved as.disabledfor one release cycle.- Gitian reproducible builds: migrated from Ubuntu 18.04 (Bionic) to 22.04
(Jammy). All five build descriptors (
gitian-linux.yml,gitian-win.yml,gitian-osx.yml,gitian-android.yml,gitian-freebsd.yml),gitian-build.py, anddockrun.shnow target Jammy. Drops GCC 7 and Python 2 dependencies in favour of the distro-default GCC 11 and Python 3. Upgrades FreeBSD cross-compiler from Clang 8 to Clang 14. Removes Bionic-specific workarounds (i686 asm symlink hack, glibcmath-finite.hhack). Addslinux-libc-dev:i386for native i686 headers. C++17 is now fully supported by the Gitian toolchain.
๐ Fixed
- Comprehensive compiler warning cleanup across all CI platforms. Eliminated
~30 unique warnings inherited from Monero across Linux, macOS, Windows, and
Arch Linux CI builds:
- Removed dead code:
add_public_key(format_utils),keys_intersect(wallet2), unusedaddressoftemplate specialization (crypto test), unusedmax_block_heightvariable (protocol_handler). - Fixed
oaes_lib.c: replaced deprecatedftime()withgettimeofday(), corrected transposedcallocargument order (5 call sites). - Fixed
rx-slow-hash.c: added(void)to K&R-style function definitions. - Suppressed GCC false positive
-Wstringop-overflowintree-hash.c. - Replaced deprecated
strand::wrap()withboost::asio::bind_executor()inlevin_notify.cpp. - Suppressed GCC
-Wuninitializedfor safe circular-reference constructors incryptonote_core.cppandlong_term_block_weight.cpp. - Added default member initializers to
BulletproofPlus(rctTypes.h),transfer_detailsandpayment_details(wallet2.h) to silence-Wmaybe-uninitialized. - Fixed Windows: removed unused variables in
windows_service.cpp, eliminated-Wcast-function-typeinutil.cppviavoid*intermediate cast, fixed-Wtype-limitsinutf8.hby usinguint32_tinstead ofwint_tfor code points. - Suppressed intentional uninitialized read in
memwipe.cpptest. - Set
MACOSX_DEPLOYMENT_TARGETfor native Darwin Cargo builds inBuildRust.cmaketo eliminate 672 linker warnings fromringcrate.
- Removed dead code:
- CI link errors: separated
shekyl-daemon-rpcfromshekyl-ffi. The daemon RPC Axum crate was bundled intolibshekyl_ffi.a, causingundefined reference to core_rpc_ffi_*on non-daemon targets (gen-ssl-cert, wallet-crypto-bench, etc.) across all 5 CI platforms. Moved FFI exports (shekyl_daemon_rpc_start,shekyl_daemon_rpc_stop) into a newffi_exports.rswithin the daemon-rpc crate, which now produces its ownlibshekyl_daemon_rpc.astaticlib. Only the daemon target links both libraries.BuildRust.cmakeupdated with a second cargo build step andSHEKYL_DAEMON_RPC_LINK_LIBS. - Wallet:
--daemon-porthelp text referenced Monero port 18081. Updated to Shekyl's default RPC port 11029. - Wallet:
account_public_addressequality after PQC. Destination and change-address checks usedmemcmpon the whole struct;m_pqc_public_keyis astd::vector, so equality was wrong when keys matched but allocations differed. All such sites now useoperator==/!=. Added astatic_assertthat the type is not trivially copyable to discourage rawmemcmpregressions. - Wallet / Ledger: constant-time comparison for 32-byte secrets.
wallet2::is_deterministicand Ledger HMAC secret lookup now usecrypto_verify_32instead ofmemcmp. - MSVC: add
<io.h>and POSIX guards inutil.cpp. Added<io.h>for_open_osfhandle/_close, expanded MinGW conditionals to cover MSVC forsetenvโputenv,mode_t/umask, andclosefromโno-op. - MSVC: replace
__threadwiththread_localinperf_timer.cppandthreadpool.cpp. GCC's__threadis not supported by MSVC. - MSVC: rename
xorparameter inslow-hash.ctoxor_pad. MSVC treatsxoras a reserved keyword in C mode. Both the x86/SSE and ARM/NEON variants ofaes_pseudo_round_xor()were affected. - MSVC: fix iterator-to-pointer cast in
http_auth.cpp. MSVCboost::as_literal()iterator is a class, not a raw pointer. Used&*data.begin()to obtain the address. - MSVC: guard
unbound.hinclude and usage inutil.cpp. The include andunbound_built_with_threads()function/call were not wrapped inHAVE_DNS_UNBOUND, causing a missing-header error. - MSVC: guard
unistd.hin easylogging++. The third-party logging library unconditionally included<unistd.h>which does not exist on MSVC. - MSVC: add
<io.h>include for_isattyinmlog.cpp. The WIN32 code path uses_isatty/_filenowhich require<io.h>on MSVC. - MSVC: fix
boost::iterator_rangeconversion inhttp_auth.cpp. Boost 1.90as_literal()returns an iterator type that does not implicitly convert toiterator_range<const char*>on MSVC. Changed toautodeduction. - MSVC: add
<cwctype>include forstd::towlowerinlanguage_base.h. MSVC does not transitively include wide-character utilities through other Boost headers. - MSVC: fix rvalue binding in portable_storage serialization. Changed
array_entry_t::insert_first_valandinsert_next_valuefrom strict rvalue-reference parameters (t_entry_type&&) to pass-by-value, allowing lvalue forwarding fromportable_storage::insert_first_value/insert_next_valueto work correctly under MSVC template deduction. - MSVC: force-include
<iso646.h>for C++ alternative tokens. The codebase usesnot,and,orextensively (hundreds of sites). MSVC does not recognise these as keywords by default. Added/FIiso646.hto the MSVC compile definitions so they are defined in every translation unit. - MSVC: enable conformant preprocessor (
/Zc:preprocessor). MSVC's traditional preprocessor breaks nested__VA_ARGS__forwarding in theTHROW_ON_RPC_RESPONSE_ERRORmacro chain, causingthrow_wallet_extemplate deduction failures. Added/Zc:preprocessorto MSVC compile flags and removed the obsolete Boost.Preprocessor-basedthrow_wallet_exfallback in favour of the standard variadic template version. - Gitian: enable
universerepository and remove apt proxy in Docker base image. Theubuntu:jammyDocker image only enablesmain restrictedby default;gitian-build.pynow patches the base image aftermake-base-vmto adduniverseand remove theapt-cacher-ngproxy configuration (/etc/apt/apt.conf.d/50cacher). The proxy routes all apt traffic through172.17.0.1:3142which is unreliable on ephemeral CI runners, causing persistent 503 failures during package installation. Usesdocker build(not run+commit) to preserve the image's CMD/USER metadata. - Gitian Linux: fix i386-dependent package installation. The i386
architecture is now enabled in the Docker base image (via
gitian-build.py'sdocker buildstep) along with passwordlesssudofor theubuntuuser, allowinglinux-libc-dev:i386,gcc-multilib, andg++-multilibto be installed normally via the descriptor'spackages:section. - Gitian macOS: add
libtinfo5andpython-is-python3, removepythonfromFAKETIME_PROGS. The pre-built Clang 9 cross-compiler requireslibtinfo.so.5. Thepythonfaketime wrapper broke CMake'sFindPythonInterpversion detection in thenative_libtapibuild (emptyPYTHON_VERSION_STRING); removingpythonfrom the faketime wrappers fixes this while preserving timestamp reproducibility forar,ranlib,date,dmg, andgenisoimage. - Gitian Android: add
python-is-python3. Android NDK r17b scripts use#!/usr/bin/env pythonwhich does not exist on Jammy without this package. - Gitian macOS: fix Rust
ringcrate cross-compilation.BuildRust.cmakeincorrectly overrode the macOS cross-compiler with the Linux systemclangwhen cross-compiling for Darwin, causing theringcrate to include Linux-onlycet.h. Now only uses system clang on native macOS builds. - Gitian Windows: drop i686 (32-bit) target. The i686-pc-windows-gnu Rust
target has an unresolved
GetHostNameW@8symbol against MinGW'sws2_32. Since the release workflow only targets x86_64, the 32-bit Gitian build is removed. - macOS cross-build: exclude
-fcf-protection=full. Intel CET is x86 Linux only; the flag defines__CET__which triggers#include <cet.h>in theringcrate's assembly, butcet.hdoes not exist in the macOS SDK. Now excluded for all Apple targets. - macOS aarch64 cross-build: set
MACOSX_DEPLOYMENT_TARGET=10.16. Clang 9 (depends cross-compiler) does not recognise macOS version 11.0+. Apple aliases 10.16 == 11.0; thecc-rscrate respects this env var, fixing theringbuild foraarch64-apple-darwin. - Gitian Docker base image: install
sudobefore creating sudoers entry. The/etc/sudoers.d/directory does not exist in the minimal Ubuntu image until thesudopackage is installed.
๐ Changed
- Replace all
BOOST_FOREACH/BOOST_REVERSE_FOREACHwith range-for loops. 31+ call sites across test and utility code replaced with standard C++11 range-based for. Adds/DNOMINMAXto MSVC definitions to prevent Windowsmin/maxmacro collisions. - Replace hardcoded
-fPICwithPOSITION_INDEPENDENT_CODE. The CMake property works across all compilers (GCC, Clang, MSVC). Applied toliblmdbandeasylogging++CMakeLists. - Guard/remove unguarded
#include <unistd.h>. POSIX header guarded behind#ifndef _WIN32inblockchain_import.cpp; unused include removed fromcrypto.cpp. - Replace C++20 designated initializers with C++17-compatible member
assignment. Rewrote 10 call sites in
cryptonote_core.cpp,blockchain.cpp,levin_notify.cpp,multisig_tx_builder_ringct.cpp, andwallet2.cpp. GCC/Clang accepted these as extensions; MSVC rejects them. - Replace all
__threadwiththread_local. Coverseasylogging++.cc,perf_timer.cpp, andthreadpool.cpp. The__threadqualifier is GCC/Clang-specific;thread_local(C++11) is portable across GCC, Clang, and MSVC. - Centralize
ssize_ttypedef insrc/common/compat.h. Replaces duplicate#if defined(_MSC_VER)guards inutil.handdownload.hwith a single include.
๐๏ธ Removed
- Classical multisig code removed from wallet2.h. Removed all classical
Monero-style multisig types (
multisig_info,multisig_sig,multisig_kLR_bundle,multisig_tx_set), public/private multisig API methods, multisig private members, MMS (message store) integration, and associated Boost serialization functions. Thesrc/multisig/directory andsrc/wallet/message_store.hare deleted;wallet2.hno longer depends on those headers. All multisig uses PQC-only authorization (scheme_id = 2) via thepqc_authlayer. - Gitian Android build. Removed from the Gitian matrix since there is no Android wallet. The Android NDK r17b is also incompatible with Ubuntu Jammy.
- Gitian Linux: drop i686-linux-gnu (32-bit x86) target. Eliminates the
need for
linux-libc-dev:i386,gcc-multilib,g++-multilib,sudo, and thedpkg --add-architecture i386workaround. Simplifies the Docker base image patching to only enable theuniverserepository.
๐ Documentation
docs/RELEASING.md: document all release artifacts. Updated the artifact table to list all 13 files produced per release (was 6), including cross-platform tarballs, aarch64.deb/.rpm, and source archive. Updated "Future Platforms" to reflect that macOS tarballs are now shipping and.dmg/AppImage remain planned.
[3.0.3-RC1] - 2026-03-31
Known Limitations
- Multisig not yet implemented. Multisig wallets are restricted to v2
transactions (no PQC authentication). PQC-enabled multisig is planned for
a future release. See
docs/PQC_MULTISIG.mdfor the design.
โจ Added
-
Rust wallet RPC server (
shekyl-wallet-rpc): New Rust crate that replaces the C++wallet_rpc_serverwith an axum-based JSON-RPC server. Calls the existing C++wallet2library through a new C FFI facade (wallet2_ffi.cpp/.h). Supports all 98 RPC methods with full parity. Can run as a standalone binary (shekyl-wallet-rpc) or be embedded as a library in the Tauri GUI wallet. Seedocs/WALLET_RPC_RUST.md. -
C++ wallet2 FFI facade (
wallet2_ffi.cpp/.h): Opaque-handle C API overwallet2with JSON serialization at the boundary. Includes a genericwallet2_ffi_json_rpc()dispatcher that routes all RPC methods to the underlying wallet2 implementation. Covers lifecycle, queries, transfers, sweeps, proofs, accounts, address book, import/export, multisig, staking, mining, background sync, and daemon management. -
GUI wallet direct FFI integration: The Tauri GUI wallet now calls wallet2 directly through the Rust FFI bridge (
wallet_bridge.rs) instead of spawning a childshekyl-wallet-rpcprocess and communicating via HTTP. Eliminates process management, port allocation, and HTTP overhead. Removedwallet_process.rsandwallet_rpc.rs.
v3-First Core Test Adaptation
- Enforced min_tx_version=3 for non-coinbase transactions: All user transactions in the test suite now construct v3 with PQC authentication (hybrid Ed25519 + ML-DSA-65). Coinbase transactions remain v2.
- Adapted chaingen framework for RCT-from-genesis: Transaction
construction helpers (
construct_tx_to_key,construct_tx_rct) threadhf_version=1anduse_view_tags=true. Coinbase outputs are indexed underamount=0for correct RCT spending. Fixed difficulty is injected for FAKECHAIN replay. Mixin checks are relaxed for FAKECHAIN. - Added RCT-aware balance verification: Pool transaction balance checks
in
gen_chain_switch_1now decrypt ecdhInfo amounts using the recipient's view key instead of relying on the plaintexto.amountfield (always 0 for RCT outputs). - Recalibrated economic constants for Shekyl: Test constants
(
TESTS_DEFAULT_FEE,FIRST_BLOCK_REWARD,MK_COINS) match Shekyl'sCOIN = 10^9,EMISSION_SPEED_FACTOR = 21, and staker/burn splits.construct_miner_tx_manuallyin block validation tests uses Shekyl's reward distribution. - Fixed Bulletproofs+ test suite: Dynamically discover miner output amounts, set HF to 1 for all block construction, correctly flag coinbase outputs as RCT. All 15 BP+ tests pass.
- Fixed txpool tests: Adjusted key image count assertions for multi-input RCT transactions and corrected unlock_time handling.
- Fixed double-spend tests: Modified output selection to pick the largest decomposed output, avoiding underflow on fee subtraction.
- Disabled legacy-incompatible tests:
gen_block_invalid_binary_format(hours-long),gen_block_invalid_nonce,gen_block_late_v1_coinbase_tx,gen_uint_overflow_1,gen_block_reward,gen_bpp_tx_invalid_before_fork,gen_bpp_tx_invalid_clsag_type,gen_ring_signature_big. These rely on pre-RCT economics, legacy fork transitions, or are prohibitively slow. - All 79 core_tests pass with 0 failures.
Test suite cleanup for Shekyl HF1
- Removed 96 dead Borromean ringct tests: All tests in
tests/unit_tests/ringct.cppthat exercised legacy Borromean range proofs were removed. Shekyl HF1 rejects Borromean proofs at thegenRctSimplelevel. Retained 9 non-Borromean tests (CLSAG, HPow2, d2h, d2b, key_ostream, zeroCommit, H, mul8). - Updated transaction construction helpers to Bulletproofs+: The
test::make_transactionhelper (used by JSON serialization and ZMQ tests) now constructs transactions with{ RangeProofPaddedBulletproof, 4 }(BP+/CLSAG) instead of the removed Borromean or unsupported BP v2 configs. Removed the obsoletebulletproofparameter. Consolidated three JSON serialization tests (RegularTransaction, RingctTransaction, BulletproofTransaction) into oneBulletproofPlusTransactiontest. Fixes all 8 zmq_pub/zmq_server test failures. - Updated serialization round-trip test to BP+: Changed
Serialization.serializes_ringct_typesfrombp_version 2(throws "Unsupported BP version") tobp_version 4(Bulletproofs+). Updated assertions from MGs to CLSAGs and frombulletproofstobulletproofs_plus. - Removed legacy Monero-era core/perf test executions: Stopped running
deprecated Borromean/pre-RCT/fork-transition test generators in
core_testsand removed Borromean/MLSAG/range-proof performance test invocations and defaults, so CI validates HF1-era behavior only. - Hardened block-weight test contract for HF1 semantics:
block_weightcomparison now enforces deterministicH/BW/LTBWparity and EMBW floor invariants instead of byte-identical legacy model output, preventing false failures from non-consensus median implementation details. - Fixed block_reward test expected values: Updated emission curve
expectations to match Shekyl's
EMISSION_SPEED_FACTOR = 21(120s blocks) and per-block tail floor ofFINAL_SUBSIDY_PER_MINUTE * target_minutes. - Rewrote mining_parity release multiplier test: Replaced legacy pre-Shekyl-NG equality assertion (which tested a non-existent version 0) with a test that verifies the release multiplier correctly scales rewards above and below the tx volume baseline.
- Fixed Ubuntu 24.04 CI test runner: Replaced
pip installwithapt install python3-*packages to comply with PEP 668 (externally-managed-environment).
๐ Fixed
-
macOS cross-compilation (depends CI): Fixed multiple build failures for Cross-Mac x86_64 and Cross-Mac aarch64 targets:
- Raised macOS minimum deployment target from 10.8 (Mountain Lion, 2012)
to 10.15 (Catalina, 2019) to enable
std::filesystemsupport in the cross-compiled libc++. - Fixed Boost discovery in depends builds by setting
Boost_NO_BOOST_CMAKEand forcing MODULE mode, preventingBoostConfig.cmakevariant-check failures on cross-compiled Darwin libraries. - Made
boost_localea conditional dependency (Windows only), since it is only used within#ifdef WIN32blocks and was unavailable for Darwin cross-builds. - Added per-target
CC_<triple>/AR_<triple>/CFLAGS_<triple>environment variables inBuildRust.cmakeso theringcrate can locate the cross-compiler for C/assembly code. - Used system clang (instead of the depends-bundled Clang 9) for Rust
crate C compilation on Darwin, since
ring0.17 requires clang features unavailable in Clang 9 (macOS 11 version strings,-fno-semantic-interposition). - Guarded
-fno-semantic-interpositionbehindcheck_c_compiler_flag()so it is only added when the compiler supports it (Clang 9 does not). - Fixed OSX SDK cache key in
depends.ymlto include the SDK version and skip the cache step for non-macOS builds.
- Raised macOS minimum deployment target from 10.8 (Mountain Lion, 2012)
to 10.15 (Catalina, 2019) to enable
-
FreeBSD cross-compilation (depends CI): Fixed multiple build failures for the x86_64 FreeBSD target:
- Switched Boost's b2 toolset from
gcctoclangfor FreeBSD, fixing C++ standard library header resolution (<cstddef>not found). - Embedded
-stdlib=libc++in the FreeBSD clang++ wrapper script so all depends packages automatically use the correct C++ standard library, regardless of whether their own$(package)_cxxflagsoverrides the host flags (previously broke zeromq, sodium, and other packages). - Fixed compiler wrapper argument quoting: replaced the broken
echo "...$$$$""@"pattern withprintf '..."$$$$@"'so"$@"passes through correctly to the generated wrapper, preventing argument mangling for flags containing quotes (e.g.-DPACKAGE_VERSION="1.0.20"). - Added
-D_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTIONto both Boost's FreeBSD cxxflags and the CMake toolchain, restoringstd::unary_functioncompatibility needed by Boost 1.74'scontainer_hash/hash.hppunder FreeBSD's strict C++17 libc++. - Removed the unsupported
no-devcryptooption from OpenSSL's FreeBSD configure flags (the devcrypto engine was removed in OpenSSL 3.0). - Added
threadapi=pthread runtime-link=sharedto Boost's FreeBSD config options for correct threading and linking behavior.
- Switched Boost's b2 toolset from
-
Linux static release build (libudev linking): Added
libudev-devto therelease-tagged.ymlCI package list. Staticlibusb-1.0.aandlibhidapi-libusb.adepend onlibudevfor USB hotplug support; without the dev package installed,find_library(udev)failed and the final link produced undefinedudev_*references, preventing the "Publish GitHub Release" step from running. -
Win64 build failure (ICU generator expression): Replaced broken CMake generator expressions
$<$<BOOL:${WIN32}>:${ICU_LIBRARIES}>withif(WIN32)blocks insimplewallet,wallet_api, andlibwallet_api_testsCMakeLists. Generator expressions cannot contain semicolon-separated lists; the old pattern passed literal fragments like$<1:icuioto the linker on MinGW cross-compilation. -
Linux static build (libunbound linking): Fixed
FindUnbound.cmakescoping bug wherelist(APPEND UNBOUND_LIBRARIES ...)created a local variable shadowing thefind_librarycache entry. The transitive static deps (libevent, libnettle, libhogweed, libgmp) were silently dropped, causing undefined reference errors inrelease-static-linux-x86_64builds. -
JSON serialization of v3 (PQC) transactions: Added missing
pqc_authfield to the RapidJSONtoJsonValue/fromJsonValueroundtrip forcryptonote::transaction. V3 transactions created underHF_VERSION_SHEKYL_NGinclude apqc_authenticationenvelope; without JSON support the field was silently dropped, causingget_transaction_hashto fail with "Inconsistent transaction prefix, unprunable and blob sizes" after a JSON roundtrip. Fixes theJsonSerialization.BulletproofPlusTransactionunit test failure.
GUI Wallet
- New project: Shekyl GUI Wallet (
shekyl-gui-wallet) at Shekyl-Foundation/shekyl-gui-wallet. Built with Tauri 2 (Rust backend) + Vite + React 19 + TypeScript + Tailwind CSS 4. Initial scaffold includes 6 pages (Dashboard, Send, Receive, Staking, Transactions, Settings), stub Tauri commands, Shekyl gold/purple design system, and verified production builds for Linux (.deb, .rpm, .AppImage). Phase 2 will add the C++ FFI bridge towallet2_api.hfor real wallet operations. - Added testing infrastructure: Vitest + React Testing Library for frontend (20 tests across 6 suites), cargo test for Rust backend (10 tests), with Tauri IPC mocking for isolated component testing.
- Added CI/CD via GitHub Actions:
ci.ymlruns ESLint, TypeScript type-check, Vitest, Rustfmt, Clippy, and cargo test on every PR;release.ymlbuilds multi-platform binaries (Linux x64, Windows x64, macOS ARM64 + Intel) viatauri-actionand creates draft GitHub releases.
Consensus timing alignment (HF1)
- Fixed remaining runtime paths that still derived timing from legacy
DIFFICULTY_TARGET_V1(60s) so active Shekyl HF1 behavior consistently usesDIFFICULTY_TARGET_V2(120s) for difficulty target selection, block reward minute-scaling, unlock-time leeway checks, sync ETA reporting, and wallet lock-time display. - Updated
docs/ECONOMY_TESTNET_READINESS_MATRIX.mdto mark the 120s block-time drift item as resolved (code_fix_requiredcompleted).
๐ Documentation
- Updated
docs/V3_ROLLOUT.mdto reflect HF1 (genesis) activation instead of the stale HF17 references. Added v3-first test strategy section. - Updated
docs/POST_QUANTUM_CRYPTOGRAPHY.mdscheme_id status table and deferred-items section from HF17 to HF1. - Updated
docs/PQC_MULTISIG.mdV3 signature list heading from HF17 to HF1. - Updated
docs/STAKER_REWARD_DISBURSEMENT.mdto reference HF1 activation. - Updated
docs/ECONOMY_TESTNET_READINESS_MATRIX.mdHF naming drift label fromdoc_correctionto resolved. - Added
core_testssection todocs/COMPILING_DEBUGGING_TESTING.mddocumenting the v3-from-genesis test approach and how to run/filter tests.
Genesis initialization compatibility
- Regenerated
GENESIS_TXfor mainnet, testnet, and stagenet to modern coinbase format (tx.version = 2) with tagged outputs. - Removed all legacy genesis compatibility exceptions and enforced strict coinbase version checks (
tx.version > 1) across all network types, includingFAKECHAIN. - Fixed genesis reward validation to accept the hardcoded
GENESIS_TXamount atheight == 0while leaving post-genesis reward accounting unchanged. - Fixed startup edge case where long-term weight median calculations could evaluate with zero historical blocks during genesis initialization (
count == 0), causing daemon boot failure on empty data dirs. - Updated genesis-construction helper (
build_genesis_coinbase_from_destinations) to emittx.version = 2with view-tagged outputs for current HF1 expectations. - Added canonical root build command
make genesis-builder(using the main release build dir withGENESIS_TOOL_SRC_DIR) to avoid split/ambiguous genesis-builder binaries across multiple build trees.
Testnet economy readiness checks
- Added
docs/ECONOMY_TESTNET_READINESS_MATRIX.mdto track design-vs-code status for economy testnet rehearsal with explicit drift tags (doc_correction,code_fix_required,needs_decision). - Added
scripts/check_testnet_genesis_consensus.pyto verify multi-node testnet tuple consistency (height 0 block hash,miner tx hash,tx hex) and optional economy field presence inget_info. - Added Rust parity/invariant tests:
shekyl-economics-sim: validatesSimParams::default()againstconfig/economics_params.json.shekyl-economics: added release monotonicity, burn bounds, and emission-share monotonicity tests.shekyl-ffi: added direct FFI-vs-Rust consistency tests for burn pct and emission share.
- Added functional RPC test
tests/functional_tests/economy_info.pyand included it infunctional_tests_rpc.pydefault test list to assert required economy fields are exposed byget_info. - Corrected documentation errors without changing design intent:
- Clarified
DESIGN_CONCEPTS.mdSection 2 as historical baseline. - Removed duplicate heading in
GENESIS_TRANSPARENCY.md. - Linked
RELEASE_CHECKLIST.mdtestnet section to the rehearsal runbook/checklist and deterministic tuple check command.
- Clarified
BREAKING: Second-pass rebrand (wallet, URI, serialization)
- URI scheme: Wallet URI generation and parsing now use
shekyl:only. The legacymonero:scheme is no longer accepted. QR codes and payment links generated by previous builds will fail to parse. Regenerate all payment URIs before upgrading wallets. - Wallet/export/cache magic strings: All file-format magic prefixes have
been rewritten from
MonerotoShekyl:UNSIGNED_TX_PREFIXโ"Shekyl unsigned tx set\005"SIGNED_TX_PREFIXโ"Shekyl signed tx set\005"MULTISIG_UNSIGNED_TX_PREFIXโ"Shekyl multisig unsigned tx set\001"KEY_IMAGE_EXPORT_FILE_MAGICโ"Shekyl key image export\003"MULTISIG_EXPORT_FILE_MAGICโ"Shekyl multisig export\001"OUTPUT_EXPORT_FILE_MAGICโ"Shekyl output export\004"ASCII_OUTPUT_MAGICโ"ShekylAsciiDataV1"- Wallet cache magic โ
"shekyl wallet cache"Old wallet caches, exported key images, multisig exports, signed/unsigned tx sets, and output exports are incompatible and must be re-exported after upgrading.
- Message signing domain:
HASH_KEY_MESSAGE_SIGNINGchanged from"MoneroMessageSignature"to"ShekylMessageSignature". Messages signed with the old domain separator will fail verification. - i18n domain: Translation catalogue domain changed from
"monero"to"shekyl". - Daemon stdout redirect: Daemonized output file changed from
bitmonero.daemon.stdout.stderrtoshekyl.daemon.stdout.stderr. - Log file names: All blockchain utility log files renamed from
monero-blockchain-*toshekyl-blockchain-*. - DNS seed/checkpoint domains: Replaced
moneroseeds.*andmoneropulse.*lookups with 5-domain consensus set:shekyl.org,shekyl.net,shekyl.com,shekyl.biz,shekyl.io. Majority threshold is 3 of 5. Seeshekyl-dev/docs/DNS_CONFIG.mdfor the full infrastructure reference. - Update check: Software name comparison for macOS
.dmgextension switched frommonero-guitoshekyl-gui. - Hardware wallet: Ledger app error message now references "Shekyl Ledger App" instead of "Monero Ledger App". Trezor protobuf namespaces are unchanged (third-party protocol dependency).
- Intentionally preserved: Trezor/Ledger protobuf includes and protocol
namespaces (
hw.trezor.messages.monero.*), Esperanto mnemonic word"monero"(means "money"), academic paper citations, copyright headers,MONERO_DEFAULT_LOG_CATEGORYbuild-internal macros, andMakeCryptoOps.pybuild artifacts.
Operator migration checklist
- Delete old wallet cache files (
.keysfiles are unaffected). - Re-export any key-image, multisig, or output export files.
- Re-export and re-sign any unsigned/signed transaction sets.
- Regenerate all
monero:QR codes/payment URIs asshekyl:URIs. - Update any scripts or integrations that parse URI scheme or file magic.
- Verify message signatures were not created with the old signing domain.
- Update log rotation configs if they reference
monero-blockchain-*paths. - Update DNS infrastructure to serve records under all 5 TLDs (
.org,.net,.com,.biz,.io). Seeshekyl-dev/docs/DNS_CONFIG.md.
Dead Monero legacy code removal
-
Dead HF branch cleanup: Collapsed all always-true / always-false hard fork version branches across
blockchain.cpp(~25 sites),wallet2.cpp(~22 sites),cryptonote_basic_impl.cpp(2 sites), andcryptonote_core.cpp(2 sites). Since allHF_VERSION_*constants are 1, everyhf_version >= HF_VERSION_*was always true and everyhf_version < HF_VERSION_*was always false. Collapsed fee algorithms, ring size ladders, tx version ladders, difficulty target selection, sync block size selection, BP/CLSAG/BP+ gating, dynamic fee scaling, long-term block weight calculations, anduse_fork_rules()call sites. Removed ~500-800 lines of dead conditional logic. -
Dropped v1 transaction support entirely:
- Consensus:
check_tx_outputsnow rejectstx.version == 1outright.check_tx_inputssetsmin_tx_version = 2unconditionally; unmixable output counting and ring-size exemptions removed. v1 ring signature verification code and threaded v1 signature checking removed fromcheck_tx_inputs.expand_transaction_2only handles CLSAG and BulletproofPlus; old RCTTypeFull/Simple/Bulletproof/Bulletproof2 branches removed. - RingCT (
rctSigs.cpp/.h): Removed ~770 lines of dead crypto code:genBorromean,verifyBorromean,MLSAG_Gen,MLSAG_Ver,proveRange,verRange,proveRctMG,proveRctMGSimple,verRctMG,verRctMGSimple,populateFromBlockchain,genRct(both overloads),verRct,decodeRct(both overloads).genRctSimple,verRctSemanticsSimple,verRctNonSemanticsSimple, anddecodeRctSimpleonly acceptRCTTypeCLSAGandRCTTypeBulletproofPlus. Header reduced from 144 to 87 lines. - Transaction construction (
cryptonote_tx_utils.cpp): Removed v1 ring signature generation block and non-simple RCT construction (genRct). All transactions now usegenRctSimple(CLSAG path). - Tx verification utils: Removed
RCTTypeSimple,RCTTypeFull,RCTTypeBulletproof,RCTTypeBulletproof2from batch semantics verification. - Test fixups: Updated all test files under
tests/to match the removed RCT primitives. Stubbed performance benchmarks for MLSAG (rct_mlsag.h,sig_mlsag.h) and Borromean range proofs (range_proof.h). ReplacedverRctwithverRctNonSemanticsSimpleincheck_tx_signature.h. RemoveddecodeRctelse-branches fromrct.cpp,rct2.cpp,bulletproofs.cpp,bulletproof_plus.cpp. Inunit_tests/ringct.cpp: removed Borromean, MLSAG, and RCTTypeFull-only tests; rewrotemake_sample_rct_sigto usegenRctSimple; replaced allverRctcalls withverRctSimple.
- Consensus:
-
Wallet v1 cleanup: Removed unmixable sweep functions, v1 fee/amount paths, v1 coinbase optimization, dead non-RCT creation branches, and replaced
RangeProofBorromeandefaults withRangeProofPaddedBulletproof.sweep_dustRPC returns error;createSweepUnmixableTransactionAPI returns empty result with error status. -
Trezor Shekyl rebrand: Renamed all include guard macros from
MONERO_*_HtoSHEKYL_*_Hin 8device_trezor/headers. Updated derivation path comment and HTTP Origin URL. Protobuf message types and wire protocol identifiers intentionally preserved (must match Trezor firmware definitions).
Epee Phase 1: Rust replacement for security-critical primitives
- SSL certificate generation migrated to Rust (
rcgen): Replaced the deprecated OpenSSL RSA/EC_KEY certificate generation innet_ssl.cppwith Rust'srcgencrate (ECDSA P-256) via FFI. Eliminates allRSA_new,RSA_generate_key_ex,EC_KEY_new,EC_KEY_generate_key, and other OpenSSL 3.0-deprecated API calls. Thecreate_rsa_ssl_certificateandcreate_ec_ssl_certificatefunctions are replaced by a singlecreate_ssl_certificatethat delegates toshekyl_generate_ssl_certificatein the Rust FFI, returning PEM-encoded key+cert for loading into OpenSSL's SSL_CTX via non-deprecated BIO APIs. - Post-quantum hybrid key exchange enabled: TLS context configuration now
prefers
X25519MLKEM768(FIPS 203 ML-KEM-768 hybrid) key exchange groups, falling back to classicalX25519:P-256:P-384when the OpenSSL build lacks PQ support. Also added explicit TLS 1.3 ciphersuite configuration. Removed deprecatedSSL_CTX_set_ecdh_autocall. - Secure memory wiping migrated to Rust (
zeroize): Replaced the platform-specificmemwipe.cimplementation (memset_s / explicit_bzero / compiler-barrier fallback) with a single call to the Rustzeroizecrate viashekyl_memwipeFFI. Thezeroizecrate useswrite_volatilewhich is guaranteed not to be optimized away, replacing the fragile compiler barrier tricks. - Memory locking migrated to Rust (
libc): Replaced the GNUC-onlymlock/munlock/sysconfcalls inmlocker.cppwith Rust FFI functions (shekyl_mlock,shekyl_munlock,shekyl_page_size) backed by thelibccrate. Adds WindowsVirtualLock/VirtualUnlocksupport that was previously missing (#warning Missing implementation). Themlocked<T>andscrubbed<T>C++ template wrappers are preserved unchanged. - New Rust FFI dependencies: Added
rcgen = "0.14",zeroize = "1",libc = "0.2"toshekyl-ffi/Cargo.toml. - C-compatible FFI header: Added
src/shekyl/shekyl_secure_mem.hwith C-linkage declarations for the secure memory primitives, usable from both C (memwipe.c) and C++ (mlocker.cpp) translation units. - CMake wiring:
epeelibrary now links${SHEKYL_FFI_LINK_LIBS}and includes${CMAKE_SOURCE_DIR}/srcfor the FFI headers.
Build fixes
- Boost CONFIG-mode compatibility shim: When Boost is found via cmake
CONFIG mode (Boost 1.85+), old-style
${Boost_XXX_LIBRARY}variables may resolve to versioned.sopaths that don't exist on rolling-release distros (e.g. Arch Linux with Boost 1.90). Added a shim in the rootCMakeLists.txtthat remaps allBoost_*_LIBRARYvariables toBoost::*imported targets when CONFIG mode is active. Fixes linker failures on Arch. - Removed duplicate
parse_amounttest: Two identicalTEST_pos(18446744073709551615, ...)entries intests/unit_tests/parse_amount.cppcaused a redefinition error on macOS Clang. Removed the duplicate. - Boost CONFIG-mode validation: Added a cmake-configure-time check that
verifies Boost imported-target
IMPORTED_LOCATIONfiles exist on disk. Gives a clearFATAL_ERRORwith remediation steps instead of a cryptic linker failure minutes into the build. - Arch Linux CI: Added
boost-libsto the Arch pacman install to provide shared.sofiles alongside theboostheaders/cmake-config package. - Ubuntu 24.04 test matrix: Added Ubuntu 24.04 to the
test-ubuntuCI matrix (previously only 22.04 was tested).
Depends system updates
- FreeBSD sysroot updated to 14.4-RELEASE: The cross-compilation
sysroot was stuck at FreeBSD 11.3 (EOL Sept 2021), whose
base.txzhad been removed from FreeBSD mirrors (404). Updated to 14.4-RELEASE (March 2026), updated SHA256 hash, and fixed clang wrapper scripts from clang-8 to clang-14 to matchhosts/freebsd.mk. Added-stdlib=libc++to CXXFLAGS and LDFLAGS since FreeBSD uses libc++ and the Ubuntu host's clang-14 defaults to libstdc++. Also addedlibc++-14-devandlibc++abi-14-devto CI packages for the FreeBSD cross-build so the host compiler can find libc++ headers when-stdlib=libc++is specified. - Boost: skip CONFIG mode for depends builds: The depends-built Boost
1.74.0 installs CMake config files whose variant detection fails for
darwin cross-builds (
boost_localereports "No suitable build variant").find_package(Boost ... CONFIG)is now skipped whenDEPENDSis true (set by the depends toolchain), falling back to the more robust MODULE mode (FindBoost.cmake). - OpenSSL: disabled
devcryptoengine for FreeBSD: Addedno-devcryptoto FreeBSD OpenSSL configure options. The/dev/cryptoengine requires thecrypto/cryptodev.hkernel header which is not available in a cross-compilation sysroot. - libsodium updated to 1.0.20: The 1.0.18 tarball was removed from
download.libsodium.org(404). Updated to 1.0.20 with new SHA256 hash. Removed the 1.0.18-specific patches (fix-whitespace.patch,disable-glibc-getrandom-getentropy.patch) which no longer apply.
Warning cleanup and dead code removal
- Removed dead fork helpers: Deleted unused
get_bulletproof_fork(),get_bulletproof_plus_fork(), andget_clsag_fork()fromwallet2.cpp. These Monero-era version ladders had no call sites; Shekyl activates all features from HF1. - Removed dead variable: Deleted unused
bool refreshedinwallet2::refresh(). - Removed legacy
result_typetypedefs: Deletedusing result_type = voidfromadd_inputandadd_outputvisitor structs injson_object.cpp. These were required byboost::static_visitorbut are unused bystd::visit. - Fixed uninitialized-variable warning: Zero-initialized
local_blocks_to_unlockandlocal_time_to_unlockinwallet2::unlocked_balance_all(). - Fixed aliasing cast in wallet serialization: Replaced C-style cast of
m_account_tagsfrompair<serializable_map, vector>topair<map, vector>&with direct.parent()accessor, eliminating formal undefined behavior. - Suppressed epee warnings: Added targeted
#pragma GCC diagnosticguards for-Wclass-memaccess(memcpy intomlocked<scrubbed<>>inkeyvalue_serialization_overloads.h) and-Wstring-compare(type_info comparisons inportable_storage.h). - Renamed test target:
monero-wallet-crypto-benchrenamed toshekyl-wallet-crypto-bench. - Trezor Protobuf fixes: Added
std::string()wrapping forGetDescriptor()->name()calls inmessages_map.cpp/.hppto handle Protobuf 22+ returningabsl::string_view/std::string_view. Added missing<cstdint>include toexceptions.hpp.
Rust crypto infrastructure
- New
shekyl-crypto-hashcrate: Implementscn_fast_hash(Keccak-256 with original padding, not SHA3) andtree_hash(Merkle tree) in Rust usingtiny-keccak. Both functions produce byte-identical output to the C implementations insrc/crypto/hash.candsrc/crypto/tree-hash.c. - FFI exports:
shekyl_cn_fast_hashandshekyl_tree_hashexposed throughshekyl-ffiwith C-ABI declarations inshekyl_ffi.h. The C++ side can now call Rust hashing alongside or instead of the C path. - Rust-preferred development rule: Added
.cursor/rules/rust-preferred.mdcestablishing policy for gradual C++ to Rust migration: new modules in Rust, crypto primitives via RustCrypto crates, computational extraction to Rust behind FFI when modifying existing C++ modules.
Hardfork reboot and testnet wallet readiness
- Hardfork schedule rebooted: All
HF_VERSION_*constants collapsed to 1. The chain starts with all features active from genesis -- no legacy migration gates. Hardfork tables reduced to single-entry{ 1, 1, 0, timestamp }for all three networks (mainnet, testnet, stagenet). - Removed all raw numeric HF version gates (
hf_version <= 3,>= 7,< 8,> 8, etc.) from consensus and transaction construction code, replacing them with namedHF_VERSION_*constants. Legacy Monero-era transition logic (borromean proofs, bulletproofs v1, grandfathered txs) removed. - Coinbase transactions always v2 RCT with single output, zero dust threshold.
- Staked outputs excluded from spendable balance:
is_transfer_unlocked()now returns false for staked outputs, preventing them from being selected during normal transfers.balance_per_subaddressandunlocked_balance_per_subaddressskip staked outputs. - Unstake transaction fixed:
create_unstake_transactionnow passes matured staked output indices directly tocreate_transactions_from, properly using the actual staked UTXOs as transaction inputs with standard ring signatures. - Claim reward validation fixed:
check_stake_claim_inputnow looks up the real staked output from the blockchain DB to get the actual amount and tier, replacing the hardcodedshekyl_stake_weight(0, 0)placeholder. - New daemon RPC
estimate_claim_reward: computes per-output reward server-side using the accrual database, returning reward amount, tier, and staked amount. Walletestimate_claimable_rewardnow calls this RPC instead of returning a hardcoded zero. - CLI improvements:
balancecommand now shows staked balance alongside liquid and unlocked balances. Newstaking_infocommand shows wallet staking overview (locked/matured output counts with tier and remaining lock blocks).stake,unstake, andclaim_rewardscommands now include daemon connectivity guards. - Wallet RPC fixes:
unstakeresponse changed from singletx_hashtotx_hash_listarray to support multi-transaction unstaking.stakerequest now acceptsaccount_indexparameter. Newget_staked_balanceRPC returns staked balance with locked/matured output counts.
Post-quantum cryptography
- Phase 4 wallet/core PQC wiring completed: all v3 transaction construction
paths now include hybrid Ed25519 + ML-DSA-65 signing via
pqc_auth. Fixedcreate_claim_transaction(staking reward claims) which previously built v3 transactions without PQC authentication, causing consensus rejection. - PQC verification enforced in both mempool acceptance and block validation for all non-coinbase v3 transactions.
- Multisig wallets intentionally restricted to v2 transactions (no PQC); the PQC secret key is cleared on multisig creation with a documented design note.
- Aligned
POST_QUANTUM_CRYPTOGRAPHY.mdfield naming:hybrid_ownership_materialrenamed tohybrid_public_keyto match the canonical code implementation. - Added three negative PQC test vectors (
docs/PQC_TEST_VECTOR_002โ004) covering tampered ownership material, wrong scheme_id, and oversized/truncated signature blobs. Each vector is generated and verified by integration tests inrust/shekyl-crypto-pq/tests/negative_vectors.rs. - Reconciled
POST_QUANTUM_CRYPTOGRAPHY.mdOpen Items: resolved Rust crate selection,RctSigningBodylayout, ownership binding, and max tx size; onlyscheme_idregistry extension remains open. - Added tentative V4 PQC Privacy Roadmap to
POST_QUANTUM_CRYPTOGRAPHY.mdwith four phases (V4-A Research, V4-B Prototype, V4-C Testnet, V4-D Activation) and explicit KEM composition decision milestone (X25519 + ML-KEM-768viaHKDF-SHA-512). - Added payload limit guidance section to
V3_ROLLOUT.mdwith recommended minimum mempool/ZMQ/relay buffer sizes for post-PQC transactions.
Economics and simulation
- Added
rust/shekyl-economics-simworkspace crate: reproducible 8-scenario simulation harness driven fromconfig/economics_params.json. Scenarios cover baseline, boom-bust, sustained growth, stuffing attack, stake concentration, mass unstaking, chain bootstrap, and late-chain tail state. Results archived indocs/economics_sim_results.json. - Provisionally locked
tx_baseline(50) andFINAL_SUBSIDY_PER_MINUTE(300,000,000) inDESIGN_CONCEPTS.mdafter simulation validation; pending final testnet confirmation. - Wired live chain-health RPC fields in
get_info:release_multipliernow computed from rollingtx_volume_avg,burn_pctfrom current chain state,total_burnedpersisted in LMDB and accumulated per block. - Wired
total_stakedinget_staking_infovia newBlockchain::get_total_staked()accessor backed by existing stake cache. - Added
total_burnedLMDB persistence:set_total_burned/get_total_burnedonBlockchainDB, with rollback support via extendedstaker_accrual_record(actually_destroyedfield).
Privacy and anonymity networks
- Updated
ANONYMITY_NETWORKS.mdwith measured v3 payload impact analysis (cell/fragment counts for Tor and I2P), known leak vectors vs mitigations matrix, and recommended pre-mainnet testing checklist. - Extended
LEVIN_PROTOCOL.mdwire inventory with per-command PQC size impact, anonymity sensitivity ratings, and a summary table covering all P2P and Cryptonote protocol commands. - Added privacy considerations section to
STAKER_REWARD_DISBURSEMENT.mdcovering claim timing, amount correlation, and staked output visibility. - Added reward-driven privacy/mixing research appendix to
DESIGN_CONCEPTS.mdevaluating random maturation delay, claim batching, and reward output shaping with adversarial analysis and go/no-go criteria.
C++17 and Boost migration
- C++17 standard bump:
CMAKE_CXX_STANDARDchanged from 14 to 17 in both the mainCMakeLists.txtand the macOS cross-compilation toolchain (contrib/depends/toolchain.cmake.in). This unblocksstd::filesystem,std::optional, and other modern C++ features. Upstream Monero cherry-picks that required C++14-to-C++17 back-ports now compile without shims. boost::optionalโstd::optional(complete): Migrated ~486 use sites across ~93 files insrc/,contrib/epee/, andtests/. Replacedboost::optional<T>withstd::optional<T>,boost::nonewithstd::nullopt,boost::make_optionalwithstd::make_optional, and.get()accessor calls with*/->. Added astd::optionalBoost.Serialization adapter incryptonote_boost_serialization.hso PQC auth fields serialize correctly. ReplacedBOOST_STATIC_ASSERT/boost::is_base_ofwithstatic_assert/std::is_base_ofin Trezormessages_map.hpp.boost::filesystemโstd::filesystem(wallet/RPC layer): Migratedwallet_manager.cpp,wallet_rpc_server.cpp,core_rpc_server.cpp, andwallet_args.cppfromboost::filesystemtostd::filesystem. Combined with the earlier utility-file migration, this covers all filesystem usage outside ofnet_ssl.cpp(epee, deferred due to permissions API coupling).boost::formatremoval (wallet/RPC layer): Replaced allboost::formatcalls inwallet2.cpp(4),wallet_rpc_server.cpp(8), andwallet_args.cpp(1) with stream output or string concatenation.simplewallet.cpp(106 uses, i18n-sensitive) remains deferred.boost::chrono/boost::this_threadin daemonizer: Replaced withstd::chrono/std::this_threadinwindows_service.cpp(PR #9544 equivalent).- Medium-effort Boost removals (completed earlier):
boost::algorithm::string(trim, to_lower, iequals, join) replaced withtools::string_utilhelpers insrc/common/string_util.h.boost::formatreplaced withsnprintf, stream output, or string concatenation inutil.cpp,message_store.cpp,gen_ssl_cert.cpp,gen_multisig.cpp.boost::regexreplaced withstd::regexinsimplewallet.cppandwallet_manager.cpp.boost::mutex,boost::lock_guard,boost::unique_lock, andboost::condition_variablereplaced withstd::mutex,std::lock_guard,std::unique_lock, andstd::condition_variableinutil.h,util.cpp,threadpool.h,threadpool.cpp, andrpc_payment.h/rpc_payment.cpp.boost::thread::hardware_concurrency()replaced withstd::thread::hardware_concurrency().
- Filesystem migration (utility files, completed earlier):
boost::filesystemreplaced withstd::filesysteminblockchain_export.cpp,blockchain_import.cpp,cn_deserialize.cpp,util.cpp,bootstrap_file.h/.cpp, andblocksdat_file.h/.cpp.- Eliminated
BOOST_VERSIONpreprocessor conditional incopy_file().
- Upstream Monero cherry-pick verification: Confirmed PRs #9628 (ASIO
io_serviceโio_context), #6690 (serialization overhaul), and #9544 (daemonizer chrono/thread) are already absorbed in our tree. boost::variantโstd::variant(complete): Full migration fromboost::variantto C++17std::variantacross the entire codebase (~100+ replacements in ~40 files):- Serialization layer rewrite (
serialization/variant.h): Replaced Boost.MPL type-list iteration with C++17if constexprrecursion for deserialization andstd::visitlambda for serialization. Removed allboost::mpl,boost::static_visitor, andboost::apply_visitorusage. - Archive headers: Replaced
boost::mpl::bool_<B>withstd::bool_constant<B>inbinary_archive.h,json_archive.h, andserialization.h. Replacedboost::true_type/false_typeandboost::is_integralwithstdequivalents. - Core typedefs: Changed
txin_v,txout_target_v,tx_extra_field,transfer_view::block, and Trezorrsig_vfromboost::varianttostd::variant. - Boost.Serialization shim: Added a local ~45-line
std::variantserialization adapter incryptonote_boost_serialization.h(save/load with index + payload, wire-compatible with oldboost::variantformat). Removed dependency on<boost/serialization/variant.hpp>. - Mechanical replacements across all
src/andtests/files:boost::get<T>(v)โstd::get<T>(v),boost::get<T>(&v)โstd::get_if<T>(&v),v.type() == typeid(T)โstd::holds_alternative<T>(v),v.which()โv.index(),boost::apply_visitor(vis, v)โstd::visit(vis, v). - P2P layer: Updated
net_peerlist_boost_serialization.hto usestd::false_type/std::true_typeinstead ofboost::mplequivalents. tests/unit_tests/net.cppretainsboost::get<N>forboost::tupleaccess viaboost::combine(not variant-related).
- Serialization layer rewrite (
- Remaining deferred Boost areas: ASIO deep plumbing,
multi-index containers, Spirit parser, multiprecision,
net_ssl.cppfilesystem,simplewallet.cppformat strings,boost::thread::attributes(stack size). Tagged withTODO(shekyl-v4)in source. SeeDOCUMENTATION_TODOS_AND_PQC.mdsection 1.11 for the full backlog.
CI/CD and build system
- Boost minimum bumped to 1.74:
BOOST_MIN_VERinCMakeLists.txtraised from 1.62 to 1.74. Thecontrib/dependssystem now pins Boost 1.74.0 (previously 1.69.0) and builds with-std=c++17. Removed legacy Boost 1.64 patches (fix_aroptions.patch,fix_arm_arch.patch) that do not apply to 1.74. - CI containers updated to Ubuntu 22.04 minimum: Dropped Debian 11 and
Ubuntu 20.04 build jobs from
build.yml,depends.yml, andrelease-tagged.yml. Ubuntu 22.04 is now the lowest-common-denominator Linux build environment (ships Boost 1.74+ and GCC 11+). Added Ubuntu 24.04 build matrix entry. - Migrated version identifiers from legacy
MONERO_*symbols to canonicalSHEKYL_*names (SHEKYL_VERSION,SHEKYL_VERSION_TAG,SHEKYL_RELEASE_NAME,SHEKYL_VERSION_FULL,SHEKYL_VERSION_IS_RELEASE) insrc/version.handsrc/version.cpp.in. The oldMONERO_*names are retained as preprocessor aliases so existing call sites and future Monero upstream cherry-picks continue to compile unchanged. The aliases will be removed in a single cleanup after v4 RingPQC stabilises. - Fixed Gitian deterministic build pipeline: replaced all hardcoded Monero
repository URLs and internal package names with Shekyl equivalents across
gitian-build.py, all 5 gitian descriptor YAMLs,dockrun.sh, and thegitian.ymlGitHub Actions workflow. The workflow now passes--urlto ensure the correct repository is cloned. Added checkout error handling with an actionable message when a tag/branch is missing. - Tag-driven versioning:
GitVersion.cmakenow extracts the version string from git tags (e.g.v3.0.2-RC1โ3.0.2-RC1). The hardcoded version inversion.cpp.inis replaced with the CMake-substituted@SHEKYL_VERSION@; a default (3.1.0) is used for development builds not on a tag.Version.cmakecentralises the fallback default inSHEKYL_VERSION_DEFAULT. - Updated RPC version string validator (
rpc_version_str.cpp) from Monero's four-number format to Shekyl's three-number semver with optional pre-release suffix (e.g.3.0.2-RC1-release). - Updated gitian descriptor names from Monero's
0.18to Shekyl3series. - Added
release/taggedGitHub Actions workflow: builds static Linux x86_64 binaries, cross-compiles Windows x64 via MinGW, and produces.tar.gz,.deb,.rpm,.zip, and NSIS.exeinstaller artifacts on everyv*tag. - Added
BuildRust.cmakecross-compilation support: detectsCMAKE_SYSTEM_NAMEandCMAKE_SYSTEM_PROCESSORto derive Rust target triples for Windows, macOS, Android, FreeBSD, and Linux cross-targets (ARM, aarch64, i686, RISC-V); automatically configures the MinGW linker for Windows cross-compilation. - Added Rust toolchain installation to all CI workflows (
build.yml,depends.yml,release-tagged.yml) and all 5 Gitian deterministic build descriptors with appropriate cross-compilation targets; required forlibshekyl_ffi.alinking. - Fixed Gitian
gitian-build.pyto fetch tags explicitly (--tags) during repository setup, preventing checkout failures for tag-based builds. - Enhanced
gitian-build.pyerror handling: robustlsb_releasedetection, auto-correction of stale clone origins when--urlchanges, and detailed diagnostics on checkout failure (lists available remote tags and suggests the push command). - Added
workflow_dispatchtrigger togitian.ymlwith configurabletagandrepo_urlinputs, allowing manual re-runs and testing against forks without retagging. - Fixed Doxygen project name from
MonerotoShekylincmake/Doxyfile.in. - Replaced bundled Google Test 1.7.0 (2013) with CMake
FetchContentfor GoogleTest v1.16.0. FixesGTEST_SKIPcompilation errors on all platforms without a system gtest. Removes 34k lines of vendored source. - Upgraded all GitHub Actions workflows to Node.js 24: bumped
actions/checkoutto v5,actions/cacheto v5,actions/upload-artifactto v6, andactions/download-artifactto v7 to resolve the Node.js 20 deprecation warnings. - Trimmed
depends.ymlcross-compilation matrix: dropped i686 Win and i686 Linux (32-bit targets are dead); deferred RISCV 64-bit and ARM v7 until user demand materialises. Active matrix is now ARM v8, Win64, x86_64 Linux, Cross-Mac x86_64, Cross-Mac aarch64, and x86_64 FreeBSD (6 targets, down from 10). Added Cross-Mac aarch64 to the artifact upload filter. - Added Linux packaging files:
contrib/packaging/linux/shekyld.service(systemd unit) andcontrib/packaging/windows/shekyl.nsi(NSIS installer).
Upstream Monero sync (March 2026)
Cherry-picked 62 upstream Monero commits (from monero-project/monero master)
across five risk-phased integration rounds. Key improvements absorbed:
- Wallet: Fee priority refactoring (
fee_priorityenum + utility functions), improved subaddress lookahead logic,set_subaddress_lookaheadRPC endpoint (no longer requires password), incoming transfers without daemon connection, HTTP body size limit, fast refresh checkpoint fix, ring index sanity checks,find_and_save_rings()deprecation, pool spend identification during scan. - Daemon/RPC: Dynamic
print_connectionscolumn width, ZMQ IPv6 support, dynamic base fee estimates via ZMQ,getblocks.binstart height validation, CryptoNight v1 error reporting, batch key image existence check, blockchain prune DB version handling, removedCOMMAND_RPC_SUBMIT_RAW_TX(light wallet deprecated). - P2P/Network: Removed
state_idleconnection state, fixed inverted peerlist ternary, removed#pragma packfrom protocol defs, connection patches for reliability, dynamic block sync span limits. - Crypto/Serialization: Fixed invalid
constexpron hash functions, addedhash_combine.h, aligned container pod-as-blob serialization, fixedapply_permutation()forstd::vector<bool>. - Build system: Removed iwyu/MSVC/obsolete CMake targets, added
MANUAL_SUBMODULEScache option, Trezor protobuf 30 compatibility, fixedFetchContent/ExternalProjectcmake usage. - Tests: New unit tests for format utils, threadpool, varint, logging, serialization static asserts, cold signing functional test fixes.
- Misc: Boost ASIO 1.87+ compatibility, fixed Trezor temporary binding,
fixed multisig key exchange intermediate message update,
constexprcn_variant1_check, extra nonce length fix, removed redundant BP consensus rule.
Skipped commits (deferred to future integration): input verification caching
(conflicts with txin_stake_claim/PQC), wallet_keys_unlocker refactoring,
get_txids_loose DB API (missing prerequisite), complex subaddress lookahead
fixes, and several CMake/depends version bumps that conflict with Shekyl's
build system divergences.
Cherry-picked code was initially adapted to C++14 compatibility; with the
subsequent C++17 standard bump, many of those back-ports are now unnecessary
and can use native std::optional, std::string_view, etc.
Documentation
- Added
docs/EXECUTABLES.md: comprehensive reference for all 17 build artifacts covering usage, CLI options, interactive commands, and examples forshekyld,shekyl-wallet-cli,shekyl-wallet-rpc, blockchain utilities, and debug tools.
Operations
- Added
utils/systemd/shekyld.servicefor Shekyl-native daemon service deployment (/usr/local/bin/shekyld+/etc/shekyl/shekyld.conf). - Updated
docs/INSTALLATION_GUIDE.mdrelated-doc references to include seed operations documentation in the companionshekyl-devdocs set. - Added
docs/BLOCKCHAIN_NETWORKS.mdwith a deep-dive comparison of network models across Bitcoin, Ethereum, Monero, Solana, Polkadot, and Avalanche, and mapped those patterns to Shekyl's mainnet/testnet/stagenet/fakechain usage guidance. - Migrated Shekyl stagenet defaults from legacy Monero ports to
13021(P2P),13029(RPC), and13025(ZMQ), and aligned test/docs references so--testnetworkflows use12029while scripts support overrideable network/daemon variables. - Updated libwallet API helper scripts to call
shekyl-wallet-cli(notmonero-wallet-cli) so test tooling matches Shekyl binary names.
Staking (end-to-end claim-based system)
- Added
txout_to_staked_keyoutput target type for locking coins at a chosen tier (short/medium/long). Outputs carrylock_tierfield enforced at the consensus layer. (Note:lock_untilwas originally stored on-chain but was removed in a subsequent fix โ see Bug 13 under Unreleased.) - Added
txin_stake_claiminput type for claiming accrued staking rewards. Claims specify a height range and are validated against deterministic per-block accrual records. - Extended LMDB schema with
staker_accrualandstaker_claimstables plus astaker_pool_balanceproperty for on-chain reward pool accounting. - Per-block accrual logic computes staker emission share and fee pool allocation at block insertion time, with full reversal on reorg (block pop).
- Consensus validation: lock period enforcement on staked outputs, claim amount verification against accrual records, watermark-based anti-double-claim, maximum claim range (10,000 blocks), pool balance sufficiency checks.
- Pure claim transactions (
txin_stake_claim-only inputs) useRCTTypeNullsignatures, cleanly separated from ring-signature transaction validation. - Extended
tx_destination_entrywithis_stakingandstake_tierfields.construct_tx_with_tx_keyemitstxout_to_staked_keyoutputs whenis_stakingis set. - Extended
transfer_detailswithm_staked,m_stake_tier, andm_stake_lock_untilfor wallet-side staking metadata tracking. (m_stake_lock_untilis computed locally fromcreation_height + tier_lock_blocks.) - Implemented wallet2 methods:
create_staking_transaction,create_unstake_transaction,create_claim_transaction,get_matured_staked_outputs,get_locked_staked_outputs,get_claimable_staked_outputs,get_staked_balance,estimate_claimable_reward. - Added simplewallet commands:
stake <tier> <amount>,unstake,claim_rewards. - Added wallet RPC endpoints:
stake,unstake,get_staked_outputs,claim_rewards. - Added daemon RPC endpoint:
get_staking_inforeturning current staking metrics (height, stake ratio, pool balance, emission share, tier lock blocks). - Wired
stake_ratioandstaker_pool_balancein/get_infoto live blockchain state. - No minimum stake amount enforced (matches design doc).
- Fixed compilation errors from
txin_stake_claimmissing in exhaustiveboost::static_visitorpatterns: addedoperator()overloads to the double-spend visitor (blockchain.cpp) and the JSON serialization visitor (json_object.cpp), added JSON deserialization branch for"stake_claim"inputs, addedtoJsonValue/fromJsonValuedeclarations and implementations fortxin_stake_claim, and added Boost.Serializationserialize()free function for wallet binary archive support (cryptonote_boost_serialization.h).
Consensus and mining economics
- Wired Four-Component economics to live chain-state inputs for miner reward
paths:
- block template construction now passes rolling
tx_volume_avg,circulating_supply, andstake_ratiotoconstruct_miner_tx - miner transaction validation now uses the release-multiplier reward path and non-placeholder fee-burn inputs
- tx pool block template estimation now uses the same rolling
tx_volume_avgreward path for consistency
- block template construction now passes rolling
- Added
Blockchain::get_tx_volume_avg(height)andBlockchain::get_stake_ratio(height)(stubbed to0until staking state is consensus-tracked).
Modular PoW
- Added pluggable PoW schema abstractions:
IPowSchemainterfaceRandomXandCryptonightschema implementations- PoW registry-based selection preserving existing behavior by block version
- Refactored
get_block_longhashto route through the PoW schema registry while keeping existing RandomX seed handling and the historical block 202612 workaround. - Updated miner thread preparation to call schema-level
prepare_miner_thread(...)(RandomX prepares thread context; Cryptonight is a no-op).