From 41c607f09badb2c3ed58ff6fb17a8ebbef2cdabd Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 27 Jun 2018 16:53:48 -0700 Subject: Implement PSBT Structures and un/serialization methods per BIP 174 --- src/script/sign.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/script/sign.cpp') diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 60a8a2655..afbcb22d1 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -429,3 +429,19 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script) } return false; } + + +bool PartiallySignedTransaction::IsNull() const +{ + return !tx && inputs.empty() && outputs.empty() && unknown.empty(); +} + +bool PSBTInput::IsNull() const +{ + return !non_witness_utxo && witness_utxo.IsNull() && partial_sigs.empty() && unknown.empty() && hd_keypaths.empty() && redeem_script.empty() && witness_script.empty(); +} + +bool PSBTOutput::IsNull() const +{ + return redeem_script.empty() && witness_script.empty() && hd_keypaths.empty() && unknown.empty(); +} -- cgit v1.2.3 From 12bcc64f277f642ece03c25653e726f2276f0d51 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 27 Jun 2018 16:56:30 -0700 Subject: Add pubkeys and whether input was witness to SignatureData Stores pubkeys in SignatureData and retrieves them when using GetPubKey(). Stores whether the signatures in a SignatureData are for a witness input. --- src/script/sign.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'src/script/sign.cpp') diff --git a/src/script/sign.cpp b/src/script/sign.cpp index afbcb22d1..b62fe2e92 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -49,9 +49,10 @@ static bool GetCScript(const SigningProvider& provider, const SignatureData& sig return false; } -static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigdata, const CKeyID& address, CPubKey& pubkey) +static bool GetPubKey(const SigningProvider& provider, SignatureData& sigdata, const CKeyID& address, CPubKey& pubkey) { if (provider.GetPubKey(address, pubkey)) { + sigdata.misc_pubkeys.emplace(pubkey.GetID(), pubkey); return true; } // Look for pubkey in all partial sigs @@ -60,6 +61,12 @@ static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigd pubkey = it->second.first; return true; } + // Look for pubkey in pubkey list + const auto& pk_it = sigdata.misc_pubkeys.find(address); + if (pk_it != sigdata.misc_pubkeys.end()) { + pubkey = pk_it->second; + return true; + } return false; } @@ -70,9 +77,9 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat sig_out = it->second.second; return true; } + CPubKey pubkey; + GetPubKey(provider, sigdata, keyid, pubkey); if (creator.CreateSig(provider, sig_out, keyid, scriptcode, sigversion)) { - CPubKey pubkey; - GetPubKey(provider, sigdata, keyid, pubkey); auto i = sigdata.signatures.emplace(keyid, SigPair(pubkey, sig_out)); assert(i.second); return true; @@ -200,6 +207,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato txnouttype subType; solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata); sigdata.scriptWitness.stack = result; + sigdata.witness = true; result.clear(); } else if (solved && whichType == TX_WITNESS_V0_SCRIPTHASH) @@ -210,7 +218,10 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata) && subType != TX_SCRIPTHASH && subType != TX_WITNESS_V0_SCRIPTHASH && subType != TX_WITNESS_V0_KEYHASH; result.push_back(std::vector(witnessscript.begin(), witnessscript.end())); sigdata.scriptWitness.stack = result; + sigdata.witness = true; result.clear(); + } else if (solved && whichType == TX_WITNESS_UNKNOWN) { + sigdata.witness = true; } if (P2SH) { -- cgit v1.2.3 From e9d86a43ad8b1ab83b324e9a7a64c43a61337501 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 27 Jun 2018 16:58:01 -0700 Subject: Methods for interacting with PSBT structs Added methods which move data to/from SignaturData objects to PSBTInput and PSBTOutput objects. Added sanity checks for PSBTs as a whole which are done immediately after deserialization. Added Merge methods to merge a PSBT into another one. --- src/script/sign.cpp | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) (limited to 'src/script/sign.cpp') diff --git a/src/script/sign.cpp b/src/script/sign.cpp index b62fe2e92..28dfdecdf 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -447,12 +447,144 @@ bool PartiallySignedTransaction::IsNull() const return !tx && inputs.empty() && outputs.empty() && unknown.empty(); } +void PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt) +{ + for (unsigned int i = 0; i < inputs.size(); ++i) { + inputs[i].Merge(psbt.inputs[i]); + } + for (unsigned int i = 0; i < outputs.size(); ++i) { + outputs[i].Merge(psbt.outputs[i]); + } +} + +bool PartiallySignedTransaction::IsSane() const +{ + for (PSBTInput input : inputs) { + if (!input.IsSane()) return false; + } + return true; +} + bool PSBTInput::IsNull() const { return !non_witness_utxo && witness_utxo.IsNull() && partial_sigs.empty() && unknown.empty() && hd_keypaths.empty() && redeem_script.empty() && witness_script.empty(); } +void PSBTInput::FillSignatureData(SignatureData& sigdata) const +{ + if (!final_script_sig.empty()) { + sigdata.scriptSig = final_script_sig; + sigdata.complete = true; + } + if (!final_script_witness.IsNull()) { + sigdata.scriptWitness = final_script_witness; + sigdata.complete = true; + } + if (sigdata.complete) { + return; + } + + sigdata.signatures.insert(partial_sigs.begin(), partial_sigs.end()); + if (!redeem_script.empty()) { + sigdata.redeem_script = redeem_script; + } + if (!witness_script.empty()) { + sigdata.witness_script = witness_script; + } + for (const auto& key_pair : hd_keypaths) { + sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair.first); + } +} + +void PSBTInput::FromSignatureData(const SignatureData& sigdata) +{ + if (sigdata.complete) { + partial_sigs.clear(); + hd_keypaths.clear(); + redeem_script.clear(); + witness_script.clear(); + + if (!sigdata.scriptSig.empty()) { + final_script_sig = sigdata.scriptSig; + } + if (!sigdata.scriptWitness.IsNull()) { + final_script_witness = sigdata.scriptWitness; + } + return; + } + + partial_sigs.insert(sigdata.signatures.begin(), sigdata.signatures.end()); + if (redeem_script.empty() && !sigdata.redeem_script.empty()) { + redeem_script = sigdata.redeem_script; + } + if (witness_script.empty() && !sigdata.witness_script.empty()) { + witness_script = sigdata.witness_script; + } +} + +void PSBTInput::Merge(const PSBTInput& input) +{ + if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo; + if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) { + witness_utxo = input.witness_utxo; + non_witness_utxo = nullptr; // Clear out any non-witness utxo when we set a witness one. + } + + partial_sigs.insert(input.partial_sigs.begin(), input.partial_sigs.end()); + hd_keypaths.insert(input.hd_keypaths.begin(), input.hd_keypaths.end()); + unknown.insert(input.unknown.begin(), input.unknown.end()); + + if (redeem_script.empty() && !input.redeem_script.empty()) redeem_script = input.redeem_script; + 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; +} + +bool PSBTInput::IsSane() const +{ + // Cannot have both witness and non-witness utxos + if (!witness_utxo.IsNull() && non_witness_utxo) return false; + + // If we have a witness_script or a scriptWitness, we must also have a witness utxo + if (!witness_script.empty() && witness_utxo.IsNull()) return false; + if (!final_script_witness.IsNull() && witness_utxo.IsNull()) return false; + + return true; +} + +void PSBTOutput::FillSignatureData(SignatureData& sigdata) const +{ + if (!redeem_script.empty()) { + sigdata.redeem_script = redeem_script; + } + if (!witness_script.empty()) { + sigdata.witness_script = witness_script; + } + for (const auto& key_pair : hd_keypaths) { + sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair.first); + } +} + +void PSBTOutput::FromSignatureData(const SignatureData& sigdata) +{ + if (redeem_script.empty() && !sigdata.redeem_script.empty()) { + redeem_script = sigdata.redeem_script; + } + if (witness_script.empty() && !sigdata.witness_script.empty()) { + witness_script = sigdata.witness_script; + } +} + bool PSBTOutput::IsNull() const { return redeem_script.empty() && witness_script.empty() && hd_keypaths.empty() && unknown.empty(); } + +void PSBTOutput::Merge(const PSBTOutput& output) +{ + hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end()); + unknown.insert(output.unknown.begin(), output.unknown.end()); + + 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; +} -- cgit v1.2.3 From 8b5ef2793748065727a9a2498805ae5b269dcb4f Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Thu, 28 Jun 2018 18:56:34 -0700 Subject: SignPSBTInput wrapper function The SignPSBTInput function takes a PSBTInput, SignatureData, SigningProvider, and other data necessary for signing. It fills the SignatureData with data from the PSBTInput, retrieves the UTXO from the PSBTInput, signs and finalizes the input if possible, and then extracts the results from the SignatureData and puts them back into the PSBTInput. --- src/script/sign.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src/script/sign.cpp') diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 28dfdecdf..f9907f5e0 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -234,6 +234,32 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato return sigdata.complete; } +bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, SignatureData& sigdata, int index, int sighash) +{ + // if this input has a final scriptsig or scriptwitness, don't do anything with it + if (!input.final_script_sig.empty() || !input.final_script_witness.IsNull()) { + return true; + } + + // Fill SignatureData with input info + input.FillSignatureData(sigdata); + + // Get UTXO + CTxOut utxo; + if (input.non_witness_utxo) { + utxo = input.non_witness_utxo->vout[tx.vin[index].prevout.n]; + } else if (!input.witness_utxo.IsNull()) { + utxo = input.witness_utxo; + } else { + return false; + } + + MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash); + bool sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); + input.FromSignatureData(sigdata); + return sig_complete; +} + class SignatureExtractorChecker final : public BaseSignatureChecker { private: -- cgit v1.2.3 From a4b06fb42eb0ad94e562ca839391b57e69285136 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Thu, 28 Jun 2018 19:05:05 -0700 Subject: Create wallet RPCs for PSBT walletprocesspsbt takes a PSBT format transaction, updates the PSBT with any inputs related to this wallet, signs, and finalizes the transaction. There is also an option to not sign and just update. walletcreatefundedpsbt creates a PSBT from user provided data in the same form as createrawtransaction. It also funds the transaction and takes an options argument in the same form as fundrawtransaction. The resulting PSBT is blank with no input or output data filled in. --- src/script/sign.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src/script/sign.cpp') diff --git a/src/script/sign.cpp b/src/script/sign.cpp index f9907f5e0..f1ac1f411 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -614,3 +614,13 @@ 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; } + +bool PublicOnlySigningProvider::GetCScript(const CScriptID &scriptid, CScript& script) const +{ + return m_provider->GetCScript(scriptid, script); +} + +bool PublicOnlySigningProvider::GetPubKey(const CKeyID &address, CPubKey& pubkey) const +{ + return m_provider->GetPubKey(address, pubkey); +} -- cgit v1.2.3