Ori Newman merged a SilverScript lock-domain fix into master on 19 August 2026, then described the bug in public. He said tx.time had been treated as seconds even though Kaspa consensus measures lock time in milliseconds, which in his words divided timestamps by 1,000 and made affected timelocks "effectively irrelevant." He also said this.age had been documented as seconds when the value is a DAA score. The patch renamed that field to this.ageDaa and introduced a temporal type so the compiler can keep clock time and DAA age apart. (post) (follow-up) (PR #214)
He said that in the same posts as the bug report. SilverScript is pre-release, still under extensive review before a v1 announcement, and should not be used in production. The merge does not change that.
A time lock is meant to delay a spend until a clock or a chain-age condition is met. If the compiler emits the delay in the wrong unit, consensus does not wait for the date the author wrote. Kaspa compares transaction lock time with past median time in milliseconds, and it uses a 500,000,000,000 threshold to decide whether a number is a DAA-score lock or a Unix-time lock. A seconds-scale timestamp sits below that threshold, so it can be read as a DAA condition rather than a calendar wait. That is the mechanism behind Newman’s “effectively irrelevant” description. It is his account of the impact. What can be verified is the merged compiler change and that first-party report. No mainnet loss was shown. (tutorial)
The merged language now names the three surfaces instead of letting them share one integer. tx.time accepts only temporal Unix milliseconds and must be at or above LOCK_TIME_THRESHOLD. tx.daa accepts only an int absolute DAA score in 0 <= value < LOCK_TIME_THRESHOLD. Relative UTXO age is this.ageDaa, an int DAA-score difference that must satisfy 0 <= value < 2^32. Duration literals are temporal milliseconds: 5 minutes is 300,000. date("2030-01-01T00:00:00") is millisecond Unix time as well. require(tx.time >= 5000) is a type error, and so is require(this.ageDaa >= 5 seconds). (PR #214) (tutorial)
temporal is still a 64-bit signed number. It supports the same arithmetic, comparisons, fields, and arrays as int. The compiler will not mix the two without an explicit temporal(...) or int(...) cast, and those casts are documented as no-ops: they change the type, they do not convert seconds into milliseconds. That is the compile-time safety Newman pointed to. It stops a DAA age and a wall-clock delay from looking interchangeable in source. It does not invent a new consensus encoding. (tutorial)
Known invalid constants fail at compile time. Dynamic values get a second check in the generated script before OpCheckLockTimeVerify or OpCheckSequenceVerify runs. The convenience fields tx.locktime and tx.inputs[i].sequence were removed. Raw access is through typed opcode builtins, OpTxLockTime() and OpTxInputSeq(0). (compiler) (builtins)
Newman merged the work himself as b5b0dc8. Check, Test Suite, Lints, and Tree-sitter Test all passed. New consensus tests compile lock contracts and spend them in real test-chain blocks, including a one-millisecond acceptance boundary for tx.time and a rejection of a dynamic this.ageDaa of 2^32. Separate tests refuse mixed int/temporal expressions and the old this.age name. That is CI coverage of the compiler and the consensus path. It is not an application audit. (merge) (checks) (consensus tests) (type tests)
The 16 August type-and-evaluation audit is a separate story. This merge is a later, narrower correction from the same pre-v1 review: a unit mix-up in the lock API, then typed domains so the next contract is less likely to confuse a clock with a DAA counter. The language is still not v1. Newman still says not to use it in production.
