Skip to content

Commit f81122f

Browse files
committed
Add asset and value blinding functionality
1 parent 8b8256d commit f81122f

File tree

6 files changed

+432
-8
lines changed

6 files changed

+432
-8
lines changed

src/Makefile.am

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ BITCOIN_CORE_H = \
9797
base58.h \
9898
bech32.h \
9999
blech32.h \
100+
blind.h \
100101
bloom.h \
101102
blockencodings.h \
102103
blockfilter.h \
@@ -114,6 +115,7 @@ BITCOIN_CORE_H = \
114115
compat/endian.h \
115116
compat/sanity.h \
116117
compressor.h \
118+
confidential_validation.h \
117119
consensus/consensus.h \
118120
consensus/tx_verify.h \
119121
core_io.h \
@@ -233,6 +235,7 @@ libbitcoin_server_a_SOURCES = \
233235
block_proof.cpp \
234236
chain.cpp \
235237
checkpoints.cpp \
238+
confidential_validation.cpp \
236239
consensus/tx_verify.cpp \
237240
httprpc.cpp \
238241
httpserver.cpp \
@@ -412,6 +415,7 @@ libbitcoin_common_a_SOURCES = \
412415
base58.cpp \
413416
bech32.cpp \
414417
blech32.cpp \
418+
blind.cpp \
415419
chainparams.cpp \
416420
coins.cpp \
417421
compressor.cpp \

src/blind.cpp

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright (c) 2017-2019 The Elements Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <blind.h>
6+
7+
#include <hash.h>
8+
#include <primitives/transaction.h>
9+
#include <primitives/confidential.h>
10+
#include <issuance.h>
11+
#include <random.h>
12+
#include <util.h>
13+
14+
static secp256k1_context* secp256k1_blind_context = NULL;
15+
16+
class Blind_ECC_Init {
17+
public:
18+
Blind_ECC_Init() {
19+
assert(secp256k1_blind_context == NULL);
20+
21+
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
22+
assert(ctx != NULL);
23+
24+
secp256k1_blind_context = ctx;
25+
}
26+
27+
~Blind_ECC_Init() {
28+
secp256k1_context *ctx = secp256k1_blind_context;
29+
secp256k1_blind_context = NULL;
30+
31+
if (ctx) {
32+
secp256k1_context_destroy(ctx);
33+
}
34+
}
35+
};
36+
37+
static Blind_ECC_Init ecc_init_on_load;
38+
39+
bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce_commitment, const CScript& committedScript, const std::vector<unsigned char>& vchRangeproof, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out)
40+
{
41+
if (!blinding_key.IsValid() || vchRangeproof.size() == 0) {
42+
return false;
43+
}
44+
CPubKey ephemeral_key(nonce_commitment.vchCommitment);
45+
if (nonce_commitment.vchCommitment.size() > 0 && !ephemeral_key.IsFullyValid()) {
46+
return false;
47+
}
48+
49+
// ECDH or not depending on if nonce commitment is non-empty
50+
uint256 nonce;
51+
bool blank_nonce = false;
52+
if (nonce_commitment.vchCommitment.size() > 0) {
53+
nonce = blinding_key.ECDH(ephemeral_key);
54+
CSHA256().Write(nonce.begin(), 32).Finalize(nonce.begin());
55+
} else {
56+
// Use blinding key directly, and don't commit to a scriptpubkey
57+
// This is used for issuance inputs.
58+
blank_nonce = true;
59+
nonce = uint256(std::vector<unsigned char>(blinding_key.begin(), blinding_key.end()));
60+
}
61+
62+
// API-prescribed sidechannel maximum size, though we only use 64 bytes
63+
unsigned char msg[4096] = {0};
64+
// 32 bytes of asset type, 32 bytes of asset blinding factor in sidechannel
65+
size_t msg_size = 64;
66+
67+
// If value is unblinded, we don't support unblinding just the asset
68+
if (!conf_value.IsCommitment()) {
69+
return false;
70+
}
71+
72+
// Valid asset commitment?
73+
secp256k1_generator observed_gen;
74+
if (conf_asset.IsCommitment()) {
75+
if (secp256k1_generator_parse(secp256k1_blind_context, &observed_gen, &conf_asset.vchCommitment[0]) != 1)
76+
return false;
77+
} else if (conf_asset.IsExplicit()) {
78+
if (secp256k1_generator_generate(secp256k1_blind_context, &observed_gen, conf_asset.GetAsset().begin()) != 1)
79+
return false;
80+
}
81+
82+
// Valid value commitment?
83+
secp256k1_pedersen_commitment value_commit;
84+
if (secp256k1_pedersen_commitment_parse(secp256k1_blind_context, &value_commit, conf_value.vchCommitment.data()) != 1) {
85+
return false;
86+
}
87+
88+
// Rewind rangeproof
89+
uint64_t min_value, max_value, amount;
90+
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)) {
91+
return false;
92+
}
93+
94+
// Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check)
95+
if (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)) {
96+
return false;
97+
}
98+
99+
// Convienience pointers to starting point of each recovered 32 byte message
100+
unsigned char *asset_type = msg;
101+
unsigned char *asset_blinder = msg+32;
102+
103+
// Asset sidechannel of asset type + asset blinder
104+
secp256k1_generator recalculated_gen;
105+
if (msg_size != 64 || secp256k1_generator_generate_blinded(secp256k1_blind_context, &recalculated_gen, asset_type, asset_blinder) != 1) {
106+
return false;
107+
}
108+
109+
// Serialize both generators then compare
110+
unsigned char observed_generator[33];
111+
unsigned char derived_generator[33];
112+
secp256k1_generator_serialize(secp256k1_blind_context, observed_generator, &observed_gen);
113+
secp256k1_generator_serialize(secp256k1_blind_context, derived_generator, &recalculated_gen);
114+
if (memcmp(observed_generator, derived_generator, sizeof(observed_generator))) {
115+
return false;
116+
}
117+
118+
amount_out = (CAmount)amount;
119+
asset_out = CAsset(std::vector<unsigned char>(asset_type, asset_type+32));
120+
asset_blinding_factor_out = uint256(std::vector<unsigned char>(asset_blinder, asset_blinder+32));
121+
return true;
122+
}
123+
124+
// Create surjection proof
125+
bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector<secp256k1_fixed_asset_tag>& surjection_targets, const std::vector<secp256k1_generator>& target_asset_generators, const std::vector<uint256 >& target_asset_blinders, const std::vector<const unsigned char*> asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset)
126+
{
127+
int ret;
128+
// 1 to 3 targets
129+
size_t nInputsToSelect = std::min((size_t)3, surjection_targets.size());
130+
unsigned char randseed[32];
131+
GetStrongRandBytes(randseed, 32);
132+
size_t input_index;
133+
secp256k1_surjectionproof proof;
134+
secp256k1_fixed_asset_tag tag;
135+
memcpy(&tag, asset.begin(), 32);
136+
// Find correlation between asset tag and listed input tags
137+
if (secp256k1_surjectionproof_initialize(secp256k1_blind_context, &proof, &input_index, &surjection_targets[0], surjection_targets.size(), nInputsToSelect, &tag, 100, randseed) == 0) {
138+
return false;
139+
}
140+
// Using the input chosen, build proof
141+
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]);
142+
assert(ret == 1);
143+
// Double-check answer
144+
ret = secp256k1_surjectionproof_verify(secp256k1_blind_context, &proof, target_asset_generators.data(), target_asset_generators.size(), &output_asset_gen);
145+
assert(ret != 0);
146+
147+
// Serialize into output witness structure
148+
size_t output_len = secp256k1_surjectionproof_serialized_size(secp256k1_blind_context, &proof);
149+
txoutwit.vchSurjectionproof.resize(output_len);
150+
secp256k1_surjectionproof_serialize(secp256k1_blind_context, &txoutwit.vchSurjectionproof[0], &output_len, &proof);
151+
assert(output_len == txoutwit.vchSurjectionproof.size());
152+
return true;
153+
}
154+
155+
bool GenerateRangeproof(std::vector<unsigned char>& rangeproof, const std::vector<unsigned char*>& 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<const unsigned char*>& asset_blindptrs)
156+
{
157+
// Prep range proof
158+
size_t nRangeProofLen = 5134;
159+
rangeproof.resize(nRangeProofLen);
160+
161+
// Compose sidechannel message to convey asset info (ID and asset blinds)
162+
unsigned char asset_message[64];
163+
memcpy(asset_message, asset.begin(), 32);
164+
memcpy(asset_message+32, asset_blindptrs[asset_blindptrs.size()-1], 32);
165+
166+
// Sign rangeproof
167+
// If min_value is 0, scriptPubKey must be unspendable
168+
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);
169+
rangeproof.resize(nRangeProofLen);
170+
return (res == 1);
171+
}
172+
173+
void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const unsigned char* asset_blindptr)
174+
{
175+
conf_asset.vchCommitment.resize(CConfidentialAsset::nCommittedSize);
176+
int ret = secp256k1_generator_generate_blinded(secp256k1_blind_context, &asset_gen, asset.begin(), asset_blindptr);
177+
assert(ret == 1);
178+
ret = secp256k1_generator_serialize(secp256k1_blind_context, conf_asset.vchCommitment.data(), &asset_gen);
179+
assert(ret != 0);
180+
}
181+
182+
void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const unsigned char* value_blindptr, const secp256k1_generator& asset_gen, const CAmount amount)
183+
{
184+
int ret;
185+
conf_value.vchCommitment.resize(CConfidentialValue::nCommittedSize);
186+
ret = secp256k1_pedersen_commit(secp256k1_blind_context, &value_commit, value_blindptr, amount, &asset_gen);
187+
assert(ret != 0);
188+
secp256k1_pedersen_commitment_serialize(secp256k1_blind_context, conf_value.vchCommitment.data(), &value_commit);
189+
assert(conf_value.IsValid());
190+
}

src/blind.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2017-2019 The Elements Core developers
2+
// // Distributed under the MIT software license, see the accompanying
3+
// // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_WALLET_BLIND_H
6+
#define BITCOIN_WALLET_BLIND_H
7+
8+
#include <key.h>
9+
#include <pubkey.h>
10+
#include <primitives/transaction.h>
11+
#include <primitives/confidential.h>
12+
13+
#include <secp256k1.h>
14+
#include <secp256k1_rangeproof.h>
15+
#include <secp256k1_surjectionproof.h>
16+
17+
//! ELEMENTS: 36-bit rangeproof size
18+
static const size_t DEFAULT_RANGEPROOF_SIZE = 2893;
19+
20+
/*
21+
* Unblind a pair of confidental asset and value.
22+
* Note that unblinded data will only be outputted if *BOTH* asset and value could be unblinded.
23+
*
24+
* 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.
25+
* Currently there is only a sidechannel message in the rangeproof so a valid rangeproof must
26+
* be included in the pair to recover value and asset data.
27+
*/
28+
bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce_commitment, const CScript& committedScript, const std::vector<unsigned char>& vchRangeproof, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out);
29+
30+
bool GenerateRangeproof(std::vector<unsigned char>& rangeproof, const std::vector<unsigned char*>& 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<const unsigned char*>& asset_blindptrs);
31+
32+
bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector<secp256k1_fixed_asset_tag>& surjection_targets, const std::vector<secp256k1_generator>& target_asset_generators, const std::vector<uint256 >& target_asset_blinders, const std::vector<const unsigned char*> asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset);
33+
34+
void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const unsigned char* asset_blindptr);
35+
36+
void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const unsigned char* value_blindptr, const secp256k1_generator& asset_gen, const CAmount amount);
37+
38+
#endif //BITCOIN_WALLET_BLIND_H

src/confidential_validation.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
2+
#include <confidential_validation.h>
3+
#include <issuance.h>
4+
#include <pegins.h>
5+
#include <script/sigcache.h>
6+
#include <blind.h>
7+
8+
namespace {
9+
static secp256k1_context *secp256k1_ctx_verify_amounts;
10+
11+
class CSecp256k1Init {
12+
public:
13+
CSecp256k1Init() {
14+
assert(secp256k1_ctx_verify_amounts == NULL);
15+
secp256k1_ctx_verify_amounts = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
16+
assert(secp256k1_ctx_verify_amounts != NULL);
17+
}
18+
~CSecp256k1Init() {
19+
assert(secp256k1_ctx_verify_amounts != NULL);
20+
secp256k1_context_destroy(secp256k1_ctx_verify_amounts);
21+
secp256k1_ctx_verify_amounts = NULL;
22+
}
23+
};
24+
static CSecp256k1Init instance_of_csecp256k1;
25+
}
26+
27+
bool CRangeCheck::operator()() {
28+
if (val->IsExplicit()) {
29+
return true;
30+
}
31+
32+
if (!CachingRangeProofChecker(store).VerifyRangeProof(rangeproof, val->vchCommitment, assetCommitment, scriptPubKey, secp256k1_ctx_verify_amounts)) {
33+
error = SCRIPT_ERR_RANGEPROOF;
34+
return false;
35+
}
36+
37+
return true;
38+
};
39+
40+
bool CBalanceCheck::operator()() {
41+
if (!secp256k1_pedersen_verify_tally(secp256k1_ctx_verify_amounts, vpCommitsIn.data(), vpCommitsIn.size(), vpCommitsOut.data(), vpCommitsOut.size())) {
42+
error = SCRIPT_ERR_PEDERSEN_TALLY;
43+
return false;
44+
}
45+
46+
return true;
47+
}
48+
49+
bool CSurjectionCheck::operator()() {
50+
return CachingSurjectionProofChecker(store).VerifySurjectionProof(proof, vTags, gen, secp256k1_ctx_verify_amounts, wtxid);
51+
}
52+
53+
// Destroys the check in the case of no queue, or passes its ownership to the queue.
54+
ScriptError QueueCheck(std::vector<CCheck*>* queue, CCheck* check) {
55+
if (queue != NULL) {
56+
queue->push_back(check);
57+
return SCRIPT_ERR_OK;
58+
}
59+
bool success = (*check)();
60+
ScriptError err = check->GetScriptError();
61+
delete check;
62+
return success ? SCRIPT_ERR_OK : err;
63+
}
64+
65+
// Helper function for VerifyAmount(), not exported
66+
static bool VerifyIssuanceAmount(secp256k1_pedersen_commitment& value_commit, secp256k1_generator& asset_gen,
67+
const CAsset& asset, const CConfidentialValue& value, const std::vector<unsigned char>& rangeproof,
68+
std::vector<CCheck*>* checks, const bool store_result)
69+
{
70+
// This is used to add in the explicit values
71+
unsigned char explicit_blinds[32];
72+
memset(explicit_blinds, 0, sizeof(explicit_blinds));
73+
int ret;
74+
75+
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &asset_gen, asset.begin());
76+
assert(ret == 1);
77+
78+
// Build value commitment
79+
if (value.IsExplicit()) {
80+
if (!MoneyRange(value.GetAmount()) || value.GetAmount() == 0) {
81+
return false;
82+
}
83+
if (!rangeproof.empty()) {
84+
return false;
85+
}
86+
87+
88+
ret = secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &value_commit, explicit_blinds, value.GetAmount(), &asset_gen);
89+
// The explicit_blinds are all 0, and the amount is not 0. So secp256k1_pedersen_commit does not fail.
90+
assert(ret == 1);
91+
} else if (value.IsCommitment()) {
92+
// Verify range proof
93+
std::vector<unsigned char> vchAssetCommitment(CConfidentialAsset::nExplicitSize);
94+
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, vchAssetCommitment.data(), &asset_gen);
95+
if (QueueCheck(checks, new CRangeCheck(&value, rangeproof, vchAssetCommitment, CScript(), store_result)) != SCRIPT_ERR_OK) {
96+
return false;
97+
}
98+
99+
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &value_commit, value.vchCommitment.data()) != 1) {
100+
return false;
101+
}
102+
} else {
103+
return false;
104+
}
105+
106+
return true;
107+
}

0 commit comments

Comments
 (0)