From 5d0ac01882de00753ed5c3fa23473960bbb02c49 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Thu, 16 Apr 2026 12:52:52 -0400 Subject: [PATCH 01/25] Initial draft of Clarity 6 SIP --- sips/sip-04x/sip-04x-clarity6.md | 320 +++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 sips/sip-04x/sip-04x-clarity6.md diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md new file mode 100644 index 00000000..7f00169a --- /dev/null +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -0,0 +1,320 @@ +# Preamble + +SIP Number: 043 + +Title: Clarity 6: Language Improvements and New Built-ins + +Author(s): + +- Jeff Bencin + +Status: Draft + +Consideration: Technical + +Type: Consensus + +Layer: Consensus (hard fork) + +Created: 2026-04-14 + +License: BSD-2-Clause + +Sign-off: + +Discussions-To: + +# Abstract + +This SIP specifies Version 6 of the Clarity smart contract language. It +introduces quality-of-life improvements to the language syntax, relaxes +unnecessarily strict naming and typing rules, and adds a new cryptographic +built-in function. These changes are motivated by real-world developer experience +and address long-standing requests from the Clarity developer community. + +# Copyright + +This SIP is made available under the terms of the BSD-2-Clause license, +available at https://opensource.org/licenses/BSD-2-Clause. This SIP's copyright +is held by the Stacks Open Internet Foundation. + +# Introduction + +Through years of production use, Clarity developers have encountered several +unnecessary limitations in the language that hinder readability, ergonomics, and +functionality. This SIP addresses five specific areas: + +1. **Constants in type positions.** Clarity currently requires literal values in + type definitions (such as list lengths) and in certain built-in functions + (such as `as-max-len?`), even though constants are equally fixed at compile + time. This forces developers to duplicate magic numbers throughout their code, + increasing the risk of inconsistencies. +2. **Numeric separators.** Large numeric literals are common in Clarity contracts + (especially when working with micro-denominated tokens), but they are + difficult to read without visual grouping. Many modern languages support + underscore separators in numeric literals for this purpose. +3. **Underscore-prefixed variable names.** The current naming rules prohibit + identifiers from starting with `_`, preventing developers from using a + widely-adopted convention for indicating intentionally unused variables. This + limitation affects developer tooling such as linters. +4. **secp256k1 public key decompression.** There is currently no way to + decompress a secp256k1 public key in Clarity. This forces protocols like + Wormhole to use cumbersome workarounds involving uncompressed keys, leading to + operational downtime when guardian sets change. + +# Specification + +## Allow Constants in Type Positions + +Originally proposed here: +https://github.com/clarity-lang/reference/issues/78 + +Currently, Clarity requires literal values when specifying type parameters such +as list lengths. Constants cannot be used in these positions, even though their +values are fixed at compile time and known during analysis. + +Beginning in Clarity 6, constants defined with `define-constant` may be used +wherever a literal unsigned integer is required in a type specification. This +includes list length parameters in type definitions, function signatures, and +anywhere else a literal is currently expected for a type parameter. + +### Example + +```clarity +(define-constant MAX_SIZE u30) + +(define-read-only (foo (l (list MAX_SIZE int))) + (len l) +) +``` + +In Clarity 5 and below, the above would produce an error. In Clarity 6, it is +valid and equivalent to writing `(list 30 int)`. + +The constant must evaluate to an unsigned integer (`uint`) value. Using a +constant that evaluates to any other type in a type position will result in an +analysis error. + +## Allow Constants in `as-max-len?` + +Originally proposed here: +https://github.com/clarity-lang/reference/issues/80 + +The `as-max-len?` function currently requires a literal unsigned integer for its +second argument, which specifies the target maximum length. Constants cannot be +substituted for this literal, even though the value is equally fixed and known at +analysis time. + +Beginning in Clarity 6, a constant defined with `define-constant` may be used as +the second argument to `as-max-len?`, provided the constant evaluates to a +`uint`. + +### Example + +```clarity +(define-constant GOV_MAX_GUARDIANS u30) + +(unwrap-panic (as-max-len? updated-public-keys GOV_MAX_GUARDIANS)) +``` + +In Clarity 5 and below, this would produce an error. In Clarity 6, it is valid +and equivalent to writing `(as-max-len? updated-public-keys u30)`. + +This change, combined with the previous change allowing constants in type +positions, enables developers to define a single constant for a length limit and +use it consistently throughout their contract. + +## Numeric Separator with `_` + +Originally proposed here: +https://github.com/clarity-lang/reference/issues/92 + +Large numeric literals are common in Clarity smart contracts, particularly when +dealing with micro-denominated values (e.g. uSTX). These large numbers are +difficult to read and error-prone to write. + +Beginning in Clarity 6, the underscore character (`_`) may be used as a visual +separator within integer and unsigned integer literals. The underscores are +purely cosmetic and have no effect on the value of the literal. They are stripped +during parsing. + +### Rules + +- Underscores may appear between any two digits in an integer or unsigned integer + literal. +- Underscores may not appear at the beginning or end of a numeric literal, nor + adjacent to the `u` prefix for unsigned integers. +- Multiple consecutive underscores are not allowed. +- The presence or absence of underscores does not affect the type or value of the + literal. + +### Examples + +```clarity +;; With separators (Clarity 6) +(define-constant INITIAL_MINT_AMOUNT u200_000_000_000_000) ;; 200,000,000 STX + +;; Equivalent to (Clarity 5 and below) +(define-constant INITIAL_MINT_AMOUNT u200000000000000) + +;; Additional examples +(define-constant ONE_MILLION 1_000_000) +(define-constant SOME_VALUE u1_234_567) +``` + +This feature is supported by many popular programming languages including Rust, +JavaScript, Solidity, and Go. + +## Allow Variables to Start with `_` + +Originally proposed here: +https://github.com/clarity-lang/reference/issues/101 + +Currently, Clarity identifiers cannot begin with the underscore character (`_`), +although underscores are permitted in other positions within a name. This +prevents developers from using the widely-adopted convention of prefixing unused +variables with `_` to indicate that they are intentionally unused. + +Beginning in Clarity 6, identifiers (including variable names, function names, +map names, data variable names, and other named definitions) may begin with an +underscore. + +Additionally, the bare identifier `_` (a single underscore) is allowed as a +variable name in `let` and `match` bindings. This serves as a discard pattern, +indicating that the value is intentionally unused. Unlike other variable names, +`_` does not create a binding that can be referenced later; attempting to +reference `_` as a variable will result in an analysis error. + +### Examples + +```clarity +;; Prefixing an unused variable +(define-public (remove-admin (address principal)) + (let ((_admin (try! (check-admin))) + (deleted (map-delete admins address))) + (ok deleted))) + +;; Using _ as a discard pattern +(define-public (remove-admin (address principal)) + (let ((_ (try! (check-admin))) + (deleted (map-delete admins address))) + (ok deleted))) +``` + +This convention is familiar to developers coming from Rust, TypeScript, Python, +and many other languages. It also enables linters and static analysis tools to +automatically detect genuinely unused variables without false positives. + +## Add `secp256k1-decompress?` Function + +Originally proposed here: +https://github.com/clarity-lang/reference/issues/103 + +There is currently no built-in function in Clarity to decompress a secp256k1 +public key. Decompression requires computing a modular square root on the +secp256k1 elliptic curve, which involves 256-bit modular arithmetic that is not +feasible to implement in Clarity's 128-bit integer system. + +This limitation has real-world consequences. For example, the Wormhole bridge +protocol on Stacks needs to store uncompressed public keys during guardian set +updates because there is no way to derive them on-chain from the compressed +form. Obtaining these uncompressed keys is operationally difficult, leading to +protocol downtime during guardian rotations. + +Beginning in Clarity 6, a new built-in function `secp256k1-decompress?` is +available. + +- **Input**: `(buff 33)` -- a compressed secp256k1 public key +- **Output**: `(optional (buff 65))` -- the uncompressed public key, or `none` + if the input is not a valid compressed public key +- **Signature**: `(secp256k1-decompress? compressed-public-key)` +- **Description**: Takes a 33-byte compressed secp256k1 public key (where the + first byte is `0x02` or `0x03` indicating the parity of the y-coordinate) and + returns the corresponding 65-byte uncompressed public key (with a `0x04` + prefix followed by the 32-byte x-coordinate and 32-byte y-coordinate). Returns + `none` if the input is not a valid compressed secp256k1 public key. +- **Example**: + ```clarity + ;; Decompress a valid compressed public key + (secp256k1-decompress? 0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798) + ;; Returns (some 0x0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8) + + ;; Invalid input returns none + (secp256k1-decompress? 0x00) + ;; Returns none + ``` + +### Deriving an Ethereum Address + +With `secp256k1-decompress?`, developers can derive an Ethereum address from a +compressed public key on-chain: + +```clarity +(define-read-only (compressed-pubkey-to-eth-address (compressed-key (buff 33))) + (match (secp256k1-decompress? compressed-key) + uncompressed-key (some + (unwrap-panic (as-max-len? + (unwrap-panic (slice? + (keccak256 (unwrap-panic (slice? uncompressed-key u1 u65))) + u12 u32)) + u20))) + none)) +``` + +# Related Work + +This SIP builds upon the existing definitions of the Clarity language: + +- [SIP-002 (Clarity 1)](../sip-002/sip-002-smart-contract-language.md) +- [SIP-015 (Clarity 2)](../sip-015/sip-015-network-upgrade.md) +- [SIP-021 (Clarity 3)](../sip-021/sip-021-nakamoto.md) +- [SIP-033 (Clarity 4)](../sip-033/sip-033-clarity4.md) +- [SIP-039 (Clarity 5)](../sip-039/sip-039-clarity5.md) + +# Backwards Compatibility + +Because this SIP introduces new syntax (numeric separators, underscore-prefixed +identifiers) and a new built-in function (`secp256k1-decompress?`), it is a +consensus-breaking change. A contract that uses any of these new features would +be invalid before this SIP is activated, and valid after it is activated. + +All new keywords introduced in Clarity 6 can no longer be used as identifiers in +a Clarity 6 smart contract. Smart contracts can continue to be published using +older versions of Clarity by specifying the version in the deploy transaction. + +Existing contracts deployed with previous Clarity versions are unaffected and +will continue to execute with their existing behavior. + +# Activation + +Users can vote to approve this SIP with either their locked/stacked STX or with +unlocked/liquid STX, or both. + +In order for this SIP to activate, the following criteria must be met: + +- At least 80 million stacked STX must vote, with at least 80% of all stacked + STX committed by voting must be in favor of the proposal (vote "yes"). +- At least 80% of all liquid STX committed by voting must be in favor of the + proposal (vote "yes"). + +All STX holders vote by sending Stacks dust to the corresponding Stacks address +from the account where their Stacks are held (stacked or liquid). Voting power +is determined by a snapshot of the amount of STX (stacked and unstacked) at the +block height at which the voting started (preventing the same STX from being +transferred between accounts and used to effectively double vote). + +Solo stackers only can also vote by sending a bitcoin dust transaction (6000 +sats) to the corresponding bitcoin address. + +| Vote | Bitcoin | Stacks | ASCII Encoding | Msg | +| ---- | ------- | ------ | -------------- | --- | +| yes | TBD | TBD | TBD | yes-sip-43 | +| no | TBD | TBD | TBD | no-sip-43 | + +If the SIP is approved, a Bitcoin block height will be selected to activate the +new behavior. + +# Reference Implementation + +No reference implementation is available at the time of writing. Links to +implementation PRs will be added here as they become available. From 472596484edbd81fe8891c287751c9115a980a95 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Thu, 16 Apr 2026 14:04:18 -0400 Subject: [PATCH 02/25] Improvements to wording/formatting --- sips/sip-04x/sip-04x-clarity6.md | 43 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index 7f00169a..18b9a8e7 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -27,8 +27,8 @@ Discussions-To: # Abstract This SIP specifies Version 6 of the Clarity smart contract language. It -introduces quality-of-life improvements to the language syntax, relaxes -unnecessarily strict naming and typing rules, and adds a new cryptographic +introduces quality-of-life improvements to the language syntax, relaxes naming rules +to work better with the new Clarity linter, and adds a new cryptographic built-in function. These changes are motivated by real-world developer experience and address long-standing requests from the Clarity developer community. @@ -40,24 +40,23 @@ is held by the Stacks Open Internet Foundation. # Introduction -Through years of production use, Clarity developers have encountered several -unnecessary limitations in the language that hinder readability, ergonomics, and -functionality. This SIP addresses five specific areas: +This SIP addresses several limitations and inconveniences that have been reported +by Clarity developers. Specifically, it makes the following changes: -1. **Constants in type positions.** Clarity currently requires literal values in - type definitions (such as list lengths) and in certain built-in functions - (such as `as-max-len?`), even though constants are equally fixed at compile - time. This forces developers to duplicate magic numbers throughout their code, - increasing the risk of inconsistencies. -2. **Numeric separators.** Large numeric literals are common in Clarity contracts +1. **Allow constants in place of literal values:** Clarity currently requires + literal values in type definitions (such as list lengths) and in certain + built-in functions (such as `as-max-len?`), even though constants are also known + during deployment. This forces developers to duplicate magic numbers throughout + their code, increasing the risk of inconsistencies. +2. **Numeric separators:** Large numeric literals are common in Clarity contracts (especially when working with micro-denominated tokens), but they are difficult to read without visual grouping. Many modern languages support underscore separators in numeric literals for this purpose. -3. **Underscore-prefixed variable names.** The current naming rules prohibit +3. **Underscore-prefixed variable names:** The current naming rules prohibit identifiers from starting with `_`, preventing developers from using a widely-adopted convention for indicating intentionally unused variables. This limitation affects developer tooling such as linters. -4. **secp256k1 public key decompression.** There is currently no way to +4. **secp256k1 public key decompression:** There is currently no way to decompress a secp256k1 public key in Clarity. This forces protocols like Wormhole to use cumbersome workarounds involving uncompressed keys, leading to operational downtime when guardian sets change. @@ -71,7 +70,7 @@ https://github.com/clarity-lang/reference/issues/78 Currently, Clarity requires literal values when specifying type parameters such as list lengths. Constants cannot be used in these positions, even though their -values are fixed at compile time and known during analysis. +values are known during analysis. Beginning in Clarity 6, constants defined with `define-constant` may be used wherever a literal unsigned integer is required in a type specification. This @@ -102,8 +101,8 @@ https://github.com/clarity-lang/reference/issues/80 The `as-max-len?` function currently requires a literal unsigned integer for its second argument, which specifies the target maximum length. Constants cannot be -substituted for this literal, even though the value is equally fixed and known at -analysis time. +substituted for this literal, even though the value is equally fixed and known +during analysis. Beginning in Clarity 6, a constant defined with `define-constant` may be used as the second argument to `as-max-len?`, provided the constant evaluates to a @@ -134,8 +133,8 @@ dealing with micro-denominated values (e.g. uSTX). These large numbers are difficult to read and error-prone to write. Beginning in Clarity 6, the underscore character (`_`) may be used as a visual -separator within integer and unsigned integer literals. The underscores are -purely cosmetic and have no effect on the value of the literal. They are stripped +separator within signed and unsigned integer literals. The underscores are +purely cosmetic and have no effect on the value of the literal: They are stripped during parsing. ### Rules @@ -217,15 +216,15 @@ feasible to implement in Clarity's 128-bit integer system. This limitation has real-world consequences. For example, the Wormhole bridge protocol on Stacks needs to store uncompressed public keys during guardian set -updates because there is no way to derive them on-chain from the compressed -form. Obtaining these uncompressed keys is operationally difficult, leading to +updates because there is no way to derive them on-chain from the VAA signatures. +Obtaining these uncompressed keys has proven to be difficult, and has led to protocol downtime during guardian rotations. Beginning in Clarity 6, a new built-in function `secp256k1-decompress?` is available. -- **Input**: `(buff 33)` -- a compressed secp256k1 public key -- **Output**: `(optional (buff 65))` -- the uncompressed public key, or `none` +- **Input**: `(buff 33)`: a compressed secp256k1 public key +- **Output**: `(optional (buff 65))`: the uncompressed public key, or `none` if the input is not a valid compressed public key - **Signature**: `(secp256k1-decompress? compressed-public-key)` - **Description**: Takes a 33-byte compressed secp256k1 public key (where the From 543b0868b741a1de54713fe71acbc94d6857d635 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Fri, 8 May 2026 14:03:09 -0400 Subject: [PATCH 03/25] Add `get-bitcoin-tx-output?` and `verify-merkle-proof` --- sips/sip-04x/sip-04x-clarity6.md | 112 +++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index 18b9a8e7..621c37f4 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -28,9 +28,11 @@ Discussions-To: This SIP specifies Version 6 of the Clarity smart contract language. It introduces quality-of-life improvements to the language syntax, relaxes naming rules -to work better with the new Clarity linter, and adds a new cryptographic -built-in function. These changes are motivated by real-world developer experience -and address long-standing requests from the Clarity developer community. +to work better with the new Clarity linter, adds a new cryptographic +built-in function, and adds new built-ins for trustlessly verifying Bitcoin +transaction outputs on-chain. These changes are motivated by real-world +developer experience and address long-standing requests from the Clarity +developer community. # Copyright @@ -60,6 +62,13 @@ by Clarity developers. Specifically, it makes the following changes: decompress a secp256k1 public key in Clarity. This forces protocols like Wormhole to use cumbersome workarounds involving uncompressed keys, leading to operational downtime when guardian sets change. +5. **Trustless Bitcoin transaction verification:** Clarity contracts have no + native way to verify that a Bitcoin transaction output exists on the Bitcoin + chain. Protocols that need this capability (such as BTC bridges and sBTC-style + peg systems) must currently rely on off-chain oracles or trusted relayers, or + reimplement Bitcoin transaction parsing and merkle-proof verification in + user-space Clarity code, which is expensive, error-prone, and difficult to + audit. # Specification @@ -260,6 +269,100 @@ compressed public key on-chain: none)) ``` +## Bitcoin Transaction Verification Built-ins + +Clarity contracts currently have no first-class way to verify that a Bitcoin +transaction output exists on the Bitcoin chain. Protocols such as BTC bridges +and sBTC-style peg systems must either rely on trusted off-chain relayers or +reimplement Bitcoin transaction parsing and merkle-proof verification in +user-space Clarity, where the cost of double-SHA-256 hashing and byte-level +parsing on large transactions is prohibitive and the risk of subtle bugs (such +as CVE-2012-2459-style merkle malleability) is high. + +Beginning in Clarity 6, two new built-in functions, `get-bitcoin-tx-output?` +and `verify-merkle-proof`, are available. They are designed as a pair: the +`txid` returned by `get-bitcoin-tx-output?` is in the internal (raw) byte +order expected by `verify-merkle-proof` as a leaf hash. Together with the +existing `get-burn-block-info?` built-in (which exposes burn-block merkle +roots), they enable contracts to verify that a Bitcoin output exists on-chain +without trusting the caller to have correctly stripped witness data or hashed +the transaction. + +### `get-bitcoin-tx-output?` + +- **Input**: `buff, uint`: a serialized Bitcoin transaction (with or without + SegWit witness data), and the output index (`vout`) to extract. +- **Output**: `(response (tuple (script (buff 1024)) (amount uint) (txid (buff 32))) uint)` +- **Signature**: `(get-bitcoin-tx-output? tx-bytes vout)` +- **Description**: Parses a serialized Bitcoin transaction and returns the + output at the given `vout` index, along with the canonical (non-witness) + `txid` of the transaction. The returned `txid` is in *internal* byte order + (the raw double-SHA-256 result), ready to be passed directly to + `verify-merkle-proof` as the leaf hash. The `script` field contains the raw + `scriptPubKey` bytes of the output, so contracts can pattern-match on script + prefixes to recognize P2WSH (`0x00 0x20 ...`), P2TR (`0x51 0x20 ...`), + P2WPKH (`0x00 0x14 ...`), `OP_RETURN` (`0x6a ...`), or any other output + script. Returns one of three error codes on failure: + - `(err u1)` — `tx-bytes` did not deserialize as a Bitcoin transaction. + - `(err u2)` — `vout` is out of range for this transaction. + - `(err u3)` — the output's `scriptPubKey` exceeds the 1024-byte cap. +- **Example**: + ```clarity + ;; Parse the Bitcoin genesis block coinbase tx and return its sole output. + (get-bitcoin-tx-output? + 0x01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000 + u0) + ;; Returns (ok (tuple + ;; (amount u5000000000) + ;; (script 0x4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac) + ;; (txid 0x3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a))) + + (get-bitcoin-tx-output? 0x00 u0) ;; Returns (err u1) + ``` + +### `verify-merkle-proof` + +- **Input**: `(buff 32), (buff 32), uint, uint, (list 24 (buff 32))`: the leaf + hash, the merkle root hash, the leaf's index in the tree, the total + transaction count of the block, and the list of sibling hashes along the + path from the leaf to the root. +- **Output**: `bool` +- **Signature**: `(verify-merkle-proof leaf-hash root-hash tx-index tx-count sibling-hashes)` +- **Description**: Verifies a Bitcoin-style merkle inclusion proof using + double-SHA-256 hashing with the "duplicate the last node on odd-sized rows" + rule. Given a `leaf-hash` (typically a Bitcoin txid), the merkle `root-hash` + of a block, the `tx-index` of the leaf within the tree (0-indexed), the + `tx-count` of transactions in the block, and the `sibling-hashes` along the + path from the leaf to the root, the function returns `true` iff hashing + pairwise up the tree in the order described by `tx-index` produces + `root-hash`. + + The `tx-count` argument pins down the canonical Bitcoin tree shape and is + required to defend against + [CVE-2012-2459](https://bitcointalk.org/?topic=102395)-style attacks, where + an intermediate node in an odd-row-padded tree could otherwise be passed off + as a leaf. The function rejects any proof whose path length does not match + `ceil(log2(tx-count))` and any `tx-index` not less than `tx-count`. It + returns `false` for any malformed proof and `true` for a valid proof. + + All 32-byte hashes (leaf, root, siblings) are passed in *internal* (raw) + byte order, not the display (reversed) order conventionally used for + Bitcoin txids and block hashes. The `txid` returned by + `get-bitcoin-tx-output?` is already in internal byte order and can be + passed directly as `leaf-hash`. +- **Example**: + ```clarity + ;; The Bitcoin genesis block contains a single tx, so its coinbase txid + ;; (in internal byte order) is also the block's merkle root. A proof + ;; with an empty sibling list verifies trivially. + (verify-merkle-proof + 0x3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a + 0x3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a + u0 + u1 + (list)) ;; Returns true + ``` + # Related Work This SIP builds upon the existing definitions of the Clarity language: @@ -273,7 +376,8 @@ This SIP builds upon the existing definitions of the Clarity language: # Backwards Compatibility Because this SIP introduces new syntax (numeric separators, underscore-prefixed -identifiers) and a new built-in function (`secp256k1-decompress?`), it is a +identifiers) and new built-in functions (`secp256k1-decompress?`, +`get-bitcoin-tx-output?`, and `verify-merkle-proof`), it is a consensus-breaking change. A contract that uses any of these new features would be invalid before this SIP is activated, and valid after it is activated. From 5fb0010cb8a1e0a6cbaba3512565ef0b2df07451 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Thu, 21 May 2026 10:27:44 -0400 Subject: [PATCH 04/25] Fix description of `get-burn-block-info?` --- sips/sip-04x/sip-04x-clarity6.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index 621c37f4..0d730afd 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -282,11 +282,12 @@ as CVE-2012-2459-style merkle malleability) is high. Beginning in Clarity 6, two new built-in functions, `get-bitcoin-tx-output?` and `verify-merkle-proof`, are available. They are designed as a pair: the `txid` returned by `get-bitcoin-tx-output?` is in the internal (raw) byte -order expected by `verify-merkle-proof` as a leaf hash. Together with the -existing `get-burn-block-info?` built-in (which exposes burn-block merkle -roots), they enable contracts to verify that a Bitcoin output exists on-chain -without trusting the caller to have correctly stripped witness data or hashed -the transaction. +order expected by `verify-merkle-proof` as a leaf hash. Combined with the +existing `get-burn-block-info?` built-in — whose `header-hash` property lets +a contract authenticate a user-supplied Bitcoin block header (and thereby +extract its merkle root) — they enable contracts to verify that a Bitcoin +output exists on-chain without trusting the caller to have correctly stripped +witness data or hashed the transaction. ### `get-bitcoin-tx-output?` From 2ef212058cc066709f4cb03d7757121ff9b0d7f2 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Thu, 21 May 2026 11:51:46 -0400 Subject: [PATCH 05/25] Add `ed25519-verify` --- sips/sip-04x/sip-04x-clarity6.md | 66 ++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index 0d730afd..da2699c6 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -27,12 +27,12 @@ Discussions-To: # Abstract This SIP specifies Version 6 of the Clarity smart contract language. It -introduces quality-of-life improvements to the language syntax, relaxes naming rules -to work better with the new Clarity linter, adds a new cryptographic -built-in function, and adds new built-ins for trustlessly verifying Bitcoin -transaction outputs on-chain. These changes are motivated by real-world -developer experience and address long-standing requests from the Clarity -developer community. +introduces quality-of-life improvements to the language syntax, relaxes naming +rules to work better with the new Clarity linter, adds new cryptographic +built-in functions, and adds new built-ins for trustlessly verifying Bitcoin +transaction outputs on-chain. +These changes are motivated by real-world developer experience and address +long-standing requests from the Clarity developer community. # Copyright @@ -62,7 +62,14 @@ by Clarity developers. Specifically, it makes the following changes: decompress a secp256k1 public key in Clarity. This forces protocols like Wormhole to use cumbersome workarounds involving uncompressed keys, leading to operational downtime when guardian sets change. -5. **Trustless Bitcoin transaction verification:** Clarity contracts have no +5. **Ed25519 signature verification:** Clarity currently supports signature + verification only on the secp256k1 curve (used by Bitcoin and Ethereum) and + the secp256r1 curve (used by Apple's Secure Enclave and WebAuthn). There is + no way to verify Ed25519 signatures, which are the standard for many other + ecosystems (including Solana, Cardano, Polkadot, Stellar, Tor, SSH, and + Signal). This blocks cross-chain bridges and attestation/identity systems + that need to verify signatures produced by those ecosystems. +6. **Trustless Bitcoin transaction verification:** Clarity contracts have no native way to verify that a Bitcoin transaction output exists on the Bitcoin chain. Protocols that need this capability (such as BTC bridges and sBTC-style peg systems) must currently rely on off-chain oracles or trusted relayers, or @@ -269,6 +276,49 @@ compressed public key on-chain: none)) ``` +## Add `ed25519-verify` Function + +Clarity currently provides signature verification only on the secp256k1 curve +(via `secp256k1-verify`) and the secp256r1 curve (via `secp256r1-verify`, added +in Clarity 5). There is no built-in for verifying Ed25519 signatures, even +though Ed25519 is the dominant signature scheme outside of the Bitcoin and +Ethereum ecosystems and is used by Solana, Cardano, Polkadot, Stellar, Tor, +SSH, and Signal, among many other systems. + +This omission prevents Clarity contracts from natively verifying messages +signed by participants in any of those ecosystems, which is a hard requirement +for cross-chain bridges and for identity/attestation systems that rely on +Ed25519-keyed credentials. + +Beginning in Clarity 6, a new built-in function `ed25519-verify` is available. + +- **Input**: `buff, (buff 64), (buff 32)`: the message, the 64-byte Ed25519 + signature, and the 32-byte Ed25519 public key. +- **Output**: `bool` +- **Signature**: `(ed25519-verify message signature public-key)` +- **Description**: Verifies that `signature` is a valid Ed25519 signature of + `message` under `public-key`, per + [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032). Returns `true` if + the signature is valid and `false` otherwise. Verification is performed in + strict mode: non-canonical signatures (for example, signatures whose + `s`-component is not in canonical range) are rejected, which prevents + signature malleability. +- **Example** (using the standard test vector from RFC 8032 §7.1, TEST 2): + ```clarity + (ed25519-verify + 0x72 + 0x92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00 + 0x3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c) + ;; Returns true + + ;; Same signature/key, but a different message: verification fails. + (ed25519-verify + 0x00 + 0x92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00 + 0x3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c) + ;; Returns false + ``` + ## Bitcoin Transaction Verification Built-ins Clarity contracts currently have no first-class way to verify that a Bitcoin @@ -378,7 +428,7 @@ This SIP builds upon the existing definitions of the Clarity language: Because this SIP introduces new syntax (numeric separators, underscore-prefixed identifiers) and new built-in functions (`secp256k1-decompress?`, -`get-bitcoin-tx-output?`, and `verify-merkle-proof`), it is a +`ed25519-verify`, `get-bitcoin-tx-output?`, and `verify-merkle-proof`), it is a consensus-breaking change. A contract that uses any of these new features would be invalid before this SIP is activated, and valid after it is activated. From dc62babad575f425fcbb5150dd08ee3fe02d0119 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Fri, 22 May 2026 11:54:00 -0400 Subject: [PATCH 06/25] Add comment about activation upon Epoch 4.0 --- sips/sip-04x/sip-04x-clarity6.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index da2699c6..8c91886b 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -79,6 +79,11 @@ by Clarity developers. Specifically, it makes the following changes: # Specification +This SIP requires a hard fork. Clarity 6 will activate at the onset of Stacks +Epoch 4.0. New contracts deployed in Epoch 4.0 will default to Clarity 6, but +contract authors can override this by specifying an earlier version in the +deploy transaction. + ## Allow Constants in Type Positions Originally proposed here: From d738e74667d5c09280b4f60eaa7338cc3a3933a2 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Fri, 22 May 2026 16:12:41 -0400 Subject: [PATCH 07/25] Add variadic `concat` --- sips/sip-04x/sip-04x-clarity6.md | 38 +++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index 8c91886b..5a4be479 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -58,18 +58,22 @@ by Clarity developers. Specifically, it makes the following changes: identifiers from starting with `_`, preventing developers from using a widely-adopted convention for indicating intentionally unused variables. This limitation affects developer tooling such as linters. -4. **secp256k1 public key decompression:** There is currently no way to +4. **Variadic `concat`:** The `concat` function currently accepts only two + arguments, so assembling a sequence from more than two parts requires deeply + nested calls. This is verbose and hard to read, particularly in code that + builds multi-field binary payloads (such as cross-chain bridge serialization). +5. **secp256k1 public key decompression:** There is currently no way to decompress a secp256k1 public key in Clarity. This forces protocols like Wormhole to use cumbersome workarounds involving uncompressed keys, leading to operational downtime when guardian sets change. -5. **Ed25519 signature verification:** Clarity currently supports signature +6. **Ed25519 signature verification:** Clarity currently supports signature verification only on the secp256k1 curve (used by Bitcoin and Ethereum) and the secp256r1 curve (used by Apple's Secure Enclave and WebAuthn). There is no way to verify Ed25519 signatures, which are the standard for many other ecosystems (including Solana, Cardano, Polkadot, Stellar, Tor, SSH, and Signal). This blocks cross-chain bridges and attestation/identity systems that need to verify signatures produced by those ecosystems. -6. **Trustless Bitcoin transaction verification:** Clarity contracts have no +7. **Trustless Bitcoin transaction verification:** Clarity contracts have no native way to verify that a Bitcoin transaction output exists on the Bitcoin chain. Protocols that need this capability (such as BTC bridges and sBTC-style peg systems) must currently rely on off-chain oracles or trusted relayers, or @@ -225,6 +229,34 @@ This convention is familiar to developers coming from Rust, TypeScript, Python, and many other languages. It also enables linters and static analysis tools to automatically detect genuinely unused variables without false positives. +## Variadic `concat` + +Originally proposed here: +https://github.com/stacks-network/stacks-core/issues/7112 + +The `concat` function currently accepts exactly two sequences (buffers, ASCII +strings, UTF-8 strings, or lists) and returns their concatenation. To combine +more than two sequences, developers must write nested `concat` calls, which +produce verbose, hard-to-read code — particularly in code that assembles +multi-field binary payloads such as cross-chain bridge serializations. + +Beginning in Clarity 6, `concat` accepts two or more arguments. All arguments +must share the same sequence type (all buffers, all ASCII strings, all UTF-8 +strings, or all lists with the same element type), and the result has a maximum +length equal to the sum of the maximum lengths of the inputs, subject to +Clarity's overall sequence-length limits. Calling `concat` with fewer than two +arguments remains an analysis error. + +### Example + +```clarity +;; Clarity 5 and below: nested calls required +(concat (concat (concat 0x11 amount-bytes) fee-bytes) chain-id) + +;; Clarity 6: flat, variadic call +(concat 0x11 amount-bytes fee-bytes chain-id) +``` + ## Add `secp256k1-decompress?` Function Originally proposed here: From b2f7680bfb6c2e758fe4e8561628ed2487c51fed Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Tue, 26 May 2026 15:49:30 -0400 Subject: [PATCH 08/25] Clarify that by "variables" we really mean all identifiers --- sips/sip-04x/sip-04x-clarity6.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index 5a4be479..3d2647b2 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -54,9 +54,9 @@ by Clarity developers. Specifically, it makes the following changes: (especially when working with micro-denominated tokens), but they are difficult to read without visual grouping. Many modern languages support underscore separators in numeric literals for this purpose. -3. **Underscore-prefixed variable names:** The current naming rules prohibit +3. **Underscore-prefixed identifiers:** The current naming rules prohibit identifiers from starting with `_`, preventing developers from using a - widely-adopted convention for indicating intentionally unused variables. This + widely-adopted convention for indicating intentionally unused bindings. This limitation affects developer tooling such as linters. 4. **Variadic `concat`:** The `concat` function currently accepts only two arguments, so assembling a sequence from more than two parts requires deeply @@ -189,7 +189,7 @@ during parsing. This feature is supported by many popular programming languages including Rust, JavaScript, Solidity, and Go. -## Allow Variables to Start with `_` +## Allow Identifiers to Start with `_` Originally proposed here: https://github.com/clarity-lang/reference/issues/101 @@ -197,22 +197,22 @@ https://github.com/clarity-lang/reference/issues/101 Currently, Clarity identifiers cannot begin with the underscore character (`_`), although underscores are permitted in other positions within a name. This prevents developers from using the widely-adopted convention of prefixing unused -variables with `_` to indicate that they are intentionally unused. +bindings with `_` to indicate that they are intentionally unused. -Beginning in Clarity 6, identifiers (including variable names, function names, -map names, data variable names, and other named definitions) may begin with an -underscore. +Beginning in Clarity 6, identifiers (including function, constant, map, and +data var names, function arguments, `let` and `match` bindings, and other named +definitions) may begin with an underscore. -Additionally, the bare identifier `_` (a single underscore) is allowed as a -variable name in `let` and `match` bindings. This serves as a discard pattern, -indicating that the value is intentionally unused. Unlike other variable names, -`_` does not create a binding that can be referenced later; attempting to -reference `_` as a variable will result in an analysis error. +Additionally, the bare identifier `_` (a single underscore) is allowed in +`let` and `match` bindings as a discard pattern, indicating that the bound +value is intentionally unused. Unlike other identifiers, `_` does not create +a binding that can be referenced later; attempting to reference `_` will +result in an analysis error. ### Examples ```clarity -;; Prefixing an unused variable +;; Prefixing an unused binding (define-public (remove-admin (address principal)) (let ((_admin (try! (check-admin))) (deleted (map-delete admins address))) @@ -227,7 +227,7 @@ reference `_` as a variable will result in an analysis error. This convention is familiar to developers coming from Rust, TypeScript, Python, and many other languages. It also enables linters and static analysis tools to -automatically detect genuinely unused variables without false positives. +automatically detect genuinely unused bindings without false positives. ## Variadic `concat` From 6560d0bc55ea06c57d115942ddc48ba946033732 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Wed, 27 May 2026 16:08:26 -0400 Subject: [PATCH 09/25] Clarity rules around using bare `_` as an identifier --- sips/sip-04x/sip-04x-clarity6.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index 3d2647b2..b1112a54 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -203,11 +203,15 @@ Beginning in Clarity 6, identifiers (including function, constant, map, and data var names, function arguments, `let` and `match` bindings, and other named definitions) may begin with an underscore. -Additionally, the bare identifier `_` (a single underscore) is allowed in -`let` and `match` bindings as a discard pattern, indicating that the bound -value is intentionally unused. Unlike other identifiers, `_` does not create -a binding that can be referenced later; attempting to reference `_` will -result in an analysis error. +Additionally, the bare identifier `_` (a single underscore) is allowed only +in `let` and `match` bindings as a discard pattern, indicating that the bound +value is intentionally unused. Using a bare `_` as a name in any other +position (for example, as a constant, function, function argument, trait, +map, or data var name) is a parse error. Unlike other identifiers, `_` does +not create a binding that can be referenced later; attempting to reference +`_` will result in an analysis error. Because `_` does not introduce a name, +multiple `_` bindings may appear in the same `let` expression — each simply +discards the value of its bound expression. ### Examples @@ -218,9 +222,10 @@ result in an analysis error. (deleted (map-delete admins address))) (ok deleted))) -;; Using _ as a discard pattern +;; Using _ as a discard pattern (multiple discards in one let) (define-public (remove-admin (address principal)) (let ((_ (try! (check-admin))) + (_ (try! (log-admin-action address))) (deleted (map-delete admins address))) (ok deleted))) ``` From 26b3850f4e8c3f50f4eca67dd75150c3997d2b7c Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Wed, 27 May 2026 16:42:54 -0400 Subject: [PATCH 10/25] Remove items we don't have time to complete --- sips/sip-04x/sip-04x-clarity6.md | 133 +++---------------------------- 1 file changed, 12 insertions(+), 121 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index b1112a54..b385dfa3 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -27,7 +27,7 @@ Discussions-To: # Abstract This SIP specifies Version 6 of the Clarity smart contract language. It -introduces quality-of-life improvements to the language syntax, relaxes naming +extends the `concat` function to accept more than two arguments, relaxes naming rules to work better with the new Clarity linter, adds new cryptographic built-in functions, and adds new built-ins for trustlessly verifying Bitcoin transaction outputs on-chain. @@ -45,35 +45,26 @@ is held by the Stacks Open Internet Foundation. This SIP addresses several limitations and inconveniences that have been reported by Clarity developers. Specifically, it makes the following changes: -1. **Allow constants in place of literal values:** Clarity currently requires - literal values in type definitions (such as list lengths) and in certain - built-in functions (such as `as-max-len?`), even though constants are also known - during deployment. This forces developers to duplicate magic numbers throughout - their code, increasing the risk of inconsistencies. -2. **Numeric separators:** Large numeric literals are common in Clarity contracts - (especially when working with micro-denominated tokens), but they are - difficult to read without visual grouping. Many modern languages support - underscore separators in numeric literals for this purpose. -3. **Underscore-prefixed identifiers:** The current naming rules prohibit +1. **Underscore-prefixed identifiers:** The current naming rules prohibit identifiers from starting with `_`, preventing developers from using a widely-adopted convention for indicating intentionally unused bindings. This limitation affects developer tooling such as linters. -4. **Variadic `concat`:** The `concat` function currently accepts only two +2. **Variadic `concat`:** The `concat` function currently accepts only two arguments, so assembling a sequence from more than two parts requires deeply nested calls. This is verbose and hard to read, particularly in code that builds multi-field binary payloads (such as cross-chain bridge serialization). -5. **secp256k1 public key decompression:** There is currently no way to +3. **secp256k1 public key decompression:** There is currently no way to decompress a secp256k1 public key in Clarity. This forces protocols like Wormhole to use cumbersome workarounds involving uncompressed keys, leading to operational downtime when guardian sets change. -6. **Ed25519 signature verification:** Clarity currently supports signature +4. **Ed25519 signature verification:** Clarity currently supports signature verification only on the secp256k1 curve (used by Bitcoin and Ethereum) and the secp256r1 curve (used by Apple's Secure Enclave and WebAuthn). There is no way to verify Ed25519 signatures, which are the standard for many other ecosystems (including Solana, Cardano, Polkadot, Stellar, Tor, SSH, and Signal). This blocks cross-chain bridges and attestation/identity systems that need to verify signatures produced by those ecosystems. -7. **Trustless Bitcoin transaction verification:** Clarity contracts have no +5. **Trustless Bitcoin transaction verification:** Clarity contracts have no native way to verify that a Bitcoin transaction output exists on the Bitcoin chain. Protocols that need this capability (such as BTC bridges and sBTC-style peg systems) must currently rely on off-chain oracles or trusted relayers, or @@ -88,107 +79,6 @@ Epoch 4.0. New contracts deployed in Epoch 4.0 will default to Clarity 6, but contract authors can override this by specifying an earlier version in the deploy transaction. -## Allow Constants in Type Positions - -Originally proposed here: -https://github.com/clarity-lang/reference/issues/78 - -Currently, Clarity requires literal values when specifying type parameters such -as list lengths. Constants cannot be used in these positions, even though their -values are known during analysis. - -Beginning in Clarity 6, constants defined with `define-constant` may be used -wherever a literal unsigned integer is required in a type specification. This -includes list length parameters in type definitions, function signatures, and -anywhere else a literal is currently expected for a type parameter. - -### Example - -```clarity -(define-constant MAX_SIZE u30) - -(define-read-only (foo (l (list MAX_SIZE int))) - (len l) -) -``` - -In Clarity 5 and below, the above would produce an error. In Clarity 6, it is -valid and equivalent to writing `(list 30 int)`. - -The constant must evaluate to an unsigned integer (`uint`) value. Using a -constant that evaluates to any other type in a type position will result in an -analysis error. - -## Allow Constants in `as-max-len?` - -Originally proposed here: -https://github.com/clarity-lang/reference/issues/80 - -The `as-max-len?` function currently requires a literal unsigned integer for its -second argument, which specifies the target maximum length. Constants cannot be -substituted for this literal, even though the value is equally fixed and known -during analysis. - -Beginning in Clarity 6, a constant defined with `define-constant` may be used as -the second argument to `as-max-len?`, provided the constant evaluates to a -`uint`. - -### Example - -```clarity -(define-constant GOV_MAX_GUARDIANS u30) - -(unwrap-panic (as-max-len? updated-public-keys GOV_MAX_GUARDIANS)) -``` - -In Clarity 5 and below, this would produce an error. In Clarity 6, it is valid -and equivalent to writing `(as-max-len? updated-public-keys u30)`. - -This change, combined with the previous change allowing constants in type -positions, enables developers to define a single constant for a length limit and -use it consistently throughout their contract. - -## Numeric Separator with `_` - -Originally proposed here: -https://github.com/clarity-lang/reference/issues/92 - -Large numeric literals are common in Clarity smart contracts, particularly when -dealing with micro-denominated values (e.g. uSTX). These large numbers are -difficult to read and error-prone to write. - -Beginning in Clarity 6, the underscore character (`_`) may be used as a visual -separator within signed and unsigned integer literals. The underscores are -purely cosmetic and have no effect on the value of the literal: They are stripped -during parsing. - -### Rules - -- Underscores may appear between any two digits in an integer or unsigned integer - literal. -- Underscores may not appear at the beginning or end of a numeric literal, nor - adjacent to the `u` prefix for unsigned integers. -- Multiple consecutive underscores are not allowed. -- The presence or absence of underscores does not affect the type or value of the - literal. - -### Examples - -```clarity -;; With separators (Clarity 6) -(define-constant INITIAL_MINT_AMOUNT u200_000_000_000_000) ;; 200,000,000 STX - -;; Equivalent to (Clarity 5 and below) -(define-constant INITIAL_MINT_AMOUNT u200000000000000) - -;; Additional examples -(define-constant ONE_MILLION 1_000_000) -(define-constant SOME_VALUE u1_234_567) -``` - -This feature is supported by many popular programming languages including Rust, -JavaScript, Solidity, and Go. - ## Allow Identifiers to Start with `_` Originally proposed here: @@ -468,11 +358,12 @@ This SIP builds upon the existing definitions of the Clarity language: # Backwards Compatibility -Because this SIP introduces new syntax (numeric separators, underscore-prefixed -identifiers) and new built-in functions (`secp256k1-decompress?`, -`ed25519-verify`, `get-bitcoin-tx-output?`, and `verify-merkle-proof`), it is a -consensus-breaking change. A contract that uses any of these new features would -be invalid before this SIP is activated, and valid after it is activated. +Because this SIP introduces new syntax (underscore-prefixed identifiers), +extends the `concat` function to accept more than two arguments, and adds new +built-in functions (`secp256k1-decompress?`, `ed25519-verify`, +`get-bitcoin-tx-output?`, and `verify-merkle-proof`), it is a consensus-breaking +change. A contract that uses any of these new features would be invalid before +this SIP is activated, and valid after it is activated. All new keywords introduced in Clarity 6 can no longer be used as identifiers in a Clarity 6 smart contract. Smart contracts can continue to be published using From a1d3bab33a79623ac222b7ed493d25f79477bbab Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Wed, 10 Jun 2026 16:01:45 -0400 Subject: [PATCH 11/25] Minor clarifications around `_`-prefixing --- sips/sip-04x/sip-04x-clarity6.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index b385dfa3..e44aac4b 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -91,17 +91,17 @@ bindings with `_` to indicate that they are intentionally unused. Beginning in Clarity 6, identifiers (including function, constant, map, and data var names, function arguments, `let` and `match` bindings, and other named -definitions) may begin with an underscore. +definitions, but excluding contract names) may begin with an underscore. Additionally, the bare identifier `_` (a single underscore) is allowed only in `let` and `match` bindings as a discard pattern, indicating that the bound value is intentionally unused. Using a bare `_` as a name in any other position (for example, as a constant, function, function argument, trait, -map, or data var name) is a parse error. Unlike other identifiers, `_` does -not create a binding that can be referenced later; attempting to reference -`_` will result in an analysis error. Because `_` does not introduce a name, -multiple `_` bindings may appear in the same `let` expression — each simply -discards the value of its bound expression. +map, or data var name) is rejected at contract analysis. +Unlike other identifiers, `_` does not create a binding that can be referenced +later; attempting to reference `_` will result in an analysis error. +Because `_` does not introduce a name, multiple `_` bindings may appear in the +same `let` expression — each simply discards the value of its bound expression. ### Examples From 1a0362bb1f71f35f0924b047478ac4d4545f454c Mon Sep 17 00:00:00 2001 From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:44:39 -0400 Subject: [PATCH 12/25] feat: add staking PCs, problematic txs, and cost-voting --- sips/sip-04x/sip-04x-clarity6.md | 430 +++++++++++++++++++++++++------ 1 file changed, 349 insertions(+), 81 deletions(-) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-04x/sip-04x-clarity6.md index e44aac4b..83b4dba4 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-04x/sip-04x-clarity6.md @@ -7,6 +7,7 @@ Title: Clarity 6: Language Improvements and New Built-ins Author(s): - Jeff Bencin +- Brice Dobry Status: Draft @@ -26,13 +27,12 @@ Discussions-To: # Abstract -This SIP specifies Version 6 of the Clarity smart contract language. It -extends the `concat` function to accept more than two arguments, relaxes naming -rules to work better with the new Clarity linter, adds new cryptographic -built-in functions, and adds new built-ins for trustlessly verifying Bitcoin -transaction outputs on-chain. -These changes are motivated by real-world developer experience and address -long-standing requests from the Clarity developer community. +This SIP specifies Version 6 of the Clarity smart contract language. It extends +the `concat` function to accept more than two arguments, relaxes naming rules to +work better with the new Clarity linter, adds new cryptographic built-in +functions, and adds new built-ins for trustlessly verifying Bitcoin transaction +outputs on-chain. These changes are motivated by real-world developer experience +and address long-standing requests from the Clarity developer community. # Copyright @@ -42,8 +42,8 @@ is held by the Stacks Open Internet Foundation. # Introduction -This SIP addresses several limitations and inconveniences that have been reported -by Clarity developers. Specifically, it makes the following changes: +This SIP addresses several limitations and inconveniences that have been +reported by Clarity developers. Specifically, it makes the following changes: 1. **Underscore-prefixed identifiers:** The current naming rules prohibit identifiers from starting with `_`, preventing developers from using a @@ -52,11 +52,12 @@ by Clarity developers. Specifically, it makes the following changes: 2. **Variadic `concat`:** The `concat` function currently accepts only two arguments, so assembling a sequence from more than two parts requires deeply nested calls. This is verbose and hard to read, particularly in code that - builds multi-field binary payloads (such as cross-chain bridge serialization). + builds multi-field binary payloads (such as cross-chain bridge + serialization). 3. **secp256k1 public key decompression:** There is currently no way to decompress a secp256k1 public key in Clarity. This forces protocols like - Wormhole to use cumbersome workarounds involving uncompressed keys, leading to - operational downtime when guardian sets change. + Wormhole to use cumbersome workarounds involving uncompressed keys, leading + to operational downtime when guardian sets change. 4. **Ed25519 signature verification:** Clarity currently supports signature verification only on the secp256k1 curve (used by Bitcoin and Ethereum) and the secp256r1 curve (used by Apple's Secure Enclave and WebAuthn). There is @@ -66,11 +67,30 @@ by Clarity developers. Specifically, it makes the following changes: that need to verify signatures produced by those ecosystems. 5. **Trustless Bitcoin transaction verification:** Clarity contracts have no native way to verify that a Bitcoin transaction output exists on the Bitcoin - chain. Protocols that need this capability (such as BTC bridges and sBTC-style - peg systems) must currently rely on off-chain oracles or trusted relayers, or - reimplement Bitcoin transaction parsing and merkle-proof verification in - user-space Clarity code, which is expensive, error-prone, and difficult to - audit. + chain. Protocols that need this capability (such as BTC bridges and + sBTC-style peg systems) must currently rely on off-chain oracles or trusted + relayers, or reimplement Bitcoin transaction parsing and merkle-proof + verification in user-space Clarity code, which is expensive, error-prone, and + difficult to audit. + +Additionally, it addresses the need for new functionality, as a result of the +new Bitcoin-staking model, described in SIP-xyz, on which this SIP is a rider: + +1. **Staking post-conditions** To enhance the security and user-friendliness of + staking, this proposal adds two new transaction level post-conditions, which + allow users to ensure that the contract calls they make cannot affect their + staking status without explicit authorization. This applies both to + transaction-level post-conditions and in-contract (`as-contract?` and + `restrict-assets?`) post-conditions. +2. **Problematic transactions** Transactions that are deemed to be problematic + by agreement between miners and signers should be included in a block, with + their fees taken. +3. **Disable `cost-voting`** This contract was originally designed as a + mechanism to allow the cost of a specific function call to be overwritten + without the need for a hard-fork. The handling of this contract complicates + the code and slows down execution, but it has never been used, so it is + better for the network to disable this functionality and continue to make + cost changes through the SIP process, which has been working smoothly. # Specification @@ -79,31 +99,32 @@ Epoch 4.0. New contracts deployed in Epoch 4.0 will default to Clarity 6, but contract authors can override this by specifying an earlier version in the deploy transaction. -## Allow Identifiers to Start with `_` +## Clarity 6 -Originally proposed here: -https://github.com/clarity-lang/reference/issues/101 +### Allow Identifiers to Start with `_` + +Originally proposed here: https://github.com/clarity-lang/reference/issues/101 Currently, Clarity identifiers cannot begin with the underscore character (`_`), although underscores are permitted in other positions within a name. This prevents developers from using the widely-adopted convention of prefixing unused bindings with `_` to indicate that they are intentionally unused. -Beginning in Clarity 6, identifiers (including function, constant, map, and -data var names, function arguments, `let` and `match` bindings, and other named +Beginning in Clarity 6, identifiers (including function, constant, map, and data +var names, function arguments, `let` and `match` bindings, and other named definitions, but excluding contract names) may begin with an underscore. -Additionally, the bare identifier `_` (a single underscore) is allowed only -in `let` and `match` bindings as a discard pattern, indicating that the bound -value is intentionally unused. Using a bare `_` as a name in any other -position (for example, as a constant, function, function argument, trait, -map, or data var name) is rejected at contract analysis. -Unlike other identifiers, `_` does not create a binding that can be referenced -later; attempting to reference `_` will result in an analysis error. -Because `_` does not introduce a name, multiple `_` bindings may appear in the -same `let` expression — each simply discards the value of its bound expression. +Additionally, the bare identifier `_` (a single underscore) is allowed only in +`let` and `match` bindings as a discard pattern, indicating that the bound value +is intentionally unused. Using a bare `_` as a name in any other position (for +example, as a constant, function, function argument, trait, map, or data var +name) is rejected at contract analysis. Unlike other identifiers, `_` does not +create a binding that can be referenced later; attempting to reference `_` will +result in an analysis error. Because `_` does not introduce a name, multiple `_` +bindings may appear in the same `let` expression — each simply discards the +value of its bound expression. -### Examples +#### Examples ```clarity ;; Prefixing an unused binding @@ -124,7 +145,7 @@ This convention is familiar to developers coming from Rust, TypeScript, Python, and many other languages. It also enables linters and static analysis tools to automatically detect genuinely unused bindings without false positives. -## Variadic `concat` +### Variadic `concat` Originally proposed here: https://github.com/stacks-network/stacks-core/issues/7112 @@ -142,7 +163,7 @@ length equal to the sum of the maximum lengths of the inputs, subject to Clarity's overall sequence-length limits. Calling `concat` with fewer than two arguments remains an analysis error. -### Example +#### Example ```clarity ;; Clarity 5 and below: nested calls required @@ -152,10 +173,9 @@ arguments remains an analysis error. (concat 0x11 amount-bytes fee-bytes chain-id) ``` -## Add `secp256k1-decompress?` Function +### Add `secp256k1-decompress?` Function -Originally proposed here: -https://github.com/clarity-lang/reference/issues/103 +Originally proposed here: https://github.com/clarity-lang/reference/issues/103 There is currently no built-in function in Clarity to decompress a secp256k1 public key. Decompression requires computing a modular square root on the @@ -172,8 +192,8 @@ Beginning in Clarity 6, a new built-in function `secp256k1-decompress?` is available. - **Input**: `(buff 33)`: a compressed secp256k1 public key -- **Output**: `(optional (buff 65))`: the uncompressed public key, or `none` - if the input is not a valid compressed public key +- **Output**: `(optional (buff 65))`: the uncompressed public key, or `none` if + the input is not a valid compressed public key - **Signature**: `(secp256k1-decompress? compressed-public-key)` - **Description**: Takes a 33-byte compressed secp256k1 public key (where the first byte is `0x02` or `0x03` indicating the parity of the y-coordinate) and @@ -181,6 +201,7 @@ available. prefix followed by the 32-byte x-coordinate and 32-byte y-coordinate). Returns `none` if the input is not a valid compressed secp256k1 public key. - **Example**: + ```clarity ;; Decompress a valid compressed public key (secp256k1-decompress? 0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798) @@ -191,7 +212,7 @@ available. ;; Returns none ``` -### Deriving an Ethereum Address +#### Deriving an Ethereum Address With `secp256k1-decompress?`, developers can derive an Ethereum address from a compressed public key on-chain: @@ -208,18 +229,18 @@ compressed public key on-chain: none)) ``` -## Add `ed25519-verify` Function +### Add `ed25519-verify` Function Clarity currently provides signature verification only on the secp256k1 curve (via `secp256k1-verify`) and the secp256r1 curve (via `secp256r1-verify`, added in Clarity 5). There is no built-in for verifying Ed25519 signatures, even though Ed25519 is the dominant signature scheme outside of the Bitcoin and -Ethereum ecosystems and is used by Solana, Cardano, Polkadot, Stellar, Tor, -SSH, and Signal, among many other systems. +Ethereum ecosystems and is used by Solana, Cardano, Polkadot, Stellar, Tor, SSH, +and Signal, among many other systems. -This omission prevents Clarity contracts from natively verifying messages -signed by participants in any of those ecosystems, which is a hard requirement -for cross-chain bridges and for identity/attestation systems that rely on +This omission prevents Clarity contracts from natively verifying messages signed +by participants in any of those ecosystems, which is a hard requirement for +cross-chain bridges and for identity/attestation systems that rely on Ed25519-keyed credentials. Beginning in Clarity 6, a new built-in function `ed25519-verify` is available. @@ -236,6 +257,7 @@ Beginning in Clarity 6, a new built-in function `ed25519-verify` is available. `s`-component is not in canonical range) are rejected, which prevents signature malleability. - **Example** (using the standard test vector from RFC 8032 §7.1, TEST 2): + ```clarity (ed25519-verify 0x72 @@ -251,7 +273,7 @@ Beginning in Clarity 6, a new built-in function `ed25519-verify` is available. ;; Returns false ``` -## Bitcoin Transaction Verification Built-ins +### Bitcoin Transaction Verification Built-ins Clarity contracts currently have no first-class way to verify that a Bitcoin transaction output exists on the Bitcoin chain. Protocols such as BTC bridges @@ -261,35 +283,37 @@ user-space Clarity, where the cost of double-SHA-256 hashing and byte-level parsing on large transactions is prohibitive and the risk of subtle bugs (such as CVE-2012-2459-style merkle malleability) is high. -Beginning in Clarity 6, two new built-in functions, `get-bitcoin-tx-output?` -and `verify-merkle-proof`, are available. They are designed as a pair: the -`txid` returned by `get-bitcoin-tx-output?` is in the internal (raw) byte -order expected by `verify-merkle-proof` as a leaf hash. Combined with the -existing `get-burn-block-info?` built-in — whose `header-hash` property lets -a contract authenticate a user-supplied Bitcoin block header (and thereby -extract its merkle root) — they enable contracts to verify that a Bitcoin -output exists on-chain without trusting the caller to have correctly stripped -witness data or hashed the transaction. +Beginning in Clarity 6, two new built-in functions, `get-bitcoin-tx-output?` and +`verify-merkle-proof`, are available. They are designed as a pair: the `txid` +returned by `get-bitcoin-tx-output?` is in the internal (raw) byte order +expected by `verify-merkle-proof` as a leaf hash. Combined with the existing +`get-burn-block-info?` built-in — whose `header-hash` property lets a contract +authenticate a user-supplied Bitcoin block header (and thereby extract its +merkle root) — they enable contracts to verify that a Bitcoin output exists +on-chain without trusting the caller to have correctly stripped witness data or +hashed the transaction. -### `get-bitcoin-tx-output?` +#### `get-bitcoin-tx-output?` - **Input**: `buff, uint`: a serialized Bitcoin transaction (with or without SegWit witness data), and the output index (`vout`) to extract. -- **Output**: `(response (tuple (script (buff 1024)) (amount uint) (txid (buff 32))) uint)` +- **Output**: + `(response (tuple (script (buff 1024)) (amount uint) (txid (buff 32))) uint)` - **Signature**: `(get-bitcoin-tx-output? tx-bytes vout)` - **Description**: Parses a serialized Bitcoin transaction and returns the output at the given `vout` index, along with the canonical (non-witness) - `txid` of the transaction. The returned `txid` is in *internal* byte order + `txid` of the transaction. The returned `txid` is in _internal_ byte order (the raw double-SHA-256 result), ready to be passed directly to `verify-merkle-proof` as the leaf hash. The `script` field contains the raw `scriptPubKey` bytes of the output, so contracts can pattern-match on script - prefixes to recognize P2WSH (`0x00 0x20 ...`), P2TR (`0x51 0x20 ...`), - P2WPKH (`0x00 0x14 ...`), `OP_RETURN` (`0x6a ...`), or any other output - script. Returns one of three error codes on failure: + prefixes to recognize P2WSH (`0x00 0x20 ...`), P2TR (`0x51 0x20 ...`), P2WPKH + (`0x00 0x14 ...`), `OP_RETURN` (`0x6a ...`), or any other output script. + Returns one of three error codes on failure: - `(err u1)` — `tx-bytes` did not deserialize as a Bitcoin transaction. - `(err u2)` — `vout` is out of range for this transaction. - `(err u3)` — the output's `scriptPubKey` exceeds the 1024-byte cap. - **Example**: + ```clarity ;; Parse the Bitcoin genesis block coinbase tx and return its sole output. (get-bitcoin-tx-output? @@ -303,14 +327,15 @@ witness data or hashed the transaction. (get-bitcoin-tx-output? 0x00 u0) ;; Returns (err u1) ``` -### `verify-merkle-proof` +#### `verify-merkle-proof` - **Input**: `(buff 32), (buff 32), uint, uint, (list 24 (buff 32))`: the leaf hash, the merkle root hash, the leaf's index in the tree, the total - transaction count of the block, and the list of sibling hashes along the - path from the leaf to the root. + transaction count of the block, and the list of sibling hashes along the path + from the leaf to the root. - **Output**: `bool` -- **Signature**: `(verify-merkle-proof leaf-hash root-hash tx-index tx-count sibling-hashes)` +- **Signature**: + `(verify-merkle-proof leaf-hash root-hash tx-index tx-count sibling-hashes)` - **Description**: Verifies a Bitcoin-style merkle inclusion proof using double-SHA-256 hashing with the "duplicate the last node on odd-sized rows" rule. Given a `leaf-hash` (typically a Bitcoin txid), the merkle `root-hash` @@ -322,17 +347,17 @@ witness data or hashed the transaction. The `tx-count` argument pins down the canonical Bitcoin tree shape and is required to defend against - [CVE-2012-2459](https://bitcointalk.org/?topic=102395)-style attacks, where - an intermediate node in an odd-row-padded tree could otherwise be passed off - as a leaf. The function rejects any proof whose path length does not match - `ceil(log2(tx-count))` and any `tx-index` not less than `tx-count`. It - returns `false` for any malformed proof and `true` for a valid proof. - - All 32-byte hashes (leaf, root, siblings) are passed in *internal* (raw) - byte order, not the display (reversed) order conventionally used for - Bitcoin txids and block hashes. The `txid` returned by - `get-bitcoin-tx-output?` is already in internal byte order and can be - passed directly as `leaf-hash`. + [CVE-2012-2459](https://bitcointalk.org/?topic=102395)-style attacks, where an + intermediate node in an odd-row-padded tree could otherwise be passed off as a + leaf. The function rejects any proof whose path length does not match + `ceil(log2(tx-count))` and any `tx-index` not less than `tx-count`. It returns + `false` for any malformed proof and `true` for a valid proof. + + All 32-byte hashes (leaf, root, siblings) are passed in _internal_ (raw) byte + order, not the display (reversed) order conventionally used for Bitcoin txids + and block hashes. The `txid` returned by `get-bitcoin-tx-output?` is already + in internal byte order and can be passed directly as `leaf-hash`. + - **Example**: ```clarity ;; The Bitcoin genesis block contains a single tx, so its coinbase txid @@ -346,6 +371,99 @@ witness data or hashed the transaction. (list)) ;; Returns true ``` +### `with-stacking` + +- **Input**: + - `amount`: `uint`: The amount of uSTX that can be locked. +- **Output**: Not applicable +- **Signature**: `(with-staking amount)` +- **Description**: Adds a staking allowance for `amount` uSTX from the + `asset-owner` of the enclosing `restrict-assets?` or `as-contract?` + expression. This restricts calls to the active PoX contract that modify the + `tx-sender`'s STX staking status, ensuring that the locked amount is limited + by the amount of uSTX specified. `with-staking` replaces Clarity 4's + `with-stacking` to match the new naming. The following public functions in the + new pox-5 contract will trigger this restriction: + - `stake` + - `register-for-bond` + - `stake-update` +- **Example**: + ```clarity + (restrict-assets? tx-sender ((with-staking u1000000000000)) + (try! (contract-call? 'SP000000000000000000002Q6VF78.pox-5 stake + 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.signer u1100000000000 u12 u1000 + none + )) + );; Returns (err u0) + (restrict-assets? tx-sender ((with-stacking u1000000000000)) + (try! (contract-call? 'SP000000000000000000002Q6VF78.pox-5 stake + 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.signer u1100000000000 u12 u1000 + none + )) + );; Returns (ok true) + ``` + +### `with-pox` + +- **Input**: Not applicable +- **Output**: Not applicable +- **Signature**: `(with-pox)` +- **Description**: Adds an allowance for interacting with the latest PoX + contract for the `asset-owner` of the enclosing `restrict-assets?` or + `as-contract?` expression, specifically calling functions that act on behalf + of the `tx-sender` and do not trigger a staking event (see `with-staking`). + This includes the following public functions from the new pox-5 contract: + - `unstake` + - `unstake-sbtc` + - `update-bond-registration` + - `announce-l1-early-exit` +- **Example**: + ```clarity + (restrict-assets? tx-sender () + (try! (contract-call? 'SP000000000000000000002Q6VF78.pox-5 unstake + 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.signer + )) + );; Returns (err u0) + (restrict-assets? tx-sender ((with-pox)) + (try! (contract-call? 'SP000000000000000000002Q6VF78.pox-5 unstake + 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.signer + )) + );; Returns (ok true) + ``` + +## Staking Post-Conditions + +Built on the same foundations as the new Clarity allowances, `with-staking` and +`with-pox`, with epoch 4.0, new transaction level post-conditions will activate, +that allow users to protect their STX from unexpected staking changes. Previous +PoX contracts have used the `allow-contract-caller` mechanism to restrict which +contracts are allowed to indirectly call into the PoX contract on behalf of the +`tx-sender`. This improves upon that, by allowing users to protect this behavior +with a post-condition on each transaction. + +### Staking + +A new post-condition with identifier `0x03` specifies that the transaction may +stake STX (or modify existing staking parameters) for the specified principal. +This post-condition specifies a principal, a **fungible condition code** (as +described in [SIP-005](../sip-005/sip-005-blocks-and-transactions.md)), and an +amount. Calls to `stake`, `register-for-bond`, and `stake-update`, will be +evaluated against these post-conditions, and the transaction will be rejected if +the conditions are not met. + +### PoX + +A new post-condition with identifier `0x04` specifies that the transaction may +modify state in the active PoX contract that does not directly change locking +status. This includes calls to `unstake`, `unstake-sbtc`, +`update-bond-registration`, and `announce-l1-early-exit` in the pox-5 contract. +This post-condition specifies a principal and a new **PoX condition code**. A +PoX condition code has the following encodings: + +- `0x30`: "The account must not perform any PoX actions" +- `0x31`: "The account may perform PoX actions" +- `0x32`: "The account must perform a PoX action" + # Related Work This SIP builds upon the existing definitions of the Clarity language: @@ -356,6 +474,156 @@ This SIP builds upon the existing definitions of the Clarity language: - [SIP-033 (Clarity 4)](../sip-033/sip-033-clarity4.md) - [SIP-039 (Clarity 5)](../sip-039/sip-039-clarity5.md) +Parts of this SIP depend upon +[SIP-xyz (Bitcoin Staking)](https://github.com/adriano-stacks/sips/blob/dfaaa4200123374bff84f6a7049d5602bf19c223/sips/sip-xxx/sip-0XX-pox-5-bitcoin-staking.md) +and intends to activate together with it. + +The new transaction-level post-conditions build upon the post-conditions defined +in [SIP-005](../sip-005/sip-005-blocks-and-transactions.md). + +## Problematic Transactions + +A Stacks miner needs a mechanism to charge a fee for all transactions for which +it does work. For example, if a miner attempts to mine a transaction and during +execution, it is determined to be too expensive to fit in a block, the miner +should still be able to collect the fee from that transaction and remove it from +the mempool. This is useful to protect both the miner, and the user, whose +account would otherwise be blocked by this unmineable nonce, requiring a +replace-by-fee, or waiting for the transaction to time out of the mempool. + +Epoch 4.0 will add a new mechanism to allow a miner to identify problematic +transactions in the block header. When other nodes verify this block, they do +not execute those problematic transactions, but simply debit the fee and +increment the nonces. The specification of this list of transactions is defined +as part of this hard fork. The mechanism for determining whether a transaction +may be marked as problematic is an agreement between miners and signers. A miner +can add transactions to this list in a block proposal and signers will analyze +the transactions and decide whether to accept that list or reject it. +Alternatively, a miner might propose a block including transactions normally, +but then the signers analyze the block and decide that one of the transactions +is problematic, and sends that information back to the miner as part of the +block rejection. + +### Block header version + +The Nakamoto block header carries a single-byte version field. The high bit +(0x80) is reserved as the shadow-block flag; the header version number is the +low 7 bits (version & 0x7f). + +This SIP defines: + +- 0 - the header version for Nakamoto epochs prior to Epoch 4.0. +- 1 - the header version for Epoch 4.0 and later. + +A block is invalid if `(version & 0x7f) != expected_version(epoch)`, where epoch +is the Stacks epoch of the block's tenure (the epoch at the block's tenure burn +height). + +### The `problematic_txs` field + +A `problematic_txs` field is appended to the `NakamotoBlockHeader`. The field is +present in the header's serialization (and in all header hashes) iff the header +version number is >= 1. For version-0 headers the field is absent from the byte +stream entirely; on deserialization of a version-0 header it decodes to the +empty list and consumes no bytes. + +### `ProblematicTxMarker` encoding + +```rust +ProblematicTxMarker { + tx_index: u32, // index into NakamotoBlock::txs + category: u8, // opaque-to-consensus reason code +} +``` + +Consensus serialization (network byte order): + +| field | size | encoding | +| ---------- | ------- | -------------- | +| `tx_index` | 4 bytes | big-endian u32 | +| `category` | 1 byte | u8 | + +Total: 5 bytes per marker. The list is length-prefixed with a big-endian u32 +count, per the standard Stacks vector encoding. + +`tx_index` is the 0-based position of the marked transaction in the block's +transaction vector. `category` is opaque to consensus, but it conveys the reason +the transaction was flagged (for observers and tooling) but is not interpreted +by validation. It participates in the block hash and must be agreed upon, but +any value is accepted. + +### Serialization and hashing + +When (and only when) the header version number is >= 1, `problematic_txs` is +included in all of: + +- `consensus_serialize` (the wire/disk encoding of the header), +- the miner signature hash preimage, +- the signer signature hash preimage, which is also the block hash + (`block_id = SHA512/256` over the header excluding signatures, combined with + the consensus hash). + +A single shared predicate governs the field's presence at all four sites, so it +can never diverge between them. + +### Consensus validation rules + +A block's `problematic_txs` is valid iff all of the following hold: + +1. **Version/epoch agreement**: `(version & 0x7f) == expected_version(epoch)`. +2. **Epoch gate**: before Epoch 4.0 the list MUST be empty. +3. **Cardinality**: `problematic_txs.len() <= MAX_PROBLEMATIC_TX_MARKERS`. +4. **Strictly increasing**: `tx_index` values are strictly increasing (which + also forbids duplicates). +5. **In range**: every `tx_index < block.txs.len()`. +6. **Never coinbase or tenure-change**: no marker may point at a `Coinbase` or + `TenureChange` transaction; those must always execute. + +`MAX_PROBLEMATIC_TX_MARKERS` is defined as +`MAX_BLOCK_LEN / MIN_TRANSACTION_LEN = 2,097,152 / 180 = 11,650`, the maximum +number of transactions that can fit in a block, since a marker addresses a +distinct transaction and as such never needs to exceed the block's transaction +count. This bound is also enforced during deserialization (the marker list is +read with this cap), so a malformed length prefix cannot force unbounded +allocation. + +### Replay semantics + +For each transaction in the block, in order, replaying nodes determine whether +its index appears in `problematic_txs`. If it does, the transaction is skipped, +performing only the following actions: + +1. Run the static transaction precheck (size, auth mode, chain ID, network, + post-condition mode, requested Clarity version, …). A precheck failure still + invalidates the block. +2. Debit the transaction fee from the payer. +3. Increment the origin nonce, and the sponsor nonce if the payer differs from + the origin. +4. Do **not** call `process_transaction_payload`. No Clarity code runs; no + contract is deployed; no events are emitted. + +The resulting transaction receipt has execution_cost = 0, an empty event list, a +result of `(err none)`, and a status of `problematic_skipped` (carrying the +category byte) in event payloads. + +All other transactions execute normally. + +## Disable `cost-voting` + +At the launch of epoch 2.0, the +[`cost-voting` contract](https://explorer.hiro.so/txid/SP000000000000000000002Q6VF78.cost-voting?chain=mainnet) +was deployed to provide a mechanism to change the cost charged for the execution +of a contract call without requiring any hard-fork. While the idea was sound, +the functionality has not been used in the 8-million+ blocks that have since +been mined. During that time, the community has clarified and successfully +exercised the SIP process several time to make changes to Clarity costs, making +this mechanism no longer necessary. By disabling the functionality of the +`cost-voting` contract, the code in the `stacks-node` can be made more +performant and at the same time, simplified. + +Once epoch 4.0 activates, the `cost-voting` contract will no longer have any +effect on consensus and can be ignored. + # Backwards Compatibility Because this SIP introduces new syntax (underscore-prefixed identifiers), @@ -393,10 +661,10 @@ transferred between accounts and used to effectively double vote). Solo stackers only can also vote by sending a bitcoin dust transaction (6000 sats) to the corresponding bitcoin address. -| Vote | Bitcoin | Stacks | ASCII Encoding | Msg | -| ---- | ------- | ------ | -------------- | --- | -| yes | TBD | TBD | TBD | yes-sip-43 | -| no | TBD | TBD | TBD | no-sip-43 | +| Vote | Bitcoin | Stacks | ASCII Encoding | Msg | +| ---- | ------- | ------ | -------------- | ---------- | +| yes | TBD | TBD | TBD | yes-sip-44 | +| no | TBD | TBD | TBD | no-sip-44 | If the SIP is approved, a Bitcoin block height will be selected to activate the new behavior. From facda372db762ddb628ab1deb3c23edf5e4ac59a Mon Sep 17 00:00:00 2001 From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:46:10 -0400 Subject: [PATCH 13/25] chore: set SIP number to 044 --- .../sip-04x-clarity6.md => sip-044/sip-044-clarity6.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename sips/{sip-04x/sip-04x-clarity6.md => sip-044/sip-044-clarity6.md} (99%) diff --git a/sips/sip-04x/sip-04x-clarity6.md b/sips/sip-044/sip-044-clarity6.md similarity index 99% rename from sips/sip-04x/sip-04x-clarity6.md rename to sips/sip-044/sip-044-clarity6.md index 83b4dba4..406ce886 100644 --- a/sips/sip-04x/sip-04x-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -1,8 +1,8 @@ # Preamble -SIP Number: 043 +SIP Number: 044 -Title: Clarity 6: Language Improvements and New Built-ins +Title: Clarity 6 and Network Improvements Author(s): From 1fb08f986394694516754d656d5dec9dab4902ab Mon Sep 17 00:00:00 2001 From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:51:34 -0400 Subject: [PATCH 14/25] chore: update activation and reference impl --- sips/sip-044/sip-044-clarity6.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index 406ce886..e76de684 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -661,15 +661,16 @@ transferred between accounts and used to effectively double vote). Solo stackers only can also vote by sending a bitcoin dust transaction (6000 sats) to the corresponding bitcoin address. -| Vote | Bitcoin | Stacks | ASCII Encoding | Msg | -| ---- | ------- | ------ | -------------- | ---------- | -| yes | TBD | TBD | TBD | yes-sip-44 | -| no | TBD | TBD | TBD | no-sip-44 | +| Vote | Bitcoin | Stacks | ASCII Encoding | Msg | +| ---- | -------------------------------- | ------------------------------------- | ---------------------- | ---------- | +| yes | `11111111111mdWK2VXcrA1f72DmUku` | `SP00000000001WPAWSDEDMQ0B9M6HQVS7Q6` | `7965732d7369702d3434` | yes-sip-44 | +| no | `111111111111ACW5wa4RwyfJspnNhu` | `SP000000000006WVSDEDMQ0B9M6K3NTSJK` | `6e6f2d7369702d3434` | no-sip-44 | If the SIP is approved, a Bitcoin block height will be selected to activate the new behavior. # Reference Implementation -No reference implementation is available at the time of writing. Links to -implementation PRs will be added here as they become available. +All functionality proposed in this SIP is implemented or in progress in the +[`pox-wf-integration` branch](https://github.com/stacks-network/stacks-core/tree/pox-wf-integration) +on https://github.com/stacks-network/stacks-core. From ec7146bb78deac304bcca6d94f775624dcbac74c Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Tue, 16 Jun 2026 13:50:35 -0400 Subject: [PATCH 15/25] chore: Change title and abstract to reflect additional (non-Clarity 6) changes --- sips/sip-044/sip-044-clarity6.md | 74 ++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index e76de684..c14b1ce5 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -2,7 +2,7 @@ SIP Number: 044 -Title: Clarity 6 and Network Improvements +Title: Stacks Epoch 4.0 (Clarity 6 + Network Improvements) Author(s): @@ -27,12 +27,20 @@ Discussions-To: # Abstract -This SIP specifies Version 6 of the Clarity smart contract language. It extends -the `concat` function to accept more than two arguments, relaxes naming rules to -work better with the new Clarity linter, adds new cryptographic built-in -functions, and adds new built-ins for trustlessly verifying Bitcoin transaction -outputs on-chain. These changes are motivated by real-world developer experience -and address long-standing requests from the Clarity developer community. +This SIP defines the consensus changes that activate with Stacks Epoch 4.0. + +First, it introduces Clarity 6, which extends the `concat` function to accept +more than two arguments, relaxes identifier naming rules to work better with +the new Clarity linter, adds new cryptographic built-in functions, adds +built-ins for trustlessly verifying Bitcoin transaction outputs on-chain, and +adds new Clarity allowances for the PoX-5 staking model. + +In addition to the Clarity changes, this SIP introduces new transaction-level +post-conditions for protecting staking state, a mechanism for handling +problematic transactions in blocks, and the deprecation of the long-unused +`cost-voting` contract. These changes are motivated by real-world developer +experience, operational concerns from miners and signers, and the ongoing +evolution of the PoX staking model. # Copyright @@ -94,10 +102,10 @@ new Bitcoin-staking model, described in SIP-xyz, on which this SIP is a rider: # Specification -This SIP requires a hard fork. Clarity 6 will activate at the onset of Stacks -Epoch 4.0. New contracts deployed in Epoch 4.0 will default to Clarity 6, but -contract authors can override this by specifying an earlier version in the -deploy transaction. +This SIP requires a hard fork. The changes specified below all activate +together at the onset of Stacks Epoch 4.0. New contracts deployed in Epoch 4.0 +will default to Clarity 6, though contract authors can override this by +specifying an earlier version in the deploy transaction. ## Clarity 6 @@ -371,7 +379,7 @@ hashed the transaction. (list)) ;; Returns true ``` -### `with-stacking` +### `with-staking` - **Input**: - `amount`: `uint`: The amount of uSTX that can be locked. @@ -434,7 +442,7 @@ hashed the transaction. ## Staking Post-Conditions Built on the same foundations as the new Clarity allowances, `with-staking` and -`with-pox`, with epoch 4.0, new transaction level post-conditions will activate, +`with-pox`, with Epoch 4.0, new transaction-level post-conditions will activate that allow users to protect their STX from unexpected staking changes. Previous PoX contracts have used the `allow-contract-caller` mechanism to restrict which contracts are allowed to indirectly call into the PoX contract on behalf of the @@ -464,23 +472,6 @@ PoX condition code has the following encodings: - `0x31`: "The account may perform PoX actions" - `0x32`: "The account must perform a PoX action" -# Related Work - -This SIP builds upon the existing definitions of the Clarity language: - -- [SIP-002 (Clarity 1)](../sip-002/sip-002-smart-contract-language.md) -- [SIP-015 (Clarity 2)](../sip-015/sip-015-network-upgrade.md) -- [SIP-021 (Clarity 3)](../sip-021/sip-021-nakamoto.md) -- [SIP-033 (Clarity 4)](../sip-033/sip-033-clarity4.md) -- [SIP-039 (Clarity 5)](../sip-039/sip-039-clarity5.md) - -Parts of this SIP depend upon -[SIP-xyz (Bitcoin Staking)](https://github.com/adriano-stacks/sips/blob/dfaaa4200123374bff84f6a7049d5602bf19c223/sips/sip-xxx/sip-0XX-pox-5-bitcoin-staking.md) -and intends to activate together with it. - -The new transaction-level post-conditions build upon the post-conditions defined -in [SIP-005](../sip-005/sip-005-blocks-and-transactions.md). - ## Problematic Transactions A Stacks miner needs a mechanism to charge a fee for all transactions for which @@ -610,20 +601,37 @@ All other transactions execute normally. ## Disable `cost-voting` -At the launch of epoch 2.0, the +At the launch of Epoch 2.0, the [`cost-voting` contract](https://explorer.hiro.so/txid/SP000000000000000000002Q6VF78.cost-voting?chain=mainnet) was deployed to provide a mechanism to change the cost charged for the execution of a contract call without requiring any hard-fork. While the idea was sound, the functionality has not been used in the 8-million+ blocks that have since been mined. During that time, the community has clarified and successfully -exercised the SIP process several time to make changes to Clarity costs, making +exercised the SIP process several times to make changes to Clarity costs, making this mechanism no longer necessary. By disabling the functionality of the `cost-voting` contract, the code in the `stacks-node` can be made more performant and at the same time, simplified. -Once epoch 4.0 activates, the `cost-voting` contract will no longer have any +Once Epoch 4.0 activates, the `cost-voting` contract will no longer have any effect on consensus and can be ignored. +# Related Work + +This SIP builds upon the existing definitions of the Clarity language: + +- [SIP-002 (Clarity 1)](../sip-002/sip-002-smart-contract-language.md) +- [SIP-015 (Clarity 2)](../sip-015/sip-015-network-upgrade.md) +- [SIP-021 (Clarity 3)](../sip-021/sip-021-nakamoto.md) +- [SIP-033 (Clarity 4)](../sip-033/sip-033-clarity4.md) +- [SIP-039 (Clarity 5)](../sip-039/sip-039-clarity5.md) + +Parts of this SIP depend upon +[SIP-xyz (Bitcoin Staking)](https://github.com/adriano-stacks/sips/blob/dfaaa4200123374bff84f6a7049d5602bf19c223/sips/sip-xxx/sip-0XX-pox-5-bitcoin-staking.md) +and intends to activate together with it. + +The new transaction-level post-conditions build upon the post-conditions defined +in [SIP-005](../sip-005/sip-005-blocks-and-transactions.md). + # Backwards Compatibility Because this SIP introduces new syntax (underscore-prefixed identifiers), From 0526d01b7ef8621e457b609fe63ce5f3f47bee8f Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Wed, 17 Jun 2026 11:11:26 -0400 Subject: [PATCH 16/25] chore: Change title --- sips/sip-044/sip-044-clarity6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index c14b1ce5..4c7f1671 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -2,7 +2,7 @@ SIP Number: 044 -Title: Stacks Epoch 4.0 (Clarity 6 + Network Improvements) +Title: Clarity 6, staking and PoX post-conditions, and removal of the cost-voting contract Author(s): From af73b349d98d7bd5168d729b66815185d0079f76 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Tue, 23 Jun 2026 15:01:04 -0400 Subject: [PATCH 17/25] Remove `_`-prefixing due to time constraints --- sips/sip-044/sip-044-clarity6.md | 70 +++++--------------------------- 1 file changed, 10 insertions(+), 60 deletions(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index 4c7f1671..74819416 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -30,8 +30,7 @@ Discussions-To: This SIP defines the consensus changes that activate with Stacks Epoch 4.0. First, it introduces Clarity 6, which extends the `concat` function to accept -more than two arguments, relaxes identifier naming rules to work better with -the new Clarity linter, adds new cryptographic built-in functions, adds +more than two arguments, adds new cryptographic built-in functions, adds built-ins for trustlessly verifying Bitcoin transaction outputs on-chain, and adds new Clarity allowances for the PoX-5 staking model. @@ -53,27 +52,23 @@ is held by the Stacks Open Internet Foundation. This SIP addresses several limitations and inconveniences that have been reported by Clarity developers. Specifically, it makes the following changes: -1. **Underscore-prefixed identifiers:** The current naming rules prohibit - identifiers from starting with `_`, preventing developers from using a - widely-adopted convention for indicating intentionally unused bindings. This - limitation affects developer tooling such as linters. -2. **Variadic `concat`:** The `concat` function currently accepts only two +1. **Variadic `concat`:** The `concat` function currently accepts only two arguments, so assembling a sequence from more than two parts requires deeply nested calls. This is verbose and hard to read, particularly in code that builds multi-field binary payloads (such as cross-chain bridge serialization). -3. **secp256k1 public key decompression:** There is currently no way to +2. **secp256k1 public key decompression:** There is currently no way to decompress a secp256k1 public key in Clarity. This forces protocols like Wormhole to use cumbersome workarounds involving uncompressed keys, leading to operational downtime when guardian sets change. -4. **Ed25519 signature verification:** Clarity currently supports signature +3. **Ed25519 signature verification:** Clarity currently supports signature verification only on the secp256k1 curve (used by Bitcoin and Ethereum) and the secp256r1 curve (used by Apple's Secure Enclave and WebAuthn). There is no way to verify Ed25519 signatures, which are the standard for many other ecosystems (including Solana, Cardano, Polkadot, Stellar, Tor, SSH, and Signal). This blocks cross-chain bridges and attestation/identity systems that need to verify signatures produced by those ecosystems. -5. **Trustless Bitcoin transaction verification:** Clarity contracts have no +4. **Trustless Bitcoin transaction verification:** Clarity contracts have no native way to verify that a Bitcoin transaction output exists on the Bitcoin chain. Protocols that need this capability (such as BTC bridges and sBTC-style peg systems) must currently rely on off-chain oracles or trusted @@ -109,50 +104,6 @@ specifying an earlier version in the deploy transaction. ## Clarity 6 -### Allow Identifiers to Start with `_` - -Originally proposed here: https://github.com/clarity-lang/reference/issues/101 - -Currently, Clarity identifiers cannot begin with the underscore character (`_`), -although underscores are permitted in other positions within a name. This -prevents developers from using the widely-adopted convention of prefixing unused -bindings with `_` to indicate that they are intentionally unused. - -Beginning in Clarity 6, identifiers (including function, constant, map, and data -var names, function arguments, `let` and `match` bindings, and other named -definitions, but excluding contract names) may begin with an underscore. - -Additionally, the bare identifier `_` (a single underscore) is allowed only in -`let` and `match` bindings as a discard pattern, indicating that the bound value -is intentionally unused. Using a bare `_` as a name in any other position (for -example, as a constant, function, function argument, trait, map, or data var -name) is rejected at contract analysis. Unlike other identifiers, `_` does not -create a binding that can be referenced later; attempting to reference `_` will -result in an analysis error. Because `_` does not introduce a name, multiple `_` -bindings may appear in the same `let` expression — each simply discards the -value of its bound expression. - -#### Examples - -```clarity -;; Prefixing an unused binding -(define-public (remove-admin (address principal)) - (let ((_admin (try! (check-admin))) - (deleted (map-delete admins address))) - (ok deleted))) - -;; Using _ as a discard pattern (multiple discards in one let) -(define-public (remove-admin (address principal)) - (let ((_ (try! (check-admin))) - (_ (try! (log-admin-action address))) - (deleted (map-delete admins address))) - (ok deleted))) -``` - -This convention is familiar to developers coming from Rust, TypeScript, Python, -and many other languages. It also enables linters and static analysis tools to -automatically detect genuinely unused bindings without false positives. - ### Variadic `concat` Originally proposed here: @@ -634,12 +585,11 @@ in [SIP-005](../sip-005/sip-005-blocks-and-transactions.md). # Backwards Compatibility -Because this SIP introduces new syntax (underscore-prefixed identifiers), -extends the `concat` function to accept more than two arguments, and adds new -built-in functions (`secp256k1-decompress?`, `ed25519-verify`, -`get-bitcoin-tx-output?`, and `verify-merkle-proof`), it is a consensus-breaking -change. A contract that uses any of these new features would be invalid before -this SIP is activated, and valid after it is activated. +Because this SIP extends the `concat` function to accept more than two +arguments and adds new built-in functions (`secp256k1-decompress?`, +`ed25519-verify`, `get-bitcoin-tx-output?`, and `verify-merkle-proof`), it is a +consensus-breaking change. A contract that uses any of these new features would +be invalid before this SIP is activated, and valid after it is activated. All new keywords introduced in Clarity 6 can no longer be used as identifiers in a Clarity 6 smart contract. Smart contracts can continue to be published using From c297d830832bd7347ecc933238ff0938a68ac5f3 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Wed, 24 Jun 2026 10:51:14 -0400 Subject: [PATCH 18/25] Apply Friedger's suggestions --- sips/sip-044/sip-044-clarity6.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index 74819416..5864ef94 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -77,7 +77,7 @@ reported by Clarity developers. Specifically, it makes the following changes: difficult to audit. Additionally, it addresses the need for new functionality, as a result of the -new Bitcoin-staking model, described in SIP-xyz, on which this SIP is a rider: +new Bitcoin-staking model, described in SIP-045, on which this SIP is a rider: 1. **Staking post-conditions** To enhance the security and user-friendliness of staking, this proposal adds two new transaction level post-conditions, which @@ -577,7 +577,7 @@ This SIP builds upon the existing definitions of the Clarity language: - [SIP-039 (Clarity 5)](../sip-039/sip-039-clarity5.md) Parts of this SIP depend upon -[SIP-xyz (Bitcoin Staking)](https://github.com/adriano-stacks/sips/blob/dfaaa4200123374bff84f6a7049d5602bf19c223/sips/sip-xxx/sip-0XX-pox-5-bitcoin-staking.md) +[SIP-045 (Bitcoin Staking)](https://github.com/adriano-stacks/sips/blob/main/sips/sip-xxx/sip-0XX-pox-5-bitcoin-staking.md) and intends to activate together with it. The new transaction-level post-conditions build upon the post-conditions defined From 7f0aa8765043143eec73965cd034953a7ea008c3 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Wed, 24 Jun 2026 11:00:28 -0400 Subject: [PATCH 19/25] Add a few words to address Cyle's comment --- sips/sip-044/sip-044-clarity6.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index 5864ef94..33adf187 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -557,7 +557,8 @@ At the launch of Epoch 2.0, the was deployed to provide a mechanism to change the cost charged for the execution of a contract call without requiring any hard-fork. While the idea was sound, the functionality has not been used in the 8-million+ blocks that have since -been mined. During that time, the community has clarified and successfully +been mined, likely because it requires locking STX, which cannot be stacked. +During that time, the community has clarified and successfully exercised the SIP process several times to make changes to Clarity costs, making this mechanism no longer necessary. By disabling the functionality of the `cost-voting` contract, the code in the `stacks-node` can be made more From 9fd7c03459013ac19fa0d9f0862a7a42274ae406 Mon Sep 17 00:00:00 2001 From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:24:49 -0400 Subject: [PATCH 20/25] chore: move post-conditions to SIP-045 --- sips/sip-044/sip-044-clarity6.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index 33adf187..2c2fbc01 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -34,12 +34,11 @@ more than two arguments, adds new cryptographic built-in functions, adds built-ins for trustlessly verifying Bitcoin transaction outputs on-chain, and adds new Clarity allowances for the PoX-5 staking model. -In addition to the Clarity changes, this SIP introduces new transaction-level -post-conditions for protecting staking state, a mechanism for handling -problematic transactions in blocks, and the deprecation of the long-unused -`cost-voting` contract. These changes are motivated by real-world developer -experience, operational concerns from miners and signers, and the ongoing -evolution of the PoX staking model. +In addition to the Clarity changes, this SIP introduces a mechanism for +handling problematic transactions in blocks, and the deprecation of the +long-unused `cost-voting` contract. These changes are motivated by real-world +developer experience, operational concerns from miners and signers, and the +ongoing evolution of the PoX staking model. # Copyright @@ -75,20 +74,20 @@ reported by Clarity developers. Specifically, it makes the following changes: relayers, or reimplement Bitcoin transaction parsing and merkle-proof verification in user-space Clarity code, which is expensive, error-prone, and difficult to audit. +5. **PoX allowance:** A new in-contract allowance, `with-pox`, is added, for + use in `as-contract?` and `restrict-assets?` expressions. This new allowance + controls whether the protected body is allowed to modify state in the active + PoX contract. This is an addition to the existing `with-stacking`, which is + also renamed to `with-staking`, which allows the body to stake (or update) + a specific amount of STX. Additionally, it addresses the need for new functionality, as a result of the new Bitcoin-staking model, described in SIP-045, on which this SIP is a rider: -1. **Staking post-conditions** To enhance the security and user-friendliness of - staking, this proposal adds two new transaction level post-conditions, which - allow users to ensure that the contract calls they make cannot affect their - staking status without explicit authorization. This applies both to - transaction-level post-conditions and in-contract (`as-contract?` and - `restrict-assets?`) post-conditions. -2. **Problematic transactions** Transactions that are deemed to be problematic +1. **Problematic transactions** Transactions that are deemed to be problematic by agreement between miners and signers should be included in a block, with their fees taken. -3. **Disable `cost-voting`** This contract was originally designed as a +2. **Disable `cost-voting`** This contract was originally designed as a mechanism to allow the cost of a specific function call to be overwritten without the need for a hard-fork. The handling of this contract complicates the code and slows down execution, but it has never been used, so it is From 5f93a834eb427fa48766ce2078c70522966bef8c Mon Sep 17 00:00:00 2001 From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:02:24 -0400 Subject: [PATCH 21/25] chore: move to `Accepted` state Editors have approved and the SIP can move to CAB votes. --- sips/sip-044/sip-044-clarity6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index 2c2fbc01..e79f9028 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -9,7 +9,7 @@ Author(s): - Jeff Bencin - Brice Dobry -Status: Draft +Status: Accepted Consideration: Technical From 437d9125061151d2ca371dee40085bae1f7b0d15 Mon Sep 17 00:00:00 2001 From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com> Date: Tue, 7 Jul 2026 11:27:30 -0400 Subject: [PATCH 22/25] add cost change paragraph --- sips/sip-044/sip-044-clarity6.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index e79f9028..cb8dc35d 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -94,6 +94,10 @@ new Bitcoin-staking model, described in SIP-045, on which this SIP is a rider: better for the network to disable this functionality and continue to make cost changes through the SIP process, which has been working smoothly. +As with every Clarity upgrade, cost computations and budgets are re-evaluated +alongside the language changes. The updated costs take effect when this SIP +activates. + # Specification This SIP requires a hard fork. The changes specified below all activate From 8d7b5c7db47cb1e901f4ab914a0c2aaf4ba1fcc3 Mon Sep 17 00:00:00 2001 From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com> Date: Tue, 7 Jul 2026 11:28:12 -0400 Subject: [PATCH 23/25] chore: formatting --- sips/sip-044/sip-044-clarity6.md | 49 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index cb8dc35d..5f83b6ca 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -2,7 +2,8 @@ SIP Number: 044 -Title: Clarity 6, staking and PoX post-conditions, and removal of the cost-voting contract +Title: Clarity 6, staking and PoX post-conditions, and removal of the +cost-voting contract Author(s): @@ -34,11 +35,11 @@ more than two arguments, adds new cryptographic built-in functions, adds built-ins for trustlessly verifying Bitcoin transaction outputs on-chain, and adds new Clarity allowances for the PoX-5 staking model. -In addition to the Clarity changes, this SIP introduces a mechanism for -handling problematic transactions in blocks, and the deprecation of the -long-unused `cost-voting` contract. These changes are motivated by real-world -developer experience, operational concerns from miners and signers, and the -ongoing evolution of the PoX staking model. +In addition to the Clarity changes, this SIP introduces a mechanism for handling +problematic transactions in blocks, and the deprecation of the long-unused +`cost-voting` contract. These changes are motivated by real-world developer +experience, operational concerns from miners and signers, and the ongoing +evolution of the PoX staking model. # Copyright @@ -74,12 +75,12 @@ reported by Clarity developers. Specifically, it makes the following changes: relayers, or reimplement Bitcoin transaction parsing and merkle-proof verification in user-space Clarity code, which is expensive, error-prone, and difficult to audit. -5. **PoX allowance:** A new in-contract allowance, `with-pox`, is added, for - use in `as-contract?` and `restrict-assets?` expressions. This new allowance +5. **PoX allowance:** A new in-contract allowance, `with-pox`, is added, for use + in `as-contract?` and `restrict-assets?` expressions. This new allowance controls whether the protected body is allowed to modify state in the active PoX contract. This is an addition to the existing `with-stacking`, which is - also renamed to `with-staking`, which allows the body to stake (or update) - a specific amount of STX. + also renamed to `with-staking`, which allows the body to stake (or update) a + specific amount of STX. Additionally, it addresses the need for new functionality, as a result of the new Bitcoin-staking model, described in SIP-045, on which this SIP is a rider: @@ -100,10 +101,10 @@ activates. # Specification -This SIP requires a hard fork. The changes specified below all activate -together at the onset of Stacks Epoch 4.0. New contracts deployed in Epoch 4.0 -will default to Clarity 6, though contract authors can override this by -specifying an earlier version in the deploy transaction. +This SIP requires a hard fork. The changes specified below all activate together +at the onset of Stacks Epoch 4.0. New contracts deployed in Epoch 4.0 will +default to Clarity 6, though contract authors can override this by specifying an +earlier version in the deploy transaction. ## Clarity 6 @@ -561,11 +562,11 @@ was deployed to provide a mechanism to change the cost charged for the execution of a contract call without requiring any hard-fork. While the idea was sound, the functionality has not been used in the 8-million+ blocks that have since been mined, likely because it requires locking STX, which cannot be stacked. -During that time, the community has clarified and successfully -exercised the SIP process several times to make changes to Clarity costs, making -this mechanism no longer necessary. By disabling the functionality of the -`cost-voting` contract, the code in the `stacks-node` can be made more -performant and at the same time, simplified. +During that time, the community has clarified and successfully exercised the SIP +process several times to make changes to Clarity costs, making this mechanism no +longer necessary. By disabling the functionality of the `cost-voting` contract, +the code in the `stacks-node` can be made more performant and at the same time, +simplified. Once Epoch 4.0 activates, the `cost-voting` contract will no longer have any effect on consensus and can be ignored. @@ -589,11 +590,11 @@ in [SIP-005](../sip-005/sip-005-blocks-and-transactions.md). # Backwards Compatibility -Because this SIP extends the `concat` function to accept more than two -arguments and adds new built-in functions (`secp256k1-decompress?`, -`ed25519-verify`, `get-bitcoin-tx-output?`, and `verify-merkle-proof`), it is a -consensus-breaking change. A contract that uses any of these new features would -be invalid before this SIP is activated, and valid after it is activated. +Because this SIP extends the `concat` function to accept more than two arguments +and adds new built-in functions (`secp256k1-decompress?`, `ed25519-verify`, +`get-bitcoin-tx-output?`, and `verify-merkle-proof`), it is a consensus-breaking +change. A contract that uses any of these new features would be invalid before +this SIP is activated, and valid after it is activated. All new keywords introduced in Clarity 6 can no longer be used as identifiers in a Clarity 6 smart contract. Smart contracts can continue to be published using From 93980a2d1d529562669f56e052834116b2c675bf Mon Sep 17 00:00:00 2001 From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com> Date: Fri, 10 Jul 2026 15:45:56 -0400 Subject: [PATCH 24/25] fix: minor updates based on editor review --- sips/sip-044/sip-044-clarity6.md | 57 +++++++++----------------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index 5f83b6ca..9b8f1bb1 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -358,9 +358,9 @@ hashed the transaction. none )) );; Returns (err u0) - (restrict-assets? tx-sender ((with-stacking u1000000000000)) + (restrict-assets? tx-sender ((with-staking u1000000000000)) (try! (contract-call? 'SP000000000000000000002Q6VF78.pox-5 stake - 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.signer u1100000000000 u12 u1000 + 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.signer u1000000000000 u12 u1000 none )) );; Returns (ok true) @@ -394,39 +394,6 @@ hashed the transaction. );; Returns (ok true) ``` -## Staking Post-Conditions - -Built on the same foundations as the new Clarity allowances, `with-staking` and -`with-pox`, with Epoch 4.0, new transaction-level post-conditions will activate -that allow users to protect their STX from unexpected staking changes. Previous -PoX contracts have used the `allow-contract-caller` mechanism to restrict which -contracts are allowed to indirectly call into the PoX contract on behalf of the -`tx-sender`. This improves upon that, by allowing users to protect this behavior -with a post-condition on each transaction. - -### Staking - -A new post-condition with identifier `0x03` specifies that the transaction may -stake STX (or modify existing staking parameters) for the specified principal. -This post-condition specifies a principal, a **fungible condition code** (as -described in [SIP-005](../sip-005/sip-005-blocks-and-transactions.md)), and an -amount. Calls to `stake`, `register-for-bond`, and `stake-update`, will be -evaluated against these post-conditions, and the transaction will be rejected if -the conditions are not met. - -### PoX - -A new post-condition with identifier `0x04` specifies that the transaction may -modify state in the active PoX contract that does not directly change locking -status. This includes calls to `unstake`, `unstake-sbtc`, -`update-bond-registration`, and `announce-l1-early-exit` in the pox-5 contract. -This post-condition specifies a principal and a new **PoX condition code**. A -PoX condition code has the following encodings: - -- `0x30`: "The account must not perform any PoX actions" -- `0x31`: "The account may perform PoX actions" -- `0x32`: "The account must perform a PoX action" - ## Problematic Transactions A Stacks miner needs a mechanism to charge a fee for all transactions for which @@ -583,7 +550,7 @@ This SIP builds upon the existing definitions of the Clarity language: Parts of this SIP depend upon [SIP-045 (Bitcoin Staking)](https://github.com/adriano-stacks/sips/blob/main/sips/sip-xxx/sip-0XX-pox-5-bitcoin-staking.md) -and intends to activate together with it. +and this SIP is intended to activate together with it. The new transaction-level post-conditions build upon the post-conditions defined in [SIP-005](../sip-005/sip-005-blocks-and-transactions.md). @@ -610,8 +577,9 @@ unlocked/liquid STX, or both. In order for this SIP to activate, the following criteria must be met: -- At least 80 million stacked STX must vote, with at least 80% of all stacked - STX committed by voting must be in favor of the proposal (vote "yes"). +- At least 80 million stacked STX must participate in the vote, and at least 80% + of all stacked STX committed by voting must be in favor of the proposal (vote + "yes"). - At least 80% of all liquid STX committed by voting must be in favor of the proposal (vote "yes"). @@ -621,16 +589,21 @@ is determined by a snapshot of the amount of STX (stacked and unstacked) at the block height at which the voting started (preventing the same STX from being transferred between accounts and used to effectively double vote). -Solo stackers only can also vote by sending a bitcoin dust transaction (6000 -sats) to the corresponding bitcoin address. +Solo stackers can also vote by sending a bitcoin dust transaction (6000 sats) to +the corresponding bitcoin address, from their PoX address. | Vote | Bitcoin | Stacks | ASCII Encoding | Msg | | ---- | -------------------------------- | ------------------------------------- | ---------------------- | ---------- | | yes | `11111111111mdWK2VXcrA1f72DmUku` | `SP00000000001WPAWSDEDMQ0B9M6HQVS7Q6` | `7965732d7369702d3434` | yes-sip-44 | | no | `111111111111ACW5wa4RwyfJspnNhu` | `SP000000000006WVSDEDMQ0B9M6K3NTSJK` | `6e6f2d7369702d3434` | no-sip-44 | -If the SIP is approved, a Bitcoin block height will be selected to activate the -new behavior. +Voting will take place over the same voting window as SIP-045, to be identified +during the community review period, following the precedent of SIP-021 and +SIP-029 of finalizing block heights during vote preparation. If the criteria are +not met within that window, the SIP is Rejected. + +If approved, the activation block height will be finalized during vote +preparation, together with SIP-045. # Reference Implementation From 49354957d581fb9aadfedb3c4076f66cbb061604 Mon Sep 17 00:00:00 2001 From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:14:56 -0400 Subject: [PATCH 25/25] add tech cab sign-off --- sips/sip-044/sip-044-clarity6.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sips/sip-044/sip-044-clarity6.md b/sips/sip-044/sip-044-clarity6.md index 9b8f1bb1..74501f64 100644 --- a/sips/sip-044/sip-044-clarity6.md +++ b/sips/sip-044/sip-044-clarity6.md @@ -24,6 +24,8 @@ License: BSD-2-Clause Sign-off: +- Brice Dobry , Interim Chairperson, Technical CAB; [minutes](../../considerations/minutes/technical-cab/2026-07-10-sip-044-and-045.md) + Discussions-To: # Abstract