Skip to content

Commit

Permalink
Merge bitcoin#13723: PSBT key path cleanups
Browse files Browse the repository at this point in the history
  • Loading branch information
kwvg committed Jun 4, 2021
1 parent 18ae7a1 commit f573fb6
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 135 deletions.
18 changes: 5 additions & 13 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1439,11 +1439,8 @@ UniValue decodepsbt(const JSONRPCRequest& request)
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("pubkey", HexStr(entry.first));

uint32_t fingerprint = entry.second.at(0);
keypath.pushKV("master_fingerprint", strprintf("%08x", bswap_32(fingerprint)));

entry.second.erase(entry.second.begin());
keypath.pushKV("path", WriteHDKeypath(entry.second));
keypath.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path", WriteHDKeypath(entry.second.path));
keypaths.push_back(keypath);
}
in.pushKV("bip32_derivs", keypaths);
Expand Down Expand Up @@ -1501,12 +1498,8 @@ UniValue decodepsbt(const JSONRPCRequest& request)
for (auto entry : output.hd_keypaths) {
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("pubkey", HexStr(entry.first));

uint32_t fingerprint = entry.second.at(0);
keypath.pushKV("master_fingerprint", strprintf("%08x", bswap_32(fingerprint)));

entry.second.erase(entry.second.begin());
keypath.pushKV("path", WriteHDKeypath(entry.second));
keypath.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path", WriteHDKeypath(entry.second.path));
keypaths.push_back(keypath);
}
out.pushKV("bip32_derivs", keypaths);
Expand Down Expand Up @@ -1627,8 +1620,7 @@ UniValue finalizepsbt(const JSONRPCRequest& request)
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput& input = psbtx.inputs.at(i);

SignatureData sigdata;
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, *psbtx.tx, input, sigdata, i, 1);
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, *psbtx.tx, input, i, 1);
}

UniValue result(UniValue::VOBJ);
Expand Down
71 changes: 60 additions & 11 deletions src/script/sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ static bool GetCScript(const SigningProvider& provider, const SignatureData& sig

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
const auto it = sigdata.signatures.find(address);
if (it != sigdata.signatures.end()) {
Expand All @@ -57,7 +53,15 @@ static bool GetPubKey(const SigningProvider& provider, SignatureData& sigdata, c
// 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;
pubkey = pk_it->second.first;
return true;
}
// Query the underlying provider
if (provider.GetPubKey(address, pubkey)) {
KeyOriginInfo info;
if (provider.GetKeyOrigin(address, info)) {
sigdata.misc_pubkeys.emplace(address, std::make_pair(pubkey, std::move(info)));
}
return true;
}
return false;
Expand Down Expand Up @@ -190,29 +194,56 @@ 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)
bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, 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
SignatureData sigdata;
input.FillSignatureData(sigdata);

// Get UTXO
bool require_witness_sig = false;
CTxOut utxo;
if (input.non_witness_utxo) {
// If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
if (input.non_witness_utxo->GetHash() != tx.vin[index].prevout.hash) return false;
// If both witness and non-witness UTXO are provided, verify that they match. This check shouldn't
// matter, as the PSBT deserializer enforces only one of both is provided, and the only way both
// can be present is when they're added simultaneously by FillPSBT (in which case they always match).
// Still, check in order to not rely on callers to enforce this.
if (!input.witness_utxo.IsNull() && input.non_witness_utxo->vout[tx.vin[index].prevout.n] != input.witness_utxo) return false;
utxo = input.non_witness_utxo->vout[tx.vin[index].prevout.n];
} else if (!input.witness_utxo.IsNull()) {
utxo = input.witness_utxo;
// When we're taking our information from a witness UTXO, we can't verify it is actually data from
// the output being spent. This is safe in case a witness signature is produced (which includes this
// information directly in the hash), but not for non-witness signatures. Remember that we require
// a witness signature in this situation.
require_witness_sig = true;
} else {
return false;
}

MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash);
sigdata.witness = false;
bool sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata);
// Verify that a witness signature was produced in case one was required.
if (require_witness_sig && !sigdata.witness) return false;
input.FromSignatureData(sigdata);

// If both UTXO types are present, drop the unnecessary one.
if (input.non_witness_utxo && !input.witness_utxo.IsNull()) {
if (sigdata.witness) {
input.non_witness_utxo = nullptr;
} else {
input.witness_utxo.SetNull();
}
}

return sig_complete;
}

Expand Down Expand Up @@ -456,7 +487,7 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const
sigdata.witness_script = witness_script;
}
for (const auto& key_pair : hd_keypaths) {
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair.first);
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair);
}
}

Expand Down Expand Up @@ -484,6 +515,9 @@ void PSBTInput::FromSignatureData(const SignatureData& sigdata)
if (witness_script.empty() && !sigdata.witness_script.empty()) {
witness_script = sigdata.witness_script;
}
for (const auto& entry : sigdata.misc_pubkeys) {
hd_keypaths.emplace(entry.second);
}
}

void PSBTInput::Merge(const PSBTInput& input)
Expand Down Expand Up @@ -525,7 +559,7 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
sigdata.witness_script = witness_script;
}
for (const auto& key_pair : hd_keypaths) {
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair.first);
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair);
}
}

Expand All @@ -537,6 +571,9 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata)
if (witness_script.empty() && !sigdata.witness_script.empty()) {
witness_script = sigdata.witness_script;
}
for (const auto& entry : sigdata.misc_pubkeys) {
hd_keypaths.emplace(entry.second);
}
}

bool PSBTOutput::IsNull() const
Expand All @@ -553,12 +590,24 @@ void PSBTOutput::Merge(const PSBTOutput& output)
if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
}

bool PublicOnlySigningProvider::GetCScript(const CScriptID &scriptid, CScript& script) const
bool HidingSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const
{
return m_provider->GetCScript(scriptid, script);
}

bool PublicOnlySigningProvider::GetPubKey(const CKeyID &address, CPubKey& pubkey) const
bool HidingSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const
{
return m_provider->GetPubKey(keyid, pubkey);
}

bool HidingSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const
{
if (m_hide_secret) return false;
return m_provider->GetKey(keyid, key);
}

bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const
{
return m_provider->GetPubKey(address, pubkey);
if (m_hide_origin) return false;
return m_provider->GetKeyOrigin(keyid, info);
}
53 changes: 35 additions & 18 deletions src/script/sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ class CTransaction;

struct CMutableTransaction;

struct KeyOriginInfo
{
unsigned char fingerprint[4];
std::vector<uint32_t> path;
};

/** An interface to be implemented by keystores that support signing. */
class SigningProvider
{
Expand All @@ -28,19 +34,24 @@ class SigningProvider
virtual bool GetCScript(const CScriptID &scriptid, CScript& script) const { return false; }
virtual bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const { return false; }
virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; }
virtual bool GetKeyOrigin(const CKeyID& id, KeyOriginInfo& info) const { return false; }
};

extern const SigningProvider& DUMMY_SIGNING_PROVIDER;

class PublicOnlySigningProvider : public SigningProvider
class HidingSigningProvider : public SigningProvider
{
private:
const bool m_hide_secret;
const bool m_hide_origin;
const SigningProvider* m_provider;

public:
PublicOnlySigningProvider(const SigningProvider* provider) : m_provider(provider) {}
bool GetCScript(const CScriptID &scriptid, CScript& script) const;
bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const;
HidingSigningProvider(const SigningProvider* provider, bool hide_secret, bool hide_origin) : m_hide_secret(hide_secret), m_hide_origin(hide_origin), m_provider(provider) {}
bool GetCScript(const CScriptID& scriptid, CScript& script) const override;
bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
bool GetKey(const CKeyID& keyid, CKey& key) const override;
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
};

/** Interface for signature creators. */
Expand Down Expand Up @@ -88,8 +99,8 @@ struct SignatureData {
bool complete = false; ///< Stores whether the scriptSig is complete
CScript scriptSig; ///< The scriptSig of an input. Contains complete signatures or the traditional partial signatures format
CScript redeem_script; ///< The redeemScript (if any) for the input
std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig.
std::map<CKeyID, CPubKey> misc_pubkeys;
std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness.
std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys;

SignatureData() {}
explicit SignatureData(const CScript& script) : scriptSig(script) {}
Expand Down Expand Up @@ -148,7 +159,7 @@ void UnserializeFromVector(Stream& s, X&... args)

// Deserialize HD keypaths into a map
template<typename Stream>
void DeserializeHDKeypaths(Stream& s, const std::vector<unsigned char>& key, std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths)
void DeserializeHDKeypaths(Stream& s, const std::vector<unsigned char>& key, std::map<CPubKey, KeyOriginInfo>& hd_keypaths)
{
// Make sure that the key is the size of pubkey + 1
if (key.size() != CPubKey::PUBLIC_KEY_SIZE + 1 && key.size() != CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1) {
Expand All @@ -165,25 +176,31 @@ void DeserializeHDKeypaths(Stream& s, const std::vector<unsigned char>& key, std

// Read in key path
uint64_t value_len = ReadCompactSize(s);
std::vector<uint32_t> keypath;
for (unsigned int i = 0; i < value_len; i += sizeof(uint32_t)) {
if (value_len % 4 || value_len == 0) {
throw std::ios_base::failure("Invalid length for HD key path");
}

KeyOriginInfo keypath;
s >> keypath.fingerprint;
for (unsigned int i = 4; i < value_len; i += sizeof(uint32_t)) {
uint32_t index;
s >> index;
keypath.push_back(index);
keypath.path.push_back(index);
}

// Add to map
hd_keypaths.emplace(pubkey, keypath);
hd_keypaths.emplace(pubkey, std::move(keypath));
}

// Serialize HD keypaths to a stream from a map
template<typename Stream>
void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths, uint8_t type)
void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, KeyOriginInfo>& hd_keypaths, uint8_t type)
{
for (auto keypath_pair : hd_keypaths) {
SerializeToVector(s, type, MakeSpan(keypath_pair.first));
WriteCompactSize(s, keypath_pair.second.size() * sizeof(uint32_t));
for (auto& path : keypath_pair.second) {
WriteCompactSize(s, (keypath_pair.second.path.size() + 1) * sizeof(uint32_t));
s << keypath_pair.second.fingerprint;
for (const auto& path : keypath_pair.second.path) {
s << path;
}
}
Expand All @@ -198,7 +215,7 @@ struct PSBTInput
CScript witness_script;
CScript final_script_sig;
CScriptWitness final_script_witness;
std::map<CPubKey, std::vector<uint32_t>> hd_keypaths;
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
std::map<CKeyID, SigPair> partial_sigs;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
int sighash_type = 0;
Expand Down Expand Up @@ -406,7 +423,7 @@ struct PSBTOutput
{
CScript redeem_script;
CScript witness_script;
std::map<CPubKey, std::vector<uint32_t>> hd_keypaths;
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;

bool IsNull() const;
Expand Down Expand Up @@ -671,8 +688,8 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType);
bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType);

/** Signs a PSBTInput */
bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, SignatureData& sigdata, int index, int sighash = 1);
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, int index, int sighash = SIGHASH_ALL);

/** Extract signature data from a transaction input, and insert it. */
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout);
Expand Down
41 changes: 41 additions & 0 deletions src/utilstrencodings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,47 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out)
return true;
}

bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath)
{
std::stringstream ss(keypath_str);
std::string item;
bool first = true;
while (std::getline(ss, item, '/')) {
if (item.compare("m") == 0) {
if (first) {
first = false;
continue;
}
return false;
}
// Finds whether it is hardened
uint32_t path = 0;
size_t pos = item.find("'");
if (pos != std::string::npos) {
// The hardened tick can only be in the last index of the string
if (pos != item.size() - 1) {
return false;
}
path |= 0x80000000;
item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick
}

// Ensure this is only numbers
if (item.find_first_not_of( "0123456789" ) != std::string::npos) {
return false;
}
uint32_t number;
if (!ParseUInt32(item, &number)) {
return false;
}
path |= number;

keypath.push_back(path);
first = false;
}
return true;
}

std::string HexStr(const Span<const uint8_t> s)
{
std::string rv;
Expand Down
3 changes: 3 additions & 0 deletions src/utilstrencodings.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,7 @@ bool ConvertBits(const O& outfn, I it, I end) {
return true;
}

/** Parse an HD keypaths like "m/7/0'/2000". */
bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath);

#endif // BITCOIN_UTILSTRENCODINGS_H
Loading

0 comments on commit f573fb6

Please sign in to comment.