PSBT for Confidential Assets

This commit is contained in:
Glenn Willen 2019-05-01 18:28:10 -07:00 committed by Steven Roose
parent 059b90f0e6
commit 16beefa2d2
No known key found for this signature in database
GPG key ID: 2F2A88D7F8D68E87
20 changed files with 1166 additions and 227 deletions

View file

@ -39,10 +39,6 @@ bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);
bool ParseHashStr(const std::string& strHex, uint256& result);
std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);
//! Decode a base64ed PSBT into a PartiallySignedTransaction
NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);
//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction
NODISCARD bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error);
int ParseSighashString(const UniValue& sighash);
// core_write.cpp

View file

@ -177,33 +177,6 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
return true;
}
bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
{
bool invalid;
std::string tx_data = DecodeBase64(base64_tx, &invalid);
if (invalid) {
error = "invalid base64";
return false;
}
return DecodeRawPSBT(psbt, tx_data, error);
}
bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error)
{
CDataStream ss_data(tx_data.data(), tx_data.data() + tx_data.size(), SER_NETWORK, PROTOCOL_VERSION);
try {
ss_data >> psbt;
if (!ss_data.empty()) {
error = "extra data after PSBT";
return false;
}
} catch (const std::exception& e) {
error = e.what();
return false;
}
return true;
}
bool ParseHashStr(const std::string& strHex, uint256& result)
{
if ((strHex.size() != 64) || !IsHex(strHex))

View file

@ -9,6 +9,7 @@
#include <issuance.h>
#include <key_io.h>
#include <script/script.h>
#include <script/sign.h>
#include <script/standard.h>
#include <serialize.h>
#include <streams.h>
@ -327,15 +328,21 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
uint64_t minv;
uint64_t maxv;
const CTxOutWitness* ptxoutwit = tx.witness.vtxoutwit.size() <= i? NULL: &tx.witness.vtxoutwit[i];
if (ptxoutwit && secp256k1_rangeproof_info(secp256k1_blind_context, &exp, &mantissa, &minv, &maxv, &ptxoutwit->vchRangeproof[0], ptxoutwit->vchRangeproof.size())) {
if (exp == -1) {
out.pushKV("value", ValueFromAmount((CAmount)minv));
} else {
out.pushKV("value-minimum", ValueFromAmount((CAmount)minv));
out.pushKV("value-maximum", ValueFromAmount((CAmount)maxv));
if (ptxoutwit) {
if (ptxoutwit->vchRangeproof.size() && secp256k1_rangeproof_info(secp256k1_blind_context, &exp, &mantissa, &minv, &maxv, &ptxoutwit->vchRangeproof[0], ptxoutwit->vchRangeproof.size())) {
if (exp == -1) {
out.pushKV("value", ValueFromAmount((CAmount)minv));
} else {
out.pushKV("value-minimum", ValueFromAmount((CAmount)minv));
out.pushKV("value-maximum", ValueFromAmount((CAmount)maxv));
}
out.pushKV("ct-exponent", exp);
out.pushKV("ct-bits", mantissa);
}
if (ptxoutwit->vchSurjectionproof.size()) {
out.pushKV("surjectionproof", HexStr(ptxoutwit->vchSurjectionproof));
}
out.pushKV("ct-exponent", exp);
out.pushKV("ct-bits", mantissa);
}
out.pushKV("valuecommitment", txout.nValue.GetHex());
}

View file

@ -33,6 +33,12 @@ std::string TransactionErrorString(const TransactionError err)
return "PSBTs not compatible (different transactions)";
case TransactionError::SIGHASH_MISMATCH:
return "Specified sighash value does not match existing value";
case TransactionError::BLINDING_REQUIRED:
return "Transaction is not yet fully blinded";
case TransactionError::VALUE_IMBALANCE:
return "Transaction values or blinders are not balanced";
case TransactionError::UTXOS_MISSING_BALANCE_CHECK:
return "Missing UTXOs that are needed to check transaction balance";
// no default case, so the compiler can warn about missing cases
}
assert(false);

View file

@ -19,6 +19,9 @@ enum class TransactionError {
INVALID_PSBT,
PSBT_MISMATCH,
SIGHASH_MISMATCH,
BLINDING_REQUIRED,
VALUE_IMBALANCE,
UTXOS_MISSING_BALANCE_CHECK,
};
std::string TransactionErrorString(const TransactionError error);

View file

@ -4,6 +4,7 @@
#include <psbt.h>
#include <util/strencodings.h>
#include <confidential_validation.h>
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
{
@ -152,6 +153,11 @@ void PSBTInput::Merge(const PSBTInput& input)
if (witness_script.empty() && !input.witness_script.empty()) witness_script = input.witness_script;
if (final_script_sig.empty() && !input.final_script_sig.empty()) final_script_sig = input.final_script_sig;
if (final_script_witness.IsNull() && !input.final_script_witness.IsNull()) final_script_witness = input.final_script_witness;
if (!value && input.value) value = input.value;
if (value_blinding_factor.IsNull() && !input.value_blinding_factor.IsNull()) value_blinding_factor = input.value_blinding_factor;
if (asset.IsNull() && !input.asset.IsNull()) asset = input.asset;
if (asset_blinding_factor.IsNull() && !input.asset_blinding_factor.IsNull()) asset_blinding_factor = input.asset_blinding_factor;
}
bool PSBTInput::IsSane() const
@ -204,6 +210,15 @@ void PSBTOutput::Merge(const PSBTOutput& output)
if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
if (!blinding_pubkey.IsValid() && output.blinding_pubkey.IsValid()) blinding_pubkey = output.blinding_pubkey;
if (value_commitment.IsNull() && !output.value_commitment.IsNull()) value_commitment = output.value_commitment;
if (value_blinding_factor.IsNull() && !output.value_blinding_factor.IsNull()) value_blinding_factor = output.value_blinding_factor;
if (asset_commitment.IsNull() && !output.asset_commitment.IsNull()) asset_commitment = output.asset_commitment;
if (asset_blinding_factor.IsNull() && !output.asset_blinding_factor.IsNull()) asset_blinding_factor = output.asset_blinding_factor;
if (nonce_commitment.IsNull() && !output.nonce_commitment.IsNull()) nonce_commitment = output.nonce_commitment;
if (range_proof.empty() && !output.range_proof.empty()) range_proof = output.range_proof;
if (surjection_proof.empty() && !output.surjection_proof.empty()) surjection_proof = output.surjection_proof;
}
bool PSBTInputSigned(PSBTInput& input)
{
@ -223,7 +238,7 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
SignatureData sigdata;
input.FillSignatureData(sigdata);
// Get UTXO
// Get UTXO for this input
bool require_witness_sig = false;
CTxOut utxo;
@ -302,10 +317,35 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
}
result = *psbtx.tx;
result.witness.vtxinwit.resize(result.vin.size());
for (unsigned int i = 0; i < result.vin.size(); ++i) {
result.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
result.witness.vtxinwit[i].scriptWitness = psbtx.inputs[i].final_script_witness;
}
result.witness.vtxoutwit.resize(result.vout.size());
for (unsigned int i = 0; i < result.vout.size(); ++i) {
PSBTOutput& output = psbtx.outputs.at(i);
CTxOut& out = result.vout[i];
CTxOutWitness& outwit = result.witness.vtxoutwit[i];
if (!output.value_commitment.IsNull()) {
out.nValue = output.value_commitment;
}
if (!output.asset_commitment.IsNull()) {
out.nAsset = output.asset_commitment;
}
if (!output.nonce_commitment.IsNull()) {
out.nNonce = output.nonce_commitment;
}
if (!output.range_proof.empty()) {
outwit.vchRangeproof = output.range_proof;
}
if (!output.surjection_proof.empty()) {
outwit.vchSurjectionproof = output.surjection_proof;
}
}
return true;
}
@ -325,3 +365,38 @@ TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector
return TransactionError::OK;
}
std::string EncodePSBT(const PartiallySignedTransaction& psbt)
{
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbt;
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
}
bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
{
bool invalid;
std::string tx_data = DecodeBase64(base64_tx, &invalid);
if (invalid) {
error = "invalid base64";
return false;
}
return DecodeRawPSBT(psbt, tx_data, error);
}
bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error)
{
CDataStream ss_data(tx_data.data(), tx_data.data() + tx_data.size(), SER_NETWORK, PROTOCOL_VERSION);
try {
ss_data >> psbt;
if (!ss_data.empty()) {
error = "extra data after PSBT";
return false;
}
} catch (const std::exception& e) {
error = e.what();
return false;
}
return true;
}

View file

@ -27,11 +27,26 @@ static constexpr uint8_t PSBT_IN_WITNESSSCRIPT = 0x05;
static constexpr uint8_t PSBT_IN_BIP32_DERIVATION = 0x06;
static constexpr uint8_t PSBT_IN_SCRIPTSIG = 0x07;
static constexpr uint8_t PSBT_IN_SCRIPTWITNESS = 0x08;
// Confidential Assets stuff (private use area)
static constexpr uint8_t PSBT_IN_VALUE = 0xf0;
static constexpr uint8_t PSBT_IN_VALUE_BLINDER = 0xf1;
static constexpr uint8_t PSBT_IN_ASSET = 0xf2;
static constexpr uint8_t PSBT_IN_ASSET_BLINDER = 0xf3;
// Output types
static constexpr uint8_t PSBT_OUT_REDEEMSCRIPT = 0x00;
static constexpr uint8_t PSBT_OUT_WITNESSSCRIPT = 0x01;
static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02;
// Confidential Assets stuff (private use area)
static constexpr uint8_t PSBT_OUT_VALUE_COMMITMENT = 0xf0;
static constexpr uint8_t PSBT_OUT_VALUE_BLINDER = 0xf1;
static constexpr uint8_t PSBT_OUT_ASSET_COMMITMENT = 0xf2;
static constexpr uint8_t PSBT_OUT_ASSET_BLINDER = 0xf3;
static constexpr uint8_t PSBT_OUT_RANGE_PROOF = 0xf4;
static constexpr uint8_t PSBT_OUT_SURJECTION_PROOF = 0xf5;
static constexpr uint8_t PSBT_OUT_BLINDING_PUBKEY = 0xf6;
static constexpr uint8_t PSBT_OUT_NONCE_COMMITMENT = 0xf7;
// The separator is 0x00. Reading this in means that the unserializer can interpret it
// as a 0 length key which indicates that this is the separator. The separator has no value.
@ -51,6 +66,11 @@ struct PSBTInput
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
int sighash_type = 0;
boost::optional<CAmount> value;
uint256 value_blinding_factor;
CAsset asset;
uint256 asset_blinding_factor;
bool IsNull() const;
void FillSignatureData(SignatureData& sigdata) const;
void FromSignatureData(const SignatureData& sigdata);
@ -100,6 +120,27 @@ struct PSBTInput
SerializeHDKeypaths(s, hd_keypaths, PSBT_IN_BIP32_DERIVATION);
}
// Write the Confidential Assets blinding data
if (value) {
SerializeToVector(s, PSBT_IN_VALUE);
SerializeToVector(s, *value);
}
if (!value_blinding_factor.IsNull()) {
SerializeToVector(s, PSBT_IN_VALUE_BLINDER);
SerializeToVector(s, value_blinding_factor);
}
if (!asset.IsNull()) {
SerializeToVector(s, PSBT_IN_ASSET);
SerializeToVector(s, asset);
}
if (!asset_blinding_factor.IsNull()) {
SerializeToVector(s, PSBT_IN_ASSET_BLINDER);
SerializeToVector(s, asset_blinding_factor);
}
// Write script sig
if (!final_script_sig.empty()) {
SerializeToVector(s, PSBT_IN_SCRIPTSIG);
@ -238,6 +279,48 @@ struct PSBTInput
UnserializeFromVector(s, final_script_witness.stack);
break;
}
case PSBT_IN_VALUE:
{
if (value != boost::none) {
throw std::ios_base::failure("Duplicate Key, input value already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Final value key is more than one byte type");
}
CAmount amt;
UnserializeFromVector(s, amt);
value = amt;
break;
}
case PSBT_IN_VALUE_BLINDER:
{
if (!value_blinding_factor.IsNull()) {
throw std::ios_base::failure("Duplicate Key, input value_blinding_factor already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Final value_blinding_factor key is more than one byte type");
}
UnserializeFromVector(s, value_blinding_factor);
break;
}
case PSBT_IN_ASSET:
{
if (!asset.IsNull()) {
throw std::ios_base::failure("Duplicate Key, input asset already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Final asset key is more than one byte type");
}
UnserializeFromVector(s, asset);
break;
}
case PSBT_IN_ASSET_BLINDER:
{
if (!asset_blinding_factor.IsNull()) {
throw std::ios_base::failure("Duplicate Key, input asset_blinding_factor already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Final asset_blinding_factor key is more than one byte type");
}
UnserializeFromVector(s, asset_blinding_factor);
break;
}
// Unknown stuff
default:
if (unknown.count(key) > 0) {
@ -268,6 +351,16 @@ struct PSBTOutput
CScript redeem_script;
CScript witness_script;
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
CPubKey blinding_pubkey;
CConfidentialValue value_commitment;
uint256 value_blinding_factor;
CConfidentialAsset asset_commitment;
uint256 asset_blinding_factor;
CConfidentialNonce nonce_commitment;
std::vector<unsigned char> range_proof;
std::vector<unsigned char> surjection_proof;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
bool IsNull() const;
@ -294,6 +387,49 @@ struct PSBTOutput
// Write any hd keypaths
SerializeHDKeypaths(s, hd_keypaths, PSBT_OUT_BIP32_DERIVATION);
if (g_con_elementsmode) {
// Write the Confidential Assets blinding data
if (!value_commitment.IsNull()) {
SerializeToVector(s, PSBT_OUT_VALUE_COMMITMENT);
SerializeToVector(s, value_commitment);
}
if (!value_blinding_factor.IsNull()) {
SerializeToVector(s, PSBT_OUT_VALUE_BLINDER);
SerializeToVector(s, value_blinding_factor);
}
if (!asset_commitment.IsNull()) {
SerializeToVector(s, PSBT_OUT_ASSET_COMMITMENT);
SerializeToVector(s, asset_commitment);
}
if (!asset_blinding_factor.IsNull()) {
SerializeToVector(s, PSBT_OUT_ASSET_BLINDER);
SerializeToVector(s, asset_blinding_factor);
}
if (!nonce_commitment.IsNull()) {
SerializeToVector(s, PSBT_OUT_NONCE_COMMITMENT);
SerializeToVector(s, nonce_commitment);
}
if (!range_proof.empty()) {
SerializeToVector(s, PSBT_OUT_RANGE_PROOF);
s << range_proof;
}
if (!surjection_proof.empty()) {
SerializeToVector(s, PSBT_OUT_SURJECTION_PROOF);
s << surjection_proof;
}
if (blinding_pubkey.IsValid()) {
SerializeToVector(s, PSBT_OUT_BLINDING_PUBKEY);
s << blinding_pubkey;
}
}
// Write unknown things
for (auto& entry : unknown) {
s << entry.first;
@ -350,6 +486,86 @@ struct PSBTOutput
DeserializeHDKeypaths(s, key, hd_keypaths);
break;
}
case PSBT_OUT_VALUE_COMMITMENT:
{
if (!value_commitment.IsNull()) {
throw std::ios_base::failure("Duplicate Key, output value_commitment already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output value_commitment key is more than one byte type");
}
UnserializeFromVector(s, value_commitment);
break;
}
case PSBT_OUT_VALUE_BLINDER:
{
if (!value_blinding_factor.IsNull()) {
throw std::ios_base::failure("Duplicate Key, output value_blinding_factor already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output value_blinding_factor key is more than one byte type");
}
UnserializeFromVector(s, value_blinding_factor);
break;
}
case PSBT_OUT_ASSET_COMMITMENT:
{
if (!asset_commitment.IsNull()) {
throw std::ios_base::failure("Duplicate Key, output asset_commitment already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output asset_commitment key is more than one byte type");
}
UnserializeFromVector(s, asset_commitment);
break;
}
case PSBT_OUT_ASSET_BLINDER:
{
if (!asset_blinding_factor.IsNull()) {
throw std::ios_base::failure("Duplicate Key, output asset_blinding_factor already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output asset_blinding_factor key is more than one byte type");
}
UnserializeFromVector(s, asset_blinding_factor);
break;
}
case PSBT_OUT_NONCE_COMMITMENT:
{
if (!nonce_commitment.IsNull()) {
throw std::ios_base::failure("Duplicate Key, output nonce_commitment already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output nonce_commitment key is more than one byte type");
}
UnserializeFromVector(s, nonce_commitment);
break;
}
case PSBT_OUT_RANGE_PROOF:
{
if (!range_proof.empty()) {
throw std::ios_base::failure("Duplicate Key, output range_proof already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output range_proof key is more than one byte type");
}
s >> range_proof;
break;
}
case PSBT_OUT_SURJECTION_PROOF:
{
if (!surjection_proof.empty()) {
throw std::ios_base::failure("Duplicate Key, output surjection_proof already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output surjection_proof key is more than one byte type");
}
s >> surjection_proof;
break;
}
case PSBT_OUT_BLINDING_PUBKEY:
{
if (blinding_pubkey.IsValid()) {
throw std::ios_base::failure("Duplicate Key, output blinding_pubkey already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output blinding_pubkey key is more than one byte type");
}
s >> blinding_pubkey;
break;
}
// Unknown stuff
default: {
if (unknown.count(key) > 0) {
@ -477,6 +693,7 @@ struct PartiallySignedTransaction
UnserializeFromVector(os, mtx);
tx = std::move(mtx);
// Make sure that all scriptSigs and scriptWitnesses are empty
tx->witness.vtxinwit.resize(tx->vin.size());
for (unsigned int i = 0; i < tx->vin.size(); i++) {
const CTxIn& txin = tx->vin[i];
if (!txin.scriptSig.empty() || !tx->witness.vtxinwit[i].scriptWitness.IsNull()) {
@ -581,4 +798,11 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
*/
NODISCARD TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs);
//! Decode a base64ed PSBT into a PartiallySignedTransaction
NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);
//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction
NODISCARD bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error);
std::string EncodePSBT(const PartiallySignedTransaction& psbt);
#endif // BITCOIN_PSBT_H

View file

@ -106,6 +106,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
{ "walletprocesspsbt", 1, "sign" },
{ "walletprocesspsbt", 3, "bip32derivs" },
{ "walletfillpsbtdata", 1, "bip32derivs" },
{ "createpsbt", 0, "inputs" },
{ "createpsbt", 1, "outputs" },
{ "createpsbt", 2, "locktime" },
@ -188,6 +189,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "blindrawtransaction", 1, "ignoreblindfail" },
{ "blindrawtransaction", 2, "asset_commitments" },
{ "blindrawtransaction", 3, "blind_issuances" },
{ "blindpsbt", 1, "ignoreblindfail" },
{ "destroyamount", 1, "amount" },
{ "sendmany", 8 , "output_assets" },
{ "sendmany", 9 , "ignoreblindfail" },

View file

@ -364,7 +364,7 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request)
return res;
}
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in)
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in, std::vector<CPubKey>* output_pubkeys_out)
{
if (inputs_in.isNull() || outputs_in.isNull())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null");
@ -468,6 +468,9 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
CTxOut out(asset, 0, CScript() << OP_RETURN << data);
rawTx.vout.push_back(out);
if (output_pubkeys_out) {
output_pubkeys_out->push_back(CPubKey());
}
} else if (name_ == "vdata") {
// ELEMENTS: support multi-push OP_RETURN
UniValue vdata = outputs[name_].get_array();
@ -479,6 +482,9 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
CTxOut out(asset, 0, datascript);
rawTx.vout.push_back(out);
if (output_pubkeys_out) {
output_pubkeys_out->push_back(CPubKey());
}
} else if (name_ == "fee") {
// ELEMENTS: explicit fee outputs
CAmount nAmount = AmountFromValue(outputs[name_]);
@ -488,6 +494,9 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
CAmount nAmount = AmountFromValue(outputs[name_]);
CTxOut out(asset, nAmount, datascript);
rawTx.vout.push_back(out);
if (output_pubkeys_out) {
output_pubkeys_out->push_back(CPubKey());
}
} else {
CTxDestination destination = DecodeDestination(name_);
if (!IsValidDestination(destination)) {
@ -502,17 +511,27 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
CAmount nAmount = AmountFromValue(outputs[name_]);
CTxOut out(asset, nAmount, scriptPubKey);
CPubKey blind_pub;
if (IsBlindDestination(destination)) {
CPubKey blind_pub = GetDestinationBlindingKey(destination);
out.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
blind_pub = GetDestinationBlindingKey(destination);
if (!output_pubkeys_out) {
// Only use the pubkey-in-nonce hack if the caller is not getting the pubkeys the nice way.
out.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
}
}
rawTx.vout.push_back(out);
if (output_pubkeys_out) {
output_pubkeys_out->push_back(blind_pub);
}
}
}
// Add fee output in the end.
if (!fee_out.nValue.IsNull() && fee_out.nValue.GetAmount() > 0) {
rawTx.vout.push_back(fee_out);
if (output_pubkeys_out) {
output_pubkeys_out->push_back(CPubKey());
}
}
if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(CTransaction(rawTx))) {
@ -843,9 +862,6 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request)
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
}
// Use CTransaction for the constant parts of the
// transaction to avoid rehashing.
const CTransaction txConst(mergedTx);
// Sign what we can:
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
CTxIn& txin = mergedTx.vin[i];
@ -1169,6 +1185,16 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
for (const auto& out : tx->vout) {
// If we have a nonce, it could be a smuggled pubkey, or it could be a
// proper nonce produced by blinding. In the latter case, the value
// will always be blinded and not explicit. In the former case, we
// error out because the transaction is not blinded properly.
if (!out.nNonce.IsNull() && out.nValue.IsExplicit()) {
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Transaction output has nonce, but is not blinded. Did you forget to call blindpsbt, blindrawtranssaction, or rawblindrawtransaction?");
}
}
bool allowhighfees = false;
if (!request.params[1].isNull()) allowhighfees = request.params[1].get_bool();
const CAmount highfee{allowhighfees ? 0 : ::maxTxFee};
@ -1285,6 +1311,126 @@ static std::string WriteHDKeypath(std::vector<uint32_t>& keypath)
return keypath_str;
}
UniValue blindpsbt(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw std::runtime_error(
RPCHelpMan{"blindpsbt",
"\nUses the blinding data from the PSBT inputs to generate the blinding data for the PSBT outputs.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The PSBT base64 string"},
{"ignoreblindfail", RPCArg::Type::BOOL, /* default*/ "true", "Return a transaction even when a blinding attempt fails due to number of blinded inputs/outputs."},
},
RPCResult{
"\"psbt\" (string) The base64-encoded partially signed transaction\n"
},
RPCExamples{
HelpExampleCli("blindpsbt", "\"psbt\"")
+ HelpExampleRpc("blindpsbt", "\"psbt\"")
}
}.ToString()
);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true);
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
bool fIgnoreBlindFail = true;
if (!request.params[1].isNull()) {
fIgnoreBlindFail = request.params[1].get_bool();
}
// TODO(gwillen): Refactor out significant duplicated code between here and rawblindrawtransaction.
std::vector<CAmount> input_amounts;
std::vector<uint256> input_blinds;
std::vector<uint256> input_asset_blinds;
std::vector<CAsset> input_assets;
std::vector<uint256> output_value_blinds;
std::vector<uint256> output_asset_blinds;
std::vector<CAsset> output_assets;
std::vector<CPubKey> output_pubkeys;
int n_blinded_ins = 0;
// TODO(gwillen): If blinding is not possible due to missing input data, we should bail here with a useful error message.
for (const auto& input : psbtx.inputs) {
input_blinds.push_back(input.value_blinding_factor);
input_asset_blinds.push_back(input.asset_blinding_factor);
input_assets.push_back(input.asset);
input_amounts.push_back(input.value ? *input.value : CAmount(-1));
if (!input_blinds.back().IsNull()) {
n_blinded_ins++;
}
}
for (auto& output : psbtx.outputs) {
output_pubkeys.push_back(output.blinding_pubkey);
}
// How many are we trying to blind?
int num_pubkeys = 0;
unsigned int keyIndex = -1;
for (unsigned int i = 0; i < output_pubkeys.size(); i++) {
const CPubKey& key = output_pubkeys[i];
if (key.IsValid()) {
num_pubkeys++;
keyIndex = i;
}
}
CMutableTransaction& tx = *psbtx.tx;
// TODO(gwillen): Replace all this with the 'bonus output' scheme to use an OP_RETURN to balance blinders, with a rangeproof exponent of -1 (public).
if (num_pubkeys == 0 && n_blinded_ins == 0) {
// Vacuous, just return the transaction
return EncodePSBT(psbtx);
} else if (n_blinded_ins > 0 && num_pubkeys == 0) {
// No notion of wallet, cannot complete this blinding without passed-in pubkey
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Add another output to blind in order to complete the blinding.");
} else if (n_blinded_ins == 0 && num_pubkeys == 1) {
if (fIgnoreBlindFail) {
// Remove the pubkey to signal that blinding is complete
psbtx.outputs[keyIndex].blinding_pubkey = CPubKey();
return EncodePSBT(psbtx);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Add another output to blind in order to complete the blinding.");
}
}
CMutableTransaction tx_tmp = tx; // We don't want to mutate the transaction in the PSBT yet, just extract blinding data
// TODO(gwillen): Make this do something better than fail silently if there are any issuances, reissuances, pegins, etc.
int ret = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_value_blinds, output_asset_blinds, output_pubkeys, std::vector<CKey>(), std::vector<CKey>(), tx_tmp);
if (ret != num_pubkeys) {
// TODO Have more rich return values, communicating to user what has been blinded
// User may be ok not blinding something that for instance has no corresponding type on input
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?");
}
for (size_t i = 0; i < psbtx.outputs.size(); ++i) {
PSBTOutput& o = psbtx.outputs[i];
o.value_commitment = tx_tmp.vout[i].nValue;
o.asset_commitment = tx_tmp.vout[i].nAsset;
o.nonce_commitment = tx_tmp.vout[i].nNonce;
o.value_blinding_factor = output_value_blinds[i];
o.asset_blinding_factor = output_asset_blinds[i];
o.range_proof = tx_tmp.witness.vtxoutwit[i].vchRangeproof;
o.surjection_proof = tx_tmp.witness.vtxoutwit[i].vchSurjectionproof;
o.blinding_pubkey = CPubKey(); // Once we're done blinding, remove the pubkeys to signal that it's complete
}
return EncodePSBT(psbtx);
}
UniValue decodepsbt(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
@ -1320,34 +1466,38 @@ UniValue decodepsbt(const JSONRPCRequest& request)
" \"partial_signatures\" : { (json object, optional)\n"
" \"pubkey\" : \"signature\", (string) The public key and signature that corresponds to it.\n"
" ,...\n"
" }\n"
" },\n"
" \"sighash\" : \"type\", (string, optional) The sighash type to be used\n"
" \"redeem_script\" : { (json object, optional)\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n"
" }\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n"
" },\n"
" \"witness_script\" : { (json object, optional)\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n"
" }\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n"
" },\n"
" \"bip32_derivs\" : { (json object, optional)\n"
" \"pubkey\" : { (json object, optional) The public key with the derivation path as the value.\n"
" \"master_fingerprint\" : \"fingerprint\" (string) The fingerprint of the master key\n"
" \"path\" : \"path\", (string) The path\n"
" }\n"
" ,...\n"
" }\n"
" \"final_scriptsig\" : { (json object, optional)\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" }\n"
" \"final_scriptwitness\": [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n"
" \"unknown\" : { (json object) The unknown global fields\n"
" \"key\" : \"value\" (key-value pair) An unknown key-value pair\n"
" ...\n"
" },\n"
" \"final_scriptsig\" : { (json object, optional)\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" },\n"
" \"final_scriptwitness\": [\"hex\", ...], (array of string) hex-encoded witness data (if any)\n"
" \"value\": x.xxx, (numeric) The (unblinded) value of the input in " + CURRENCY_UNIT + "\n"
" \"value_blinding_factor\": \"hex\" , (string) The value blinding factor from the output being spent\n"
" \"asset\": \"hex\" , (string) The (unblinded) asset id of the input\n"
" \"asset_blinding_factor\": \"hex\" , (string) The asset blinding factor from the output being spent\n"
" \"unknown\" : { (json object) The unknown input fields\n"
" \"key\" : \"value\" (key-value pair) An unknown key-value pair\n"
" ...\n"
" }\n"
" }\n"
" ,...\n"
" ]\n"
@ -1372,7 +1522,14 @@ UniValue decodepsbt(const JSONRPCRequest& request)
" }\n"
" ,...\n"
" ],\n"
" \"unknown\" : { (json object) The unknown global fields\n"
" \"value_commitment\": \"hex\" , (string) The blinded value of the output\n"
" \"value_blinding_factor\": \"hex\" , (string) The value blinding factor for the output\n"
" \"asset_commitment\": \"hex\" , (string) The blinded asset id of the output\n"
" \"asset_blinding_factor\": \"hex\" , (string) The asset blinding factor for the output\n"
" \"nonce_commitment\": \"hex\" , (string) The nonce for the output\n"
" \"surjection_proof\": \"hex\" , (string) The surjection proof for the output\n"
" \"blinding_pubkey\": \"hex\" , (string) The blinding pubkey for the output\n"
" \"unknown\" : { (json object) The unknown output fields\n"
" \"key\" : \"value\" (key-value pair) An unknown key-value pair\n"
" ...\n"
" },\n"
@ -1493,6 +1650,26 @@ UniValue decodepsbt(const JSONRPCRequest& request)
in.pushKV("final_scriptwitness", txinwitness);
}
// Value
if (input.value) {
in.pushKV("value", ValueFromAmount(*input.value));
}
// Value blinder
if (!input.value_blinding_factor.IsNull()) {
in.pushKV("value_blinding_factor", input.value_blinding_factor.GetHex());
}
// Asset
if (!input.asset.IsNull()) {
in.pushKV("asset", input.asset.id.GetHex());
}
// Asset blinder
if (!input.asset_blinding_factor.IsNull()) {
in.pushKV("asset_blinding_factor", input.asset_blinding_factor.GetHex());
}
// Unknown data
if (input.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
@ -1536,6 +1713,43 @@ UniValue decodepsbt(const JSONRPCRequest& request)
out.pushKV("bip32_derivs", keypaths);
}
// Value commitment
if (!output.value_commitment.IsNull()) {
out.pushKV("value_commitment", output.value_commitment.GetHex());
}
// Value blinder
if (!output.value_blinding_factor.IsNull()) {
out.pushKV("value_blinding_factor", output.value_blinding_factor.GetHex());
}
// Asset commitment
if (!output.asset_commitment.IsNull()) {
out.pushKV("asset_commitment", output.asset_commitment.GetHex());
}
// Asset blinder
if (!output.asset_blinding_factor.IsNull()) {
out.pushKV("asset_blinding_factor", output.asset_blinding_factor.GetHex());
}
// Nonce commitment
if (!output.nonce_commitment.IsNull()) {
out.pushKV("nonce_commitment", output.nonce_commitment.GetHex());
}
// Range proof omitted due to size
// Surjection proof
if (!output.surjection_proof.empty()) {
out.pushKV("surjection_proof", HexStr(output.surjection_proof));
}
// Blinding pubkey
if (output.blinding_pubkey.IsValid()) {
out.pushKV("blinding_pubkey", HexStr(output.blinding_pubkey));
}
// Unknown data
if (output.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
@ -1597,10 +1811,7 @@ UniValue combinepsbt(const JSONRPCRequest& request)
throw JSONRPCTransactionError(error);
}
UniValue result(UniValue::VOBJ);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << merged_psbt;
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
return EncodePSBT(merged_psbt);
}
UniValue finalizepsbt(const JSONRPCRequest& request)
@ -1649,12 +1860,6 @@ UniValue finalizepsbt(const JSONRPCRequest& request)
std::string result_str;
if (complete && extract) {
CMutableTransaction mtx(*psbtx.tx);
mtx.witness.vtxinwit.resize(mtx.vin.size());
for (unsigned int i = 0; i < mtx.vin.size(); ++i) {
mtx.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
mtx.witness.vtxinwit[i].scriptWitness = psbtx.inputs[i].final_script_witness;
}
ssTx << mtx;
result_str = HexStr(ssTx.str());
result.pushKV("hex", result_str);
@ -1664,7 +1869,6 @@ UniValue finalizepsbt(const JSONRPCRequest& request)
result.pushKV("psbt", result_str);
}
result.pushKV("complete", complete);
return result;
}
@ -1731,23 +1935,16 @@ UniValue createpsbt(const JSONRPCRequest& request)
}, true
);
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4]);
std::vector<CPubKey> output_pubkeys;
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4], &output_pubkeys);
// Make a blank psbt
PartiallySignedTransaction psbtx;
psbtx.tx = rawTx;
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
psbtx.inputs.push_back(PSBTInput());
}
PartiallySignedTransaction psbtx(rawTx);
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
psbtx.outputs.push_back(PSBTOutput());
psbtx.outputs[i].blinding_pubkey = output_pubkeys[i];
}
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
return EncodePSBT(psbtx);
}
UniValue converttopsbt(const JSONRPCRequest& request)
@ -1807,14 +2004,20 @@ UniValue converttopsbt(const JSONRPCRequest& request)
// Make a blank psbt
PartiallySignedTransaction psbtx;
psbtx.tx = tx;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
psbtx.inputs.push_back(PSBTInput());
}
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
psbtx.outputs.push_back(PSBTOutput());
// At this point, if the nonce field is present it should be a smuggled
// pubkey, and not a real nonce. Convert it back to a pubkey and strip
// it out.
psbtx.outputs[i].blinding_pubkey = CPubKey(tx.vout[i].nNonce.vchCommitment);
tx.vout[i].nNonce.SetNull();
}
psbtx.tx = tx;
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
@ -2679,6 +2882,7 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees"} },
{ "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} },
{ "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} },
{ "rawtransactions", "blindpsbt", &blindpsbt, {"psbt","ignoreblindfail"} },
{ "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} },
{ "rawtransactions", "createpsbt", &createpsbt, {"inputs","outputs","locktime","replaceable"} },
{ "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} },

View file

@ -16,7 +16,9 @@ class Chain;
/** Sign a transaction with the given keystore and previous transactions */
UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType);
/** Create a transaction from univalue parameters */
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in);
/** Create a transaction from univalue parameters. If (and only if)
output_pubkeys_out is null, the "nonce hack" of storing Confidential
Assets output pubkeys in nonces will be used. */
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in, std::vector<CPubKey>* output_pubkeys_out = nullptr);
#endif // BITCOIN_RPC_RAWTRANSACTION_H

View file

@ -2,15 +2,17 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <confidential_validation.h>
#include <wallet/psbtwallet.h>
TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs)
TransactionError FillPSBTInputsData(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool bip32derivs)
{
LOCK(pwallet->cs_wallet);
CMutableTransaction& tx = *psbtx.tx;
// Get all of the previous transactions
complete = true;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
const CTxIn& txin = tx.vin[i];
PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) {
@ -22,39 +24,165 @@ TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& ps
return TransactionError::INVALID_PSBT;
}
// If we have no utxo, grab it from the wallet.
if (!input.non_witness_utxo && input.witness_utxo.IsNull()) {
const uint256& txhash = txin.prevout.hash;
const auto it = pwallet->mapWallet.find(txhash);
if (it != pwallet->mapWallet.end()) {
const CWalletTx& wtx = it->second;
const uint256& txhash = txin.prevout.hash;
const auto it = pwallet->mapWallet.find(txhash);
if (it != pwallet->mapWallet.end()) {
const CWalletTx& wtx = it->second;
// If we have no utxo, use the one from the wallet.
if (!input.non_witness_utxo && input.witness_utxo.IsNull()) {
// We only need the non_witness_utxo, which is a superset of the witness_utxo.
// The signing code will switch to the smaller witness_utxo if this is ok.
input.non_witness_utxo = wtx.tx;
}
// Grab the CA data
CAmount val_tmp;
wtx.GetNonIssuanceBlindingData(txin.prevout.n, nullptr, &val_tmp, &input.value_blinding_factor, &input.asset, &input.asset_blinding_factor);
if (val_tmp != -1) {
input.value = val_tmp;
}
}
// Get key origin info for input, if bip32derivs is true. Does not actually sign anything.
SignPSBTInput(HidingSigningProvider(pwallet, true /* don't sign */, !bip32derivs), psbtx, i, 1 /* SIGHASH_ALL, ignored */);
}
return TransactionError::OK;
}
TransactionError SignPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool imbalance_ok)
{
complete = false;
// If we're signing, check that the transaction is not still in need of blinding
if (sign) {
for (const PSBTOutput& o : psbtx.outputs) {
if (o.blinding_pubkey.IsValid()) {
return TransactionError::BLINDING_REQUIRED;
}
}
}
// Save the original transaction since we need to munge it temporarily, which would violate the PSBT rules
CTransaction oldtx = CTransaction(*psbtx.tx);
LOCK(pwallet->cs_wallet);
CMutableTransaction& tx = *psbtx.tx;
tx.witness.vtxoutwit.resize(tx.vout.size());
// Stuff in auxiliary CA blinding data, if we have it
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
PSBTOutput& output = psbtx.outputs.at(i);
CTxOut& out = tx.vout[i];
if (!output.value_commitment.IsNull()) {
out.nValue = output.value_commitment;
}
if (!output.asset_commitment.IsNull()) {
out.nAsset = output.asset_commitment;
}
if (!output.nonce_commitment.IsNull()) {
out.nNonce = output.nonce_commitment;
}
// The signature can't depend on witness contents, so these are technically not necessary to sign.
// HOWEVER, as long as we're checking that values balance before signing, they are required.
CTxOutWitness& outwit = tx.witness.vtxoutwit[i];
if (!output.range_proof.empty()) {
outwit.vchRangeproof = output.range_proof;
}
if (!output.surjection_proof.empty()) {
outwit.vchSurjectionproof = output.surjection_proof;
}
}
// This is a convenience/usability check -- it's not invalid to sign an unbalanced transaction, but it's easy to shoot yourself in the foot.
if (!imbalance_ok) {
// Get UTXOs for all inputs, to check that amounts balance before signing.
std::vector<CTxOut> inputs_utxos;
for (size_t i = 0; i < psbtx.inputs.size(); ++i) {
PSBTInput& inp = psbtx.inputs[i];
if (inp.non_witness_utxo) {
if (inp.non_witness_utxo->GetHash() != tx.vin[i].prevout.hash) {
return TransactionError::INVALID_PSBT;
}
if (!inp.witness_utxo.IsNull() && inp.non_witness_utxo->vout[tx.vin[i].prevout.n] != inp.witness_utxo) {
return TransactionError::INVALID_PSBT;
}
inputs_utxos.push_back(inp.non_witness_utxo->vout[tx.vin[i].prevout.n]);
} else if (!inp.witness_utxo.IsNull()) {
inputs_utxos.push_back(inp.witness_utxo);
} else {
return TransactionError::UTXOS_MISSING_BALANCE_CHECK;
}
}
CTransaction tx_tmp(tx);
if (!VerifyAmounts(inputs_utxos, tx_tmp, nullptr, false)) {
return TransactionError::VALUE_IMBALANCE;
}
}
complete = true;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
// Get the Sighash type
if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) {
if (sign && psbtx.inputs[i].sighash_type > 0 && psbtx.inputs[i].sighash_type != sighash_type) {
complete = false;
return TransactionError::SIGHASH_MISMATCH;
}
complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), psbtx, i, sighash_type);
// Here we _only_ sign, and do not e.g. fill in key origin data.
complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, true /* no key origins */), psbtx, i, sighash_type);
}
// Restore the saved transaction, to remove our temporary munging.
psbtx.tx = (CMutableTransaction)oldtx;
return TransactionError::OK;
}
void FillPSBTOutputsData(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool bip32derivs) {
LOCK(pwallet->cs_wallet);
const CMutableTransaction& tx = *psbtx.tx;
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
const CTxOut& out = psbtx.tx->vout.at(i);
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
const CTxOut& out = tx.vout.at(i);
PSBTOutput& psbt_out = psbtx.outputs.at(i);
// Fill a SignatureData with output info
SignatureData sigdata;
psbt_out.FillSignatureData(sigdata);
MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1);
ProduceSignature(HidingSigningProvider(pwallet, true, !bip32derivs), creator, out.scriptPubKey, sigdata);
MutableTransactionSignatureCreator creator(&tx, 0 /* nIn, ignored */, out.nValue, 1 /* sighashtype, ignored */);
ProduceSignature(HidingSigningProvider(pwallet, true /* don't sign */, !bip32derivs), creator, out.scriptPubKey, sigdata);
psbt_out.FromSignatureData(sigdata);
}
}
TransactionError FillPSBTData(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool bip32derivs) {
LOCK(pwallet->cs_wallet);
TransactionError te;
te = FillPSBTInputsData(pwallet, psbtx, bip32derivs);
if (te != TransactionError::OK) {
return te;
}
FillPSBTOutputsData(pwallet, psbtx, bip32derivs);
return TransactionError::OK;
}
// This function remains for backwards compatibility. It will not succeed in Elements unless everything involved is non-blinded.
TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs)
{
complete = false;
TransactionError te;
te = FillPSBTInputsData(pwallet, psbtx, bip32derivs);
if (te != TransactionError::OK) {
return te;
}
// For backwards compatibility, do not check if amounts balance before signing in this case.
te = SignPSBT(pwallet, psbtx, complete, sighash_type, sign, true);
if (te != TransactionError::OK) {
return te;
}
FillPSBTOutputsData(pwallet, psbtx, bip32derivs);
return TransactionError::OK;
}

View file

@ -31,4 +31,8 @@ NODISCARD TransactionError FillPSBT(const CWallet* pwallet,
bool sign = true,
bool bip32derivs = false);
NODISCARD TransactionError FillPSBTData(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool bip32derivs = false);
NODISCARD TransactionError SignPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool& complete, int sighash_type = 1, bool sign = true, bool imbalance_ok = false);
#endif // BITCOIN_WALLET_PSBTWALLET_H

View file

@ -3256,7 +3256,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
std::map<std::string, UniValue> kvMap;
options["changeAddress"].getObjMap(kvMap);
for (const std::pair<std::string, UniValue>& kv : kvMap) {
for (const auto& kv : kvMap) {
CAsset asset = GetAssetFromString(kv.first);
if (asset.IsNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "changeAddress key must be a valid asset label or hex");
@ -4355,58 +4355,118 @@ void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubK
hd_keypaths.emplace(vchPubKey, std::move(info));
}
bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs)
UniValue walletfillpsbtdata(const JSONRPCRequest& request)
{
LOCK(pwallet->cs_wallet);
// Get all of the previous transactions
bool complete = true;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();
if (PSBTInputSigned(input)) {
continue;
}
// Verify input looks sane. This will check that we have at most one uxto, witness or non-witness.
if (!input.IsSane()) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "PSBT input is not sane.");
}
// If we have no utxo, grab it from the wallet.
if (!input.non_witness_utxo && input.witness_utxo.IsNull()) {
const uint256& txhash = txin.prevout.hash;
const auto it = pwallet->mapWallet.find(txhash);
if (it != pwallet->mapWallet.end()) {
const CWalletTx& wtx = it->second;
// We only need the non_witness_utxo, which is a superset of the witness_utxo.
// The signing code will switch to the smaller witness_utxo if this is ok.
input.non_witness_utxo = wtx.tx;
}
}
// Get the Sighash type
if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match.");
}
complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), psbtx, i, sighash_type);
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
const CTxOut& out = psbtx.tx->vout.at(i);
PSBTOutput& psbt_out = psbtx.outputs.at(i);
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
throw std::runtime_error(
RPCHelpMan{"walletfillpsbtdata",
"\nUpdate a PSBT with input information from our wallet\n"
+ HelpRequiringPassphrase(pwallet) + "\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
{"bip32derivs", RPCArg::Type::BOOL, /* default */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"},
},
RPCResult{
"{\n"
" \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n"
"}\n"
},
RPCExamples{
HelpExampleCli("walletfillpsbtdata", "\"psbt\"")
+ HelpExampleRpc("walletfillpsbtdata", "\"psbt\"")
}
}.ToString()
);
// Fill a SignatureData with output info
SignatureData sigdata;
psbt_out.FillSignatureData(sigdata);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL});
MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue.GetAmount(), 1);
ProduceSignature(HidingSigningProvider(pwallet, true, !bip32derivs), creator, out.scriptPubKey, sigdata);
psbt_out.FromSignatureData(sigdata);
// Unserialize the transaction
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
return complete;
bool bip32derivs = request.params[1].isNull() ? false : request.params[1].get_bool();
const TransactionError err = FillPSBTData(pwallet, psbtx, bip32derivs);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err);
}
UniValue result(UniValue::VOBJ);
result.pushKV("psbt", EncodePSBT(psbtx));
return result;
}
UniValue walletsignpsbt(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
throw std::runtime_error(
RPCHelpMan{"walletsignpsbt",
"\nSign all PSBT iputs that we can sign for.\n"
+ HelpRequiringPassphrase(pwallet) + "\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
{"sighashtype", RPCArg::Type::STR, /* default */ "ALL", "The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
" \"ALL\"\n"
" \"NONE\"\n"
" \"SINGLE\"\n"
" \"ALL|ANYONECANPAY\"\n"
" \"NONE|ANYONECANPAY\"\n"
" \"SINGLE|ANYONECANPAY\""},
{"imbalance_ok", RPCArg::Type::BOOL, /* default */ "false", "Sign even if the transaction amounts do not balance"},
},
RPCResult{
"{\n"
" \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n"
" \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n"
"}\n"
},
RPCExamples{
HelpExampleCli("walletsignpsbt", "\"psbt\"")
+ HelpExampleRpc("walletsignpsbt", "\"psbt\"")
}
}.ToString()
);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR, UniValue::VBOOL});
// Unserialize the transaction
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
// Get the sighash type
int nHashType = ParseSighashString(request.params[1]);
bool imbalance_ok = request.params[2].isNull() ? false : request.params[2].get_bool();
bool complete;
const TransactionError err = SignPSBT(pwallet, psbtx, complete, nHashType, true, imbalance_ok);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err);
}
UniValue result(UniValue::VOBJ);
result.pushKV("psbt", EncodePSBT(psbtx));
result.pushKV("complete", complete);
return result;
}
UniValue walletprocesspsbt(const JSONRPCRequest& request)
@ -4573,11 +4633,23 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
CAmount fee;
int change_position;
std::vector<CPubKey> output_pubkeys;
// It's hard to control the behavior of FundTransaction, so we will wait
// until after it's done, then extract the blinding keys from the output
// nonces.
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"], NullUniValue /* CA: assets_in */);
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
// Make a blank psbt
PartiallySignedTransaction psbtx(rawTx);
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
if (!psbtx.tx->vout[i].nNonce.IsNull()) {
// Extract blinding key and clear the nonce
psbtx.outputs[i].blinding_pubkey = CPubKey(psbtx.tx->vout[i].nNonce.vchCommitment);
psbtx.tx->vout[i].nNonce.SetNull();
}
}
// Fill transaction with out data but don't sign
bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool();
@ -6578,6 +6650,8 @@ static const CRPCCommand commands[] =
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
{ "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
{ "wallet", "walletfillpsbtdata", &walletfillpsbtdata, {"psbt","bip32derivs"} },
{ "wallet", "walletsignpsbt", &walletsignpsbt, {"psbt","sighashtype","imbalance_ok"} },
// ELEMENTS:
{ "wallet", "getpeginaddress", &getpeginaddress, {} },
{ "wallet", "claimpegin", &claimpegin, {"bitcoin_tx", "txoutproof", "claim_script"} },

View file

@ -30,4 +30,5 @@ bool EnsureWalletIsAvailable(CWallet *, bool avoidException);
UniValue getaddressinfo(const JSONRPCRequest& request);
UniValue signrawtransactionwithwallet(const JSONRPCRequest& request);
#endif //BITCOIN_WALLET_RPCWALLET_H

View file

@ -56,20 +56,18 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
m_wallet.SetHDSeed(master_pub_key);
m_wallet.NewKeyPool();
// Call FillPSBT
PartiallySignedTransaction psbtx;
CDataStream ssData(ParseHex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"), SER_NETWORK, PROTOCOL_VERSION);
ssData >> psbtx;
// Fill transaction with our data
bool complete = true;
BOOST_REQUIRE_EQUAL(TransactionError::OK, FillPSBT(&m_wallet, psbtx, complete, SIGHASH_ALL, false, true));
BOOST_REQUIRE_EQUAL(TransactionError::OK, FillPSBTData(&m_wallet, psbtx, true));
// Get the final tx
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
std::string final_hex = HexStr(ssTx.begin(), ssTx.end());
BOOST_CHECK_EQUAL(final_hex, "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000");
BOOST_CHECK_EQUAL(final_hex, "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f00000080000000800100008001090880f0fa02000000000001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008001090800c2eb0b0000000000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080010521010000000000000000000000000000000000000000000000000000000000000000002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008001052101000000000000000000000000000000000000000000000000000000000000000000");
}
BOOST_AUTO_TEST_CASE(parse_hd_keypath)

View file

@ -3122,7 +3122,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
mapScriptChange.clear();
if (coin_control.destChange.size() > 0) {
for (const std::pair<CAsset, CTxDestination>& dest : coin_control.destChange) {
for (const auto& dest : coin_control.destChange) {
// No need to test we cover all assets. We produce error for that later.
mapScriptChange[dest.first] = std::pair<int, CScript>(-1, GetScriptForDestination(dest.second));
}
@ -3662,7 +3662,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
}
// Release any change keys that we didn't use.
for (const std::pair<CAsset, std::pair<int, CScript>>& it : mapScriptChange) {
for (const auto& it : mapScriptChange) {
int index = it.second.first;
if (index < 0) {
continue;

View file

@ -485,7 +485,8 @@ class RawTransactionsTest(BitcoinTestFramework):
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
fundedTx = self.nodes[2].fundrawtransaction(rawtx)
signedTx = self.nodes[2].signrawtransactionwithwallet(fundedTx['hex'])
blindedTx = self.nodes[2].blindrawtransaction(fundedTx['hex'])
signedTx = self.nodes[2].signrawtransactionwithwallet(blindedTx)
txId = self.nodes[2].sendrawtransaction(signedTx['hex'])
self.sync_all()
self.nodes[1].generate(1)

View file

@ -5,12 +5,16 @@
"""Test the Partially Signed Transaction RPCs.
"""
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, disconnect_nodes, find_output, sync_blocks
from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, disconnect_nodes, sync_blocks
# These imports are used by commented-out tests.
"""
from decimal import Decimal
from test_framework.util import find_output
import json
import os
"""
MAX_BIP125_RBF_SEQUENCE = 0xfffffffd
@ -63,35 +67,74 @@ class PSBTTest(BitcoinTestFramework):
connect_nodes_bi(self.nodes, 0, 1)
connect_nodes_bi(self.nodes, 0, 2)
def run_test(self):
def get_address(self, confidential, node_num, addr_mode=None):
if (addr_mode):
addr = self.nodes[node_num].getnewaddress()
else:
addr = self.nodes[node_num].getnewaddress("", addr_mode)
if confidential:
addr = self.nodes[node_num].getaddressinfo(addr)['confidential']
else:
addr = self.nodes[node_num].getaddressinfo(addr)['unconfidential']
return addr
def to_unconf_addr(self, node_num, addr):
return self.nodes[node_num].getaddressinfo(addr)['unconfidential']
def num_blinded_outputs(self, tx):
result = 0
decoded = self.nodes[0].decoderawtransaction(tx)
for out in decoded["vout"]:
if out["scriptPubKey"]["type"] == "fee":
pass
if "valuecommitment" in out:
result += 1
return result
def run_basic_tests(self, confidential):
# Create and fund a raw tx for sending 10 BTC
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.get_address(confidential, 2):10})['psbt']
# Node 1 should not be able to add anything to it but still return the psbtx same as before
psbtx = self.nodes[1].walletprocesspsbt(psbtx1)['psbt']
psbtx = self.nodes[1].walletfillpsbtdata(psbtx1)['psbt']
assert_equal(psbtx1, psbtx)
# Sign the transaction and send
signed_tx = self.nodes[0].walletprocesspsbt(psbtx)['psbt']
filled_tx = self.nodes[0].walletfillpsbtdata(psbtx)['psbt']
blinded_tx = self.nodes[0].blindpsbt(filled_tx)
signed_tx = self.nodes[0].walletsignpsbt(blinded_tx)['psbt']
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
if confidential:
# Can't use assert_equal because there may or may not be change
assert(self.num_blinded_outputs(final_tx) > 0)
self.nodes[0].sendrawtransaction(final_tx)
# Create p2sh, p2wpkh, and p2wsh addresses
pubkey0 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())['pubkey']
pubkey1 = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey']
pubkey2 = self.nodes[2].getaddressinfo(self.nodes[2].getnewaddress())['pubkey']
pubkey0 = self.nodes[0].getaddressinfo(self.get_address(confidential, 0))['pubkey']
pubkey1 = self.nodes[1].getaddressinfo(self.get_address(confidential, 1))['pubkey']
pubkey2 = self.nodes[2].getaddressinfo(self.get_address(confidential, 2))['pubkey']
p2sh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address']
p2sh_unconf = self.to_unconf_addr(1, p2sh)
p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address']
p2wsh_unconf = self.to_unconf_addr(1, p2wsh)
p2sh_p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address']
p2wpkh = self.nodes[1].getnewaddress("", "bech32")
p2pkh = self.nodes[1].getnewaddress("", "legacy")
p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit")
p2sh_p2wsh_unconf = self.to_unconf_addr(1, p2sh_p2wsh)
p2wpkh = self.get_address(confidential, 1, "bech32")
p2wpkh_unconf = self.to_unconf_addr(1, p2wpkh)
p2pkh = self.get_address(confidential, 1, "legacy")
p2pkh_unconf = self.to_unconf_addr(1, p2pkh)
p2sh_p2wpkh = self.get_address(confidential, 1, "p2sh-segwit")
p2sh_p2wpkh_unconf = self.to_unconf_addr(1, p2sh_p2wpkh)
# fund those addresses
rawtx = self.nodes[0].createrawtransaction([], {p2sh:10, p2wsh:10, p2wpkh:10, p2sh_p2wsh:10, p2sh_p2wpkh:10, p2pkh:10})
rawtx = self.nodes[0].fundrawtransaction(rawtx, {"changePosition":3})
signed_tx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])['hex']
rawtx = self.nodes[0].blindrawtransaction(rawtx['hex'])
signed_tx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
txid = self.nodes[0].sendrawtransaction(signed_tx)
self.nodes[0].generate(6)
self.sync_all()
@ -104,52 +147,67 @@ class PSBTTest(BitcoinTestFramework):
p2sh_p2wpkh_pos = -1
decoded = self.nodes[0].decoderawtransaction(signed_tx)
for out in decoded['vout']:
if out['scriptPubKey']['addresses'][0] == p2sh:
if out['scriptPubKey']['type'] == 'fee':
next
elif out['scriptPubKey']['addresses'][0] == p2sh_unconf:
p2sh_pos = out['n']
elif out['scriptPubKey']['addresses'][0] == p2wsh:
elif out['scriptPubKey']['addresses'][0] == p2wsh_unconf:
p2wsh_pos = out['n']
elif out['scriptPubKey']['addresses'][0] == p2wpkh:
elif out['scriptPubKey']['addresses'][0] == p2wpkh_unconf:
p2wpkh_pos = out['n']
elif out['scriptPubKey']['addresses'][0] == p2sh_p2wsh:
elif out['scriptPubKey']['addresses'][0] == p2sh_p2wsh_unconf:
p2sh_p2wsh_pos = out['n']
elif out['scriptPubKey']['addresses'][0] == p2sh_p2wpkh:
elif out['scriptPubKey']['addresses'][0] == p2sh_p2wpkh_unconf:
p2sh_p2wpkh_pos = out['n']
elif out['scriptPubKey']['addresses'][0] == p2pkh:
elif out['scriptPubKey']['addresses'][0] == p2pkh_unconf:
p2pkh_pos = out['n']
# spend single key from node 1
rawtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99})['psbt']
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(rawtx)
assert_equal(walletprocesspsbt_out['complete'], True)
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
rawtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.get_address(confidential, 1):29.99})['psbt']
filled = self.nodes[1].walletfillpsbtdata(rawtx)['psbt']
blinded = self.nodes[1].blindpsbt(filled)
walletsignpsbt_out = self.nodes[1].walletsignpsbt(blinded)
assert_equal(walletsignpsbt_out['complete'], True)
hex_tx = self.nodes[1].finalizepsbt(walletsignpsbt_out['psbt'])['hex']
if confidential:
# Can't use assert_equal because there may or may not be change
assert(self.num_blinded_outputs(hex_tx) > 0)
self.nodes[1].sendrawtransaction(hex_tx)
# partially sign multisig things with node 1
psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], {self.nodes[1].getnewaddress():29.99})['psbt']
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx)
psbtx = walletprocesspsbt_out['psbt']
assert_equal(walletprocesspsbt_out['complete'], False)
psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], {self.get_address(confidential, 1):29.99})['psbt']
filled = self.nodes[1].walletfillpsbtdata(psbtx)['psbt']
# have both nodes fill before we try to blind and sign
filled = self.nodes[2].walletfillpsbtdata(filled)['psbt']
blinded = self.nodes[1].blindpsbt(filled)
walletsignpsbt_out = self.nodes[1].walletsignpsbt(blinded)
psbtx = walletsignpsbt_out['psbt']
assert_equal(walletsignpsbt_out['complete'], False)
# partially sign with node 2. This should be complete and sendable
walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx)
assert_equal(walletprocesspsbt_out['complete'], True)
self.nodes[2].sendrawtransaction(self.nodes[2].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
walletsignpsbt_out = self.nodes[2].walletsignpsbt(psbtx)
assert_equal(walletsignpsbt_out['complete'], True)
hex_tx = self.nodes[2].finalizepsbt(walletsignpsbt_out['psbt'])['hex']
if confidential:
# Can't use assert_equal because there may or may not be change
assert(self.num_blinded_outputs(hex_tx) > 0)
self.nodes[2].sendrawtransaction(hex_tx)
# check that walletprocesspsbt fails to decode a non-psbt
rawtx = self.nodes[1].createrawtransaction([{"txid":txid,"vout":p2wpkh_pos}], {self.nodes[1].getnewaddress():9.99})
rawtx = self.nodes[1].createrawtransaction([{"txid":txid,"vout":p2wpkh_pos}], {self.get_address(confidential, 1):9.99})
assert_raises_rpc_error(-22, "TX decode failed", self.nodes[1].walletprocesspsbt, rawtx)
# Convert a non-psbt to psbt and make sure we can decode it
rawtx = self.nodes[0].createrawtransaction([], {self.nodes[1].getnewaddress():10})
rawtx = self.nodes[0].createrawtransaction([], {self.get_address(confidential, 1):10})
rawtx = self.nodes[0].fundrawtransaction(rawtx)
new_psbt = self.nodes[0].converttopsbt(rawtx['hex'])
self.nodes[0].decodepsbt(new_psbt)
# Make sure that a psbt with signatures cannot be converted
signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])
assert_raises_rpc_error(-22, "Inputs must not have scriptWitnesses", self.nodes[0].converttopsbt, signedtx['hex'], False)
assert_raises_rpc_error(-22, "Inputs must not have scriptWitnesses", self.nodes[0].converttopsbt, signedtx['hex'])
assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'])
assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'], False)
# Can be either a scriptSig or a scriptWitness that it yells about, depending on which UTXOs are selected for the TX
assert_raises_rpc_error(-22, "Inputs must not have", self.nodes[0].converttopsbt, signedtx['hex'], False)
assert_raises_rpc_error(-22, "Inputs must not have", self.nodes[0].converttopsbt, signedtx['hex'])
# Unless we allow it to convert and strip signatures
self.nodes[0].converttopsbt(signedtx['hex'], True)
@ -158,40 +216,69 @@ class PSBTTest(BitcoinTestFramework):
self.nodes[0].decodepsbt(new_psbt)
# Create outputs to nodes 1 and 2
node1_addr = self.nodes[1].getnewaddress()
node2_addr = self.nodes[2].getnewaddress()
txid1 = self.nodes[0].sendtoaddress(node1_addr, 13)
txid2 = self.nodes[0].sendtoaddress(node2_addr, 13)
blockhash = self.nodes[0].generate(6)[0]
self.sync_all()
vout1 = find_output(self.nodes[1], txid1, 13, blockhash=blockhash)
vout2 = find_output(self.nodes[2], txid2, 13, blockhash=blockhash)
# We do a whole song-and-dance here (instead of calling sendtoaddress) to get access to the unblinded transaction data to find our outputs
node1_addr = self.get_address(confidential, 1)
node1_unconf_addr = self.to_unconf_addr(1, node1_addr)
node2_addr = self.get_address(confidential, 2)
node2_unconf_addr = self.to_unconf_addr(2, node2_addr)
rt1 = self.nodes[0].createrawtransaction([], {node1_addr:13})
rt1 = self.nodes[0].fundrawtransaction(rt1)
rt1 = self.nodes[0].blindrawtransaction(rt1['hex'])
rt1 = self.nodes[0].signrawtransactionwithwallet(rt1)
txid1 = self.nodes[0].sendrawtransaction(rt1['hex'])
rt1 = self.nodes[0].decoderawtransaction(rt1['hex'])
# Create a psbt spending outputs from nodes 1 and 2
psbt_orig = self.nodes[0].createpsbt([{"txid":txid1, "vout":vout1}, {"txid":txid2, "vout":vout2}], {self.nodes[0].getnewaddress():25.999})
rt2 = self.nodes[0].createrawtransaction([], {node2_addr:13})
rt2 = self.nodes[0].fundrawtransaction(rt2)
rt2 = self.nodes[0].blindrawtransaction(rt2['hex'])
rt2 = self.nodes[0].signrawtransactionwithwallet(rt2)
txid2 = self.nodes[0].sendrawtransaction(rt2['hex'])
rt2 = self.nodes[0].decoderawtransaction(rt2['hex'])
# Update psbts, should only have data for one input and not the other
psbt1 = self.nodes[1].walletprocesspsbt(psbt_orig)['psbt']
psbt1_decoded = self.nodes[0].decodepsbt(psbt1)
assert psbt1_decoded['inputs'][0] and not psbt1_decoded['inputs'][1]
psbt2 = self.nodes[2].walletprocesspsbt(psbt_orig)['psbt']
psbt2_decoded = self.nodes[0].decodepsbt(psbt2)
assert not psbt2_decoded['inputs'][0] and psbt2_decoded['inputs'][1]
# Combine, finalize, and send the psbts
combined = self.nodes[0].combinepsbt([psbt1, psbt2])
finalized = self.nodes[0].finalizepsbt(combined)['hex']
self.nodes[0].sendrawtransaction(finalized)
self.nodes[0].generate(6)
self.sync_all()
for out in rt1['vout']:
if out['scriptPubKey']['type'] == "fee":
pass
elif out['scriptPubKey']['addresses'][0] == node1_unconf_addr:
vout1 = out['n']
for out in rt2['vout']:
if out['scriptPubKey']['type'] == "fee":
pass
elif out['scriptPubKey']['addresses'][0] == node2_unconf_addr:
vout2 = out['n']
# This test doesn't work with Confidential Assets yet.
if not confidential:
# Create a psbt spending outputs from nodes 1 and 2
psbt_orig = self.nodes[0].createpsbt([{"txid":txid1, "vout":vout1}, {"txid":txid2, "vout":vout2}], [{self.get_address(confidential, 0):25.999}, {"fee":0.001}])
# Update psbts, should only have data for one input and not the other
psbt1 = self.nodes[1].walletprocesspsbt(psbt_orig)['psbt']
psbt1_decoded = self.nodes[0].decodepsbt(psbt1)
assert psbt1_decoded['inputs'][0] and not psbt1_decoded['inputs'][1]
psbt1 = self.nodes[1].walletsignpsbt(psbt1, "ALL", True)['psbt'] # Allow signing incomplete tx
psbt2 = self.nodes[2].walletprocesspsbt(psbt_orig)['psbt']
psbt2_decoded = self.nodes[0].decodepsbt(psbt2)
assert not psbt2_decoded['inputs'][0] and psbt2_decoded['inputs'][1]
psbt2 = self.nodes[2].walletsignpsbt(psbt2, "ALL", True)['psbt'] # Allow signing incomplete tx
# Combine, finalize, and send the psbts
combined = self.nodes[0].combinepsbt([psbt1, psbt2])
finalized = self.nodes[0].finalizepsbt(combined)['hex']
self.nodes[0].sendrawtransaction(finalized)
self.nodes[0].generate(6)
self.sync_all()
# Test additional args in walletcreatepsbt
# Make sure both pre-included and funded inputs
# have the correct sequence numbers based on
# replaceable arg
block_height = self.nodes[0].getblockcount()
unspent = self.nodes[0].listunspent()[0]
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable":True}, False)
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.get_address(confidential, 2):unspent["amount"]+1}], block_height+2, {"replaceable":True}, False)
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
@ -199,7 +286,7 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(decoded_psbt["tx"]["locktime"], block_height+2)
# Same construction with only locktime set
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {}, True)
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.get_address(confidential, 2):unspent["amount"]+1}], block_height, {}, True)
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
@ -207,7 +294,7 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(decoded_psbt["tx"]["locktime"], block_height)
# Same construction without optional arguments
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.get_address(confidential, 2):unspent["amount"]+1}])
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
for tx_in in decoded_psbt["tx"]["vin"]:
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
@ -219,12 +306,48 @@ class PSBTTest(BitcoinTestFramework):
# Regression test for 14473 (mishandling of already-signed witness transaction):
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"])
double_processed_psbt = self.nodes[0].walletprocesspsbt(complete_psbt["psbt"])
assert_equal(complete_psbt, double_processed_psbt)
filled = self.nodes[0].walletfillpsbtdata(psbtx_info["psbt"])
blinded = self.nodes[0].blindpsbt(filled["psbt"])
signed = self.nodes[0].walletsignpsbt(blinded)
signed_again = self.nodes[0].walletsignpsbt(signed["psbt"])
assert_equal(signed, signed_again)
# We don't care about the decode result, but decoding must succeed.
self.nodes[0].decodepsbt(double_processed_psbt["psbt"])
self.nodes[0].decodepsbt(signed["psbt"])
# Test the imbalance_ok argument of walletsignpsbt by manually constructing a psbt that doesn't balance.
node1_addr = self.get_address(confidential, 1)
node1_unconf_addr = self.to_unconf_addr(1, node1_addr)
rt1 = self.nodes[0].createrawtransaction([], {node1_addr:11.11})
rt1 = self.nodes[0].fundrawtransaction(rt1)
rt1 = self.nodes[0].blindrawtransaction(rt1['hex'])
rt1 = self.nodes[0].signrawtransactionwithwallet(rt1)
txid1 = self.nodes[0].sendrawtransaction(rt1['hex'])
rt1 = self.nodes[0].decoderawtransaction(rt1['hex'])
self.nodes[0].generate(6)
self.sync_all()
for out in rt1['vout']:
if out['scriptPubKey']['type'] == "fee":
pass
elif out['scriptPubKey']['addresses'][0] == node1_unconf_addr:
vout1 = out['n']
psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], [{self.get_address(confidential, 2):1}, {"fee":0.001}])
psbt = self.nodes[1].walletfillpsbtdata(psbt)
psbt = self.nodes[1].blindpsbt(psbt["psbt"])
# If imbalance_ok is false, should fail
assert_raises_rpc_error(-25, "Transaction values or blinders are not balanced", self.nodes[1].walletsignpsbt, psbt, "ALL", False)
# If imbalance_ok is true, should succeed
psbt = self.nodes[1].walletsignpsbt(psbt, "ALL", True)
psbt = self.nodes[1].finalizepsbt(psbt["psbt"])
# ... but you still can't send it.
assert_raises_rpc_error(-26, "bad-txns-in-ne-out, value in != value out (code 16)", self.nodes[1].sendrawtransaction, psbt['hex'])
# BIP 174 tests are disabled because they don't work with CA yet. Comment the function so it doesn't flag lint as unused.
"""
def run_bip174_tests(self):
# BIP 174 Test Vectors
# Check that unknown values are just passed through
@ -286,7 +409,124 @@ class PSBTTest(BitcoinTestFramework):
# Unload extra wallets
for i, signer in enumerate(signers):
self.nodes[2].unloadwallet("wallet{}".format(i))
"""
def run_ca_tests(self):
# Confidential Assets tests
# Start by sending some coins to a nonconf address
unconf_addr_0 = self.get_address(False, 0)
unconf_addr_1 = self.get_address(False, 0)
unconf_addr_4 = self.get_address(False, 0)
rawtx = self.nodes[0].createrawtransaction([], {unconf_addr_0:50, unconf_addr_1:50, unconf_addr_4:50})
rawtx = self.nodes[0].fundrawtransaction(rawtx, {"changePosition":3}) # our outputs will be 0, 1, 2
rawtx = self.nodes[0].blindrawtransaction(rawtx['hex'])
signed_tx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
txid_nonconf = self.nodes[0].sendrawtransaction(signed_tx)
self.nodes[0].generate(1)
self.sync_all()
# Now use PSBT to send some coins nonconf->nonconf
unconf_addr_2 = self.get_address(False, 1)
psbt = self.nodes[0].createpsbt([{"txid": txid_nonconf, "vout": 0}], [{unconf_addr_2: 49.999}, {"fee": 0.001}])
psbt = self.nodes[0].walletfillpsbtdata(psbt)['psbt']
psbt = self.nodes[0].walletsignpsbt(psbt)['psbt']
tx_hex = self.nodes[0].finalizepsbt(psbt)['hex']
txid_nonconf_2 = self.nodes[0].sendrawtransaction(tx_hex)
self.nodes[0].generate(1)
self.sync_all()
# Now send nonconf->conf
conf_addr = self.get_address(True, 2)
psbt = self.nodes[1].createpsbt([{"txid": txid_nonconf_2, "vout": 0}], [{conf_addr: 49.998}, {"fee": 0.001}])
psbt = self.nodes[1].walletfillpsbtdata(psbt)['psbt']
# Currently can't blind a transaction like this, so it fails
assert_raises_rpc_error(-8, "Unable to blind transaction: Add another output to blind in order to complete the blinding.", self.nodes[1].blindpsbt, psbt, False)
# Signing without blinding should not work either.
assert_raises_rpc_error(-25, "Transaction is not yet fully blinded", self.nodes[1].walletsignpsbt, psbt)
# If we pass "ignore_blind_fail", then it succeeds in this case without blinding.
psbt = self.nodes[1].blindpsbt(psbt, True)
psbt = self.nodes[1].walletsignpsbt(psbt)['psbt']
hex_tx = self.nodes[1].finalizepsbt(psbt)['hex']
self.nodes[1].sendrawtransaction(hex_tx)
self.nodes[0].generate(1)
self.sync_all()
# Now send nonconf->conf (with two outputs, blinding succeeds)
conf_addr_1 = self.get_address(True, 2)
conf_addr_2 = self.get_address(True, 2)
psbt = self.nodes[0].createpsbt([{"txid": txid_nonconf, "vout": 1}], [{conf_addr_1: 24.999}, {conf_addr_2: 24.999}, {"fee": 0.002}])
psbt = self.nodes[0].walletfillpsbtdata(psbt)['psbt']
psbt = self.nodes[0].blindpsbt(psbt, False)
psbt = self.nodes[0].walletsignpsbt(psbt)['psbt']
hex_tx = self.nodes[0].finalizepsbt(psbt)['hex']
assert_equal(self.num_blinded_outputs(hex_tx), 2)
txid_conf_2 = self.nodes[0].sendrawtransaction(hex_tx)
self.nodes[0].generate(1)
self.sync_all()
# Try to send conf->nonconf: This will fail because we can't balance the blinders
unconf_addr_3 = self.get_address(False, 0)
psbt = self.nodes[2].createpsbt([{"txid": txid_conf_2, "vout": 0}], [{unconf_addr_3: 24.998}, {"fee": 0.001}])
psbt = self.nodes[2].walletfillpsbtdata(psbt)['psbt']
assert_raises_rpc_error(-8, "Unable to blind transaction: Add another output to blind in order to complete the blinding.", self.nodes[2].blindpsbt, psbt, False)
# Try to send conf->(nonconf + conf), so we have a conf output to balance blinders
conf_addr_3 = self.get_address(True, 0)
psbt = self.nodes[2].createpsbt([{"txid": txid_conf_2, "vout": 0}], [{unconf_addr_3: 10}, {conf_addr_3: 14.998}, {"fee": 0.001}])
psbt = self.nodes[2].walletfillpsbtdata(psbt)['psbt']
psbt = self.nodes[2].blindpsbt(psbt, False)
psbt = self.nodes[2].walletsignpsbt(psbt)['psbt']
hex_tx = self.nodes[2].finalizepsbt(psbt)['hex']
assert_equal(self.num_blinded_outputs(hex_tx), 1)
self.nodes[2].sendrawtransaction(hex_tx)
self.nodes[0].generate(1)
self.sync_all()
# Try to send conf->conf
conf_addr_4 = self.get_address(True, 0)
psbt = self.nodes[2].createpsbt([{"txid": txid_conf_2, "vout": 1}], [{conf_addr_4: 24.998}, {"fee": 0.001}])
psbt = self.nodes[2].walletfillpsbtdata(psbt)['psbt']
psbt = self.nodes[2].blindpsbt(psbt, False)
psbt = self.nodes[2].walletsignpsbt(psbt)['psbt']
hex_tx = self.nodes[2].finalizepsbt(psbt)['hex']
assert_equal(self.num_blinded_outputs(hex_tx), 1)
self.nodes[2].sendrawtransaction(hex_tx)
self.nodes[0].generate(1)
self.sync_all()
# Try to send nonconf->(nonconf + conf + conf) -- two conf to make blinders balance
nonconf_addr_5 = self.get_address(False, 1)
conf_addr_5 = self.get_address(True, 1)
conf_addr_6 = self.get_address(True, 2)
psbt = self.nodes[0].createpsbt([{"txid": txid_nonconf, "vout": 2}], [{nonconf_addr_5: 24.999}, {conf_addr_5: 14.999}, {conf_addr_6: 10}, {"fee": 0.002}])
psbt = self.nodes[0].walletfillpsbtdata(psbt)['psbt']
psbt = self.nodes[0].blindpsbt(psbt, False)
psbt = self.nodes[0].walletsignpsbt(psbt)['psbt']
hex_tx = self.nodes[0].finalizepsbt(psbt)['hex']
assert_equal(self.num_blinded_outputs(hex_tx), 2)
self.nodes[0].sendrawtransaction(hex_tx)
self.nodes[0].generate(1)
self.sync_all()
def run_test(self):
self.nodes[0].generate(200)
self.sync_all()
# Run all the pre-Elements, tests first with non-confidential addresses, then again with confidential addresses
self.run_basic_tests(False)
self.run_basic_tests(True)
# BIP 174 test vectors are disabled, because they have embedded serialized CTransactions, and
# the transaction serialization format changed in Elements so none of them work
#self.run_bip174_tests()
# Some Confidential-Assets-specific tests
self.run_ca_tests()
# Tests added in the 0.18 rebase don't pass on Elements yet.
"""
self.test_utxo_conversion()
# Test that psbts with p2pkh outputs are created properly
@ -363,6 +603,7 @@ class PSBTTest(BitcoinTestFramework):
signed = self.nodes[1].walletprocesspsbt(updated)['psbt']
analyzed = self.nodes[0].analyzepsbt(signed)
assert analyzed['inputs'][0]['has_utxo'] and analyzed['inputs'][0]['is_final'] and analyzed['next'] == 'extractor'
"""
if __name__ == '__main__':
PSBTTest().main()

View file

@ -139,8 +139,7 @@ BASE_SCRIPTS = [
'wallet_createwallet.py --usecli',
'interface_http.py',
'interface_rpc.py',
# ELEMENTS: hard-coded test vectors don't work with different tx serialization
#'rpc_psbt.py',
'rpc_psbt.py',
'rpc_users.py',
'feature_proxy.py',
'rpc_signrawtransaction.py',

View file

@ -6,3 +6,4 @@ objext
unselect
useable
mut
te