Skip to content

Commit

Permalink
Merge ElementsProject#600: PSBT for Confidential Assets
Browse files Browse the repository at this point in the history
3127c65 Add legacy help text for walletprocesspsbt suggesting to use new RPCs instead. (Glenn Willen)
d39925b Disable PSBT RPCs when not in g_con_elementsmode. (Glenn Willen)
2848b52 PSBT for Confidential Assets (Glenn Willen)
f87684a Add 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
  • Loading branch information
instagibbs committed Jul 31, 2019
2 parents 065401b + 3127c65 commit c67ef29
Show file tree
Hide file tree
Showing 21 changed files with 1,214 additions and 242 deletions.
4 changes: 0 additions & 4 deletions src/core_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 0 additions & 27 deletions src/core_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
23 changes: 15 additions & 8 deletions src/core_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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());
}
Expand Down
6 changes: 6 additions & 0 deletions src/node/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/node/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
77 changes: 76 additions & 1 deletion src/psbt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <psbt.h>
#include <util/strencodings.h>
#include <confidential_validation.h>

PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;

Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
Loading

0 comments on commit c67ef29

Please sign in to comment.