mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Merge #600: PSBT for Confidential Assets
3127c6561Add legacy help text for walletprocesspsbt suggesting to use new RPCs instead. (Glenn Willen)d39925b08Disable PSBT RPCs when not in g_con_elementsmode. (Glenn Willen)2848b520bPSBT for Confidential Assets (Glenn Willen)f87684a00Add 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:
commit
c67ef2938c
21 changed files with 1217 additions and 245 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
77
src/psbt.cpp
77
src/psbt.cpp
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
224
src/psbt.h
224
src/psbt.h
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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"} },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"} },
|
||||
|
|
|
|||
|
|
@ -30,4 +30,5 @@ bool EnsureWalletIsAvailable(CWallet *, bool avoidException);
|
|||
|
||||
UniValue getaddressinfo(const JSONRPCRequest& request);
|
||||
UniValue signrawtransactionwithwallet(const JSONRPCRequest& request);
|
||||
|
||||
#endif //BITCOIN_WALLET_RPCWALLET_H
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@ objext
|
|||
unselect
|
||||
useable
|
||||
mut
|
||||
te
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue