From 9c55d0a1751b7c0a8fdb9a21e5a388ad23960457 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 19 Sep 2021 21:14:10 +0000 Subject: [PATCH 1/7] pset: only check asset/amount proofs in case both explicit+blinded values are provided --- doc/pset.mediawiki | 8 ++++---- src/psbt.h | 22 ++-------------------- src/wallet/wallet.cpp | 16 ++++++++++------ 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/doc/pset.mediawiki b/doc/pset.mediawiki index fc6e011cb1..bc04590e06 100644 --- a/doc/pset.mediawiki +++ b/doc/pset.mediawiki @@ -94,7 +94,7 @@ The currently defined elements per-input proprietary types are as folows: | None | No key data | <33 byte commitment> -| The 33 byte Value Commitment. If provided, PSBT_ELEMENTS_IN_ISSUANCE_BLIND_VALUE_PROOF must be provided too. +| The 33 byte Value Commitment. If provided, either PSBT_ELEMENTS_IN_ISSUANCE_VALUE must be removed, or PSBT_ELEMENTS_IN_ISSUANCE_BLIND_VALUE_PROOF must be provided too. | | 0 | 2 @@ -194,7 +194,7 @@ The currently defined elements per-input proprietary types are as folows: | None | No key data | <33 byte commitment> -| The 33 byte commitment to the inflation keys output value in this issuance. If provided, PSBT_ELEMENTS_IN_ISSUANCE_BLIND_INFLATION_KEYS_PROOF must be provided too. +| The 33 byte commitment to the inflation keys output value in this issuance. If provided, either PSBT_ELEMENTS_IN_ISSUANCE_INFLATION_KEYS must be removed or PSBT_ELEMENTS_IN_ISSUANCE_BLIND_INFLATION_KEYS_PROOF must be provided too. | | 0 | 2 @@ -268,7 +268,7 @@ The currently defined elements per-output proprietary types are as follows: | None | No key data | <33 byte commitment> -| The 33 byte Value Commitment for this output. If provided, PSBT_ELEMENTS_OUT_BLIND_VALUE_PROOF must be provided too. +| The 33 byte Value Commitment for this output. If provided, either PSBT_OUT_VALUE must be removed or PSBT_ELEMENTS_OUT_BLIND_VALUE_PROOF must be provided too. | | 0 | 2 @@ -288,7 +288,7 @@ The currently defined elements per-output proprietary types are as follows: | None | No key data | <33 byte commitment> -| The 33 byte Asset Commitment for this output. If provided, PSBT_ELEMENTS_OUT_BLIND_ASSET_PROOF must be provided too. +| The 33 byte Asset Commitment for this output. If provided, either PSBT_ELEMENTS_OUT_ASSET must be removed or PSBT_ELEMENTS_OUT_BLIND_ASSET_PROOF must be provided too. | | 0 | 2 diff --git a/src/psbt.h b/src/psbt.h index f68fa51c50..e0521a613c 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -926,18 +926,6 @@ struct PSBTInput if (prev_out == nullopt) { throw std::ios_base::failure("Previous output's index is required in PSBTv2"); } - if (!m_issuance_value_commitment.IsNull() && m_issuance_value == nullopt) { - throw std::ios_base::failure("Explicit issuance value must be provided if its commitment is provided too"); - } - if (!m_issuance_value_commitment.IsNull() && m_blind_issuance_value_proof.empty()) { - throw std::ios_base::failure("Blind issuance value proof must be provided if its commitment is provided too"); - } - if (!m_issuance_inflation_keys_commitment.IsNull() && m_issuance_inflation_keys_amount == nullopt) { - throw std::ios_base::failure("Explicit issuance inflation keys amount must be provided if its commitment is provided too"); - } - if (!m_issuance_inflation_keys_commitment.IsNull() && m_blind_issuance_inflation_keys_proof.empty()) { - throw std::ios_base::failure("Blind issuance inflation keys value proof must be provided if its commitment is provided too"); - } if (!m_issuance_value_commitment.IsNull() && m_issuance_rangeproof.empty()) { throw std::ios_base::failure("Issuance value commitment provided without value rangeproof"); } @@ -1319,21 +1307,15 @@ struct PSBTOutput // Make sure required PSBTv2 fields are present if (m_psbt_version >= 2) { - if (amount == nullopt) { + if (amount == nullopt && m_value_commitment.IsNull()) { throw std::ios_base::failure("Output amount is required in PSBTv2"); } if (script == nullopt) { throw std::ios_base::failure("Output script is required in PSBTv2"); } - if (m_asset.IsNull()) { + if (m_asset.IsNull() && m_asset_commitment.IsNull()) { throw std::ios_base::failure("Output asset is required in PSET"); } - if (!m_value_commitment.IsNull() && m_blind_value_proof.empty()) { - throw std::ios_base::failure("Blind value proof must be provided if value commitment is provided"); - } - if (!m_asset_commitment.IsNull() && m_blind_asset_proof.empty()) { - throw std::ios_base::failure("Blind asset proof must be provided if asset commitment is provided"); - } if (m_blinding_pubkey.IsValid() && m_blinder_index == nullopt) { throw std::ios_base::failure("Output is blinded but does not have a blinder index"); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cd75175d79..4588f53293 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2837,13 +2837,17 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp if (!o.IsFullyBlinded()) { return TransactionError::BLINDING_REQUIRED; } - assert(!o.m_blind_value_proof.empty()); - assert(!o.m_blind_asset_proof.empty()); - if (!VerifyBlindValueProof(*o.amount, o.m_value_commitment, o.m_blind_value_proof, o.m_asset_commitment)) { - return TransactionError::INVALID_VALUE_PROOF; + if (o.amount) { + assert(!o.m_blind_value_proof.empty()); + if (!VerifyBlindValueProof(*o.amount, o.m_value_commitment, o.m_blind_value_proof, o.m_asset_commitment)) { + return TransactionError::INVALID_VALUE_PROOF; + } } - if (!VerifyBlindAssetProof(o.m_blind_asset_proof, o.m_asset_commitment)) { - return TransactionError::INVALID_ASSET_PROOF; + if (!o.m_asset.IsNull()) { + assert(!o.m_blind_asset_proof.empty()); + if (!VerifyBlindAssetProof(o.m_blind_asset_proof, o.m_asset_commitment)) { + return TransactionError::INVALID_ASSET_PROOF; + } } } } From c88eb96e7468072742beb7985e320547d88cc92e Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 19 Sep 2021 21:48:06 +0000 Subject: [PATCH 2/7] pset: check that we can get the blinding factors from any IsMine outputs before signing Arguably we should do this for signrawtransaction too but it'd be a lot of duplicated code for a deprecated workflow. --- src/wallet/wallet.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4588f53293..e7ce05a317 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2829,6 +2829,8 @@ BlindingStatus CWallet::WalletBlindPSBT(PartiallySignedTransaction& psbtx) const } TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool imbalance_ok, bool bip32derivs, size_t* n_signed) const { + LOCK(cs_wallet); + // If we're signing, check that the transaction is not still in need of blinding // Also check that the amount and asset proofs are valid if (sign) { @@ -2849,6 +2851,33 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp return TransactionError::INVALID_ASSET_PROOF; } } + + if (o.script && IsMine(*o.script)) { + CKey blinding_key; + if ((blinding_key = GetBlindingKey(&*o.script)).IsValid()) { + CAmount value; + uint256 value_factor; + CAsset asset; + uint256 asset_factor; + + CConfidentialNonce nonce; + nonce.vchCommitment.insert(nonce.vchCommitment.end(), o.m_ecdh_pubkey.begin(), o.m_ecdh_pubkey.end()); + if (!UnblindConfidentialPair(blinding_key, o.m_value_commitment, o.m_asset_commitment, nonce, o.script.get(), o.m_value_rangeproof, value, value_factor, asset, asset_factor)) { + // These assertions are cryptographically impossible to trigger, as we + // checked the proofs above, and then `UnblindConfidentialPair` checks + // the extracted value/asset against the commitments. + if (o.amount) { + assert(*o.amount == value); + } + if (!o.m_asset.IsNull()) { + assert(CAsset(o.m_asset) == asset); + } + return TransactionError::INVALID_ASSET_PROOF; // FIXME + } + } else { + return TransactionError::INVALID_ASSET_PROOF; // FIXME + } + } } } } @@ -2857,7 +2886,6 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp *n_signed = 0; } - LOCK(cs_wallet); CMutableTransaction tx = psbtx.GetUnsignedTx(); tx.witness.vtxoutwit.resize(tx.vout.size()); From 35cfda73b9ba93c61e2c92b527e3ae84cc5a9d82 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 21 Sep 2021 13:14:11 +0000 Subject: [PATCH 3/7] PSET: do not assume in GetUnsignedTx that explicit amounts/assets are available --- src/psbt.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/psbt.cpp b/src/psbt.cpp index ebcb06e4c6..776b107191 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -135,15 +135,20 @@ CMutableTransaction PartiallySignedTransaction::GetUnsignedTx(bool force_unblind CTxOut txout; CTxOutWitness txoutwit; txout.scriptPubKey = *output.script; - if (output.IsFullyBlinded() && !force_unblinded) { - txout.nValue = output.m_value_commitment; - txout.nAsset = output.m_asset_commitment; - txout.nNonce.vchCommitment.insert(txout.nNonce.vchCommitment.end(), output.m_ecdh_pubkey.begin(), output.m_ecdh_pubkey.end()); - txoutwit.vchRangeproof = output.m_value_rangeproof; - txoutwit.vchSurjectionproof = output.m_asset_surjection_proof; - } else { + if (output.m_value_commitment.IsNull() || (output.amount != nullopt && force_unblinded)) { txout.nValue.SetToAmount(*output.amount); + } else { + txout.nValue = output.m_value_commitment; + txoutwit.vchRangeproof = output.m_value_rangeproof; + } + if (output.m_asset_commitment.IsNull() || (!output.m_asset.IsNull() && force_unblinded)) { txout.nAsset.SetToAsset(CAsset(output.m_asset)); + } else { + txout.nAsset = output.m_asset_commitment; + txoutwit.vchSurjectionproof = output.m_asset_surjection_proof; + } + if (output.m_ecdh_pubkey.IsValid() && !force_unblinded) { + txout.nNonce.vchCommitment.insert(txout.nNonce.vchCommitment.end(), output.m_ecdh_pubkey.begin(), output.m_ecdh_pubkey.end()); } mtx.vout.push_back(txout); mtx.witness.vtxoutwit.push_back(txoutwit); From c6f801d4ce4b6ace168f951bddfcf54cf6e9f450 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 20 Sep 2021 22:40:22 +0000 Subject: [PATCH 4/7] PSET: encapsulate blind proof checks into one method --- src/blindpsbt.cpp | 31 +++++++++++++++++++++++++++++-- src/blindpsbt.h | 13 +++++++++++-- src/wallet/wallet.cpp | 21 +++++++++------------ 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/blindpsbt.cpp b/src/blindpsbt.cpp index 5ced63e87b..11b640c10c 100644 --- a/src/blindpsbt.cpp +++ b/src/blindpsbt.cpp @@ -66,7 +66,7 @@ bool CreateAssetSurjectionProof(std::vector& output_proof, const return true; } -bool VerifyBlindAssetProof(const std::vector& proof, const CConfidentialAsset& conf_asset) +static bool VerifyBlindAssetProof(const std::vector& proof, const CConfidentialAsset& conf_asset) { secp256k1_surjectionproof surj_proof; if (secp256k1_surjectionproof_parse(secp256k1_blind_context, &surj_proof, proof.data(), proof.size()) == 0) { @@ -132,7 +132,7 @@ bool CreateBlindValueProof(std::vector& rangeproof, const uint256 return res == 1; } -bool VerifyBlindValueProof(CAmount value, const CConfidentialValue& conf_value, const std::vector& proof, const CConfidentialAsset& conf_asset) +static bool VerifyBlindValueProof(CAmount value, const CConfidentialValue& conf_value, const std::vector& proof, const CConfidentialAsset& conf_asset) { secp256k1_pedersen_commitment value_commit; if (secp256k1_pedersen_commitment_parse(secp256k1_blind_context, &value_commit, conf_value.vchCommitment.data()) == 0) { @@ -152,6 +152,33 @@ bool VerifyBlindValueProof(CAmount value, const CConfidentialValue& conf_value, return min_value == (uint64_t)value; } +BlindProofResult VerifyBlindProofs(const PSBTOutput& o) { + // No blinding, no problem + if (!o.IsBlinded()) { + return BlindProofResult::OK; + } else if (!o.IsFullyBlinded()) { + return BlindProofResult::NOT_FULLY_BLINDED; + } + + if (o.amount != nullopt) { + if (o.m_blind_value_proof.empty()) { + return BlindProofResult::MISSING_VALUE_PROOF; + } else if (!VerifyBlindValueProof(*o.amount, o.m_value_commitment, o.m_blind_value_proof, o.m_asset_commitment)) { + return BlindProofResult::INVALID_VALUE_PROOF; + } + } + + if (!o.m_asset.IsNull()) { + if (o.m_blind_asset_proof.empty()) { + return BlindProofResult::MISSING_ASSET_PROOF; + } else if (!VerifyBlindAssetProof(o.m_blind_asset_proof, o.m_asset_commitment)) { + return BlindProofResult::INVALID_ASSET_PROOF; + } + } + + return BlindProofResult::OK; +} + void CreateAssetCommitment(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const uint256& asset_blinder) { conf_asset.vchCommitment.resize(CConfidentialAsset::nCommittedSize); diff --git a/src/blindpsbt.h b/src/blindpsbt.h index 2917334d91..fd3d0e13f1 100644 --- a/src/blindpsbt.h +++ b/src/blindpsbt.h @@ -16,6 +16,7 @@ #include struct PartiallySignedTransaction; +struct PSBTOutput; enum class BlindingStatus { @@ -29,16 +30,24 @@ enum class BlindingStatus NO_BLIND_OUTPUTS, }; +enum class BlindProofResult { + OK, + NOT_FULLY_BLINDED, + MISSING_VALUE_PROOF, + MISSING_ASSET_PROOF, + INVALID_VALUE_PROOF, + INVALID_ASSET_PROOF, +}; + std::string GetBlindingStatusError(const BlindingStatus& status); bool CreateAssetSurjectionProof(std::vector& output_proof, const std::vector& fixed_input_tags, const std::vector& ephemeral_input_tags, const std::vector& input_asset_blinders, const uint256& output_asset_blinder, const secp256k1_generator& output_asset_tag, const CAsset& asset, size_t num_targets = MAX_SURJECTION_TARGETS); -bool VerifyBlindAssetProof(const std::vector& proof, const CConfidentialAsset& conf_asset); uint256 GenerateRangeproofECDHKey(CPubKey& ephemeral_pubkey, const CPubKey blinding_pubkey); bool CreateValueRangeProof(std::vector& rangeproof, const uint256& value_blinder, const uint256& nonce, const CAmount amount, const CScript& scriptPubKey, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen, const CAsset& asset, const uint256& asset_blinder); bool CreateBlindValueProof(std::vector& rangeproof, const uint256& value_blinder, const CAmount amount, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen); -bool VerifyBlindValueProof(CAmount value, const CConfidentialValue& conf_value, const std::vector& proof, const CConfidentialAsset& conf_asset); void CreateAssetCommitment(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const uint256& asset_blinder); void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const uint256& value_blinder, const secp256k1_generator& asset_gen, const CAmount amount); BlindingStatus BlindPSBT(PartiallySignedTransaction& psbt, std::map> our_input_data, std::map> our_issuances_to_blind); +BlindProofResult VerifyBlindProofs(const PSBTOutput& o); #endif //BITCOIN_BLINDPSBT_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e7ce05a317..08b545454a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2836,20 +2836,17 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp if (sign) { for (const PSBTOutput& o : psbtx.outputs) { if (o.IsBlinded()) { - if (!o.IsFullyBlinded()) { - return TransactionError::BLINDING_REQUIRED; - } - if (o.amount) { - assert(!o.m_blind_value_proof.empty()); - if (!VerifyBlindValueProof(*o.amount, o.m_value_commitment, o.m_blind_value_proof, o.m_asset_commitment)) { + switch (VerifyBlindProofs(o)) { + case BlindProofResult::OK: + break; + case BlindProofResult::NOT_FULLY_BLINDED: + return TransactionError::BLINDING_REQUIRED; + case BlindProofResult::INVALID_VALUE_PROOF: + case BlindProofResult::MISSING_VALUE_PROOF: return TransactionError::INVALID_VALUE_PROOF; - } - } - if (!o.m_asset.IsNull()) { - assert(!o.m_blind_asset_proof.empty()); - if (!VerifyBlindAssetProof(o.m_blind_asset_proof, o.m_asset_commitment)) { + case BlindProofResult::INVALID_ASSET_PROOF: + case BlindProofResult::MISSING_ASSET_PROOF: return TransactionError::INVALID_ASSET_PROOF; - } } if (o.script && IsMine(*o.script)) { From 694ec795e48149e80338e249c332c9e4f649befb Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 21 Sep 2021 13:01:03 +0000 Subject: [PATCH 5/7] PSET: fix asset proof generation and verification A couple issues with this -- one is that it was verifying the asset commitment against itself, rather than against the explicit asset, and the other is that the verification logic had an extra `== 0` at the end which inverted the verification check. Both pretty embarassing to have not caught in review.. --- src/blindpsbt.cpp | 55 +++++++++++++++++++++++++++++++++++++++++------ src/blindpsbt.h | 1 - 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/blindpsbt.cpp b/src/blindpsbt.cpp index 11b640c10c..201f035865 100644 --- a/src/blindpsbt.cpp +++ b/src/blindpsbt.cpp @@ -66,19 +66,23 @@ bool CreateAssetSurjectionProof(std::vector& output_proof, const return true; } -static bool VerifyBlindAssetProof(const std::vector& proof, const CConfidentialAsset& conf_asset) +static bool VerifyBlindAssetProof(const uint256& asset, const std::vector& proof, const CConfidentialAsset& conf_asset) { secp256k1_surjectionproof surj_proof; if (secp256k1_surjectionproof_parse(secp256k1_blind_context, &surj_proof, proof.data(), proof.size()) == 0) { return false; } - secp256k1_generator gen; - if (secp256k1_generator_parse(secp256k1_blind_context, &gen, conf_asset.vchCommitment.data()) == 0) { + secp256k1_generator blinded_asset_gen; + if (secp256k1_generator_parse(secp256k1_blind_context, &blinded_asset_gen, conf_asset.vchCommitment.data()) == 0) { + return false; + } + secp256k1_generator asset_gen; + if (secp256k1_generator_generate(secp256k1_blind_context, &asset_gen, asset.data()) == 0) { return false; } - return secp256k1_surjectionproof_verify(secp256k1_blind_context, &surj_proof, &gen, 1, &gen) == 0; + return secp256k1_surjectionproof_verify(secp256k1_blind_context, &surj_proof, &asset_gen, 1, &blinded_asset_gen); } uint256 GenerateRangeproofECDHKey(CPubKey& ephemeral_pubkey, const CPubKey blinding_pubkey) @@ -116,7 +120,7 @@ bool CreateValueRangeProof(std::vector& rangeproof, const uint256 } // Create an explicit value rangeproof which proves that the commitment commits to an explicit value -bool CreateBlindValueProof(std::vector& rangeproof, const uint256& value_blinder, const CAmount amount, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen) +static bool CreateBlindValueProof(std::vector& rangeproof, const uint256& value_blinder, const CAmount amount, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen) { // Prep rangeproof size_t rangeproof_len = 5134; @@ -132,6 +136,43 @@ bool CreateBlindValueProof(std::vector& rangeproof, const uint256 return res == 1; } +// Create an explicit value rangeproof which proves that the commitment commits to an explicit value +static bool CreateBlindAssetProof(std::vector& assetproof, const CAsset& asset, const CConfidentialAsset& asset_commit, const uint256& asset_blinder) +{ + const unsigned char zero32[32] = {0}; + secp256k1_surjectionproof proof; + size_t input_index; + secp256k1_generator asset_gen; + secp256k1_generator blinded_asset_gen; + secp256k1_fixed_asset_tag fixed_tag; + memcpy(&fixed_tag, asset.begin(), 32); + + if (!secp256k1_generator_generate(secp256k1_blind_context, &asset_gen, asset.begin())) { + return false; + } + if (secp256k1_generator_parse(secp256k1_blind_context, &blinded_asset_gen, asset_commit.vchCommitment.data()) == 0) { + return false; + } + + if (!secp256k1_surjectionproof_initialize(secp256k1_blind_context, &proof, &input_index, &fixed_tag, 1, 1, &fixed_tag, 1, zero32)) { + return false; + } + assert(input_index == 0); + + if (!secp256k1_surjectionproof_generate(secp256k1_blind_context, &proof, &asset_gen, 1, &blinded_asset_gen, 0, zero32, asset_blinder.data())) { + return false; + } + if (!secp256k1_surjectionproof_verify(secp256k1_blind_context, &proof, &asset_gen, 1, &blinded_asset_gen)) { + return false; + } + + size_t output_len = secp256k1_surjectionproof_serialized_size(secp256k1_blind_context, &proof); + assetproof.resize(output_len); + secp256k1_surjectionproof_serialize(secp256k1_blind_context, &assetproof[0], &output_len, &proof); + assert(output_len == assetproof.size()); + return true; +} + static bool VerifyBlindValueProof(CAmount value, const CConfidentialValue& conf_value, const std::vector& proof, const CConfidentialAsset& conf_asset) { secp256k1_pedersen_commitment value_commit; @@ -171,7 +212,7 @@ BlindProofResult VerifyBlindProofs(const PSBTOutput& o) { if (!o.m_asset.IsNull()) { if (o.m_blind_asset_proof.empty()) { return BlindProofResult::MISSING_ASSET_PROOF; - } else if (!VerifyBlindAssetProof(o.m_blind_asset_proof, o.m_asset_commitment)) { + } else if (!VerifyBlindAssetProof(o.m_asset, o.m_blind_asset_proof, o.m_asset_commitment)) { return BlindProofResult::INVALID_ASSET_PROOF; } } @@ -498,7 +539,7 @@ BlindingStatus BlindPSBT(PartiallySignedTransaction& psbt, std::map blind_asset_proof; - if (!CreateAssetSurjectionProof(blind_asset_proof, fixed_input_tags, ephemeral_input_tags, input_asset_blinders, asset_blinder, asset_generator, asset, /* num_targets */ 1)) { + if (!CreateBlindAssetProof(blind_asset_proof, asset, asset_commitment, asset_blinder)) { return BlindingStatus::ASP_UNABLE; } diff --git a/src/blindpsbt.h b/src/blindpsbt.h index fd3d0e13f1..51cc09dff5 100644 --- a/src/blindpsbt.h +++ b/src/blindpsbt.h @@ -44,7 +44,6 @@ std::string GetBlindingStatusError(const BlindingStatus& status); bool CreateAssetSurjectionProof(std::vector& output_proof, const std::vector& fixed_input_tags, const std::vector& ephemeral_input_tags, const std::vector& input_asset_blinders, const uint256& output_asset_blinder, const secp256k1_generator& output_asset_tag, const CAsset& asset, size_t num_targets = MAX_SURJECTION_TARGETS); uint256 GenerateRangeproofECDHKey(CPubKey& ephemeral_pubkey, const CPubKey blinding_pubkey); bool CreateValueRangeProof(std::vector& rangeproof, const uint256& value_blinder, const uint256& nonce, const CAmount amount, const CScript& scriptPubKey, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen, const CAsset& asset, const uint256& asset_blinder); -bool CreateBlindValueProof(std::vector& rangeproof, const uint256& value_blinder, const CAmount amount, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen); void CreateAssetCommitment(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const uint256& asset_blinder); void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const uint256& value_blinder, const secp256k1_generator& asset_gen, const CAmount amount); BlindingStatus BlindPSBT(PartiallySignedTransaction& psbt, std::map> our_input_data, std::map> our_issuances_to_blind); From 03b835c88796177dd7f28c0c604e9afcfa278121 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 21 Sep 2021 16:35:37 +0000 Subject: [PATCH 6/7] PSET: output warnings in decodepsbt and analyzepsbt about blinding status --- src/node/psbt.cpp | 10 +++++-- src/node/psbt.h | 10 +++++++ src/rpc/rawtransaction.cpp | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index a9e76c1493..d3712dbadf 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -87,9 +87,15 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) } } - for (const PSBTOutput& output : psbtx.outputs) { + result.outputs.resize(psbtx.outputs.size()); + for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) { + const PSBTOutput& output = psbtx.outputs[i]; + PSBTOutputAnalysis& output_analysis = result.outputs[i]; CTxOut txout = output.GetTxOut(); - if (output.IsBlinded()) { + + output_analysis.is_blind = output.IsBlinded(); + output_analysis.proof_result = VerifyBlindProofs(output); + if (output_analysis.is_blind) { has_blinded_outputs = true; if (!output.IsFullyBlinded()) { result.next = PSBTRole::BLINDER; diff --git a/src/node/psbt.h b/src/node/psbt.h index 2d4cf70c00..35f8de7667 100644 --- a/src/node/psbt.h +++ b/src/node/psbt.h @@ -6,6 +6,7 @@ #define BITCOIN_NODE_PSBT_H #include +#include /** * Holds an analysis of one input from a PSBT @@ -21,6 +22,14 @@ struct PSBTInputAnalysis { uint256 missing_witness_script; //!< SHA256 of witness script, if missing }; +/** + * Holds an analysis of one output from a PSBT + */ +struct PSBTOutputAnalysis { + bool is_blind; //!< Whether the output should be blinded (has a set ECDH pubkey) + BlindProofResult proof_result; //!< Result of checking the explicit-confidential-matching proof +}; + /** * Holds the results of AnalyzePSBT (miscellaneous information about a PSBT) */ @@ -29,6 +38,7 @@ struct PSBTAnalysis { Optional estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction Optional fee; //!< Amount of fee being paid by the transaction std::vector inputs; //!< More information about the individual inputs of the transaction + std::vector outputs; //!< More information about the individual outputs of the transaction PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next std::string error; //!< Error message diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 13c57af1e3..f60953eee0 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1227,6 +1227,7 @@ static RPCHelpMan decodepsbt() {RPCResult::Type::STR_HEX, "blinding_pubkey", "The blinding pubkey for the output"}, {RPCResult::Type::STR_HEX, "blind_value_proof", "Explicit value rangeproof that proves the value commitment matches the value"}, {RPCResult::Type::STR_HEX, "blind_asset_proof", "Assert surjection proof that proves the assert commitment matches the asset"}, + {RPCResult::Type::STR, "status", "information about how the output has been blinded, if available"}, {RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, @@ -1673,6 +1674,27 @@ static RPCHelpMan decodepsbt() out.pushKV("blind_asset_proof", HexStr(output.m_blind_asset_proof)); } + switch (VerifyBlindProofs(output)) { + case BlindProofResult::OK: + // all good + break; + case BlindProofResult::NOT_FULLY_BLINDED: + out.pushKV("status", "needs blinding"); + break; + case BlindProofResult::MISSING_VALUE_PROOF: + out.pushKV("status", "WARNING: has confidential and explicit values but no proof connecting them"); + break; + case BlindProofResult::MISSING_ASSET_PROOF: + out.pushKV("status", "WARNING: has confidential and explicit assets but no proof connecting them"); + break; + case BlindProofResult::INVALID_VALUE_PROOF: + out.pushKV("status", "ERROR: has invalid value proof, the value may be a lie!"); + break; + case BlindProofResult::INVALID_ASSET_PROOF: + out.pushKV("status", "ERROR: has invalid asset proof, the asset may be a lie!"); + break; + } + // Proprietary if (!output.m_proprietary.empty()) { UniValue proprietary(UniValue::VARR); @@ -2316,6 +2338,14 @@ static RPCHelpMan analyzepsbt() {RPCResult::Type::STR, "next", /* optional */ true, "Role of the next person that this input needs to go to"}, }}, }}, + {RPCResult::Type::ARR, "outputs", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::BOOL, "blind", "whether the output should be blinded"}, + {RPCResult::Type::STR, "status", "to what extent the output has been blinded"}, + }}, + }}, {RPCResult::Type::NUM, "estimated_vsize", /* optional */ true, "Estimated vsize of the final signed transaction"}, {RPCResult::Type::STR_AMOUNT, "estimated_feerate", /* optional */ true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kB. Shown only if all UTXO slots in the PSBT have been filled"}, {RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled"}, @@ -2376,6 +2406,36 @@ static RPCHelpMan analyzepsbt() } if (!inputs_result.empty()) result.pushKV("inputs", inputs_result); + UniValue outputs_result(UniValue::VARR); + for (const auto& output : psbta.outputs) { + UniValue output_univ(UniValue::VOBJ); + + output_univ.pushKV("blind", output.is_blind); + switch (output.proof_result) { + case BlindProofResult::OK: + output_univ.pushKV("status", "done"); + break; + case BlindProofResult::NOT_FULLY_BLINDED: + output_univ.pushKV("status", "unblinded"); + break; + case BlindProofResult::MISSING_VALUE_PROOF: + output_univ.pushKV("status", "WARNING: has confidential and explicit values but no proof connecting them"); + break; + case BlindProofResult::MISSING_ASSET_PROOF: + output_univ.pushKV("status", "WARNING: has confidential and explicit assets but no proof connecting them"); + break; + case BlindProofResult::INVALID_VALUE_PROOF: + output_univ.pushKV("status", "ERROR: has invalid value proof, the value may be a lie!"); + break; + case BlindProofResult::INVALID_ASSET_PROOF: + output_univ.pushKV("status", "ERROR: has invalid asset proof, the asset may be a lie!"); + break; + } + + outputs_result.push_back(output_univ); + } + if (!outputs_result.empty()) result.pushKV("outputs", outputs_result); + if (psbta.estimated_vsize != nullopt) { result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize); } From d1a1d1911301dfb42924166e89f5da706cd01c3b Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 21 Sep 2021 18:34:36 +0000 Subject: [PATCH 7/7] test: add decodepsbt and analyzepsbt tests for blinding proofs --- test/functional/rpc_psbt.py | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 4edb9869e5..ec25408a41 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -716,6 +716,87 @@ class PSBTTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() + def pset_confidential_proofs(self): + BLINDED = "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" + NO_VALUE_PROOF = "cHNldP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAP1UAQIAAAAAASopobdl5W15RSedscp/8bxEXKuKIMOZw+JTqgD8qJEKBAAAAAD9////Awrye7Xu4kI5VnpTDeGaq8sYdXP3qdzYaHrLDRzaC8y51ggl1U8hJxSo+8GcTzHv926wsqTTkOrdBnJo8qcLwLQauQKktt71EJU7HTH5HsgG4kJV/tC32F992/WgieIPRkUkmxYAFPrs/iioimRS5hoJKl/hua83d7rwC1uuuLvfuQh38wHS+0Vg2ecXzypsUabYofOFaGSrICByCKvjgTF6TdHNp2el7Cwi+94dy4qMDrEh/25Aqnc+5qABAqWPEY9ZNCz7m64pANrr04bVgPxaWCr7LvvWGH5FLzvRFgAU96wAzcLFRah7B8gq17sVY9Uso18BIw9PXUt8b6hFgG7k9ncTRZ4baejmD87i5JQMeg1d4bIBAAAAAAAAKfQAAAAAAAABAXoK8nu17uJCOVZ6Uw3hmqvLGHVz96nc2Gh6yw0c2gvMudYIJdVPIScUqPvBnE8x7/dusLKk05Dq3QZyaPKnC8C0GrkCpLbe9RCVOx0x+R7IBuJCVf7Qt9hffdv1oIniD0ZFJJsWABT67P4oqIpkUuYaCSpf4bmvN3e68CIGA3pgD7iheh1WkyCWvviXQBa9KOJk6JBeYxEpPuxiRBOvEElHIxkAAACAAQAAgAMAAIABDiB25bQww62kp1L1uQVb7MxEVoem8kCzSmM5DW09I9V6DQEPBAAAAAABEAT/////B/wEcHNldA79CwFgAgAHY7+9IRzxAXWemL7C9M7CBAqQoSrXRoxI5/YnMLV6nV/GBMEhmvoDFJcNzRXI/LrIRMLZFvNrP5IupN8OZ+4q+++aJTnuYCZIDR1pssb0JHA0z2UXkEYdHv26qoW26RbLf2LNh29yVIOHG3jqqc7+L7F4UELZmjlEs6R1sulqQ0ePCUUgAsqURkdnNKtl0nORiyLN/9JfqGGTC30WhsdXifWRmqOfkWil0Va1bDYumMU7zJdW/go83ODuZ5VZVWFsBLFSn9HxF1SaFCGt197qo8dr+vhPZwb72k13A72D+5Lx7UKoYqamRJsoAZdUZ/oVd9GRlPbAmRPV7iOxmPYf+t9AQiEd0Z4AIgICuujF5+Lk/uCeX9+RWtJ8ioG51rogGduwt+iY1tZFtjUQSUcjGQAAAIAAAACACwAAgAEEFgAUg+8ATSQ8VvNg+WJAuweXm6kXlFkH/ARwc2V0ASEIBoHxCnQKKMcpdKYCHdu36jzQ0zSc49oGuDQl7Nvus3gBAwgAv3xIGAkAAAf8BHBzZXQDIQsuVWSYT/UkUbq/hYsdWuoo3ARSy5K7e//36h8QhjdKRAf8BHBzZXQCICMPT11LfG+oRYBu5PZ3E0WeG2no5g/O4uSUDHoNXeGyB/wEcHNldAT9CwFgAgAACRhIfL75AJaUOCJ2q+YnbnYTFqluECvtDoJFGcrYvu5VsxPdASJNduFIJRBglnPdW73QRjqt+r3KlxBQ3XUWTce6is6cGED9eySEVJwBXz4Mt8SjqM2GsyUfqC+Ey3+APGgh54MYLt+HHKmt6ibcvE1DDU/UGpVo+I3cY/kgKJzrWMG6y/jDm/CHcF49L8EBtYC7iSrBhwzmDk7DmiViiQFCTUDfIqilX/piqS9ZlO4JNydA5kmLqXkj/xtR2hKt57wknqqvM7/car1S4Do8VljtG9lCzvSOBtBvijSwpFY1KaVFjpj0UZI9XJQ2eEbMrqC0qygNBi1f+ULyZFccNSGpXaZnrZAH/ARwc2V0BUMBAAECnwdoJ4rVnGgLT0He5GaLEhDnGqCKcH0nlTi1T53tBYMI8InonQGT61IAjoLcRxOqzMLgEC3KXg7yW8x6d6VmB/wEcHNldAYhAwxmNPa94Vg9u/nZBWC/8IYTgnp85V5TMOEFWTTAcF2pB/wEcHNldAchAitGVbG/bZNcV2ifjimuh04FOwRlxNrNPva66U6/RiHFB/wEcHNldAgEAAAAAAf8BHBzZXQKQwEAAYmb3UN+9UFQ5oQVnS4fVFtPZyAj0w32TWUTWGVZzYC0AukwnXuSGSK+3wXt2AIBV0G9wcVrDXfxmxV+IPuDjzQAAQQAAQMIAOH1BQAAAAAH/ARwc2V0AiAjD09dS3xvqEWAbuT2dxNFnhtp6OYPzuLklAx6DV3hsgf8BHBzZXQIBAAAAAAA" + BAD_VALUE_PROOF = "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" + NO_ASSET_PROOF = "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" + BAD_ASSET_PROOF = "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" + ONLY_BLIND = "cHNldP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAP1UAQIAAAAAASopobdl5W15RSedscp/8bxEXKuKIMOZw+JTqgD8qJEKBAAAAAD9////Awrye7Xu4kI5VnpTDeGaq8sYdXP3qdzYaHrLDRzaC8y51ggl1U8hJxSo+8GcTzHv926wsqTTkOrdBnJo8qcLwLQauQKktt71EJU7HTH5HsgG4kJV/tC32F992/WgieIPRkUkmxYAFPrs/iioimRS5hoJKl/hua83d7rwC1uuuLvfuQh38wHS+0Vg2ecXzypsUabYofOFaGSrICByCKvjgTF6TdHNp2el7Cwi+94dy4qMDrEh/25Aqnc+5qABAqWPEY9ZNCz7m64pANrr04bVgPxaWCr7LvvWGH5FLzvRFgAU96wAzcLFRah7B8gq17sVY9Uso18BIw9PXUt8b6hFgG7k9ncTRZ4baejmD87i5JQMeg1d4bIBAAAAAAAAKfQAAAAAAAABAXoK8nu17uJCOVZ6Uw3hmqvLGHVz96nc2Gh6yw0c2gvMudYIJdVPIScUqPvBnE8x7/dusLKk05Dq3QZyaPKnC8C0GrkCpLbe9RCVOx0x+R7IBuJCVf7Qt9hffdv1oIniD0ZFJJsWABT67P4oqIpkUuYaCSpf4bmvN3e68CIGA3pgD7iheh1WkyCWvviXQBa9KOJk6JBeYxEpPuxiRBOvEElHIxkAAACAAQAAgAMAAIABDiB25bQww62kp1L1uQVb7MxEVoem8kCzSmM5DW09I9V6DQEPBAAAAAABEAT/////B/wEcHNldA79CwFgAgAHY7+9IRzxAXWemL7C9M7CBAqQoSrXRoxI5/YnMLV6nV/GBMEhmvoDFJcNzRXI/LrIRMLZFvNrP5IupN8OZ+4q+++aJTnuYCZIDR1pssb0JHA0z2UXkEYdHv26qoW26RbLf2LNh29yVIOHG3jqqc7+L7F4UELZmjlEs6R1sulqQ0ePCUUgAsqURkdnNKtl0nORiyLN/9JfqGGTC30WhsdXifWRmqOfkWil0Va1bDYumMU7zJdW/go83ODuZ5VZVWFsBLFSn9HxF1SaFCGt197qo8dr+vhPZwb72k13A72D+5Lx7UKoYqamRJsoAZdUZ/oVd9GRlPbAmRPV7iOxmPYf+t9AQiEd0Z4AIgICuujF5+Lk/uCeX9+RWtJ8ioG51rogGduwt+iY1tZFtjUQSUcjGQAAAIAAAACACwAAgAEEFgAUg+8ATSQ8VvNg+WJAuweXm6kXlFkH/ARwc2V0ASEIBoHxCnQKKMcpdKYCHdu36jzQ0zSc49oGuDQl7Nvus3gH/ARwc2V0AyELLlVkmE/1JFG6v4WLHVrqKNwEUsuSu3v/9+ofEIY3SkQH/ARwc2V0BP0LAWACAAAJGEh8vvkAlpQ4Inar5idudhMWqW4QK+0OgkUZyti+7lWzE90BIk124UglEGCWc91bvdBGOq36vcqXEFDddRZNx7qKzpwYQP17JIRUnAFfPgy3xKOozYazJR+oL4TLf4A8aCHngxgu34ccqa3qJty8TUMNT9QalWj4jdxj+SAonOtYwbrL+MOb8IdwXj0vwQG1gLuJKsGHDOYOTsOaJWKJAUJNQN8iqKVf+mKpL1mU7gk3J0DmSYupeSP/G1HaEq3nvCSeqq8zv9xqvVLgOjxWWO0b2ULO9I4G0G+KNLCkVjUppUWOmPRRkj1clDZ4RsyuoLSrKA0GLV/5QvJkVxw1IaldpmetkAf8BHBzZXQFQwEAAQKfB2gnitWcaAtPQd7kZosSEOcaoIpwfSeVOLVPne0FgwjwieidAZPrUgCOgtxHE6rMwuAQLcpeDvJbzHp3pWYH/ARwc2V0BiEDDGY09r3hWD27+dkFYL/whhOCenzlXlMw4QVZNMBwXakH/ARwc2V0ByECK0ZVsb9tk1xXaJ+OKa6HTgU7BGXE2s0+9rrpTr9GIcUH/ARwc2V0CAQAAAAAAAEEAAEDCADh9QUAAAAAB/wEcHNldAIgIw9PXUt8b6hFgG7k9ncTRZ4baejmD87i5JQMeg1d4bIH/ARwc2V0CAQAAAAAAA==" + + ## Check warnings for PSETs + for output in self.nodes[0].decodepsbt(BLINDED)["outputs"]: + assert "status" not in output + for output in self.nodes[0].analyzepsbt(BLINDED)["outputs"]: + assert_equal (output["status"], "done") + + stats = [output.get("status") for output in self.nodes[0].decodepsbt(NO_VALUE_PROOF)["outputs"]] + assert_equal(stats, [ + "WARNING: has confidential and explicit values but no proof connecting them", + None, + ]) + stats = [output for output in self.nodes[0].analyzepsbt(NO_VALUE_PROOF)["outputs"]] + assert_equal(stats, [ + {"blind": True, "status": "WARNING: has confidential and explicit values but no proof connecting them" }, + {"blind": False, "status": "done" }, + ]) + + stats = [output.get("status") for output in self.nodes[0].decodepsbt(BAD_VALUE_PROOF)["outputs"]] + assert_equal(stats, [ + "ERROR: has invalid value proof, the value may be a lie!", + None, + ]) + stats = [output for output in self.nodes[0].analyzepsbt(BAD_VALUE_PROOF)["outputs"]] + assert_equal(stats, [ + {"blind": True, "status": "ERROR: has invalid value proof, the value may be a lie!" }, + {"blind": False, "status": "done" }, + ]) + + stats = [output.get("status") for output in self.nodes[0].decodepsbt(NO_ASSET_PROOF)["outputs"]] + assert_equal(stats, [ + "WARNING: has confidential and explicit assets but no proof connecting them", + None, + ]) + stats = [output for output in self.nodes[0].analyzepsbt(NO_ASSET_PROOF)["outputs"]] + assert_equal(stats, [ + {"blind": True, "status": "WARNING: has confidential and explicit assets but no proof connecting them" }, + {"blind": False, "status": "done" }, + ]) + + stats = [output.get("status") for output in self.nodes[0].decodepsbt(BAD_ASSET_PROOF)["outputs"]] + assert_equal(stats, [ + "ERROR: has invalid asset proof, the asset may be a lie!", + None, + ]) + stats = [output for output in self.nodes[0].analyzepsbt(BAD_ASSET_PROOF)["outputs"]] + assert_equal(stats, [ + {"blind": True, "status": "ERROR: has invalid asset proof, the asset may be a lie!" }, + {"blind": False, "status": "done" }, + ]) + + stats = [output.get("status") for output in self.nodes[0].decodepsbt(ONLY_BLIND)["outputs"]] + assert_equal(stats, [None, None]) + stats = [output for output in self.nodes[0].analyzepsbt(ONLY_BLIND)["outputs"]] + assert_equal(stats, [ + {"blind": True, "status": "done" }, + {"blind": False, "status": "done" }, + ]) + + # Check that we can combine these in any combination, as they all have explicit data + assert_equal (self.nodes[0].combinepsbt([BLINDED, BLINDED]), BLINDED) + for pset1 in [ BLINDED, NO_VALUE_PROOF, BAD_VALUE_PROOF, NO_ASSET_PROOF, BAD_ASSET_PROOF ]: + for pset2 in [ BLINDED, NO_VALUE_PROOF, BAD_VALUE_PROOF, NO_ASSET_PROOF, BAD_ASSET_PROOF ]: + combo = self.nodes[0].combinepsbt([pset1, pset2]) + if combo != pset1: + assert_equal (combo, BLINDED) + + # On the other hand, none of these can be combined with the "only confidential, no + # explicit values" version + assert_equal (self.nodes[0].combinepsbt([ONLY_BLIND, ONLY_BLIND]), ONLY_BLIND) + for pset in [ BLINDED, NO_VALUE_PROOF, BAD_VALUE_PROOF, NO_ASSET_PROOF, BAD_ASSET_PROOF ]: + assert_raises_rpc_error(-8, "PSBTs not compatible (different transactions)", self.nodes[0].combinepsbt, [pset, ONLY_BLIND]) + assert_raises_rpc_error(-8, "PSBTs not compatible (different transactions)", self.nodes[0].combinepsbt, [ONLY_BLIND, pset]) + def run_test(self): self.nodes[0].generate(200) self.sync_all() @@ -876,5 +957,8 @@ class PSBTTest(BitcoinTestFramework): assert_raises_rpc_error(-25, 'Inputs missing or spent', self.nodes[0].walletprocesspsbt, "cHNldP8BAgQCAAAAAQMEAAAAAAEEAQEBBQEDAfsEAgAAAAABAKICAAAAAAHH6k+xEgicvmA3NdivY741Mkb1NOcXWr0NNl6hrR/WbgAAAEAA/////wIBIw9PXUt8b6hFgG7k9ncTRZ4baejmD87i5JQMeg1d4bIBAAAAAlQLx/QAFgAUTwXL7rzz4++YOM52QVixAcDETlwBIw9PXUt8b6hFgG7k9ncTRZ4baejmD87i5JQMeg1d4bIBAAAAAAAAHAwAAAAAAAABDiBzQYOL5jKoCOgksiRTvw0zfNZ+6QwsBsCZRoqc3PHoygEPBAIAAAABEAT9////ACICAt/pWo4sGJOHmHcQ8znTQCNWAZbCdkdGx3JaRfNNtbr6EAm9XegAAACAAQAAgEgAAIABBBYAFCuDv44MRC5Qj+VetbjoeiSUS5p3AQMIzLoIvwEAAAAH/ARwc2V0AiAjD09dS3xvqEWAbuT2dxNFnhtp6OYPzuLklAx6DV3hsgf8BHBzZXQIBAAAAAAAAQQWABSNJKzjaUb3uOxixsvh1GGE3fW7zQEDCAD5ApUAAAAAB/wEcHNldAIgIw9PXUt8b6hFgG7k9ncTRZ4baejmD87i5JQMeg1d4bIH/ARwc2V0CAQAAAAAAAEEAAEDCCgUAAAAAAAAB/wEcHNldAIgIw9PXUt8b6hFgG7k9ncTRZ4baejmD87i5JQMeg1d4bIH/ARwc2V0CAQAAAAAAA==") + self.log.info("Try decoding and combining transactions in various states of blindedness") + self.pset_confidential_proofs() + if __name__ == '__main__': PSBTTest().main()