Skip to content

Conversation

instagibbs
Copy link
Member

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.

src/base58.cpp Outdated
if (!IsValid() || vchVersion != Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS))
return false;
uint160 id;
memcpy(&id, &vchData[0], 20);
Copy link
Contributor

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.

Copy link
Contributor

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.

@laanwj laanwj added the Wallet label Oct 26, 2016
src/rpc/misc.cpp Outdated
CKeyID keyID;
CPubKey pubkey;
if (pwalletMain->GetWitnessKeyID(scriptID, keyID) &&
pwalletMain->GetPubKey(keyID, pubkey) && pubkey.IsCompressed()) {
Copy link
Contributor

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()));

Copy link
Member Author

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.

@@ -277,6 +277,20 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
return false;
}

bool CCryptoKeyStore::GetWitnessKeyID(const CScriptID &scriptID, CKeyID &keyID) const
Copy link
Contributor

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&.

Copy link
Member Author

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.

Copy link
Member Author

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.

std::vector<unsigned char> witnessProgram;
if (!GetCScript(scriptID, subscript) ||
!subscript.IsWitnessProgram(witnessVer, witnessProgram) ||
witnessProgram.size() != 20) {
Copy link
Contributor

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);
Copy link
Contributor

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.

@@ -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())
Copy link
Contributor

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.

@@ -1016,51 +1016,6 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
return CBitcoinAddress(innerID).ToString();
}

class Witnessifier : public boost::static_visitor<bool>
Copy link
Contributor

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).

@@ -986,4 +986,49 @@ class CAccount
}
};

class Witnessifier : public boost::static_visitor<bool>
Copy link
Contributor

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.

Copy link
Member Author

@instagibbs instagibbs Nov 4, 2016

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.

@@ -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;
Copy link
Contributor

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:

https://github.com/instagibbs/bitcoin/blob/1237cee5ab3506188fec78d33577f49ac78cd570/src/wallet/rpcwallet.cpp#L1053

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.

Copy link
Member Author

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.

  1. 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)
  2. 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, then addwitnessaddress. 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.

@@ -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)) {
Copy link
Contributor

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.

@sipa
Copy link
Member

sipa commented Nov 4, 2016

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.

@instagibbs instagibbs force-pushed the p2shp2wpkhstuff branch 9 times, most recently from 2cba903 to 1c6fcd5 Compare November 4, 2016 15:53
@instagibbs
Copy link
Member Author

@sipa it certainly does not need to be there and is likely inappropriate. I'm not sure exactly where to put it:
Will require access to a wallet, and is used in rpcmisc, rpcdump, and rpcwallet.cpp.

@sipa
Copy link
Member

sipa commented Nov 4, 2016

It only needs access to a keystore, no? Not to a wallet.

@instagibbs
Copy link
Member Author

instagibbs commented Nov 4, 2016

@sipa yes, I misspoke

@instagibbs
Copy link
Member Author

Moved GetWitnessKeyID outside of Keystore and addressed some nits.

@jtimon
Copy link
Contributor

jtimon commented Jan 25, 2017

Concept ACK

throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Given P2SH address does not match the signature.");
}
}

Copy link
Member

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())
Copy link
Contributor

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
Copy link
Contributor

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)
Copy link
Contributor

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)))
Copy link
Contributor

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)) {
Copy link
Contributor

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)))
Copy link
Contributor

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))

@instagibbs
Copy link
Member Author

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.

@instagibbs instagibbs closed this Feb 28, 2017
@luke-jr
Copy link
Member

luke-jr commented Aug 18, 2017

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.

@instagibbs
Copy link
Member Author

Re-opening since segwit is actually activating.

@instagibbs instagibbs reopened this Aug 18, 2017
@instagibbs
Copy link
Member Author

oops, @luke-jr opened his own pared-down copy, closing

@instagibbs instagibbs closed this Aug 18, 2017
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants