Merge branch 'master' into 2021-10--rc2

This commit is contained in:
Andrew Poelstra 2021-10-06 23:13:15 +00:00
commit af441ca7bb
10 changed files with 287 additions and 54 deletions

View file

@ -242,7 +242,8 @@ assert all([x["next"] == "updater" for x in analysis["inputs"]])
# in turn, or by both updating their copies and then one party calls
# `combinepsbt` to combine the results.
#
# We will do the latter for symmetry reasons.
# We will do the latter since `combinepsbt` will automatically check that
# neither party changed the transaction out from under the other.
#
print ("3. Both parties fill in their UTXO data.")
@ -257,17 +258,23 @@ assert all([x["has_utxo"] for x in analysis["inputs"]])
assert not any([x["is_final"] for x in analysis["inputs"]])
assert all([x["next"] == "signer" for x in analysis["inputs"]])
## 3. Blind the PSET
## 4. Blind the PSET
#
# Both parties now blind the PSET. Importantly, the final person to blind must
# have a combined PSET that has everyone else's blinding data included, so that
# they can make the blinding factors add up.
#
# In the 2-party case this is particularly simple because there is no need to
# combine anything.
# The most straightforward way to implement this is to have each party blind
# the PSET, then pass to the next party, who blinds in-turn. If there are more
# than 2 parties, then you can have all parties but one blind independently,
# `combinepsbt` the results, and give this to the final party to blind.
#
# In the two-party case these are equivalent.
#
# Just for fun, try to have both parties blind independently and combine
# Just for fun, try to have both parties blind independently and combine. This
# will fail because `combinepsbt` needs there to be at least one remaining
# unblinded output.
alice_blinded = alice.walletprocesspsbt(filled_pset)
carol_blinded = carol.walletprocesspsbt(filled_pset)
try:
@ -278,13 +285,20 @@ else:
raise Exception("combinepsbt should return 'Cannot combine PSETs as the values and blinders would become imbalanced'")
# Ok, back to the tutorial
print ("3. One party blinds the PSET and passes to the other party, who also blinds")
print ("4. One party blinds the PSET and passes to the other party, who also blinds")
# We set sign=False here, because otherwise carol's `walletprocesspsbt`
# will sign the transaction (since after she does her blinding, the
# transaction will be completely blinded and therefore signable).
# But for this tutorial we want that to happen only in the next step.
alice_blinded = alice.walletprocesspsbt(filled_pset)
# Alice gives `alice_blinded` to Carol, who uses `combinepsbt` to verify
# that Alice blinded honestly (not changing any of the output amounts/assets)
carol.combinepsbt([filled_pset, alice_blinded["psbt"]])
# If this passes (does not throw any exception) she can blind.
carol_blinded = carol.walletprocesspsbt(psbt=alice_blinded["psbt"], sign=False)
# Similarly, Carol passes the result to Alice, who checks it
carol.combinepsbt([alice_blinded["psbt"], carol_blinded["psbt"]])
# Now both parties have a copy of the fully-blinded transaction.
blinded = carol_blinded
# We won't print these out because they're very big now
@ -300,7 +314,7 @@ assert all([x["has_utxo"] for x in analysis["inputs"]])
assert not any([x["is_final"] for x in analysis["inputs"]])
assert all([x["next"] == "signer" for x in analysis["inputs"]])
## 4. Sign the PSET
## 5. Sign the PSET
#
# When signing, there are a couple options for workflows. Each party can sign
# in turn, like we did when blinding, or they can each sign independently and
@ -308,7 +322,7 @@ assert all([x["next"] == "signer" for x in analysis["inputs"]])
# demonstration purposes.
#
print ("4. Both parties sign the PSET")
print ("5. Both parties sign the PSET")
alice_signed = alice.walletprocesspsbt(blinded["psbt"])
carol_signed = carol.walletprocesspsbt(blinded["psbt"])
@ -324,7 +338,7 @@ assert all([x["has_utxo"] for x in analysis["inputs"]])
assert all([x["is_final"] for x in analysis["inputs"]])
assert all([x["next"] == "extractor" for x in analysis["inputs"]])
print ("5. Finalize and Extract")
print ("6. Finalize and Extract")
x = alice.finalizepsbt(complete, True)
assert x["complete"]
# The complete transaction is in x["hex"] but again we won't print it because

View file

@ -66,7 +66,7 @@ bool CreateAssetSurjectionProof(std::vector<unsigned char>& output_proof, const
return true;
}
static bool VerifyBlindAssetProof(const uint256& asset, const std::vector<unsigned char>& proof, const CConfidentialAsset& conf_asset)
bool VerifyBlindAssetProof(const uint256& asset, const std::vector<unsigned char>& proof, const CConfidentialAsset& conf_asset)
{
secp256k1_surjectionproof surj_proof;
if (secp256k1_surjectionproof_parse(secp256k1_blind_context, &surj_proof, proof.data(), proof.size()) == 0) {
@ -173,7 +173,7 @@ static bool CreateBlindAssetProof(std::vector<unsigned char>& assetproof, const
return true;
}
static bool VerifyBlindValueProof(CAmount value, const CConfidentialValue& conf_value, const std::vector<unsigned char>& proof, const CConfidentialAsset& conf_asset)
bool VerifyBlindValueProof(CAmount value, const CConfidentialValue& conf_value, const std::vector<unsigned char>& 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) {
@ -263,6 +263,7 @@ bool SubtractScalars(uint256& a, const uint256& b)
// Compute the scalar offset used for the final blinder computation
// value * asset_blinder + value_blinder
// FIXME this method should be in libsecp, as should `ComputeAndAddToScalarOffset`
bool CalculateScalarOffset(uint256& out, CAmount value, const uint256& asset_blinder, const uint256& value_blinder)
{
// If the asset_blinder is 0, then the equation resolves to just the value_blinder
@ -276,8 +277,24 @@ bool CalculateScalarOffset(uint256& out, CAmount value, const uint256& asset_bli
// tweak_mul expects a 32 byte, big endian tweak.
// We need to pack the 8 byte CAmount into a uint256 with the correct padding, so start it at 24 bytes from the front
WriteBE64(val.begin() + 24, value);
if (secp256k1_ec_privkey_tweak_mul(secp256k1_blind_context, out.begin(), val.begin()) != 1) return false;
if (!value_blinder.IsNull() && secp256k1_ec_privkey_tweak_add(secp256k1_blind_context, out.begin(), value_blinder.begin()) != 1) return false;
if (value > 0) {
if (secp256k1_ec_privkey_tweak_mul(secp256k1_blind_context, out.begin(), val.begin()) != 1) return false;
} else {
out = value_blinder;
return true;
}
if (!value_blinder.IsNull()) {
uint256 value_negated = value_blinder;
if (secp256k1_ec_seckey_negate(secp256k1_blind_context, value_negated.begin()) != 1) {
return false;
}
// Special-case zero, which would otherwise cause `secp256k1_ec_privkey_tweak_add` to fail
if (value_negated == out) {
out = uint256{};
return true;
}
if (secp256k1_ec_privkey_tweak_add(secp256k1_blind_context, out.begin(), value_blinder.begin()) != 1) return false;
}
return true;
}
@ -294,8 +311,17 @@ bool ComputeAndAddToScalarOffset(uint256& a, CAmount value, const uint256& asset
if (a.IsNull()) {
a = scalar;
} else {
// If we have a, then add the scalar to it.
if (secp256k1_ec_privkey_tweak_add(secp256k1_blind_context, a.begin(), scalar.begin()) != 1) return false;
uint256 scalar_negated = scalar;
if (secp256k1_ec_seckey_negate(secp256k1_blind_context, scalar_negated.begin()) != 1) {
return false;
}
// Special-case zero, which would otherwise cause `secp256k1_ec_privkey_tweak_add` to fail
if (scalar_negated == a) {
a = uint256{};
} else {
// If we have a, then add the scalar to it.
if (secp256k1_ec_privkey_tweak_add(secp256k1_blind_context, a.begin(), scalar.begin()) != 1) return false;
}
}
return true;
}

View file

@ -47,6 +47,10 @@ bool CreateValueRangeProof(std::vector<unsigned char>& rangeproof, const uint256
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<uint32_t, std::tuple<CAmount, CAsset, uint256, uint256>> our_input_data, std::map<uint32_t, std::pair<CKey, CKey>> our_issuances_to_blind);
bool VerifyBlindValueProof(CAmount value, const CConfidentialValue& conf_value, const std::vector<unsigned char>& proof, const CConfidentialAsset& conf_asset);
bool VerifyBlindAssetProof(const uint256& asset, const std::vector<unsigned char>& proof, const CConfidentialAsset& conf_asset);
BlindProofResult VerifyBlindProofs(const PSBTOutput& o);
#endif //BITCOIN_BLINDPSBT_H

View file

@ -916,8 +916,8 @@ public:
// Legacy PAK list
consensus.first_extension_space = {
ParseHex("0362f0cf4898e44a20472664daed460156976bab5cc8bb8431b206bbafddd230c9"
"0399dadeeedc2cefe9042ffa596c553cad1967cda04de6aa0f9fbd96b6044292e7"),
ParseHex("02555f97c44ad9286ef060a02b00e8e6be2626ed3eb9230705d3ca2f977daae61e"
"03cddbc847f64f898b883d717a7f637bedf9ac2ecd243721eada223f1b1790f75b"),
ParseHex("033fad80bd2b818d1ca8a8d4a25dafcf5e740be07db6788be1f2f15266e3c6805d"
"0253ff3f140ef8f594d54996eab810a82550c79204279920d95681afe699d00da5"),
ParseHex("03f2d35e88741f930a3938bfa7075377ec2da4f1d7699a779e2cbf7a389195dc67"
@ -940,8 +940,12 @@ public:
"034e93391cea816e5141dace7e5477bbed90c9daa0670b68b7acc8a44af556bbc1"),
ParseHex("03156b39a4bce80e68c1582aa78f81f0252ccbb039766b5395ee9a0224f41c236d"
"0399a5d1d42f5b6cb587560394e1581eb0c76916db317c0d644a1b9f509a06c4e6"),
ParseHex("029ce033e1dc81164deb04b4c55966b823a025ef47bb1f767017696b68ab9ae201"
"03e612d646e71b07e5ce0eaa3a0178e4606dd9a6e8f0d5ace9171fb1e808a3865b"),
ParseHex("029797b15de24dc43a6556e58159c5aa0b69ea390ccdebcd7be10751d8085da08f"
"03248e52371b2c3bce2478a3c3aaf37e4f0d6ba711e058ba407f44fdaaf280ac95"),
ParseHex("03d6a14ab496777401e2eae7992404011537860af7b46c3a8fdea65d29fe4bf26c"
"02dca82e552228f3808b1ea9b38b3342b51e9453dcb1414c551ce08bd726311e30"),
ParseHex("035c9c770ed88e29b364038d68b1c623fbf71e93e6d5357e278e9b64160984ed3c"
"02659aabb69b8413bc46026830ad1e2284901350a75c2bc97906f49cff01503f0f"),
ParseHex("02a8300f0cff92b23e402459e83c52ec5824de82ee4004cf9d254e788304027ef6"
"0389cbda672fa9efea51706863f1d7ae5e5015b2e519003ef0178c99f71be6e8be"),
ParseHex("03fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f"
@ -950,30 +954,48 @@ public:
"02228dfd7ff95506dd67b1118803eb8ab49352b2e24cd5f38da043847e722009ba"),
ParseHex("03fcc2963daaf8249bfd220e52c693626254b9295ac4f947ae2e0cddb3046724c1"
"02dac03530ac9712a71eafb87766644b61cf4be85d0fdc6a859875b41e7a1dc8e6"),
ParseHex("039bfd22bf5c41ce14d3fbd50ef226d2066e826b2efba455150d23d958d52bfddf"
"03211678d22c45402c993d96ea4a6d861d3e1da33798aebd5424fe5725a7ce8f4b"),
ParseHex("02d67fcb027c5d8fe354fb36235192cb4fffabffdcc6ce74be255fe869f62d8675"
"03d61d857b2a8cb060fd4b9a98a862f250df5825068665a3c8d93f2ac8a7085888"),
ParseHex("02cfe983eb588975958e9ce832937ba7f24592882cf5c0fc0f07896097fd66a8e7"
"0344744d01c091eacea5730ed1205b0a83378418644ea7938ed664649e88dcbb29"),
ParseHex("02cddb51ea42acf38762418939be0a9227f0212ff96a870a2c1d85ec65905a7629"
"03d986a2181a38cfef5b5e2a1915aa2d37f193fcbafab9bf311d6138209f316f5b"),
ParseHex("029ec6dd0c310513b3720800025a7ad9013d60a7fb041f6e9b9d3963485ba28657"
"0277247f28eb9481dd21d664093a2bc19a496c7ffebeca0026a1726a5041e671ba"),
ParseHex("03f9dea372c4a667dcfe234ff8e0410c22341149ff7d8780c46954ff74998fbe44"
"0340c4e534906c06b73874cef00a880ab602641c7883de94296f0f601e6517ae7e"),
ParseHex("03cf8520f2db93e1ba75fa9043ac7e3476719b2a33a12d7e725688a2de68852c88"
"0343b7551ba662fa7071ac93e7e25517967bb8a9420af64d35d41c6d88056ad4ba"),
ParseHex("027661f1530dfc88b34b0c8f606d215f30fb0edfa116b331ff44b2fbe040893c6f"
"029d3160731eddc316121b2a31c82270baa4bbe7f08549891af3b444eb690b2df1"),
ParseHex("03f79461a5559f360c407069b92a8075958bf1f70918872d9dd702db145bccbd42"
"0395058fc702f126176ae13e0ebed05107288900a5a35b121f62923e58798b7b2f"),
ParseHex("02d7f049d9e87c861fc9decfbe167cb13ccc87cce99113f69e3a5dca8bb71b6aed"
"03e82197b2e9cc0ee11a59808cfdb52e824445f8fa99e44dc9c30d1e49950ff9d6"),
ParseHex("0281bfeffcc6841d1355dce039f5d64f72714a4c3adc4d351eaf3c28acbcee15f0"
"0270a16ee1cdfc78755a783efbdb66fe822605cc5f53af707e5038615e22b288e2"),
ParseHex("022d58f7f198f3fe7e0ae45f93aa28fdb483ac25a258663ac593860e11ac1d1abc"
"035049635f866b921f7cd0481c6165f19e14ba52c67f7c4fade1dcd22f9aacea20"),
ParseHex("02d40ea20996c882a75fd8cd433484bd8af92791752b4c2d2f24660de36a9f3f82"
"02d874a87df633068c2eacceed3345ce5fb2dbc9f94c30b93ef4c844a77f2651c0"),
ParseHex("024158f76e16888a49492d4913e45c1b4cba19d87dd5bd24346ef601d31d062537"
"0366e9ad4ce16b65a95fb63aae98fdff6bcbd31816d6336039e529a40a828e9851"),
ParseHex("02d2283a929584cdf557096a7f473ae25c04fd6f73467657c4bc49dfb3095892bd"
"03599136ea1f66a80a2eb1a144458561f4791d2fc5fcd06e32a88c9cb2976c8aac"),
ParseHex("036f4b5f3ae46163fb53b0d6c19c78ea2fdf49c8b419c354f3c24fa1ce9547e6b8"
"0340a79f2477ff2a077fb0b8ebb96714a9aaf242f4b96253260264ed031f2a7ee4"),
ParseHex("02d6825aaa063083567f6d4f35ea62c2af8d34f67ef4c2afa565791fd7efc5f3a6"
"02b1e0d671f91f756a7613797d84c33daddc1dc1df9badf68d4e2c2216a288c923"),
ParseHex("03effb766a6f3729c220b0ffa156ffa66d656e5ec16f15bc513b8d0b1298c761d2"
"0252831192e573788271e235afca8f72736d97e26b3a1406cac34711b6ab670c26"),
ParseHex("038c245fa632a0b6c2712cbadb6f6e346284ee0fba3202875abd774faee2deca29"
"032ff781357db141528b1c7ea2cfed3ebe6bb9a028954665cfba355bbcf3d14c8e"),
ParseHex("0356c22fab025b3e661331ed4dcf8645a4a4fd4a2cae69680339e05df209ef4556"
"032d60805593864388d073193fb9fcf66c389813778dcd4a2e93c8fd164d387f7f"),
ParseHex("0238de9c098e83c4d244294ac394355c8e80b49af10f7c1e23001e6c88be5d45b8"
"03bc04885be94ceffbac90178ef18d4dd6958d7488f7861f0994c659412d9e9463"),
ParseHex("025651f14b6347a000e15473eaf631fd78c9307e07db85e177e31fcde0b3f2a574"
"03d5303909fe1c6665cbc96a538b17274068c8e79757705f68db3df2b561a4c110"),
ParseHex("03627a4855be1edc657927f30a4a869ad830041c1f0e74ab4670588af9532b8de8"
"03444cb85aef9fbba10b3e2662d533858db771010b57b7aedb1ecaa1c5a34918f1"),
ParseHex("0286951fdc1e81652cdd10a10971966792e5c2a2bbe524f32a561f585b2b3d2057"
"034294862542484e49c6fb835919212352527298c689ff7be57e445bf0fe3536de"),
ParseHex("032d9af13c8d5f5316fd27a14bafb8ec55684ef2e3b5c64b2645e088f570e5d2cb"
"0239590f39508465decfd8a1bdc61b42333297e80588ed826ddd43678edfa6caae")
};
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28;
@ -1155,7 +1177,7 @@ public:
assert(IsHex(extprvprefix) && extprvprefix.size() == 8 && "-extprvkeyprefix must be hex string of length 8");
base58Prefixes[EXT_SECRET_KEY] = ParseHex(extprvprefix);
const std::string magic_str = args.GetArg("-pchmessagestart", "FABFB5DA");
const std::string magic_str = args.GetArg("-pchmessagestart", "143EFCB1");
assert(IsHex(magic_str) && magic_str.size() == 8 && "-pchmessagestart must be hex string of length 8");
const std::vector<unsigned char> magic_byte = ParseHex(magic_str);
std::copy(begin(magic_byte), end(magic_byte), pchMessageStart);

View file

@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <blindpsbt.h>
#include <psbt.h>
#include <chainparams.h>
@ -135,13 +136,29 @@ CMutableTransaction PartiallySignedTransaction::GetUnsignedTx(bool force_unblind
CTxOut txout;
CTxOutWitness txoutwit;
txout.scriptPubKey = *output.script;
if (output.m_value_commitment.IsNull() || (output.amount != nullopt && force_unblinded)) {
bool exp_value = output.m_value_commitment.IsNull() || force_unblinded;
exp_value = exp_value && output.amount != nullopt;
if (!output.m_value_commitment.IsNull() && output.amount != nullopt) {
exp_value = exp_value && !output.m_blind_value_proof.empty();
exp_value = exp_value && !output.m_asset_commitment.IsNull();
exp_value = exp_value && VerifyBlindValueProof(*output.amount, output.m_value_commitment, output.m_blind_value_proof, output.m_asset_commitment);
}
if (exp_value) {
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)) {
bool exp_asset = output.m_asset_commitment.IsNull() || force_unblinded;
exp_asset = exp_asset && !output.m_asset.IsNull();
if (!output.m_asset_commitment.IsNull() && !output.m_asset.IsNull()) {
exp_asset = exp_asset && !output.m_blind_asset_proof.empty();
exp_asset = exp_asset && !output.m_asset.IsNull();
exp_asset = exp_asset && VerifyBlindAssetProof(output.m_asset, output.m_blind_asset_proof, output.m_asset_commitment);
}
if (exp_asset) {
txout.nAsset.SetToAsset(CAsset(output.m_asset));
} else {
txout.nAsset = output.m_asset_commitment;
@ -417,7 +434,7 @@ bool PSBTInput::Merge(const PSBTInput& input)
}
if (m_issuance_rangeproof.empty() && !input.m_issuance_rangeproof.empty()) m_issuance_rangeproof = input.m_issuance_rangeproof;
if (m_issuance_inflation_keys_rangeproof.empty() && !input.m_issuance_inflation_keys_rangeproof.empty()) m_issuance_inflation_keys_rangeproof = input.m_issuance_inflation_keys_rangeproof;
if (m_issuance_inflation_keys_amount == nullopt && m_issuance_inflation_keys_commitment.IsNull() && input.m_issuance_inflation_keys_amount != nullopt) m_issuance_inflation_keys_amount = m_issuance_inflation_keys_amount;
if (m_issuance_inflation_keys_amount == nullopt && m_issuance_inflation_keys_commitment.IsNull() && input.m_issuance_inflation_keys_amount != nullopt) m_issuance_inflation_keys_amount = input.m_issuance_inflation_keys_amount;
if (m_issuance_inflation_keys_commitment.IsNull() && !input.m_issuance_inflation_keys_commitment.IsNull()) {
m_issuance_inflation_keys_commitment = input.m_issuance_inflation_keys_commitment;
m_issuance_inflation_keys_amount.reset();

View file

@ -41,6 +41,10 @@ bilingual_str TransactionErrorString(const TransactionError err)
return Untranslated("Proof of blinded value is invalid");
case TransactionError::INVALID_ASSET_PROOF:
return Untranslated("Proof of blinded asset is invalid");
case TransactionError::MISSING_BLINDING_KEY:
return Untranslated("Wallet does not have necessary blinding key");
case TransactionError::MISSING_SIDECHANNEL_DATA:
return Untranslated("A rangeproof did not encode necessary blinding data");
// no default case, so the compiler can warn about missing cases
}
assert(false);

View file

@ -35,6 +35,8 @@ enum class TransactionError {
UTXOS_MISSING_BALANCE_CHECK,
INVALID_VALUE_PROOF,
INVALID_ASSET_PROOF,
MISSING_BLINDING_KEY,
MISSING_SIDECHANNEL_DATA,
};
bilingual_str TransactionErrorString(const TransactionError error);

View file

@ -4951,8 +4951,16 @@ static RPCHelpMan walletcreatefundedpsbt()
// Automatically select coins, unless at least one is manually selected. Can
// be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
// FundTransaction expects blinding keys, if present, to appear in the output nonces
for (CTxOut& txout : rawTx.vout) {
auto search_it = psbt_outs.find(txout);
assert (search_it != psbt_outs.end());
CPubKey& blind_pub = search_it->second.m_blinding_pubkey;
if (blind_pub.IsFullyValid()) {
txout.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
}
}
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3], coin_control, /* solving_data */ request.params[5], /* override_min_fee */ true);
PartiallySignedTransaction psbtx(rawTx, psbt_version);
// Find an input that is ours
unsigned int blinder_index = 0;
{
@ -4965,6 +4973,17 @@ static RPCHelpMan walletcreatefundedpsbt()
}
}
assert(blinder_index < rawTx.vin.size()); // We added inputs, or existing inputs are ours, we should have a blinder index at this point.
// It may add outputs (change, and in some edge case OP_RETURN) which need to be
// blinded. So pull these into `psbt_outs`.
for (const CTxOut& txout : rawTx.vout) {
if (!txout.nNonce.IsNull() && !psbt_outs.count(txout)) {
PSBTOutput new_out{2}; // psbtv2 output
new_out.m_blinding_pubkey.Set(txout.nNonce.vchCommitment.begin(), txout.nNonce.vchCommitment.end());
new_out.m_blinder_index = blinder_index;
psbt_outs.insert(std::make_pair(txout, new_out));
}
}
PartiallySignedTransaction psbtx(rawTx, psbt_version);
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
PSBTOutput& output = psbtx.outputs[i];
auto it = psbt_outs.find(rawTx.vout.at(i));

View file

@ -19,6 +19,7 @@
#include <policy/policy.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <rpc/util.h> // for GetDestinationBlindingKey and IsBlindDestination
#include <script/descriptor.h>
#include <script/pegins.h>
#include <script/script.h>
@ -2853,7 +2854,7 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp
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)) {
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.
@ -2863,10 +2864,11 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp
if (!o.m_asset.IsNull()) {
assert(CAsset(o.m_asset) == asset);
}
return TransactionError::INVALID_ASSET_PROOF; // FIXME
} else {
return TransactionError::MISSING_SIDECHANNEL_DATA;
}
} else {
return TransactionError::INVALID_ASSET_PROOF; // FIXME
return TransactionError::MISSING_BLINDING_KEY;
}
}
}
@ -3226,6 +3228,8 @@ bool fillBlindDetails(BlindDetails* det, CWallet* wallet, CMutableTransaction& t
// We need to make sure to dupe an asset that is in input set
//TODO Have blinding do some extremely minimal rangeproof
CTxOut newTxOut(det->o_assets.back(), 0, CScript() << OP_RETURN);
CPubKey blind_pub = wallet->GetBlindingPubKey(newTxOut.scriptPubKey); // irrelevent, just needs to be non-null
newTxOut.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
txNew.vout.push_back(newTxOut);
det->o_pubkeys.push_back(wallet->GetBlindingPubKey(newTxOut.scriptPubKey));
det->o_amount_blinds.push_back(uint256());
@ -3346,6 +3350,9 @@ bool CWallet::CreateTransactionInternal(
// A map that keeps track of the change script for each asset and also
// the index of the reservedest used for that script (-1 if none).
std::map<CAsset, std::pair<int, CScript>> mapScriptChange;
// For manually set change, we need to use the blinding pubkey associated
// with the manually-set address rather than generating one from the wallet
std::map<CAsset, Optional<CPubKey> > mapBlindingKeyChange;
LOCK(cs_wallet);
txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
@ -3360,6 +3367,11 @@ bool CWallet::CreateTransactionInternal(
for (const auto& dest : coin_control.destChange) {
// No need to test we cover all assets. We produce error for that later.
mapScriptChange[dest.first] = std::pair<int, CScript>(-1, GetScriptForDestination(dest.second));
if (IsBlindDestination(dest.second)) {
mapBlindingKeyChange[dest.first] = GetDestinationBlindingKey(dest.second);
} else {
mapBlindingKeyChange[dest.first] = nullopt;
}
}
} else { // no coin control: send change to newly generated address
// Note: We use a new key here to keep it from being obvious which side is the change.
@ -3642,15 +3654,36 @@ bool CWallet::CreateTransactionInternal(
}
std::vector<CTxOut>::iterator position = txNew.vout.begin()+vChangePosInOut[assetChange.first];
Optional<CPubKey> blind_pub;
if (blind_details) {
CPubKey blind_pub = GetBlindingPubKey(itScript->second.second);
blind_details->o_pubkeys.insert(blind_details->o_pubkeys.begin() + vChangePosInOut[assetChange.first], blind_pub);
assert(blind_pub.IsFullyValid());
const auto itBlindingKey = mapBlindingKeyChange.find(assetChange.first);
if (itBlindingKey != mapBlindingKeyChange.end()) {
// If the change output was specified, use the blinding key that
// came with the specified address (if any)
blind_pub = itBlindingKey->second;
} else {
// Otherwise, we generated it from our own wallet, so get the
// blinding key from our own wallet.
blind_pub = GetBlindingPubKey(itScript->second.second);
}
} else {
// ...and if we are not blinding at all, use no blinding key. (This
// assignment is technically unnecessary as `blind_pub` was initialized
// above to nullopt, but we leave it here for clarity.)
blind_pub = nullopt;
}
if (blind_pub) {
blind_details->o_pubkeys.insert(blind_details->o_pubkeys.begin() + vChangePosInOut[assetChange.first], *blind_pub);
assert(blind_pub->IsFullyValid());
blind_details->num_to_blind++;
blind_details->change_to_blind++;
blind_details->only_change_pos = vChangePosInOut[assetChange.first];
// Place the blinding pubkey here in case of fundraw calls
newTxOut.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
newTxOut.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub->begin(), blind_pub->end());
} else if (blind_details) {
// Insert placeholder
blind_details->o_pubkeys.insert(blind_details->o_pubkeys.begin() + vChangePosInOut[assetChange.first], CPubKey{});
}
txNew.vout.insert(position, newTxOut);
}

View file

@ -24,6 +24,17 @@ import os
MAX_BIP125_RBF_SEQUENCE = 0xfffffffd
# Utility function to extract info from outputs for use in assert_equal
# Returns a set of tuples (address, is blinded, is OP_RETURN). (It would
# feel nicer to return a set of dicts, but you can't do that in Python.)
def outputs_info(outputs):
return {(
"" if x["script"].get("addresses") is None else x["script"]["addresses"][0],
x.get("blinding_pubkey") is not None,
x["script"]["asm"] == "OP_RETURN",
) for x in outputs}
# Create one-input, one-output, no-fee transaction:
class PSBTTest(BitcoinTestFramework):
@ -317,13 +328,49 @@ class PSBTTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, msg, self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"feeRate": 1, "add_inputs": bool_add})
self.log.info("Test various PSBT operations")
addr = self.get_address(confidential, 1)
unconf_addr = self.nodes[1].getaddressinfo(addr)['unconfidential']
change_addr = self.nodes[1].getrawchangeaddress()
conf_change_addr = self.nodes[1].getaddressinfo(change_addr)['confidential']
unconf_change_addr = self.nodes[1].getaddressinfo(change_addr)['unconfidential']
# partially sign multisig things with node 1
psbtx = wmulti.walletcreatefundedpsbt(inputs=[{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], outputs=[{self.get_address(confidential, 1):29.99}], options={'changeAddress': self.nodes[1].getrawchangeaddress()})['psbt']
psbtx = wmulti.walletcreatefundedpsbt(inputs=[{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], outputs=[{addr:29.99}], options={'changeAddress': unconf_change_addr})['psbt']
filled = wmulti.walletprocesspsbt(psbtx)
# have both nodes fill before we try to blind and sign
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(filled["psbt"])
psbtx = walletprocesspsbt_out['psbt']
assert_equal(walletprocesspsbt_out['complete'], False)
# check that the unblinded change address led to unblinded change
assert_equal(
outputs_info(self.nodes[1].decodepsbt(psbtx)["outputs"]),
{
(unconf_addr, confidential, False),
(unconf_change_addr, False, False),
("", False, False), # fee
},
)
# Repeat the above, with a confidential change address
psbtx = wmulti.walletcreatefundedpsbt(inputs=[{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], outputs=[{addr:29.99}], options={'changeAddress': conf_change_addr})['psbt']
filled = wmulti.walletprocesspsbt(psbtx)
# have both nodes fill before we try to blind and sign
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(filled["psbt"])
psbtx = walletprocesspsbt_out['psbt']
assert_equal(walletprocesspsbt_out['complete'], False)
# check that the blinded change address led to blinded change (and below,
# when we call `walletprocesspsbt` with nodes[2], it will make sure that
# node 2 is able to unblind this change, even though wmulti created it).
# Notice that if `confidential` is False, the change is not blinded. This
# is a quirk of the wallet.cpp blinding logic and will go away when we
# overhaul this.
assert_equal(
outputs_info(self.nodes[1].decodepsbt(psbtx)["outputs"]),
{
(unconf_addr, confidential, False),
(unconf_change_addr, confidential, False),
("", False, False), # fee
},
)
# Unload wmulti, we don't need it anymore
wmulti.unloadwallet()
@ -717,7 +764,42 @@ class PSBTTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all()
# Regression for #1049
# 1. Create a one-blinded-output PSET and check that it is blinded correctly
addr = self.nodes[0].getnewaddress()
conf_addr = self.nodes[0].getaddressinfo(addr)['confidential']
unconf_addr = self.nodes[0].getaddressinfo(addr)['unconfidential']
# 1a. Funding should succeed and *not* add a OP_RETURN output
funded = self.nodes[1].walletcreatefundedpsbt([], [{conf_addr: self.nodes[1].getbalance()['bitcoin']}], 0, {"subtractFeeFromOutputs": [0]})["psbt"]
assert_equal(
outputs_info(self.nodes[1].decodepsbt(funded)["outputs"]),
{
(unconf_addr, True, False),
("", False, False), # fee
},
)
# 1b. `walletprocesspsbt` should then succeed in creating a full transaction
signed = self.nodes[1].walletprocesspsbt(funded)["psbt"]
tx = self.nodes[1].finalizepsbt(signed)["hex"]
assert self.nodes[1].testmempoolaccept([tx])[0]['allowed']
# 2. Create a one-unblinded-output PSET and check that it is blinded correctly
# 2a. Funding should succeed and add a OP_RETURN output
funded = self.nodes[1].walletcreatefundedpsbt([], [{unconf_addr: self.nodes[1].getbalance()['bitcoin']}], 0, {"subtractFeeFromOutputs": [0]})["psbt"]
assert_equal(
outputs_info(self.nodes[1].decodepsbt(funded)["outputs"]),
{
(unconf_addr, False, False),
("", True, True), # blinded OP_RETURN
("", False, False), # fee
},
)
# 2b. `walletprocesspsbt` should then succeed in creating a full transaction
signed = self.nodes[1].walletprocesspsbt(funded)["psbt"]
tx = self.nodes[1].finalizepsbt(signed)["hex"]
assert self.nodes[1].testmempoolaccept([tx])[0]['allowed']
def pset_confidential_proofs(self):
UNBLINDED = "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+WJAuweXm6kXlFkBAwgAv3xIGAkAAAf8BHBzZXQCICMPT11LfG+oRYBu5PZ3E0WeG2no5g/O4uSUDHoNXeGyB/wEcHNldAYhAwxmNPa94Vg9u/nZBWC/8IYTgnp85V5TMOEFWTTAcF2pB/wEcHNldAgEAAAAAAABBAABAwgA4fUFAAAAAAf8BHBzZXQCICMPT11LfG+oRYBu5PZ3E0WeG2no5g/O4uSUDHoNXeGyB/wEcHNldAgEAAAAAAA="
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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"
@ -726,6 +808,14 @@ class PSBTTest(BitcoinTestFramework):
ONLY_BLIND = "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"
## Check warnings for PSETs
stats = [output.get("status") for output in self.nodes[0].decodepsbt(UNBLINDED)["outputs"]]
assert_equal(stats, ["needs blinding", None])
stats = [output for output in self.nodes[0].analyzepsbt(UNBLINDED)["outputs"]]
assert_equal(stats, [
{"blind": True, "status": "unblinded" },
{"blind": False, "status": "done" },
])
for output in self.nodes[0].decodepsbt(BLINDED)["outputs"]:
assert "status" not in output
for output in self.nodes[0].analyzepsbt(BLINDED)["outputs"]:
@ -783,20 +873,22 @@ class PSBTTest(BitcoinTestFramework):
{"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])
# The fully-blinded (with proofs) PSET will combine with the blinded one,
# and will copy the blinded data
assert_equal (self.nodes[0].combinepsbt([UNBLINDED, BLINDED]), BLINDED)
assert_equal (self.nodes[0].combinepsbt([BLINDED, UNBLINDED]), BLINDED)
# When combining, "bad proofs" are the same as blinded data. So these transactions
# will be interpreted as partially blinded (exp asset blind value, or vice-versa),
# and will combine accordingly. These details are not so important, but what *IS*
# important is that you cannot combine any of the bad-proof PSETs with the
# unblinded one.
self.nodes[0].combinepsbt([NO_VALUE_PROOF, BAD_VALUE_PROOF])
self.nodes[0].combinepsbt([NO_ASSET_PROOF, BAD_ASSET_PROOF])
assert_raises_rpc_error(-8, "PSBTs not compatible (different transactions)", self.nodes[0].combinepsbt, [BAD_VALUE_PROOF, BAD_ASSET_PROOF])
for bad_pset in [ 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, [UNBLINDED, bad_pset])
assert_raises_rpc_error(-8, "PSBTs not compatible (different transactions)", self.nodes[0].combinepsbt, [ONLY_BLIND, bad_pset])
assert_raises_rpc_error(-8, "PSBTs not compatible (different transactions)", self.nodes[0].combinepsbt, [BLINDED, bad_pset])
def run_test(self):
self.nodes[0].generate(200)