diff --git a/src/blindpsbt.cpp b/src/blindpsbt.cpp index d89e8919ac..7d972e3269 100644 --- a/src/blindpsbt.cpp +++ b/src/blindpsbt.cpp @@ -64,6 +64,21 @@ bool CreateAssetSurjectionProof(std::vector& output_proof, const return true; } +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) { + return false; + } + + secp256k1_generator gen; + if (secp256k1_generator_parse(secp256k1_blind_context, &gen, conf_asset.vchCommitment.data()) == 0) { + return false; + } + + return secp256k1_surjectionproof_verify(secp256k1_blind_context, &surj_proof, &gen, 1, &gen) == 0; +} + uint256 GenerateRangeproofECDHKey(CPubKey& ephemeral_pubkey, const CPubKey blinding_pubkey) { // Generate ephemeral key for ECDH nonce generation @@ -115,6 +130,26 @@ 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) +{ + secp256k1_pedersen_commitment value_commit; + if (secp256k1_pedersen_commitment_parse(secp256k1_blind_context, &value_commit, conf_value.vchCommitment.data()) == 0) { + return false; + } + + secp256k1_generator gen; + if (secp256k1_generator_parse(secp256k1_blind_context, &gen, conf_asset.vchCommitment.data()) == 0) { + return false; + } + + uint64_t min_value; + uint64_t max_value; + if (secp256k1_rangeproof_verify(secp256k1_blind_context, &min_value, &max_value, &value_commit, proof.data(), proof.size(), /* extra_commit */ nullptr, /* extra_commit_len */ 0, &gen) == 0) { + return false; + } + return min_value == (uint64_t)value; +} + 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 bc94ca50db..22d4e316fc 100644 --- a/src/blindpsbt.h +++ b/src/blindpsbt.h @@ -31,9 +31,11 @@ enum class BlindingStatus 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); diff --git a/src/util/error.cpp b/src/util/error.cpp index dc104edfb3..18131f2cdf 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -37,6 +37,10 @@ bilingual_str TransactionErrorString(const TransactionError err) return Untranslated("Transaction values or blinders are not balanced"); case TransactionError::UTXOS_MISSING_BALANCE_CHECK: return Untranslated("Missing UTXOs that are needed to check transaction balance"); + case TransactionError::INVALID_VALUE_PROOF: + return Untranslated("Proof of blinded value is invalid"); + case TransactionError::INVALID_ASSET_PROOF: + return Untranslated("Proof of blinded asset is invalid"); // no default case, so the compiler can warn about missing cases } assert(false); diff --git a/src/util/error.h b/src/util/error.h index 39a11f90d5..831d1a8df5 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -33,6 +33,8 @@ enum class TransactionError { BLINDING_REQUIRED, VALUE_IMBALANCE, UTXOS_MISSING_BALANCE_CHECK, + INVALID_VALUE_PROOF, + INVALID_ASSET_PROOF, }; bilingual_str TransactionErrorString(const TransactionError error); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d0809de4dc..d94894f569 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2830,10 +2830,21 @@ 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 { // 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) { for (const PSBTOutput& o : psbtx.outputs) { - if (o.IsBlinded() && !o.IsFullyBlinded()) { - return TransactionError::BLINDING_REQUIRED; + if (o.IsBlinded()) { + 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 (!VerifyBlindAssetProof(o.m_blind_asset_proof, o.m_asset_commitment)) { + return TransactionError::INVALID_ASSET_PROOF; + } } } }