feat(psbt): allow UnrestrictedRelayer to bypass change<min_input rule#49
Open
karim-en wants to merge 1 commit into
Open
feat(psbt): allow UnrestrictedRelayer to bypass change<min_input rule#49karim-en wants to merge 1 commit into
karim-en wants to merge 1 commit into
Conversation
frolvanya
approved these changes
May 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lets accounts with the
UnrestrictedRelayerrole submit withdraw PSBTs where a change output is>= min(inputs). The standardchange < smallest_inputrule still applies to everyone else.Motivation
The off-chain consolidation/anchor-fill selector in bridge-sdk-rs#281 intentionally produces withdraws that consume many small UTXOs (fillers) with a single large anchor input, emitting one sizable change output. That's the shape the contract rule blocks: with
min_input = filler_size, the rule would force the change into 100+ sub-min_change_amountpieces — no valid split exists, so consolidation withdraws are unbuildable today.The relaxation only affects a UTXO-management invariant. All value-flow checks remain enforced for every caller:
min_change_amountandmax_change_amountchange_amounts.all(|v| v < max_change_amount) when input_num > change_num(still caps any single change piece)min_btc_gas_fee/max_btc_gas_feeactual_received_amountwindow (max - min_change_amount..max)A misbehaving role holder can fragment / waste UTXOs (each input requires a Chain Signatures MPC sign), not steal funds. This matches the trust model the role already implies —
UnrestrictedRelayeralready gates the bridge's#[trusted_relayer]methods.Changes
contracts/satoshi-bridge/src/psbt.rssigner_is_unrestricted = acl_has_role(UnrestrictedRelayer, signer)check, hoisted out of the per-output loop (one ACL read per call instead of one per change output).change < min_input_amountwhen the signer hasUnrestrictedRelayer.minandsumwith a singlefoldovervutxos.contracts/satoshi-bridge/tests/setup/context.rsbridge_acl_revoke_rolehelper (mirrorsbridge_acl_grant_role).contracts/satoshi-bridge/tests/test_satoshi_bridge.rstest_utxo_passive_managementto revokealice's role, assert the rule still fires with the new message, then re-grant the role and assert the same PSBT succeeds.Why
signer_account_id()In both code paths that reach
check_withdraw_psbt:ft_on_transfer→check_withdraw_psbt: signer is the end user (NEAR forwards the original tx signer across the nBTC → bridge promise chain).predecessoris the nBTC contract, so it would be wrong here.withdraw_rbf→check_withdraw_psbt: signer is the caller, which is alreadyUnrestrictedRelayer-gated via#[trusted_relayer].Behavior note
The
min/sumrefactor changes the empty-vutxoscase frompanic(via.min().unwrap()) to(u128::MAX, 0). The empty case isn't reachable in practice and is still caught downstream by theactual_received_amounts.len() == 1check,gas_fee >= min_btc_gas_fee, andoverflow-checks = trueon thetotal_input - total_outputsubtraction.Test plan
cargo test --features bitcoin -p satoshi-bridge --test test_satoshi_bridge— 15/15 passing, includingtest_utxo_passive_managementexercising both branches.make clippy-bitcoinclean.