Merge #600: PSBT for Confidential Assets

3127c6561 Add legacy help text for walletprocesspsbt suggesting to use new RPCs instead. (Glenn Willen)
d39925b08 Disable PSBT RPCs when not in g_con_elementsmode. (Glenn Willen)
2848b520b PSBT for Confidential Assets (Glenn Willen)
f87684a00 Add convenience method GetNonIssuanceBlindingData (Glenn Willen)

Pull request description:

  This PR extends the PSBT format and RPCs to handle Confidential Assets transactions.

  New fields in PSBT inputs: Unblinded value, Value blinder, Unblinded asset, Asset blinder.
  New fields in PSBT outputs: Recipient blinding pubkey, Value commitment, Value blinder, Asset commitment, Asset blinder, Nonce commitment, Range proof, Surjection proof.

  We preserve the existing invariant that the unsigned transaction inside a PSBT never changes during the process; all updates that need to be applied to produce the final signed transaction are accumulated in the PSBT fields listed above.

  The process is as follows:
  * Create a PSBT using `converttopsbt` [deprecated], `walletcreatefundedpsbt`, `createpsbt`
     * At this point, the output pubkey fields will be filled, for any outputs to confidential addresses.
     * Input fields may be filled, if the wallet was used; otherwise they will be filled in the next step.
     * Incremental creation of the unsigned transaction itself is OUTSIDE the scope of this work. The unsigned transaction must be fully populated with inputs and outputs before the PSBT is created.
     * Transactions with peg-in, issuance, or reissuance outputs are NOT supported at this time.
  * If necessary, update the psbt with input data using `walletfillpsbtdata`.
     * This is like the old `walletprocesspsbt` RPC [deprecated], which tried to both fill and sign the PSBT (which is not workable in the Confidential Assets setting.)
  * Once all inputs have had necessary data updated, possibly using multiple wallets if necessary, any wallet can be used to blind the transaction using `blindpsbt`.
     * This uses the input blinding data, along with the output blinding pubkeys, to compute the output blinding data.
     * Incremental blinding is not supported. All input blinding data must be available when `blindpsbt` is called.
  * Then, the blinded PSBT must be signed using `walletsignpsbt`. As with updating, this can be done by multiple wallets as necessary for the inputs being signed.
  * Once all signatures are present, `finalizepsbt` is used to create the final transaction in the regular transaction format, as before.
  * Then `sendrawtransaction` is used, which will check to make sure that blinding was performed properly before sending.

Tree-SHA512: 2844a1545383cdab6025b90791c9e66f280d988bd40e37354c831b309c4c41eead144ca6d69c956cac40e849978c3450d1a22910f51ca3c6245236b29c05faa8
This commit is contained in:
Gregory Sanders 2019-07-31 15:06:43 -04:00
commit c67ef2938c
No known key found for this signature in database
GPG key ID: F3F68E2D86A48FDB
21 changed files with 1217 additions and 245 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

@ -107,6 +107,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" },
@ -189,6 +190,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

@ -363,7 +363,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");
@ -467,6 +467,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();
@ -478,6 +481,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_]);
@ -487,6 +493,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)) {
@ -501,17 +510,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))) {
@ -842,9 +861,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];
@ -1168,6 +1184,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};
@ -1284,8 +1310,134 @@ static std::string WriteHDKeypath(std::vector<uint32_t>& keypath)
return keypath_str;
}
UniValue blindpsbt(const JSONRPCRequest& request)
{
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
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 (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
RPCHelpMan{"decodepsbt",
@ -1319,34 +1471,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"
@ -1371,7 +1527,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"
@ -1492,6 +1655,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);
@ -1535,6 +1718,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);
@ -1553,6 +1773,9 @@ UniValue decodepsbt(const JSONRPCRequest& request)
UniValue combinepsbt(const JSONRPCRequest& request)
{
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
RPCHelpMan{"combinepsbt",
@ -1596,14 +1819,14 @@ 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)
{
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw std::runtime_error(
RPCHelpMan{"finalizepsbt",
@ -1648,12 +1871,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);
@ -1663,12 +1880,14 @@ UniValue finalizepsbt(const JSONRPCRequest& request)
result.pushKV("psbt", result_str);
}
result.pushKV("complete", complete);
return result;
}
UniValue createpsbt(const JSONRPCRequest& request)
{
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
if (request.fHelp || request.params.size() < 2 || request.params.size() > 4)
throw std::runtime_error(
RPCHelpMan{"createpsbt",
@ -1730,27 +1949,23 @@ 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)
{
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
throw std::runtime_error(
RPCHelpMan{"converttopsbt",
@ -1806,14 +2021,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;
@ -2678,6 +2899,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

@ -3262,7 +3262,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");
@ -4370,62 +4370,131 @@ 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);
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
if (PSBTInputSigned(input)) {
continue;
}
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();
// 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)
{
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
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)
{
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();
@ -4437,7 +4506,14 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request)
throw std::runtime_error(
RPCHelpMan{"walletprocesspsbt",
"\nUpdate a PSBT with input information from our wallet and then sign inputs\n"
"that we can sign for." +
"that we can sign for.\n\n"
"NOTE: When working with Confidential Assets transactions, it is necessary to\n"
"blind the transaction after filling it in from the wallet and before signing\n"
"it. This RPC will fail when working with such transaction. Instead of using\n"
"this RPC, use the following sequence:\n"
" - walletfillpsbtdata\n"
" - blindpsbt\n"
" - walletsignpsbt\n" +
HelpRequiringPassphrase(pwallet) + "\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
@ -4495,6 +4571,9 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request)
UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
{
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();
@ -4588,11 +4667,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();
@ -6593,6 +6684,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

@ -3124,7 +3124,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));
}
@ -3664,7 +3664,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;
@ -5386,48 +5386,41 @@ void CWalletTx::GetBlindingData(const unsigned int map_index, const std::vector<
if (asset_out) *asset_out = asset_tag;
}
CAmount CWalletTx::GetOutputValueOut(unsigned int output_index) const {
void CWalletTx::GetNonIssuanceBlindingData(const unsigned int output_index, CPubKey* blinding_pubkey_out, CAmount* value_out, uint256* value_factor_out, CAsset* asset_out, uint256* asset_factor_out) const {
assert(output_index < tx->vout.size());
const CTxOut& out = tx->vout[output_index];
const CTxWitness& wit = tx->witness;
GetBlindingData(output_index, wit.vtxoutwit.size() <= output_index ? std::vector<unsigned char>() : wit.vtxoutwit[output_index].vchRangeproof, out.nValue, out.nAsset, out.nNonce, out.scriptPubKey,
blinding_pubkey_out, value_out, value_factor_out, asset_out, asset_factor_out);
}
CAmount CWalletTx::GetOutputValueOut(unsigned int output_index) const {
CAmount ret;
GetBlindingData(output_index, wit.vtxoutwit.size() <= output_index ? std::vector<unsigned char>() : wit.vtxoutwit[output_index].vchRangeproof, out.nValue, out.nAsset, out.nNonce, out.scriptPubKey, nullptr, &ret, nullptr, nullptr, nullptr);
GetNonIssuanceBlindingData(output_index, nullptr, &ret, nullptr, nullptr, nullptr);
return ret;
}
uint256 CWalletTx::GetOutputAmountBlindingFactor(unsigned int output_index) const {
assert(output_index < tx->vout.size());
const CTxOut& out = tx->vout[output_index];
const CTxWitness& wit = tx->witness;
uint256 ret;
GetBlindingData(output_index, wit.vtxoutwit.size() <= output_index ? std::vector<unsigned char>() : wit.vtxoutwit[output_index].vchRangeproof, out.nValue, out.nAsset, out.nNonce, out.scriptPubKey, nullptr, nullptr, &ret, nullptr, nullptr);
GetNonIssuanceBlindingData(output_index, nullptr, nullptr, &ret, nullptr, nullptr);
return ret;
}
uint256 CWalletTx::GetOutputAssetBlindingFactor(unsigned int output_index) const {
assert(output_index < tx->vout.size());
const CTxOut& out = tx->vout[output_index];
const CTxWitness& wit = tx->witness;
uint256 ret;
GetBlindingData(output_index, wit.vtxoutwit.size() <= output_index ? std::vector<unsigned char>() : wit.vtxoutwit[output_index].vchRangeproof, out.nValue, out.nAsset, out.nNonce, out.scriptPubKey, nullptr, nullptr, nullptr, nullptr, &ret);
GetNonIssuanceBlindingData(output_index, nullptr, nullptr, nullptr, nullptr, &ret);
return ret;
}
CAsset CWalletTx::GetOutputAsset(unsigned int output_index) const {
assert(output_index < tx->vout.size());
const CTxOut& out = tx->vout[output_index];
const CTxWitness& wit = tx->witness;
CAsset ret;
GetBlindingData(output_index, wit.vtxoutwit.size() <= output_index ? std::vector<unsigned char>() : wit.vtxoutwit[output_index].vchRangeproof, out.nValue, out.nAsset, out.nNonce, out.scriptPubKey, nullptr, nullptr, nullptr, &ret, nullptr);
GetNonIssuanceBlindingData(output_index, nullptr, nullptr, nullptr, &ret, nullptr);
return ret;
}
CPubKey CWalletTx::GetOutputBlindingPubKey(unsigned int output_index) const {
assert(output_index < tx->vout.size());
const CTxOut& out = tx->vout[output_index];
const CTxWitness& wit = tx->witness;
CPubKey ret;
GetBlindingData(output_index, wit.vtxoutwit.size() <= output_index ? std::vector<unsigned char>() : wit.vtxoutwit[output_index].vchRangeproof, out.nValue, out.nAsset, out.nNonce, out.scriptPubKey, &ret, nullptr, nullptr, nullptr, nullptr);
GetNonIssuanceBlindingData(output_index, &ret, nullptr, nullptr, nullptr, nullptr);
return ret;
}

View file

@ -573,6 +573,9 @@ public:
// Unneeded for issuance.
void SetBlindingData(const unsigned int output_index, const CPubKey& blinding_pubkey, const CAmount value, const uint256& value_factor, const CAsset& asset, const uint256& asset_factor);
// Convenience method to retrieve all blinding data at once, for an ordinary non-issuance tx
void GetNonIssuanceBlindingData(const unsigned int output_index, CPubKey* blinding_pubkey_out, CAmount* value_out, uint256* value_factor_out, CAsset* asset_out, uint256* asset_factor_out) const;
//! Returns either the value out (if it is known) or -1
CAmount GetOutputValueOut(unsigned int ouput_index) const;

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

@ -138,8 +138,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