Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions doc/release-notes-bip444.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# BIP-0444 Policy Changes (Taproot and Script Limits)

## Summary

This release introduces policy-level enforcement of BIP-0444 to prevent large arbitrary-data inscriptions and reduce UTXO/script bloat. These changes are active immediately as relay/mempool policy defaults; consensus enforcement will follow after BIP8 activation (parameters TBD).

## Policy Changes (Active Now)

### scriptPubKey Limits
- **Non-NULL_DATA scriptPubKey size cap**: Transactions with non-NULL_DATA outputs exceeding 34 bytes are rejected (`scriptpubkey-size-34`).
- **scriptPubKey push length cap**: Any single push operation in a scriptPubKey exceeding 256 bytes is rejected (`scriptpubkey-pushlen`).

### Taproot/Tapscript Limits
- **Control block size cap**: Taproot control blocks exceeding 257 bytes (33-byte base + 7 merkle path nodes) are rejected (`taproot-controlblock-size`).
- **Per-input witness size cap**: Segwit v1 inputs with total witness data exceeding 1024 bytes are rejected (`taproot-perinput-witness`). Configurable via `-v1perinputwitnesslimit` (min 128, max 8192).
- **Tapscript IF ban**: OP_IF and OP_NOTIF opcodes are disallowed in Tapscript leaves (`taproot-if-disallowed`).
- **Tapscript push-only run cap**: Contiguous push-only regions in Tapscript leaves exceeding 256 bytes total payload are rejected (`taproot-pushrun`).
- **Tapscript IF-body cap**: Push-only IF/NOTIF branch bodies exceeding 80 bytes total payload are rejected (`taproot-if-pushonly`).

### Unknown Witness Versions
- **Default reject unknown witness**: The default for `-acceptunknownwitness` is now `false`. Transactions sending to undefined witness program versions are rejected by default unless explicitly allowed via `-acceptunknownwitness=1`.

## Configuration Options

- `-v1perinputwitnesslimit=<n>`: Set maximum total witness bytes per segwit v1 input (default: 1024; min 128, max 8192).
- `-acceptunknownwitness=<bool>`: Allow relay of transactions to unknown/future witness versions (default: 0).

## Rationale

These policy defaults target the common inscription vectors (Taproot witness/script abuse) while preserving legitimate usage:
- Standard P2TR/P2WSH outputs remain valid (≤34 bytes).
- Normal signatures, keys, and small scripts are unaffected.
- Taproot key-path spends are unaffected.
- The limits are conservative and tunable for advanced use cases.

## Deployment

BIP-0444 includes a soft-fork component with BIP8 activation (parameters TBD). Policy enforcement is active immediately; consensus rules will activate after the signaling period and delayed activation height.

For testing on regtest/testnet/signet, use `-vbparams=taproot_script_limits:start:end[:min_activation_height]` to override deployment parameters.

## Compatibility

- **Wallets**: Avoid creating transactions with oversized scriptPubKeys, large push payloads, or Tapscript leaves using OP_IF/NOTIF.
- **Contracts**: Multi-party protocols with large witness data (e.g., complex lightning channels, vaults) may need to tune `-v1perinputwitnesslimit` or split across multiple inputs.
- **Unknown witness versions**: If your application relies on future witness versions, set `-acceptunknownwitness=1`.

## References

- BIP-0444: https://github.com/bitcoin/bips/blob/master/bip-0444.mediawiki (pending merge)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BIP-0444 link shows "(pending merge)" but links to non-existent file (404).

Update when BIP number is assigned or remove until finalized.

- Discussion: https://gnusha.org/pi/bitcoindev/CALeFGL0PDjtRt2rfbY4gTkoc+5oNQ0mn_obraE7PrtHuNYFpQw@mail.gmail.com/T/#mb71350c5dfb119efeb92c5ee738b6c8225bf15b6

1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_
enum DeploymentPos : uint16_t {
DEPLOYMENT_TESTDUMMY,
DEPLOYMENT_TAPROOT, // Deployment of Schnorr/Taproot (BIPs 340-342)
DEPLOYMENT_TAPROOT_SCRIPT_LIMITS, // BIP-0444: Taproot and Script Limits
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
MAX_VERSION_BITS_DEPLOYMENTS
};
Expand Down
4 changes: 4 additions & 0 deletions src/deploymentinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
/*.name =*/ "taproot",
/*.gbt_force =*/ true,
},
{
/*.name =*/ "taproot_script_limits",
/*.gbt_force =*/ true,
},
};

std::string DeploymentName(Consensus::BuriedDeployment dep)
Expand Down
3 changes: 3 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
strprintf("Maximum size of data in data carrier transactions we relay and mine, in bytes (default: %u)",
MAX_OP_RETURN_RELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-v1perinputwitnesslimit", "Set maximum total witness bytes per segwit v1 input for policy (default: 1024; min 128, max 8192)", ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-maxscriptsize", strprintf("Maximum size of scripts (including the entire witness stack) we relay and mine, in bytes (default: %s)", DEFAULT_SCRIPT_SIZE_POLICY_LIMIT), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-maxtxlegacysigops",
strprintf("Maximum number of legacy sigops allowed in transactions we relay and mine, as measured by BIP54 (default: %s)",
Expand Down Expand Up @@ -866,6 +867,8 @@ void InitParameterInteraction(ArgsManager& args)
args.SoftSetArg("-blockmaxweight", "4000000");
}


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty lines added without purpose. Remove.


// when specifying an explicit binding address, you want to listen on it
// even when -connect or -proxy is specified
if (!args.GetArgs("-bind").empty()) {
Expand Down
24 changes: 24 additions & 0 deletions src/kernel/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ class CMainParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 709632; // Approximately November 12th, 2021

// Deployment of BIP-0444 Taproot and Script Limits (parameters TBD)
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].bit = 3; // TBD: select final bit
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; // set via -vbparams or when finalized
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; // set via -vbparams or when finalized
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].min_activation_height = 0; // set to Timeout+period when finalized

consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000000dee8e2a309ad8a9820433c68"};
consensus.defaultAssumeValid = uint256{"00000000000000000000611fd22f2df7c8fbd0688745c3a6c3bb5109cc2a12cb"}; // 912683

Expand Down Expand Up @@ -280,6 +286,12 @@ class CTestNetParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay

// BIP-0444 Taproot and Script Limits (parameters TBD)
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].bit = 3; // TBD
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].min_activation_height = 0;

consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000000000015f5e0c9f13455b0eb17"};
consensus.defaultAssumeValid = uint256{"00000000000003fc7967410ba2d0a8a8d50daedc318d43e8baf1a9782c236a57"}; // 3974606

Expand Down Expand Up @@ -379,6 +391,12 @@ class CTestNet4Params : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay

// BIP-0444 Taproot and Script Limits (parameters TBD)
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].bit = 3; // TBD
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; // ALWAYS_ACTIVE for easy signet testing
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].min_activation_height = 0;

consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000000000001d6dce8651b6094e4c1"};
consensus.defaultAssumeValid = uint256{"0000000000003ed4f08dbdf6f7d6b271a6bcffce25675cb40aa9fa43179a89f3"}; // 72600

Expand Down Expand Up @@ -517,6 +535,12 @@ class SigNetParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay

// BIP-0444 Taproot and Script Limits (parameters overridable via -vbparams)
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].bit = 3; // TBD
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT_SCRIPT_LIMITS].min_activation_height = 0;

// message start is defined as the first 4 bytes of the sha256d of the block script
HashWriter h{};
h << consensus.signet_challenge;
Expand Down
4 changes: 3 additions & 1 deletion src/kernel/mempool_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ static constexpr bool DEFAULT_ACCEPT_NON_STD_DATACARRIER{false};
/** Default for -acceptnonstdtxn */
static constexpr bool DEFAULT_ACCEPT_NON_STD_TXN{false};
/** Default for -acceptunknownwitness */
static constexpr bool DEFAULT_ACCEPTUNKNOWNWITNESS{true};
static constexpr bool DEFAULT_ACCEPTUNKNOWNWITNESS{false};

namespace kernel {
/**
Expand Down Expand Up @@ -95,6 +95,8 @@ struct MemPoolOptions {
bool permitephemeral_send{DEFAULT_PERMITEPHEMERAL_SEND};
bool permitephemeral_dust{DEFAULT_PERMITEPHEMERAL_DUST};
bool persist_v1_dat{DEFAULT_PERSIST_V1_DAT};
// Policy to limit inscription-like data in witnesses/tapscripts

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"inscription-like data" is vague.

Be more specific: "Total witness bytes per v1 input to prevent witness stuffing/inscription abuse."

unsigned int policy_max_v1_perinput_witness{1024};
MemPoolLimits limits{};

ValidationSignals* signals{nullptr};
Expand Down
9 changes: 9 additions & 0 deletions src/node/mempool_args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,15 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP

mempool_opts.persist_v1_dat = argsman.GetBoolArg("-persistmempoolv1", mempool_opts.persist_v1_dat);

// Policy flags to constrain per-input witness size for segwit v1
if (auto lim = argsman.GetIntArg("-v1perinputwitnesslimit")) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User sets -v1perinputwitnesslimit=50, gets 128 with no warning.

Should log when clamping occurs or return error for out-of-range values.

// Enforce reasonable bounds to avoid footguns
unsigned val = *lim;
if (val < 128) val = 128;
if (val > 8192) val = 8192;
mempool_opts.policy_max_v1_perinput_witness = val;
}

ApplyArgsManOptions(argsman, mempool_opts.limits);

return {};
Expand Down
Loading