Skip to content

Commit 35bdef3

Browse files
authored
Merge pull request #143 from instagibbs/explicit0
fixup disallowing 0-value spendable outputs
2 parents 3dfbd20 + c44b979 commit 35bdef3

9 files changed

Lines changed: 125 additions & 15 deletions

File tree

qa/rpc-tests/getchaintips.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ def run_test (self):
2727
# Split the network and build two chains of different lengths.
2828
self.split_network ()
2929
self.nodes[0].generate(10)
30+
# Need a send to split the chains since empty blocks are identical
31+
self.nodes[2].sendtoaddress(self.nodes[2].getnewaddress(), 1)
3032
self.nodes[2].generate(20)
3133
self.sync_all ()
3234

qa/rpc-tests/wallet.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@ def run_test (self):
101101
assert_equal(self.nodes[0].getbalance("", 0, False, "bitcoin"), 21000000-21)
102102
assert_equal(self.nodes[2].getbalance("", 0, False, "bitcoin"), 21)
103103

104-
# Node0 should have three non-zero unspent outputs and 101 from generate.
104+
# Node0 should have three spendable outputs since 0-value coinbase outputs will be OP_RETURN.
105105
# Create a couple of transactions to send them to node2, submit them through
106106
# node1, and make sure both node0 and node2 pick them up properly:
107107
node0utxos = self.nodes[0].listunspent(1, 9999999, [], "bitcoin")
108-
assert_equal(len(node0utxos), 104)
108+
assert_equal(len(node0utxos), 3)
109109

110110
# create both transactions
111111
txns_to_send = []

src/Makefile.test.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ BITCOIN_TESTS =\
9494
test/versionbits_tests.cpp \
9595
test/uint256_tests.cpp \
9696
test/univalue_tests.cpp \
97-
test/util_tests.cpp
97+
test/util_tests.cpp \
98+
test/verify_amounts_tests.cpp
9899

99100
if ENABLE_WALLET
100101
BITCOIN_TESTS += \

src/blind.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,11 @@ void BlindAsset(CConfidentialAsset& confAsset, secp256k1_generator& gen, const C
164164
assert(ret != 0);
165165
}
166166

167-
void CreateValueCommitment(CConfidentialValue& confValue, secp256k1_pedersen_commitment& commit, const std::vector<unsigned char*> blindptrs, const secp256k1_generator& gen, const CAmount amount)
167+
void CreateValueCommitment(CConfidentialValue& confValue, secp256k1_pedersen_commitment& commit, const unsigned char* blindptr, const secp256k1_generator& gen, const CAmount amount)
168168
{
169169
int ret;
170170
confValue.vchCommitment.resize(CConfidentialValue::nCommittedSize);
171-
ret = secp256k1_pedersen_commit(secp256k1_blind_context, &commit, (unsigned char*)blindptrs.back(), amount, &gen);
171+
ret = secp256k1_pedersen_commit(secp256k1_blind_context, &commit, blindptr, amount, &gen);
172172
assert(ret != 0);
173173
secp256k1_pedersen_commitment_serialize(secp256k1_blind_context, &confValue.vchCommitment[0], &commit);
174174
assert(confValue.IsValid());
@@ -416,7 +416,7 @@ int BlindTransaction(std::vector<uint256 >& input_blinding_factors, const std::v
416416
BlindAsset(confAsset, gen, asset, assetblindptrs.back());
417417

418418
// Create value commitment
419-
CreateValueCommitment(confValue, commit, blindptrs, gen, amount);
419+
CreateValueCommitment(confValue, commit, blindptrs.back(), gen, amount);
420420

421421
// nonce should just be blinding key
422422
uint256 nonce = nPseudo ? uint256(std::vector<unsigned char>(vBlindIssuanceToken[nIn].begin(), vBlindIssuanceToken[nIn].end())) : uint256(std::vector<unsigned char>(vBlindIssuanceAsset[nIn].begin(), vBlindIssuanceAsset[nIn].end()));
@@ -485,7 +485,7 @@ int BlindTransaction(std::vector<uint256 >& input_blinding_factors, const std::v
485485
BlindAsset(confAsset, gen, asset, assetblindptrs.back());
486486

487487
// Create value commitment
488-
CreateValueCommitment(confValue, commit, blindptrs, gen, amount);
488+
CreateValueCommitment(confValue, commit, blindptrs.back(), gen, amount);
489489

490490
// Generate nonce for rewind by owner
491491
uint256 nonce = GenerateOutputRangeproofNonce(out, output_pubkeys[nOut]);

src/blind.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ uint256 GenerateOutputRangeproofNonce(CTxOut& out, const CPubKey output_pubkey);
1717

1818
void BlindAsset(CConfidentialAsset& confAsset, secp256k1_generator& gen, const CAsset& asset, const unsigned char* assetblindptr);
1919

20-
void CreateValueCommitment(CConfidentialValue& value, secp256k1_pedersen_commitment& commit, const std::vector<unsigned char*> blindptrs, const secp256k1_generator& gen, const CAmount amount);
20+
void CreateValueCommitment(CConfidentialValue& value, secp256k1_pedersen_commitment& commit, const unsigned char* blindptr, const secp256k1_generator& gen, const CAmount amount);
2121

2222
/*
2323
* blinding_key is used to create the nonce to rewind the rangeproof in conjunction with the nNonce commitment. In the case of a 0-length nNonce, the blinding key is directly used as the nonce.

src/main.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,8 +1299,7 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve
12991299
if (!MoneyRange(val.GetAmount()))
13001300
return false;
13011301

1302-
if (val.GetAmount() == 0)
1303-
continue;
1302+
assert(val.GetAmount() != 0);
13041303

13051304
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, val.GetAmount(), &gen) != 1)
13061305
return false;
@@ -1474,8 +1473,14 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve
14741473
if (!MoneyRange(val.GetAmount()))
14751474
return false;
14761475

1477-
if (val.GetAmount() == 0)
1478-
continue;
1476+
if (val.GetAmount() == 0) {
1477+
if (tx.vout[i].scriptPubKey.IsUnspendable()) {
1478+
continue;
1479+
} else {
1480+
// No spendable 0-value outputs
1481+
return false;
1482+
}
1483+
}
14791484

14801485
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, val.GetAmount(), &gen) != 1)
14811486
return false;
@@ -1559,9 +1564,14 @@ bool VerifyCoinbaseAmount(const CTransaction& tx, const CAmountMap& mapFees)
15591564
assert(tx.IsCoinBase());
15601565
CAmountMap remaining = mapFees;
15611566
for (unsigned int i = 0; i < tx.vout.size(); i++) {
1562-
if (!tx.vout[i].nValue.IsExplicit() || !tx.vout[i].nAsset.IsExplicit())
1567+
const CTxOut& out = tx.vout[i];
1568+
if (!out.nValue.IsExplicit() || !out.nAsset.IsExplicit()) {
1569+
return false;
1570+
}
1571+
if (!MoneyRange(out.nValue.GetAmount()) || (out.nValue.GetAmount() == 0 && !out.scriptPubKey.IsUnspendable())) {
15631572
return false;
1564-
remaining[tx.vout[i].nAsset.GetAsset()] -= tx.vout[i].nValue.GetAmount();
1573+
}
1574+
remaining[out.nAsset.GetAsset()] -= out.nValue.GetAmount();
15651575
}
15661576
return MoneyRange(remaining);
15671577
}

src/miner.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ CBlockTemplate* BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn)
175175
coinbaseTx.vin.resize(1);
176176
coinbaseTx.vin[0].prevout.SetNull();
177177
coinbaseTx.vout.resize(1);
178-
coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
178+
coinbaseTx.vout[0].scriptPubKey = nFees ? scriptPubKeyIn : CScript() << OP_RETURN;
179179
coinbaseTx.vout[0].nValue = nFees;
180180
coinbaseTx.vout[0].nAsset = policyAsset;
181181
coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;

src/test/blind_tests.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,10 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test)
337337
BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp) == 4);
338338
BOOST_CHECK(VerifyAmounts(cache, txtemp));
339339

340+
// Transaction may not have spendable 0-value output
341+
txtemp.vout.push_back(CTxOut(CAsset(), 0, CScript() << OP_TRUE));
342+
BOOST_CHECK(!VerifyAmounts(cache, txtemp));
343+
340344
// Create imbalance by removing fees, should still be able to blind
341345
txtemp = tx5;
342346
txtemp.vout.resize(5);

src/test/verify_amounts_tests.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) 2017-2017 Blockstream Inc.
2+
// Distributed under the MIT/X11 software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "arith_uint256.h"
6+
#include "blind.h"
7+
#include "uint256.h"
8+
#include "main.h"
9+
10+
#include "test/test_bitcoin.h"
11+
12+
#include <boost/test/unit_test.hpp>
13+
14+
15+
BOOST_FIXTURE_TEST_SUITE(amount_tests, TestingSetup)
16+
17+
BOOST_AUTO_TEST_CASE(verify_coinbase_test)
18+
{
19+
// Generate some asset types and feemap
20+
std::vector<CAsset> types;
21+
CAmountMap mapFees;
22+
for (unsigned int i = 1; i < 4; i++) {
23+
types.emplace_back(GetRandHash());
24+
mapFees[types.back()] = i;
25+
}
26+
27+
// Make coinbase transaction
28+
CMutableTransaction tx;
29+
tx.vin.push_back(CTxIn(COutPoint(), CScript(), 0));
30+
31+
// Blank amount check
32+
BOOST_CHECK(VerifyCoinbaseAmount(tx, CAmountMap()));
33+
34+
// Check against non-null fee, surplus is ok
35+
BOOST_CHECK(VerifyCoinbaseAmount(tx, mapFees));
36+
37+
// Add non-fee outputs to coinbase
38+
for (unsigned int i = 1; i < 4; i++) {
39+
BOOST_CHECK(VerifyCoinbaseAmount(tx, mapFees));
40+
tx.vout.push_back(CTxOut(types[i-1], i, CScript() << OP_TRUE));
41+
}
42+
// All outputs added, should still balance
43+
BOOST_CHECK(VerifyCoinbaseAmount(tx, mapFees));
44+
45+
// Adding 0-value unspendable output should not change balance
46+
tx.vout.push_back(CTxOut(types.back(), 0, CScript() << OP_RETURN));
47+
BOOST_CHECK(VerifyCoinbaseAmount(tx, mapFees));
48+
49+
// But you cannot add spendable 0-value output
50+
tx.vout.push_back(CTxOut(types.back(), 0, CScript() << OP_TRUE));
51+
BOOST_CHECK(!VerifyCoinbaseAmount(tx, mapFees));
52+
tx.vout.pop_back();
53+
BOOST_CHECK(VerifyCoinbaseAmount(tx, mapFees));
54+
55+
// Adding values outside MAX_MONEY also causes failure
56+
mapFees[CAsset()] = MAX_MONEY+1;
57+
tx.vout.push_back(CTxOut(CAsset(), MAX_MONEY+1, CScript() << OP_RETURN));
58+
BOOST_CHECK(!VerifyCoinbaseAmount(tx, mapFees));
59+
tx.vout.pop_back();
60+
mapFees[CAsset()] = 0;
61+
BOOST_CHECK(VerifyCoinbaseAmount(tx, mapFees));
62+
63+
// Change the unspendable 0-value output to have asset commitment
64+
secp256k1_generator gen;
65+
const unsigned char blind[32] = {0};
66+
CAsset asset = tx.vout.back().nAsset.GetAsset();
67+
68+
BlindAsset(tx.vout.back().nAsset, gen, asset, blind);
69+
70+
BOOST_CHECK(!VerifyCoinbaseAmount(tx, mapFees));
71+
tx.vout.back().nAsset = types.back();
72+
BOOST_CHECK(VerifyCoinbaseAmount(tx, mapFees));
73+
74+
// Change the same output's value to an unblinded commitment to non-zero value (unblinded zero is blank commitment)
75+
secp256k1_pedersen_commitment commit;
76+
CreateValueCommitment(tx.vout.back().nValue, commit, blind, gen, 1);
77+
BOOST_CHECK(!VerifyCoinbaseAmount(tx, mapFees));
78+
79+
tx.vout.pop_back();
80+
BOOST_CHECK(VerifyCoinbaseAmount(tx, mapFees));
81+
82+
// Negative output
83+
tx.vout.push_back(CTxOut(CAsset(), -1, CScript() << OP_RETURN));
84+
BOOST_CHECK(!VerifyCoinbaseAmount(tx, mapFees));
85+
tx.vout.pop_back();
86+
87+
// Transaction claiming too much fees
88+
tx.vout.push_back(CTxOut(CAsset(), 1, CScript() << OP_RETURN));
89+
BOOST_CHECK(!VerifyCoinbaseAmount(tx, mapFees));
90+
tx.vout.pop_back();
91+
}
92+
93+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)