Kaspa ($KAS) core R&D - what developers discussed, debated, and shipped. Mar 25-27, 2026.
CI Build Regression Hunted Down
Sutton noticed the Build Linux Release step in CI was taking 75 minutes. (flag) He pointed to PR #924 as where it likely started. (source)
PR #924, by smartgoo, changed TransactionOutput to use consensus-client::CovenantBinding instead of consensus-core::CovenantBinding - a type system alignment change bringing the struct in line with how other consensus client types are defined. (PR #924) Sutton had merged it and then noticed a flaky integration test failing in CI: daemon_utxos_propagation_test. smartgoo investigated: he pushed a clean clone of the PR branch to a new branch and the tests passed there, suggesting the failure was a sporadic GitHub runner performance issue - a timeout in the mine_block function - rather than a real regression. (smartgoo) Sutton merged it after it passed on the third try. (merged)
The CI slowdown had a separate root cause. Max traced it to the stratum bridge PR changing where rustc flags were exported. (explanation) Before that PR, a single set of rustc flags covered all binaries. The stratum PR restructured it:
1. cargo build foo
2. export rustc flags
3. cargo build bar
Because rustc flags change the compiler fingerprint, all inner crates compiled twice - generating brand new artifacts for the second build. Max's fix: move the rustc flags export above the first cargo call.
Separately, Max shared a CI run showing the install llvm step alone taking 50 minutes. (link) IzioDev identified the cause: 164MB fetched in 47 minutes 43 seconds - a bandwidth issue on the runner, not a code problem. (bandwidth)
Max also identified that the musl toolchain was not being cached between runs. The build.sh script had a specific line taking ~40 minutes to complete, and the caching mechanism was unreliable. (toolchain) Sutton said it used to be cached - Alex had worked on that specifically - but something in the environment had changed. (history)
IzioDev suggested removing build-release from ci.yaml entirely since cargo check already runs and the deploy pipeline handles actual release builds on tagged commits. (suggestion) Max pushed back: he had seen cases where check passed but build failed and pointed to a PR from two years ago as evidence. (counter) IzioDev noted the original reason for musl - statically linked portable binaries - was in a comment that had since been removed from the file. (musl origin)
Max opened PR #934. (PR #934) The root cause of the toolchain rebuild problem was GitHub's branch-scoped caching: PR branches cannot access caches created on other branches, so every PR was rebuilding the musl toolchain from source at ~36 minutes per run. The fix stores the pre-built toolchain tarball as a GitHub release asset instead. CI now downloads it and validates a hash of preset.sh; if the hash mismatches, it fails fast rather than burning 35 minutes. A new musl-toolchain.yaml workflow builds and uploads the toolchain, triggered manually or on preset.sh changes to master. Fork contributors can also test new toolchain versions on their fork before merging upstream. After the fix, build times dropped back to 18 minutes. (18 min result) Sutton merged PR #934 and asked Ori to merge it into tn12 and covpp-reset2 as well. (merged) Ori confirmed. (done)
Silverscript PR #84 Merged — and PR #86 Has Breaking Changes
The re-assignment compiler optimization merged into Silverscript as PR #84. (merge) The actual change: inline calls now allow mutable scalar stack locals unconditionally. The previous flag was redundant once both normal and inline compilation used the stack-local rebinding path. The fix keeps the compiler environment synchronized when a stack-bound scalar is reassigned — after emitting the stack rebinding, the compiler rewrites self-references in the new RHS against the previous semantic value and resolves the updated expression back into the environment. This fixes inline returns reading stale values without introducing the self-referential cycles from a naive env.insert(name, lowered_expr) approach. (PR #84) Sutton called it interesting from a compiler theory standpoint and said the team should write up what was done. (writeup note)
When Sutton merged PR #84, Ori flagged breaking changes in PR #86. (note) PR #86 tightens Silverscript's type system around bytes, fixed-size byte arrays, and array comparisons. (PR #86) Key breaking changes: comparisons now require compatible types - byte[] == byte[32], int == bool, and similar implicit coercions now fail. Fixed-size byte-array casts are stricter: byte[M] -> byte[N] is rejected when M ≠ N, so byte31 no longer works as a reshaping mechanism. The byte type no longer behaves as a small int for arithmetic. Any existing contracts relying on implicit coercions will need explicit casts.
Silverscript: State Decoder Proposal
Sutton proposed a state decoder for Silverscript and shared a full spec document. (proposal) (spec) Most of the implementation is already on the branch.
The goal: let any external indexer recover and verify covenant state transitions from a spending transaction and a compiled contract, with no chain-specific logic required. The decoder executes the real Silverscript program, stops at compiler-recorded bytecode offsets, reads raw VM stack values at those points, and independently verifies the authored output against the transaction itself. The compiler metadata is explicitly a navigation aid, not a source of truth.
The compiler attaches state_decoder metadata to the compiled contract JSON output alongside existing data. The metadata contains state_layouts — field names, type names, sizes — and validation_calls — which builtin to invoke, where to read each value from the stack by bytecode offset. Two initial scope functions: validateOutputState and validateOutputStateWithTemplate. Input-state decoding is out of initial scope since it can usually be recovered directly from per-input sigscripts.
IzioDev asked what state_layouts[n] is for when not equal to zero. (question) Sutton confirmed: sub-covenants. (confirmed) IzioDev added that the metadata is useful for SDKs. (note)
CheckSigFromStack and the NULLFAIL Debate
Ori opened PR #926, adding CheckSigFromStack to rusty-kaspa. (PR ready) The opcode lets scripts verify a signature over arbitrary stack data rather than only transaction data. It is called CheckSigFromStack in Bitcoin Script and CheckDataSig in BCH; Ori chose the BTC naming. (naming)
The PR opened a debate about the NULLFAIL rule. Bitcoin's NULLFAIL rule (BIP-146) requires that when OP_CHECKSIG returns false, the signature must be an empty byte array. Ori proposed extending this for Silverscript's type system: a signature of 64 zeroes should also be treated as a valid "false" signal rather than causing a reject. (proposal) The reason: Silverscript's sig type is always exactly 65 bytes. You cannot pass an empty byte array because the type system enforces fixed width. Without this extension, there is no clean way to signal "false" from a signature check in Silverscript. Ori: "I don't want to get rid of it, because I think it's good practice for future upgrades, but to extend it to the all zeroes case as well." (Ori)
Max asked whether 65 zeroes with a valid sighash byte should return false or reject. (Max) Ori confirmed the current behavior is reject, per NULLFAIL. (Ori) Max said returning false made more sense to him and was fine with the extension. (Max) Ori merged PR #926. (fixed)
IzioDev Publishes Kaspa Programmability Overview
IzioDev shared a digest overview of Kaspa programmability options aimed at builders. (shared) (doc)
The document frames the first decision as: does the application need execution concurrency? Sequential execution maps naturally to Covenants, expressed directly on Kaspa L1. Concurrent execution maps to Based Apps, which run in Rust with built-in accounts and shared state. If an app can be split into independent sub-apps, Covenants can still work even when some concurrency exists across those separate states.
The document also covers Inline ZK as a specialized option, with a clear warning: consider Based Apps and Covenants first, as Inline ZK demands significantly more builder effort. Full vProgs are listed as the future direction for app-to-app composition across independent apps. KaffinPX responded that any optimization is good and it feels like a nice idea. (KaffinPX)
A Custom UDP Block Relay
Dstakes published PR #930 - a custom fire-and-forget UDP block propagation layer for rusty-kaspa, modeled on BTC Fibre. (PR) The protocol fragments Kaspa blocks into UDP packets and begins relaying them before the sending node has fully received or decoded the block itself, at individual packet granularity. (relay before decode)
Unlike TCP or QUIC, it makes no attempt at full delivery guarantees. Forward error correcting codes handle packet loss, and if they fail, the standard TCP relay recovers in the worst case. The goal is raw speed. (design) Consensus verification still runs on every receiving node - only the wait before relaying is skipped. (verification)
Max asked why UDP over QUIC, which is also UDP-based but provides a stream API. (question) Dstakes: QUIC requires TLS and the protocol is already more low-level than QUIC alone, with custom block fragmentation and reassembly. IzioDev noted QUIC does require TLS. (TLS note)
IzioDev asked why the relay is closed and authenticated rather than open. (question) Dstakes: spam and DDoS. (answer) Per-packet HMAC authentication handles authenticity. (HMAC)
The architecture has two planes: a TCP control plane for peer state signaling (peer connected, peer ready to receive), and a UDP transport plane for fragmentation and broadcast. They share a peer directory and otherwise run in isolation. The intersection with rusty-kaspa is a dedicated fast relay flow in the P2P layer and triggers in the existing block relay flow of TCP routers. (architecture)
The relay is designed for trusted, geographically dispersed nodes belonging to the same operator, not as an open public network. (trusted use case) Dstakes successfully sent his first blocks from home to a remote server purely over the protocol and described it as "minimal usable state". (first block) He is looking for intercontinental test partners to stress the relay under real conditions. (testers) An experimental branch already combines the fast relay with perigee routing - best-latency peers gather blocks and propagate over UDP. (perigee branch)
All sources link to public messages in the Kaspa Core R&D (public) Telegram channel.