diff --git a/src/blindpsbt.cpp b/src/blindpsbt.cpp index fde37a5dcf..97404a4b01 100644 --- a/src/blindpsbt.cpp +++ b/src/blindpsbt.cpp @@ -68,11 +68,13 @@ bool CreateAssetSurjectionProof(std::vector& output_proof, const bool VerifyBlindAssetProof(const uint256& asset, const std::vector& proof, const CConfidentialAsset& conf_asset) { + if (conf_asset.vchCommitment.size() != CConfidentialAsset::nCommittedSize || proof.empty()) { + return false; + } secp256k1_surjectionproof surj_proof; if (secp256k1_surjectionproof_parse(secp256k1_blind_context, &surj_proof, proof.data(), proof.size()) == 0) { return false; } - secp256k1_generator blinded_asset_gen; if (secp256k1_generator_parse(secp256k1_blind_context, &blinded_asset_gen, conf_asset.vchCommitment.data()) == 0) { return false; diff --git a/src/confidential_validation.cpp b/src/confidential_validation.cpp index efc7b753c4..62190a92fd 100644 --- a/src/confidential_validation.cpp +++ b/src/confidential_validation.cpp @@ -390,6 +390,9 @@ bool VerifyAmounts(const std::vector& inputs, const CTransaction& tx, st } if (!ptxoutwit) return false; + if (asset.vchCommitment.size() != CConfidentialAsset::nCommittedSize || ptxoutwit->vchSurjectionproof.empty()) { + return false; + } if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1) return false; diff --git a/test/functional/feature_confidential_transactions.py b/test/functional/feature_confidential_transactions.py index 749c4daae2..62692570b7 100755 --- a/test/functional/feature_confidential_transactions.py +++ b/test/functional/feature_confidential_transactions.py @@ -106,6 +106,44 @@ def test_wallet_recovery(self): # clean up blind_details os.remove(file_path) + def test_no_surj(self): + self.generate(self.nodes[0], 1) + + tx_hex = self.nodes[0].createrawtransaction([], [{self.nodes[1].getnewaddress(): 1000}]) + tx_hex = self.nodes[0].fundrawtransaction(tx_hex)['hex'] + tx_hex = self.nodes[0].blindrawtransaction(tx_hex) + # coming from initial free coins: no need to sign + assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True) # tx is ok + + # remove a surjection proof from the tx + tx = CTransaction() + tx.deserialize(io.BytesIO(bytes.fromhex(tx_hex))) + tx.wit.vtxoutwit[0].vchSurjectionproof = b'' + tx_hex = tx.serialize().hex() + + # Both of these make the node crash + assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], False) + assert_raises_rpc_error(-26, "bad-txns-in-ne-out", self.nodes[0].sendrawtransaction, tx_hex) + + def test_no_range(self): + self.generate(self.nodes[0], 1) + + tx_hex = self.nodes[0].createrawtransaction([], [{self.nodes[1].getnewaddress(): 1000}]) + tx_hex = self.nodes[0].fundrawtransaction(tx_hex)['hex'] + tx_hex = self.nodes[0].blindrawtransaction(tx_hex) + # coming from initial free coins: no need to sign + assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True) # tx is ok + + # remove a surjection proof from the tx + tx = CTransaction() + tx.deserialize(io.BytesIO(bytes.fromhex(tx_hex))) + tx.wit.vtxoutwit[0].vchRangeproof = b'' + tx_hex = tx.serialize().hex() + + # Both of these make the node crash + assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], False) + assert_raises_rpc_error(-26, "bad-txns-in-ne-out", self.nodes[0].sendrawtransaction, tx_hex) + def test_null_rangeproof_enforcement(self): self.generate(self.nodes[0], 1) @@ -160,6 +198,12 @@ def test_null_rangeproof_enforcement(self): def run_test(self): + print("Testing a transaction with a missing surjection proof") + self.test_no_surj() + + print("Testing a transaction with a missing range proof") + self.test_no_range() + print("Testing that null issuances must have null rangeproofs") self.test_null_rangeproof_enforcement()