From f81122f9483db8ccf908d063d1e4ab8551afbe57 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 19 Mar 2019 19:14:07 +0000 Subject: [PATCH] Add asset and value blinding functionality --- src/Makefile.am | 4 + src/blind.cpp | 190 ++++++++++++++++++++++++++++++++ src/blind.h | 38 +++++++ src/confidential_validation.cpp | 107 ++++++++++++++++++ src/confidential_validation.h | 87 +++++++++++++++ src/primitives/txwitness.h | 14 +-- 6 files changed, 432 insertions(+), 8 deletions(-) create mode 100644 src/blind.cpp create mode 100644 src/blind.h create mode 100644 src/confidential_validation.cpp create mode 100644 src/confidential_validation.h diff --git a/src/Makefile.am b/src/Makefile.am index 67037e8853a71..8b2e1bcd69c06 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -97,6 +97,7 @@ BITCOIN_CORE_H = \ base58.h \ bech32.h \ blech32.h \ + blind.h \ bloom.h \ blockencodings.h \ blockfilter.h \ @@ -114,6 +115,7 @@ BITCOIN_CORE_H = \ compat/endian.h \ compat/sanity.h \ compressor.h \ + confidential_validation.h \ consensus/consensus.h \ consensus/tx_verify.h \ core_io.h \ @@ -233,6 +235,7 @@ libbitcoin_server_a_SOURCES = \ block_proof.cpp \ chain.cpp \ checkpoints.cpp \ + confidential_validation.cpp \ consensus/tx_verify.cpp \ httprpc.cpp \ httpserver.cpp \ @@ -412,6 +415,7 @@ libbitcoin_common_a_SOURCES = \ base58.cpp \ bech32.cpp \ blech32.cpp \ + blind.cpp \ chainparams.cpp \ coins.cpp \ compressor.cpp \ diff --git a/src/blind.cpp b/src/blind.cpp new file mode 100644 index 0000000000000..5909452784d2a --- /dev/null +++ b/src/blind.cpp @@ -0,0 +1,190 @@ +// Copyright (c) 2017-2019 The Elements Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include + +static secp256k1_context* secp256k1_blind_context = NULL; + +class Blind_ECC_Init { +public: + Blind_ECC_Init() { + assert(secp256k1_blind_context == NULL); + + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + assert(ctx != NULL); + + secp256k1_blind_context = ctx; + } + + ~Blind_ECC_Init() { + secp256k1_context *ctx = secp256k1_blind_context; + secp256k1_blind_context = NULL; + + if (ctx) { + secp256k1_context_destroy(ctx); + } + } +}; + +static Blind_ECC_Init ecc_init_on_load; + +bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce_commitment, const CScript& committedScript, const std::vector& vchRangeproof, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out) +{ + if (!blinding_key.IsValid() || vchRangeproof.size() == 0) { + return false; + } + CPubKey ephemeral_key(nonce_commitment.vchCommitment); + if (nonce_commitment.vchCommitment.size() > 0 && !ephemeral_key.IsFullyValid()) { + return false; + } + + // ECDH or not depending on if nonce commitment is non-empty + uint256 nonce; + bool blank_nonce = false; + if (nonce_commitment.vchCommitment.size() > 0) { + nonce = blinding_key.ECDH(ephemeral_key); + CSHA256().Write(nonce.begin(), 32).Finalize(nonce.begin()); + } else { + // Use blinding key directly, and don't commit to a scriptpubkey + // This is used for issuance inputs. + blank_nonce = true; + nonce = uint256(std::vector(blinding_key.begin(), blinding_key.end())); + } + + // API-prescribed sidechannel maximum size, though we only use 64 bytes + unsigned char msg[4096] = {0}; + // 32 bytes of asset type, 32 bytes of asset blinding factor in sidechannel + size_t msg_size = 64; + + // If value is unblinded, we don't support unblinding just the asset + if (!conf_value.IsCommitment()) { + return false; + } + + // Valid asset commitment? + secp256k1_generator observed_gen; + if (conf_asset.IsCommitment()) { + if (secp256k1_generator_parse(secp256k1_blind_context, &observed_gen, &conf_asset.vchCommitment[0]) != 1) + return false; + } else if (conf_asset.IsExplicit()) { + if (secp256k1_generator_generate(secp256k1_blind_context, &observed_gen, conf_asset.GetAsset().begin()) != 1) + return false; + } + + // Valid value commitment? + secp256k1_pedersen_commitment value_commit; + if (secp256k1_pedersen_commitment_parse(secp256k1_blind_context, &value_commit, conf_value.vchCommitment.data()) != 1) { + return false; + } + + // Rewind rangeproof + uint64_t min_value, max_value, amount; + if (!secp256k1_rangeproof_rewind(secp256k1_blind_context, blinding_factor_out.begin(), &amount, msg, &msg_size, nonce.begin(), &min_value, &max_value, &value_commit, &vchRangeproof[0], vchRangeproof.size(), (committedScript.size() && !blank_nonce)? &committedScript.front(): NULL, blank_nonce ? 0 : committedScript.size(), &observed_gen)) { + return false; + } + + // Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check) + if (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)) { + return false; + } + + // Convienience pointers to starting point of each recovered 32 byte message + unsigned char *asset_type = msg; + unsigned char *asset_blinder = msg+32; + + // Asset sidechannel of asset type + asset blinder + secp256k1_generator recalculated_gen; + if (msg_size != 64 || secp256k1_generator_generate_blinded(secp256k1_blind_context, &recalculated_gen, asset_type, asset_blinder) != 1) { + return false; + } + + // Serialize both generators then compare + unsigned char observed_generator[33]; + unsigned char derived_generator[33]; + secp256k1_generator_serialize(secp256k1_blind_context, observed_generator, &observed_gen); + secp256k1_generator_serialize(secp256k1_blind_context, derived_generator, &recalculated_gen); + if (memcmp(observed_generator, derived_generator, sizeof(observed_generator))) { + return false; + } + + amount_out = (CAmount)amount; + asset_out = CAsset(std::vector(asset_type, asset_type+32)); + asset_blinding_factor_out = uint256(std::vector(asset_blinder, asset_blinder+32)); + return true; +} + +// Create surjection proof +bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector& surjection_targets, const std::vector& target_asset_generators, const std::vector& target_asset_blinders, const std::vector asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset) +{ + int ret; + // 1 to 3 targets + size_t nInputsToSelect = std::min((size_t)3, surjection_targets.size()); + unsigned char randseed[32]; + GetStrongRandBytes(randseed, 32); + size_t input_index; + secp256k1_surjectionproof proof; + secp256k1_fixed_asset_tag tag; + memcpy(&tag, asset.begin(), 32); + // Find correlation between asset tag and listed input tags + if (secp256k1_surjectionproof_initialize(secp256k1_blind_context, &proof, &input_index, &surjection_targets[0], surjection_targets.size(), nInputsToSelect, &tag, 100, randseed) == 0) { + return false; + } + // Using the input chosen, build proof + ret = secp256k1_surjectionproof_generate(secp256k1_blind_context, &proof, target_asset_generators.data(), target_asset_generators.size(), &output_asset_gen, input_index, target_asset_blinders[input_index].begin(), asset_blindptrs[asset_blindptrs.size()-1]); + assert(ret == 1); + // Double-check answer + ret = secp256k1_surjectionproof_verify(secp256k1_blind_context, &proof, target_asset_generators.data(), target_asset_generators.size(), &output_asset_gen); + assert(ret != 0); + + // Serialize into output witness structure + size_t output_len = secp256k1_surjectionproof_serialized_size(secp256k1_blind_context, &proof); + txoutwit.vchSurjectionproof.resize(output_len); + secp256k1_surjectionproof_serialize(secp256k1_blind_context, &txoutwit.vchSurjectionproof[0], &output_len, &proof); + assert(output_len == txoutwit.vchSurjectionproof.size()); + return true; +} + +bool GenerateRangeproof(std::vector& rangeproof, const std::vector& value_blindptrs, const uint256& nonce, const CAmount amount, const CScript& scriptPubKey, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen, const CAsset& asset, std::vector& asset_blindptrs) +{ + // Prep range proof + size_t nRangeProofLen = 5134; + rangeproof.resize(nRangeProofLen); + + // Compose sidechannel message to convey asset info (ID and asset blinds) + unsigned char asset_message[64]; + memcpy(asset_message, asset.begin(), 32); + memcpy(asset_message+32, asset_blindptrs[asset_blindptrs.size()-1], 32); + + // Sign rangeproof + // If min_value is 0, scriptPubKey must be unspendable + int res = secp256k1_rangeproof_sign(secp256k1_blind_context, rangeproof.data(), &nRangeProofLen, scriptPubKey.IsUnspendable() ? 0 : 1, &value_commit, value_blindptrs.back(), nonce.begin(), std::min(std::max((int)gArgs.GetArg("-ct_exponent", 0), -1),18), std::min(std::max((int)gArgs.GetArg("-ct_bits", 36), 1), 51), amount, asset_message, sizeof(asset_message), scriptPubKey.size() ? &scriptPubKey.front() : NULL, scriptPubKey.size(), &gen); + rangeproof.resize(nRangeProofLen); + return (res == 1); +} + +void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const unsigned char* asset_blindptr) +{ + conf_asset.vchCommitment.resize(CConfidentialAsset::nCommittedSize); + int ret = secp256k1_generator_generate_blinded(secp256k1_blind_context, &asset_gen, asset.begin(), asset_blindptr); + assert(ret == 1); + ret = secp256k1_generator_serialize(secp256k1_blind_context, conf_asset.vchCommitment.data(), &asset_gen); + assert(ret != 0); +} + +void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const unsigned char* value_blindptr, const secp256k1_generator& asset_gen, const CAmount amount) +{ + int ret; + conf_value.vchCommitment.resize(CConfidentialValue::nCommittedSize); + ret = secp256k1_pedersen_commit(secp256k1_blind_context, &value_commit, value_blindptr, amount, &asset_gen); + assert(ret != 0); + secp256k1_pedersen_commitment_serialize(secp256k1_blind_context, conf_value.vchCommitment.data(), &value_commit); + assert(conf_value.IsValid()); +} diff --git a/src/blind.h b/src/blind.h new file mode 100644 index 0000000000000..e54ce56362a26 --- /dev/null +++ b/src/blind.h @@ -0,0 +1,38 @@ +// Copyright (c) 2017-2019 The Elements Core developers +// // Distributed under the MIT software license, see the accompanying +// // file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_WALLET_BLIND_H +#define BITCOIN_WALLET_BLIND_H + +#include +#include +#include +#include + +#include +#include +#include + +//! ELEMENTS: 36-bit rangeproof size +static const size_t DEFAULT_RANGEPROOF_SIZE = 2893; + +/* + * Unblind a pair of confidental asset and value. + * Note that unblinded data will only be outputted if *BOTH* asset and value could be unblinded. + * + * blinding_key is used to create the nonce to rewind the rangeproof in conjunction with the nNonce commitment. In the case of a 0-length nNonce, the blinding key is directly used as the nonce. + * Currently there is only a sidechannel message in the rangeproof so a valid rangeproof must + * be included in the pair to recover value and asset data. + */ +bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce_commitment, const CScript& committedScript, const std::vector& vchRangeproof, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out); + +bool GenerateRangeproof(std::vector& rangeproof, const std::vector& value_blindptrs, const uint256& nonce, const CAmount amount, const CScript& scriptPubKey, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen, const CAsset& asset, std::vector& asset_blindptrs); + +bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector& surjection_targets, const std::vector& target_asset_generators, const std::vector& target_asset_blinders, const std::vector asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset); + +void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const unsigned char* asset_blindptr); + +void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const unsigned char* value_blindptr, const secp256k1_generator& asset_gen, const CAmount amount); + +#endif //BITCOIN_WALLET_BLIND_H diff --git a/src/confidential_validation.cpp b/src/confidential_validation.cpp new file mode 100644 index 0000000000000..c06b489ec3f9b --- /dev/null +++ b/src/confidential_validation.cpp @@ -0,0 +1,107 @@ + +#include +#include +#include +#include