-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Enable various p2sh-p2wpkh functionality #9017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
src/base58.cpp
Outdated
if (!IsValid() || vchVersion != Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS)) | ||
return false; | ||
uint160 id; | ||
memcpy(&id, &vchData[0], 20); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is safe, but technically you are copying the vchData, which is specified to be zero_after_free memory, so you lose any guarantee that isn't somewhere in memory still.
You also may as well just directly use &scriptID (or scriptID.begin()) here rather than creating a temp.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should at least replace &id
with id.begin()` on this line to avoid making an assumption about the layout of uint160 objects.
src/rpc/misc.cpp
Outdated
CKeyID keyID; | ||
CPubKey pubkey; | ||
if (pwalletMain->GetWitnessKeyID(scriptID, keyID) && | ||
pwalletMain->GetPubKey(keyID, pubkey) && pubkey.IsCompressed()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious when the pubkey.IsCompressed condition would not be true? Might be nice to note when the condition is expected in a comment. Alternately it might make more sense as an assertion if it's never expected, or as a separate JSON attribute like in the CKeyID handler above (line 123): obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed()));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True an assert may make the most sense here. It really shouldn't happen, and if it is we want to find out as soon as possible to avoid loss of funds.
src/wallet/crypter.cpp
Outdated
@@ -277,6 +277,20 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co | |||
return false; | |||
} | |||
|
|||
bool CCryptoKeyStore::GetWitnessKeyID(const CScriptID &scriptID, CKeyID &keyID) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm missing something, nothing in this function seems specific to the CCryptoKeyStore
type as opposed to a more general type like CKeyStore
. If this is the case, it might be better to make this a CKeyStore
member or a standalone function taking a const CKeyStore&
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CKeyStore doesn't have access to keys if they're crypted.
A standalone function may make more sense, I'm really ambivalent about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, actually you're right, nothing in particular needs a CCryptoKeyStore. I thoroughly convinced myself otherwise previously somehow. Regardless, I'll divorce it from the keystore altogether.
src/wallet/crypter.cpp
Outdated
std::vector<unsigned char> witnessProgram; | ||
if (!GetCScript(scriptID, subscript) || | ||
!subscript.IsWitnessProgram(witnessVer, witnessProgram) || | ||
witnessProgram.size() != 20) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The size 20 check and explicit uint160 construction below seem like low level encoding details that are out of place here. Maybe this logic could go into to a new CScript method like: bool CScript::GetWitnessKeyID(CKeyID& keyID)
or bool CScript::GetV0WitnessKeyID(uint160& keyID)
.
src/base58.cpp
Outdated
if (!IsValid() || vchVersion != Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS)) | ||
return false; | ||
uint160 id; | ||
memcpy(&id, &vchData[0], 20); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should at least replace &id
with id.begin()` on this line to avoid making an assumption about the layout of uint160 objects.
src/wallet/rpcdump.cpp
Outdated
@@ -539,11 +539,14 @@ UniValue dumpprivkey(const JSONRPCRequest& request) | |||
|
|||
string strAddress = request.params[0].get_str(); | |||
CBitcoinAddress address; | |||
if (!address.SetString(strAddress)) | |||
if (!address.SetString(strAddress) || !address.IsValid()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change makes the error message below somewhat inaccurate. Maybe replace "Invalid Bitcoin address"
with string("Invalid Bitcoin address for network ") + Params().NetworkIDString()
below.
src/wallet/rpcwallet.cpp
Outdated
@@ -1016,51 +1016,6 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) | |||
return CBitcoinAddress(innerID).ToString(); | |||
} | |||
|
|||
class Witnessifier : public boost::static_visitor<bool> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe note in commit description that this change is moveonly (no code changes).
src/wallet/wallet.h
Outdated
@@ -986,4 +986,49 @@ class CAccount | |||
} | |||
}; | |||
|
|||
class Witnessifier : public boost::static_visitor<bool> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be easier for callers if instead of exposing the Witnessifier
class in wallet.h, you expose a simpler function that can be called by both importprivkey
and addwitnessaddress
and hides all the boost visitor implementation details:
bool AddWitnessCScript(CTxDestination dest, CScriptID &id);
This could even be a CWallet member function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good refactor for another PR, especially if I get rid of importprivkey commit. I'll drop this commit as well otherwise.
src/wallet/rpcdump.cpp
Outdated
@@ -133,6 +133,14 @@ UniValue importprivkey(const JSONRPCRequest& request) | |||
pwalletMain->MarkDirty(); | |||
pwalletMain->SetAddressBook(vchAddress, strLabel, "receive"); | |||
|
|||
if (IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) || GetBoolArg("-walletprematurewitness", false)) { | |||
Witnessifier w; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block of code seems very similar to code in addwitnessaddress
:
except this ignores the apply_visitor return value and passes a nonempty strlabel to SetAddressBook. Can both places be changed to call a common function?
Also is it ok to ignore the apply_visitor return value here? Consider adding a comment explaining why if it is ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I should over-optimize this for 2 instances. Although I'm also getting concerned about the logic in this commit.
- If the user already has the p2pkh key in address book but not the nested version, it won't rescan(just a bug I can fix)
- I'm concerned of the user flow here. As soon as segwit activates it starts adding segwit addresses to the address book. This kind of behavior would be really unsafe for
getnewaddress
and it seems unsafe for the same reason here. It might make more sense to simply tell the user to importprivkey, thenaddwitnessaddress
. That is literally equivalent.
In future wallet updates when getnewaddress
is flipped to nested p2sh using some command line init variable, we can have this flip as well, or simply add both as a precaution.
Unless there is a strong counter-argument here I think I should just delete this commit.
src/wallet/rpcdump.cpp
Outdated
@@ -133,6 +133,14 @@ UniValue importprivkey(const JSONRPCRequest& request) | |||
pwalletMain->MarkDirty(); | |||
pwalletMain->SetAddressBook(vchAddress, strLabel, "receive"); | |||
|
|||
if (IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) || GetBoolArg("-walletprematurewitness", false)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be useful to callers to have an error message in the case where an update to the address book is skipped because this condition is false? Either way an explanatory comment might be good here.
Why do you need GetWitnessKeyID in keystore? That doesn't seem to be something that belongs in the interface, and could just be implemented on top in the signing logic. |
2cba903
to
1c6fcd5
Compare
@sipa it certainly does not need to be there and is likely inappropriate. I'm not sure exactly where to put it: |
It only needs access to a keystore, no? Not to a wallet. |
@sipa yes, I misspoke |
1c6fcd5
to
6a67000
Compare
Moved GetWitnessKeyID outside of Keystore and addressed some nits. |
Concept ACK |
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Given P2SH address does not match the signature."); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we want to extend the current message signing to new address formats as-is. Maybe better to leave this out for now...
@@ -204,7 +212,8 @@ UniValue validateaddress(const JSONRPCRequest& request) | |||
if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) | |||
ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name)); | |||
CKeyID keyID; | |||
if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapKeyMetadata.count(keyID) && !pwalletMain->mapKeyMetadata[keyID].hdKeypath.empty()) | |||
CScriptID scriptID; | |||
if (pwalletMain && (address.GetKeyID(keyID) || (address.GetScriptID(scriptID) && GetWitnessKeyID(pwalletMain, scriptID, keyID))) && pwalletMain->mapKeyMetadata.count(keyID) && !pwalletMain->mapKeyMetadata[keyID].hdKeypath.empty()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit hard to interpret this line. Maybe
if (pwalletMain &&
(address.GetKeyID(keyID) || (address.GetScriptID(scriptID) && GetWitnessKeyID(pwalletMain, scriptID, keyID))) &&
pwalletMain->mapKeyMetadata.count(keyID) &&
!pwalletMain->mapKeyMetadata[keyID].hdKeypath.empty())
I also think a simple inline method like
inline bool FetchWalletKeyID(CWallet* pwallet, const CBitcoinAddress& address, CKeyID& keyID)
{
CScriptID scriptID;
return pwallet && (address.GetKeyID(keyID) || (address.GetScriptID(scriptID) && GetWitnessKeyID(pwallet, scriptID, keyID)));
}
would help, as it would reduce the above to
if (FetchWalletKeyID(pwalletMain, address, keyID) &&
pwalletMain->mapKeyMetadata.count(keyID) &&
!pwalletMain->mapKeyMetadata[keyID].hdKeypath.empty())
Also see line 255 below.
return true; | ||
} | ||
|
||
bool CBitcoinAddress::GetScriptID(CScriptID& scriptID) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is done a lot elsewhere, but I think Get
prefixed methods should return the result, not store it in a by-ref parameter. It's super easy to get confused especially with lines like if (GetX(y)) ...
, where you presume it's some operation based on y
returning an x
, when in reality y
is set and something else is returned.
Maybe use a different verb like FetchScriptID
or MakeScriptID
to hint at the difference.
@@ -3740,3 +3740,17 @@ bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& | |||
{ | |||
return ::AcceptToMemoryPool(mempool, state, *this, true, NULL, false, nAbsurdFee); | |||
} | |||
|
|||
bool GetWitnessKeyID(const CKeyStore* store, const CScriptID &scriptID, CKeyID &keyID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See previous comment regarding Get
prefix.
@@ -242,7 +251,8 @@ CScript _createmultisig_redeemScript(const UniValue& params) | |||
if (pwalletMain && address.IsValid()) | |||
{ | |||
CKeyID keyID; | |||
if (!address.GetKeyID(keyID)) | |||
CScriptID scriptID; | |||
if (!address.GetKeyID(keyID) && (!address.GetScriptID(scriptID) || !GetWitnessKeyID(pwalletMain, scriptID, keyID))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming an inline method as described on line 216 is added,
if (!address.GetKeyID(keyID) && (!address.GetScriptID(scriptID) || !GetWitnessKeyID(pwalletMain, scriptID, keyID)))
->
if (!FetchWalletKeyID(pwalletMain, address, keyID))
if (!address.GetKeyID(keyID)) | ||
CScriptID scriptID; | ||
if ((!address.GetScriptID(scriptID) || !GetWitnessKeyID(pwalletMain, scriptID, keyID)) && | ||
!address.GetKeyID(keyID)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I read this right, the logic is the same as previous two cases, except you are preferring witness style fetch over address.GetKeyID fetch. If this is not done for a specific reason, you can
if (!FetchWalletKeyID(pwalletMain, address, keyID)) {
@@ -520,7 +520,8 @@ UniValue signmessage(const JSONRPCRequest& request) | |||
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); | |||
|
|||
CKeyID keyID; | |||
if (!addr.GetKeyID(keyID)) | |||
CScriptID scriptID; | |||
if (!addr.GetKeyID(keyID) && (!addr.GetScriptID(scriptID) || !GetWitnessKeyID(pwalletMain, scriptID, keyID))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!FetchWalletKeyID(pwalletMain, addr, keyID))
I'm going to close this. Segwit activation is a bit away so there's not much rush for this code, and we're expecting a new address format regardless so we'll likely only be supporting that. |
Unlikely. Current wallets, even 0.15, can't send to bech32, so we'll need to support P2SH-wrapped addresses at least initially. |
Re-opening since segwit is actually activating. |
oops, @luke-jr opened his own pared-down copy, closing |
A followup to #8992 which includes a number of additional changes.
The only addition of data to wallets is done in importprivkey with the normal
IsWitnessEnabled
and-prematurewalletwitness
gating.I can write up tests for these if I get concept ACKs.
I wrote and manually tested these by turning on p2sh-p2wpkh by default via getnewaddress, and seeing what failed. Rest of failures seem to involve
no-witness-yet
and related sigop counting errors.