Skip to content

Commit cc473d6

Browse files
authored
Merge pull request #1440 from psgreco/elem-23.2.7rc5
Prepare 23.2.7rc5
2 parents b74ac92 + 8126f51 commit cc473d6

10 files changed

Lines changed: 243 additions & 278 deletions

SECURITY.md

Lines changed: 61 additions & 274 deletions
Large diffs are not rendered by default.

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ AC_PREREQ([2.69])
22
define(_CLIENT_VERSION_MAJOR, 23)
33
define(_CLIENT_VERSION_MINOR, 2)
44
define(_CLIENT_VERSION_BUILD, 7)
5-
define(_CLIENT_VERSION_RC, 4)
5+
define(_CLIENT_VERSION_RC, 5)
66
define(_CLIENT_VERSION_IS_RELEASE, true)
77
define(_COPYRIGHT_YEAR, 2025)
88
define(_COPYRIGHT_HOLDERS,[The %s developers])

src/blindpsbt.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,13 @@ bool CreateAssetSurjectionProof(std::vector<unsigned char>& output_proof, const
6868

6969
bool VerifyBlindAssetProof(const uint256& asset, const std::vector<unsigned char>& proof, const CConfidentialAsset& conf_asset)
7070
{
71+
if (conf_asset.vchCommitment.size() != CConfidentialAsset::nCommittedSize || proof.empty()) {
72+
return false;
73+
}
7174
secp256k1_surjectionproof surj_proof;
7275
if (secp256k1_surjectionproof_parse(secp256k1_blind_context, &surj_proof, proof.data(), proof.size()) == 0) {
7376
return false;
7477
}
75-
7678
secp256k1_generator blinded_asset_gen;
7779
if (secp256k1_generator_parse(secp256k1_blind_context, &blinded_asset_gen, conf_asset.vchCommitment.data()) == 0) {
7880
return false;

src/confidential_validation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,9 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
390390
}
391391
if (!ptxoutwit)
392392
return false;
393+
if (asset.vchCommitment.size() != CConfidentialAsset::nCommittedSize || ptxoutwit->vchSurjectionproof.empty()) {
394+
return false;
395+
}
393396
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
394397
return false;
395398

src/policy/policy.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ static const unsigned int MAX_STANDARD_SCRIPTSIG_SIZE = 1650;
5555
* standard and should be done with care and ideally rarely. It makes sense to
5656
* only increase the dust limit after prior releases were already not creating
5757
* outputs below the new threshold */
58-
static const unsigned int DUST_RELAY_TX_FEE = 3000;
58+
static const unsigned int DUST_RELAY_TX_FEE = 100;
5959
/**
6060
* Standard script verification flags that standard transactions will comply
6161
* with. However scripts violating these flags may still be present in valid
@@ -89,6 +89,9 @@ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCR
8989
static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE |
9090
LOCKTIME_MEDIAN_TIME_PAST;
9191

92+
// ELEMENTS: keep a copy of the upstream default dust relay fee rate
93+
static const unsigned int DUST_RELAY_TX_FEE_BITCOIN = 3000;
94+
9295
CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee);
9396

9497
bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee);

src/test/transaction_tests.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
794794
CheckIsStandard(t);
795795

796796
// Check dust with default relay fee:
797+
dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE_BITCOIN); // ELEMENTS: use the Bitcoin default dust relay feerate
797798
CAmount nDustThreshold = 182 * dustRelayFee.GetFeePerK() / 1000;
798799
BOOST_CHECK_EQUAL(nDustThreshold, 546);
799800
// dust:
@@ -829,7 +830,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
829830
// not dust:
830831
t.vout[0].nValue = 674;
831832
CheckIsStandard(t);
832-
dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE);
833+
dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE_BITCOIN); // ELEMENTS: use the Bitcoin default dust relay feerate
833834

834835
t.vout[0].scriptPubKey = CScript() << OP_1;
835836
CheckIsNotStandard(t, "scriptpubkey");

test/functional/feature_confidential_transactions.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,44 @@ def test_wallet_recovery(self):
106106
# clean up blind_details
107107
os.remove(file_path)
108108

109+
def test_no_surj(self):
110+
self.generate(self.nodes[0], 1)
111+
112+
tx_hex = self.nodes[0].createrawtransaction([], [{self.nodes[1].getnewaddress(): 1000}])
113+
tx_hex = self.nodes[0].fundrawtransaction(tx_hex)['hex']
114+
tx_hex = self.nodes[0].blindrawtransaction(tx_hex)
115+
# coming from initial free coins: no need to sign
116+
assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True) # tx is ok
117+
118+
# remove a surjection proof from the tx
119+
tx = CTransaction()
120+
tx.deserialize(io.BytesIO(bytes.fromhex(tx_hex)))
121+
tx.wit.vtxoutwit[0].vchSurjectionproof = b''
122+
tx_hex = tx.serialize().hex()
123+
124+
# Both of these make the node crash
125+
assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], False)
126+
assert_raises_rpc_error(-26, "bad-txns-in-ne-out", self.nodes[0].sendrawtransaction, tx_hex)
127+
128+
def test_no_range(self):
129+
self.generate(self.nodes[0], 1)
130+
131+
tx_hex = self.nodes[0].createrawtransaction([], [{self.nodes[1].getnewaddress(): 1000}])
132+
tx_hex = self.nodes[0].fundrawtransaction(tx_hex)['hex']
133+
tx_hex = self.nodes[0].blindrawtransaction(tx_hex)
134+
# coming from initial free coins: no need to sign
135+
assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True) # tx is ok
136+
137+
# remove a surjection proof from the tx
138+
tx = CTransaction()
139+
tx.deserialize(io.BytesIO(bytes.fromhex(tx_hex)))
140+
tx.wit.vtxoutwit[0].vchRangeproof = b''
141+
tx_hex = tx.serialize().hex()
142+
143+
# Both of these make the node crash
144+
assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], False)
145+
assert_raises_rpc_error(-26, "bad-txns-in-ne-out", self.nodes[0].sendrawtransaction, tx_hex)
146+
109147
def test_null_rangeproof_enforcement(self):
110148
self.generate(self.nodes[0], 1)
111149

@@ -160,6 +198,12 @@ def test_null_rangeproof_enforcement(self):
160198

161199
def run_test(self):
162200

201+
print("Testing a transaction with a missing surjection proof")
202+
self.test_no_surj()
203+
204+
print("Testing a transaction with a missing range proof")
205+
self.test_no_range()
206+
163207
print("Testing that null issuances must have null rangeproofs")
164208
self.test_null_rangeproof_enforcement()
165209

test/functional/mempool_accept.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def set_test_params(self):
4646
'-txindex',
4747
'-txindex','-permitbaremultisig=0',
4848
'-multi_data_permitted=1', # Elements test
49+
'-dustrelayfee=0.00003000', # ELEMENTS: use the Bitcoin default dust relay fee rate
4950
]] * self.num_nodes
5051
self.supports_cli = False
5152

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
'wallet_elements_regression_1172.py --legacy-wallet',
114114
'wallet_elements_regression_1259.py --legacy-wallet',
115115
'wallet_elements_21million.py',
116+
'wallet_elements_dust_relay.py',
116117
'feature_trim_headers.py',
117118
# Longest test should go first, to favor running tests in parallel
118119
'wallet_hd.py --legacy-wallet',
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2017-2020 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
from decimal import Decimal
7+
from test_framework.test_framework import BitcoinTestFramework
8+
from test_framework.util import (
9+
assert_equal,
10+
assert_raises_rpc_error,
11+
)
12+
13+
class WalletTest(BitcoinTestFramework):
14+
def set_test_params(self):
15+
self.setup_clean_chain = False
16+
self.num_nodes = 2
17+
args = [
18+
"-blindedaddresses=1",
19+
"-minrelaytxfee=0.00000100",
20+
]
21+
self.extra_args = [
22+
args,
23+
args + ["-dustrelayfee=0.00003000"], # second node uses default upstream dustrelayfee
24+
]
25+
26+
def skip_test_if_missing_module(self):
27+
self.skip_if_no_wallet()
28+
29+
def select_unblinded_utxo(self, node):
30+
for utxo in node.listunspent():
31+
if utxo["amountblinder"] == "00" * 32:
32+
return utxo
33+
else:
34+
continue
35+
raise Exception("no unblinded utxo")
36+
37+
def run_test(self):
38+
assert_equal(self.nodes[0].getbalance(), {'bitcoin': 1250})
39+
assert_equal(self.nodes[1].getbalance(), {'bitcoin': 1250})
40+
41+
addr = self.nodes[0].getnewaddress()
42+
43+
# test dust threshold for upstream dustrelayfee=3sat/vb
44+
# 495 sats should succeed for blinded output value
45+
amt = "0.00000495"
46+
self.nodes[1].sendtoaddress(address=addr, amount=amt)
47+
self.generate(self.nodes[1], 1, sync_fun=self.no_op)
48+
49+
# 494 sats should fail for blinded output value
50+
amt = "0.00000494"
51+
assert_raises_rpc_error(-6, "Transaction amount too small", self.nodes[1].sendtoaddress, address=addr, amount=amt)
52+
53+
addr = self.nodes[1].getnewaddress()
54+
55+
# test dust threshold for elements default dustrelayfee=0.1sat/vb
56+
# 17 sats should succeed for blinded output value
57+
amt = "0.00000017"
58+
self.nodes[0].sendtoaddress(address=addr, amount=amt)
59+
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
60+
61+
# 16 sats should fail for blinded output value
62+
amt = "0.00000016"
63+
assert_raises_rpc_error(-6, "Transaction amount too small", self.nodes[0].sendtoaddress, address=addr, amount=amt)
64+
65+
# a blinded transaction created manually can have an output value as low as 1 sat
66+
addr = self.nodes[1].getnewaddress()
67+
changeaddr = self.nodes[0].getnewaddress()
68+
utxo = self.nodes[0].listunspent()[0]
69+
amt = Decimal("0.00000001")
70+
fee = Decimal("0.00000258")
71+
change = utxo["amount"] - amt - fee
72+
inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}]
73+
outputs = [{addr: amt}, {changeaddr: change}, {"fee": fee}]
74+
raw = self.nodes[0].createrawtransaction(inputs, outputs)
75+
blinded = self.nodes[0].blindrawtransaction(raw)
76+
signed = self.nodes[0].signrawtransactionwithwallet(blinded)
77+
assert signed["complete"]
78+
tx = signed["hex"]
79+
assert self.nodes[1].testmempoolaccept([tx])[0]["allowed"]
80+
assert self.nodes[0].testmempoolaccept([tx])[0]["allowed"]
81+
assert_equal(self.nodes[1].sendrawtransaction(tx), self.nodes[0].sendrawtransaction(tx))
82+
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
83+
84+
# explicit transactions have slightly smaller outputs
85+
# node 0 will accept an output value of 14 sats but node 1 will not
86+
addr = self.nodes[1].getnewaddress(address_type="bech32")
87+
changeaddr = self.nodes[0].getnewaddress(address_type="bech32")
88+
utxo = self.select_unblinded_utxo(self.nodes[0])
89+
amt = Decimal("0.00000014")
90+
fee = Decimal("0.00000258")
91+
change = utxo["amount"] - amt - fee
92+
inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}]
93+
outputs = [{addr: amt}, {changeaddr: change}, {"fee": fee}]
94+
raw = self.nodes[0].createrawtransaction(inputs, outputs)
95+
signed = self.nodes[0].signrawtransactionwithwallet(raw)
96+
assert signed["complete"]
97+
tx = signed["hex"]
98+
assert self.nodes[0].testmempoolaccept([tx])[0]["allowed"]
99+
assert not self.nodes[1].testmempoolaccept([tx])[0]["allowed"]
100+
self.nodes[0].sendrawtransaction(tx)
101+
assert_raises_rpc_error(-26, "dust", self.nodes[1].sendrawtransaction, tx)
102+
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
103+
104+
# neither node will accept an explicit output value of 13 sats
105+
addr = self.nodes[1].getnewaddress(address_type="bech32")
106+
changeaddr = self.nodes[0].getnewaddress(address_type="bech32")
107+
utxo = self.select_unblinded_utxo(self.nodes[0])
108+
amt = Decimal("0.00000013")
109+
fee = Decimal("0.00000258")
110+
change = utxo["amount"] - amt - fee
111+
inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}]
112+
outputs = [{addr: amt}, {changeaddr: change}, {"fee": fee}]
113+
raw = self.nodes[0].createrawtransaction(inputs, outputs)
114+
signed = self.nodes[0].signrawtransactionwithwallet(raw)
115+
assert signed["complete"]
116+
tx = signed["hex"]
117+
assert not self.nodes[0].testmempoolaccept([tx])[0]["allowed"]
118+
assert not self.nodes[1].testmempoolaccept([tx])[0]["allowed"]
119+
assert_raises_rpc_error(-26, "dust", self.nodes[0].sendrawtransaction, tx)
120+
assert_raises_rpc_error(-26, "dust", self.nodes[1].sendrawtransaction, tx)
121+
122+
if __name__ == '__main__':
123+
WalletTest().main()

0 commit comments

Comments
 (0)