Skip to content
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

Multisignature and OP_EVAL support #669

Merged
merged 12 commits into from
Dec 20, 2011
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
OP_EVAL implementation
OP_EVAL is a new opcode that evaluates an item on the stack as a script.
It enables a new type of bitcoin address that needs an arbitrarily
complex script to redeem.
  • Loading branch information
gavinandresen committed Dec 19, 2011
commit e679ec969c8b22c676ebb10bea1038f6c8f13b33
25 changes: 25 additions & 0 deletions src/base58.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,16 +268,33 @@ class CBitcoinAddress : public CBase58Data
SetHash160(Hash160(vchPubKey));
}

bool SetScriptHash160(const uint160& hash160)
{
SetData(fTestNet ? 112 : 1, &hash160, 20);
return true;
}

bool IsValid() const
{
int nExpectedSize = 20;
bool fExpectTestNet = false;
switch(nVersion)
{
case 0:
nExpectedSize = 20; // Hash of public key
fExpectTestNet = false;
break;
case 1:
nExpectedSize = 20; // OP_EVAL, hash of CScript
fExpectTestNet = false;
break;

case 111:
nExpectedSize = 20;
fExpectTestNet = true;
break;
case 112:
nExpectedSize = 20;
fExpectTestNet = true;
break;

Expand All @@ -286,6 +303,14 @@ class CBitcoinAddress : public CBase58Data
}
return fExpectTestNet == fTestNet && vchData.size() == nExpectedSize;
}
bool IsScript() const
{
if (!IsValid())
return false;
if (fTestNet)
return nVersion == 112;
return nVersion == 1;
}

CBitcoinAddress()
{
Expand Down
122 changes: 52 additions & 70 deletions src/bitcoinrpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ Value getreceivedbyaccount(const Array& params, bool fHelp)
if (params.size() > 1)
nMinDepth = params[1].get_int();

// Get the set of pub keys that have the label
// Get the set of pub keys assigned to account
string strAccount = AccountFromValue(params[0]);
set<CBitcoinAddress> setAddress;
GetAccountAddresses(strAccount, setAddress);
Expand Down Expand Up @@ -936,56 +936,30 @@ Value sendmany(const Array& params, bool fHelp)
return wtx.GetHash().GetHex();
}

Value sendmultisig(const Array& params, bool fHelp)
Value addmultisigaddress(const Array& params, bool fHelp)
{
if (fHelp || params.size() < 4 || params.size() > 7)
if (fHelp || params.size() < 2 || params.size() > 3)
{
string msg = "sendmultisig <fromaccount> <type> <[\"key\",\"key\"]> <amount> [minconf=1] [comment] [comment-to]\n"
"<type> is one of: \"and\", \"or\", \"escrow\"\n"
"<keys> is an array of strings (in JSON array format); each key is a bitcoin address, hex or base58 public key\n"
"<amount> is a real and is rounded to the nearest 0.00000001";
if (pwalletMain->IsCrypted())
msg += "\nrequires wallet passphrase to be set with walletpassphrase first";
string msg = "addmultisigaddress <nrequired> <'[\"key\",\"key\"]'> [account]\n"
"Add a nrequired-to-sign multisignature address to the wallet\"\n"
"each key is a bitcoin address, hex or base58 public key\n"
"If [account] is specified, assign address to [account].";
throw runtime_error(msg);
}

string strAccount = AccountFromValue(params[0]);
string strType = params[1].get_str();
const Array& keys = params[2].get_array();
int64 nAmount = AmountFromValue(params[3]);
int nMinDepth = 1;
if (params.size() > 4)
nMinDepth = params[4].get_int();

CWalletTx wtx;
wtx.strFromAccount = strAccount;
if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty())
wtx.mapValue["comment"] = params[5].get_str();
if (params.size() > 6 && params[6].type() != null_type && !params[6].get_str().empty())
wtx.mapValue["to"] = params[6].get_str();

if (pwalletMain->IsLocked())
throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.");

// Check funds
int64 nBalance = GetAccountBalance(strAccount, nMinDepth);
if (nAmount > nBalance)
throw JSONRPCError(-6, "Account has insufficient funds");
int nRequired = params[0].get_int();
const Array& keys = params[1].get_array();
string strAccount;
if (params.size() > 2)
strAccount = AccountFromValue(params[2]);

// Gather public keys
int nKeysNeeded = 0;
if (strType == "and" || strType == "or")
nKeysNeeded = 2;
else if (strType == "escrow")
nKeysNeeded = 3;
else
throw runtime_error("sendmultisig: <type> must be one of: and or and_or");
if (keys.size() != nKeysNeeded)
if (keys.size() < nRequired)
throw runtime_error(
strprintf("sendmultisig: wrong number of keys (got %d, need %d)", keys.size(), nKeysNeeded));
strprintf("addmultisigaddress: wrong number of keys (got %d, need at least %d)", keys.size(), nRequired));
std::vector<CKey> pubkeys;
pubkeys.resize(nKeysNeeded);
for (int i = 0; i < nKeysNeeded; i++)
pubkeys.resize(keys.size());
for (int i = 0; i < keys.size(); i++)
{
const std::string& ks = keys[i].get_str();
if (ks.size() == 130) // hex public key
Expand All @@ -1003,32 +977,23 @@ Value sendmultisig(const Array& params, bool fHelp)
CBitcoinAddress address(ks);
if (!pwalletMain->GetKey(address, pubkeys[i]))
throw runtime_error(
strprintf("sendmultisig: unknown address: %s",ks.c_str()));
strprintf("addmultisigaddress: unknown address: %s",ks.c_str()));
}
}

// Send
CScript scriptPubKey;
if (strType == "and")
scriptPubKey.SetMultisigAnd(pubkeys);
else if (strType == "or")
scriptPubKey.SetMultisigOr(pubkeys);
else
scriptPubKey.SetMultisigEscrow(pubkeys);
// Construct using OP_EVAL
CScript inner;
inner.SetMultisig(nRequired, pubkeys);

CReserveKey keyChange(pwalletMain);
int64 nFeeRequired = 0;
bool fCreated = pwalletMain->CreateTransaction(scriptPubKey, nAmount, wtx, keyChange, nFeeRequired);
if (!fCreated)
{
if (nAmount + nFeeRequired > pwalletMain->GetBalance())
throw JSONRPCError(-6, "Insufficient funds");
throw JSONRPCError(-4, "Transaction creation failed");
}
if (!pwalletMain->CommitTransaction(wtx, keyChange))
throw JSONRPCError(-4, "Transaction commit failed");
uint160 scriptHash = Hash160(inner);
CScript scriptPubKey;
scriptPubKey.SetEval(inner);
pwalletMain->AddCScript(scriptHash, inner);
CBitcoinAddress address;
address.SetScriptHash160(scriptHash);

return wtx.GetHash().GetHex();
pwalletMain->SetAddressBookName(address, strAccount);
return address.ToString();
}


Expand Down Expand Up @@ -1700,6 +1665,24 @@ Value validateaddress(const Array& params, bool fHelp)
std::string strPubKey(vchPubKey.begin(), vchPubKey.end());
ret.push_back(Pair("pubkey58", EncodeBase58(vchPubKey)));
}
else if (pwalletMain->HaveCScript(address.GetHash160()))
{
ret.push_back(Pair("isscript", true));
CScript subscript;
pwalletMain->GetCScript(address.GetHash160(), subscript);
ret.push_back(Pair("ismine", ::IsMine(*pwalletMain, subscript)));
std::vector<CBitcoinAddress> addresses;
txntype whichType;
int nRequired;
ExtractAddresses(subscript, pwalletMain, whichType, addresses, nRequired);
ret.push_back(Pair("script", GetTxnTypeName(whichType)));
Array a;
BOOST_FOREACH(const CBitcoinAddress& addr, addresses)
a.push_back(addr.ToString());
ret.push_back(Pair("addresses", a));
if (whichType == TX_MULTISIG)
ret.push_back(Pair("sigsrequired", nRequired));
}
else
ret.push_back(Pair("ismine", false));
if (pwalletMain->mapAddressBook.count(address))
Expand Down Expand Up @@ -1946,7 +1929,7 @@ pair<string, rpcfn_type> pCallTable[] =
make_pair("move", &movecmd),
make_pair("sendfrom", &sendfrom),
make_pair("sendmany", &sendmany),
make_pair("sendmultisig", &sendmultisig),
make_pair("addmultisigaddress", &addmultisigaddress),
make_pair("gettransaction", &gettransaction),
make_pair("listtransactions", &listtransactions),
make_pair("signmessage", &signmessage),
Expand Down Expand Up @@ -2590,16 +2573,15 @@ int CommandLineRPC(int argc, char *argv[])
params[1] = v.get_obj();
}
if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]);
if (strMethod == "sendmultisig" && n > 2)
if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]);
if (strMethod == "addmultisigaddress" && n > 1)
{
string s = params[2].get_str();
string s = params[1].get_str();
Value v;
if (!read_string(s, v) || v.type() != array_type)
throw runtime_error("sendmultisig: type mismatch "+s);
params[2] = v.get_array();
throw runtime_error("addmultisigaddress: type mismatch "+s);
params[1] = v.get_array();
}
if (strMethod == "sendmultisig" && n > 3) ConvertTo<double>(params[3]);
if (strMethod == "sendmultisig" && n > 4) ConvertTo<boost::int64_t>(params[4]);

// Execute
Object reply = CallRPC(strMethod, params);
Expand Down
9 changes: 9 additions & 0 deletions src/db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,15 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
if (nMinVersion > CLIENT_VERSION)
return DB_TOO_NEW;
}
else if (strType == "cscript")
{
uint160 hash;
ssKey >> hash;
std::vector<unsigned char> script;
ssValue >> script;
if (!pwallet->LoadCScript(hash, script))
return DB_CORRUPT;
}
}
pcursor->close();
}
Expand Down
12 changes: 12 additions & 0 deletions src/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,18 @@ class CWalletDB : public CDB
return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
}

bool ReadCScript(const uint160 &hash, std::vector<unsigned char>& data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add comments in keystore.h and db.h indicating why you might want to store scripts keyed by hash in your wallet, as this is not obvious at all given the readers basic mental model of how Bitcoin works.

{
data.clear();
return Read(std::make_pair(std::string("cscript"), hash), data);
}

bool WriteCScript(const uint160& hash, const std::vector<unsigned char>& data)
{
nWalletDBUpdated++;
return Write(std::make_pair(std::string("cscript"), hash), data, false);
}

bool WriteBestBlock(const CBlockLocator& locator)
{
nWalletDBUpdated++;
Expand Down
30 changes: 30 additions & 0 deletions src/keystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,36 @@ bool CBasicKeyStore::AddKey(const CKey& key)
return true;
}

bool CBasicKeyStore::AddCScript(const uint160 &hash, const std::vector<unsigned char>& data)
{
CRITICAL_BLOCK(cs_KeyStore)
mapData[hash] = data;
return true;
}

bool CBasicKeyStore::HaveCScript(const uint160& hash) const
{
bool result;
CRITICAL_BLOCK(cs_KeyStore)
result = (mapData.count(hash) > 0);
return result;
}


bool CBasicKeyStore::GetCScript(const uint160 &hash, std::vector<unsigned char>& dataOut) const
{
CRITICAL_BLOCK(cs_KeyStore)
{
DataMap::const_iterator mi = mapData.find(hash);
if (mi != mapData.end())
{
dataOut = (*mi).second;
return true;
}
}
return false;
}

bool CCryptoKeyStore::SetCrypted()
{
CRITICAL_BLOCK(cs_KeyStore)
Expand Down
9 changes: 9 additions & 0 deletions src/keystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class CKeyStore
virtual void GetKeys(std::set<CBitcoinAddress> &setAddress) const =0;
virtual bool GetPubKey(const CBitcoinAddress &address, std::vector<unsigned char>& vchPubKeyOut) const;

virtual bool AddCScript(const uint160 &hash, const std::vector<unsigned char>& data) =0;
virtual bool HaveCScript(const uint160 &hash) const =0;
virtual bool GetCScript(const uint160 &hash, std::vector<unsigned char>& dataOut) const =0;

// Generate a new key, and add it to the store
virtual std::vector<unsigned char> GenerateNewKey();
virtual bool GetSecret(const CBitcoinAddress &address, CSecret& vchSecret) const
Expand All @@ -44,12 +48,14 @@ class CKeyStore
};

typedef std::map<CBitcoinAddress, CSecret> KeyMap;
typedef std::map<uint160, std::vector<unsigned char> > DataMap;

// Basic key store, that keeps keys in an address->secret map
class CBasicKeyStore : public CKeyStore
{
protected:
KeyMap mapKeys;
DataMap mapData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapData is not a very descriptive name, don't all maps contain data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate naming things... must be my Australian roots. I'll call it Bruce.


public:
bool AddKey(const CKey& key);
Expand Down Expand Up @@ -86,6 +92,9 @@ class CBasicKeyStore : public CKeyStore
}
return false;
}
virtual bool AddCScript(const uint160 &hash, const std::vector<unsigned char>& data);
virtual bool HaveCScript(const uint160 &hash) const;
virtual bool GetCScript(const uint160 &hash, std::vector<unsigned char>& dataOut) const;
};

typedef std::map<CBitcoinAddress, std::pair<std::vector<unsigned char>, std::vector<unsigned char> > > CryptedKeyMap;
Expand Down
Loading