diff --git a/qa/rpc-tests/wallet_broadcast.py b/qa/rpc-tests/wallet_broadcast.py index f0cebe355..f625c8aac 100755 --- a/qa/rpc-tests/wallet_broadcast.py +++ b/qa/rpc-tests/wallet_broadcast.py @@ -13,7 +13,7 @@ class WalletBroadcastTest(BitcoinTestFramework): #do some -walletbroadcast tests stop_nodes(self.nodes) wait_bitcoinds() - self.nodes = start_nodes(3, self.options.tmpdir, [["-walletbroadcast=0"],["-walletbroadcast=0"],["-walletbroadcast=0"]]) + self.nodes = start_nodes(3, self.options.tmpdir, [["-walletbroadcast=0"]] * 3) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) diff --git a/qa/rpc-tests/wallet_import_export.py b/qa/rpc-tests/wallet_import_export.py index ddda99ee7..f0c01bf6e 100755 --- a/qa/rpc-tests/wallet_import_export.py +++ b/qa/rpc-tests/wallet_import_export.py @@ -3,13 +3,16 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or https://www.opensource.org/licenses/mit-license.php . +from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_true, start_nodes class WalletImportExportTest (BitcoinTestFramework): def setup_network(self, split=False): num_nodes = 3 - extra_args = [["-exportdir={}/export{}".format(self.options.tmpdir, i)] for i in range(num_nodes)] + extra_args = [([ + "-exportdir={}/export{}".format(self.options.tmpdir, i), + ] + (["-walletrequirebackup"] if i == 0 else [])) for i in range(num_nodes)] self.nodes = start_nodes(num_nodes, self.options.tmpdir, extra_args) def run_test(self): @@ -17,12 +20,28 @@ class WalletImportExportTest (BitcoinTestFramework): privkey2 = self.nodes[2].z_exportkey(sapling_address2) self.nodes[0].z_importkey(privkey2) + # test walletconfirmbackup + try: + self.nodes[0].getnewaddress() + except JSONRPCException as e: + errorString = e.error['message'] + assert_equal("Error: Please acknowledge that you have backed up" in errorString, True) + try: + self.nodes[0].z_getnewaddress('sapling') + except JSONRPCException as e: + errorString = e.error['message'] + assert_equal("Error: Please acknowledge that you have backed up" in errorString, True) + dump_path0 = self.nodes[0].z_exportwallet('walletdumpmnem') + (mnemonic, t_keys0, sprout_keys0, sapling_keys0) = parse_wallet_file(dump_path0) + self.nodes[0].walletconfirmbackup(mnemonic) + + # Now that we've confirmed backup, we can generate addresses sprout_address0 = self.nodes[0].z_getnewaddress('sprout') sapling_address0 = self.nodes[0].z_getnewaddress('sapling') # node 0 should have the keys dump_path0 = self.nodes[0].z_exportwallet('walletdump') - (t_keys0, sprout_keys0, sapling_keys0) = parse_wallet_file(dump_path0) + (_, t_keys0, sprout_keys0, sapling_keys0) = parse_wallet_file(dump_path0) sapling_line_lengths = [len(sapling_key0.split(' #')[0].split()) for sapling_key0 in sapling_keys0.splitlines()] assert_equal(2, len(sapling_line_lengths), "Should have 2 sapling keys") @@ -35,7 +54,7 @@ class WalletImportExportTest (BitcoinTestFramework): # node 1 should not have the keys dump_path1 = self.nodes[1].z_exportwallet('walletdumpbefore') - (t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path1) + (_, t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path1) assert_true(sprout_address0 not in sprout_keys1) assert_true(sapling_address0 not in sapling_keys1) @@ -45,7 +64,7 @@ class WalletImportExportTest (BitcoinTestFramework): # node 1 should now have the keys dump_path1 = self.nodes[1].z_exportwallet('walletdumpafter') - (t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path1) + (_, t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path1) assert_true(sprout_address0 in sprout_keys1) assert_true(sapling_address0 in sapling_keys1) @@ -62,11 +81,13 @@ def parse_wallet_file(dump_path): assert_true("Emergency Recovery Phrase" in file_lines[4], "Expected Emergency Recovery Phrase") assert_true("language" in file_lines[5], "Expected mnemonic seed language") assert_true("fingerprint" in file_lines[6], "Expected mnemonic seed fingerprint") + mnemonic = file_lines[4].split("=")[1].strip() + print(mnemonic) (t_keys, i) = parse_wallet_file_lines(file_lines, 0) (sprout_keys, i) = parse_wallet_file_lines(file_lines, i) (sapling_keys, i) = parse_wallet_file_lines(file_lines, i) - return (t_keys, sprout_keys, sapling_keys) + return (mnemonic, t_keys, sprout_keys, sapling_keys) def parse_wallet_file_lines(file_lines, i): keys = [] diff --git a/src/chainparams.cpp b/src/chainparams.cpp index d9a7cb182..4df45f935 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -729,6 +729,9 @@ public: // Founders reward script expects a vector of 2-of-3 multisig addresses vFoundersRewardAddress = { "t2FwcEhFdNXuFMv1tcYwaBJtYVtMj8b1uTg" }; assert(vFoundersRewardAddress.size() <= consensus.GetLastFoundersRewardBlockHeight(0)); + + // do not require the wallet backup to be confirmed in regtest mode + fRequireWalletBackup = false; } void UpdateNetworkUpgradeParameters(Consensus::UpgradeIndex idx, int nActivationHeight) diff --git a/src/chainparams.h b/src/chainparams.h index cf1062778..8ff3632f6 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -50,7 +50,7 @@ public: * a regression test mode which is intended for private networks only. It has * minimal difficulty to ensure that blocks can be found instantly. */ -class CChainParams: public KeyConstants +class CChainParams: public KeyConstants { public: const Consensus::Params& GetConsensus() const { return consensus; } @@ -62,6 +62,7 @@ public: CAmount SproutValuePoolCheckpointBalance() const { return nSproutValuePoolCheckpointBalance; } uint256 SproutValuePoolCheckpointBlockHash() const { return hashSproutValuePoolCheckpointBlock; } bool ZIP209Enabled() const { return fZIP209Enabled; } + bool RequireWalletBackup() const { return fRequireWalletBackup; } const CBlock& GenesisBlock() const { return genesis; } /** Make miner wait to have peers to avoid wasting work */ @@ -80,11 +81,11 @@ public: /** Return the BIP70 network string (main, test or regtest) */ std::string NetworkIDString() const { return keyConstants.NetworkIDString(); } const std::vector& DNSSeeds() const { return vSeeds; } - const std::vector& Base58Prefix(Base58Type type) const { - return keyConstants.Base58Prefix(type); + const std::vector& Base58Prefix(Base58Type type) const { + return keyConstants.Base58Prefix(type); } - const std::string& Bech32HRP(Bech32Type type) const { - return keyConstants.Bech32HRP(type); + const std::string& Bech32HRP(Bech32Type type) const { + return keyConstants.Bech32HRP(type); } const std::vector& FixedSeeds() const { return vFixedSeeds; } const CCheckpointData& Checkpoints() const { return checkpointData; } @@ -121,6 +122,7 @@ protected: CAmount nSproutValuePoolCheckpointBalance = 0; uint256 hashSproutValuePoolCheckpointBlock; bool fZIP209Enabled = false; + bool fRequireWalletBackup = true; }; /** diff --git a/src/key_constants.h b/src/key_constants.h index 58ac61e4b..aa999dd58 100644 --- a/src/key_constants.h +++ b/src/key_constants.h @@ -5,7 +5,7 @@ #ifndef ZCASH_KEY_CONSTANTS_H #define ZCASH_KEY_CONSTANTS_H -class KeyConstants +class KeyConstants { public: enum Base58Type { diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 0557975fc..83c1ef79c 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -75,6 +75,7 @@ enum RPCErrorCode RPC_WALLET_WRONG_ENC_STATE = -15, //! Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) RPC_WALLET_ENCRYPTION_FAILED = -16, //! Failed to encrypt the wallet RPC_WALLET_ALREADY_UNLOCKED = -17, //! Wallet is already unlocked + RPC_WALLET_BACKUP_REQUIRED = -18, //! User must acknowledge backup of the mnemonic seed. }; std::string JSONRPCRequest(const std::string& strMethod, const UniValue& params, const UniValue& id); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 82477411e..9b8f8692d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -37,6 +37,8 @@ #include #include +#include +#include #include #include @@ -53,6 +55,7 @@ using namespace libzcash; const std::string ADDR_TYPE_SPROUT = "sprout"; const std::string ADDR_TYPE_SAPLING = "sapling"; +const bool DEFAULT_WALLET_REQUIRE_BACKUP = true; extern UniValue TxJoinSplitToJSON(const CTransaction& tx); @@ -81,6 +84,16 @@ bool EnsureWalletIsAvailable(bool avoidException) return true; } +void EnsureWalletIsBackedUp(const CChainParams& params) +{ + if (GetBoolArg("-walletrequirebackup", params.RequireWalletBackup()) && !pwalletMain->MnemonicVerified()) + throw JSONRPCError( + RPC_WALLET_BACKUP_REQUIRED, + "Error: Please acknowledge that you have backed up the wallet's emergency recovery phrase " + "with walletconfirmbackup first." + ); +} + void EnsureWalletIsUnlocked() { if (pwalletMain->IsLocked()) @@ -163,6 +176,9 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); + const CChainParams& chainparams = Params(); + EnsureWalletIsBackedUp(chainparams); + if (!pwalletMain->IsLocked()) pwalletMain->TopUpKeyPool(); @@ -175,7 +191,7 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) std::string dummy_account; pwalletMain->SetAddressBook(keyID, dummy_account, "receive"); - KeyIO keyIO(Params()); + KeyIO keyIO(chainparams); return keyIO.EncodeDestination(keyID); } @@ -198,6 +214,9 @@ UniValue getrawchangeaddress(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); + const CChainParams& chainparams = Params(); + EnsureWalletIsBackedUp(chainparams); + if (!pwalletMain->IsLocked()) pwalletMain->TopUpKeyPool(); @@ -210,7 +229,7 @@ UniValue getrawchangeaddress(const UniValue& params, bool fHelp) CKeyID keyID = vchPubKey.GetID(); - KeyIO keyIO(Params()); + KeyIO keyIO(chainparams); return keyIO.EncodeDestination(keyID); } @@ -1586,6 +1605,8 @@ UniValue keypoolrefill(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); + EnsureWalletIsBackedUp(Params()); + // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool unsigned int kpSize = 0; if (params.size() > 0) { @@ -1718,6 +1739,47 @@ UniValue walletpassphrasechange(const UniValue& params, bool fHelp) return NullUniValue; } +UniValue walletconfirmbackup(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 1) + throw runtime_error( + "walletconfirmbackup \"emergency recovery phrase\"\n" + "\nNotify the wallet that the user has backed up the emergency recovery phrase,\n" + "which can be obtained by making a call to z_exportwallet. The zcashd embedded wallet\n" + "requires confirmation that the emergency recovery phrase has been backed up before it\n" + "will permit new spending keys or addresses to be generated.\n" + "\nArguments:\n" + "1. \"emergency recovery phrase\" (string, required) The full recovery phrase returned as part\n" + " of the data returned by z_exportwallet. An error will be returned if the value provided\n" + " does not match the wallet's existing emergency recovery phrase.\n" + "\nExamples:\n" + + HelpExampleCli("walletconfirmbackup", "\"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art\"") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + EnsureWalletIsUnlocked(); + + auto strMnemonicPhrase = params[0].get_str(); + boost::erase_all(strMnemonicPhrase, "\""); + boost::trim(strMnemonicPhrase); + if (strMnemonicPhrase.length() > 0) { + if (!pwalletMain->VerifyMnemonicSeed(strMnemonicPhrase)) + throw JSONRPCError( + RPC_WALLET_PASSPHRASE_INCORRECT, + "Error: The emergency recovery phrase entered was incorrect."); + } else { + throw runtime_error( + "walletconfirmbackup \"emergency recovery phrase\"\n" + "Notify the wallet that the user has backed up the emergency recovery phrase"); + } + + return NullUniValue; +} + UniValue walletlock(const UniValue& params, bool fHelp) { @@ -2929,14 +2991,17 @@ UniValue z_getnewaddress(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); + const CChainParams& chainparams = Params(); + EnsureWalletIsUnlocked(); + EnsureWalletIsBackedUp(chainparams); auto addrType = defaultType; if (params.size() > 0) { addrType = params[0].get_str(); } - KeyIO keyIO(Params()); + KeyIO keyIO(chainparams); if (addrType == ADDR_TYPE_SPROUT) { return keyIO.EncodePaymentAddress(pwalletMain->GenerateNewSproutZKey()); } else if (addrType == ADDR_TYPE_SAPLING) { @@ -4910,6 +4975,7 @@ static const CRPCCommand commands[] = { "wallet", "walletlock", &walletlock, true }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, true }, { "wallet", "walletpassphrase", &walletpassphrase, true }, + { "wallet", "walletconfirmbackup", &walletconfirmbackup, true }, { "wallet", "zcbenchmark", &zc_benchmark, true }, { "wallet", "zcrawkeygen", &zc_raw_keygen, true }, { "wallet", "zcrawjoinsplit", &zc_raw_joinsplit, true }, diff --git a/src/wallet/test/rpc_wallet_tests.cpp b/src/wallet/test/rpc_wallet_tests.cpp index c12546a82..28c94e9c2 100644 --- a/src/wallet/test/rpc_wallet_tests.cpp +++ b/src/wallet/test/rpc_wallet_tests.cpp @@ -197,6 +197,11 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) *********************************/ BOOST_CHECK_NO_THROW(CallRPC("listaddressgroupings")); + /********************************* + * walletconfirmbackup + *********************************/ + BOOST_CHECK_THROW(CallRPC(string("walletconfirmbackup \"badmnemonic\"")), runtime_error); + /********************************* * getrawchangeaddress *********************************/ @@ -798,12 +803,6 @@ void CheckHaveAddr(const libzcash::PaymentAddress& addr) { BOOST_AUTO_TEST_CASE(rpc_wallet_z_getnewaddress) { using namespace libzcash; - if (!pwalletMain->HaveLegacyHDSeed()) { - // fake a legacy seed by creating a separate mnemonic seed - auto seed = MnemonicSeed::Random(1); - pwalletMain->LoadLegacyHDSeed(seed); - } - UniValue addr; KeyIO keyIO(Params()); @@ -1562,12 +1561,6 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys) { LOCK2(cs_main, pwalletMain->cs_wallet); - if (!pwalletMain->HaveLegacyHDSeed()) { - // fake a legacy seed by creating a separate mnemonic seed - auto seed = MnemonicSeed::Random(1); - pwalletMain->LoadLegacyHDSeed(seed); - } - UniValue retValue; int n = 100; @@ -1625,12 +1618,6 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_sapzkeys) { LOCK2(cs_main, pwalletMain->cs_wallet); - if (!pwalletMain->HaveLegacyHDSeed()) { - // fake a legacy seed by creating a separate mnemonic seed - auto seed = MnemonicSeed::Random(1); - pwalletMain->LoadLegacyHDSeed(seed); - } - UniValue retValue; int n = 100; @@ -1694,12 +1681,6 @@ BOOST_AUTO_TEST_CASE(rpc_z_listunspent_parameters) { SelectParams(CBaseChainParams::TESTNET); - if (!pwalletMain->HaveLegacyHDSeed()) { - // fake a legacy seed by creating a separate mnemonic seed - auto seed = MnemonicSeed::Random(1); - pwalletMain->LoadLegacyHDSeed(seed); - } - LOCK2(cs_main, pwalletMain->cs_wallet); UniValue retValue; diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index dd591af56..0b5694f47 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -11,8 +11,10 @@ WalletTestingSetup::WalletTestingSetup(): TestingSetup() bool fFirstRun; pwalletMain = new CWallet(Params(), "wallet_test.dat"); pwalletMain->LoadWallet(fFirstRun); - if (!pwalletMain->HaveMnemonicSeed()) + if (!pwalletMain->HaveMnemonicSeed()) { pwalletMain->GenerateNewSeed(); + pwalletMain->VerifyMnemonicSeed(pwalletMain->GetMnemonicSeed().value().GetMnemonic()); + } RegisterValidationInterface(pwalletMain); RegisterWalletRPCCommands(tableRPC); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 67ef4f911..77ad2fa1c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -110,14 +110,13 @@ libzcash::SproutPaymentAddress CWallet::GenerateNewSproutZKey() return addr; } -// Generate a new Sapling spending key (generate a new account) based upon -// the legacy HD seed associated with this wallet and return its -// public payment address. +// Generate a new Sapling spending key as a child of the legacy Sapling account +// return its public payment address. // -// The z_getnewaddress API must use the legacy HD seed, and fail if that seed -// is not present. When using legacy HD seeds, the account index is determined -// by trial of legacyHDChain.GetAccountCounter(); for unified addresses this must use -// valued derived from legacyHDChain.unifiedAccountCounter +// The z_getnewaddress API must use the mnemonic HD seed, and fail if that seed +// is not present. The account index is determined by trial of values of +// mnemonicHDChain.GetLegacySaplingKeyCounter() until one is found that produces +// a valid Sapling key. SaplingPaymentAddress CWallet::GenerateNewLegacySaplingZKey() { AssertLockHeld(cs_wallet); @@ -2351,6 +2350,31 @@ bool CWallet::SetCryptedMnemonicSeed(const uint256& seedFp, const std::vectorGetMnemonicSeed(); if (!seed.has_value()) { @@ -4783,6 +4807,7 @@ std::string CWallet::GetWalletHelpString(bool showDebug) strUsage += HelpMessageOpt("-walletnotify=", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)")); strUsage += HelpMessageOpt("-zapwallettxes=", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") + " " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)")); + strUsage += HelpMessageOpt("-walletrequirebackup=false", _("Allow generation of new spending keys & addresses from the mnemonic seed, even if the backup of that seed has not yet been confirmed with `walletconfirmbackup`.")); if (showDebug) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index cbfe6fce1..615bb5cbc 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -806,8 +806,6 @@ protected: /* the hd chain metadata for keys derived from the mnemonic seed */ std::optional mnemonicHDChain; - /* the hd chain metadata for keys derived from the legacy seed */ - std::optional legacyHDChain; /* the network ID string for the network for which this wallet was created */ std::string networkIdString; @@ -1306,6 +1304,10 @@ public: bool SetMnemonicSeed(const MnemonicSeed& seed); bool SetCryptedMnemonicSeed(const uint256& seedFp, const std::vector &vchCryptedSecret); + /* Checks the wallet's seed against the specified mnemonic, and marks the + * wallet's seed as having been backed up if the phrases match. */ + bool VerifyMnemonicSeed(std::string mnemonic); + bool MnemonicVerified(); /* Set the current HD seed, without saving it to disk (used by LoadWallet) */ bool LoadMnemonicSeed(const MnemonicSeed& seed); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 2f808822d..4896d608d 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -51,6 +51,7 @@ private: uint32_t accountCounter; uint32_t legacyTKeyCounter; uint32_t legacySaplingKeyCounter; + bool mnemonicSeedBackupConfirmed; CHDChain() { SetNull(); } @@ -62,12 +63,13 @@ private: accountCounter = 0; legacyTKeyCounter = 0; legacySaplingKeyCounter = 0; + mnemonicSeedBackupConfirmed = false; } public: static const int VERSION_HD_BASE = 1; static const int CURRENT_VERSION = VERSION_HD_BASE; - CHDChain(uint256 seedFpIn, int64_t nCreateTimeIn): nVersion(CHDChain::CURRENT_VERSION), seedFp(seedFpIn), nCreateTime(nCreateTimeIn), accountCounter(0), legacyTKeyCounter(0), legacySaplingKeyCounter(0) {} + CHDChain(uint256 seedFpIn, int64_t nCreateTimeIn): nVersion(CHDChain::CURRENT_VERSION), seedFp(seedFpIn), nCreateTime(nCreateTimeIn), accountCounter(0), legacyTKeyCounter(0), legacySaplingKeyCounter(0), mnemonicSeedBackupConfirmed(false) {} ADD_SERIALIZE_METHODS; @@ -80,6 +82,7 @@ public: READWRITE(accountCounter); READWRITE(legacyTKeyCounter); READWRITE(legacySaplingKeyCounter); + READWRITE(mnemonicSeedBackupConfirmed); } template @@ -116,6 +119,14 @@ public: void IncrementLegacySaplingKeyCounter() { legacySaplingKeyCounter += 1; } + + void SetMnemonicSeedBackupConfirmed() { + mnemonicSeedBackupConfirmed = true; + } + + bool IsMnemonicSeedBackupConfirmed() { + return mnemonicSeedBackupConfirmed; + } }; /** Access to the wallet database */ diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh index a9fdab331..29489c702 100755 --- a/test/lint/lint-includes.sh +++ b/test/lint/lint-includes.sh @@ -42,10 +42,12 @@ EXPECTED_BOOST_INCLUDES=( boost/algorithm/string.hpp boost/algorithm/string/case_conv.hpp boost/algorithm/string/classification.hpp + boost/algorithm/string/erase.hpp boost/algorithm/string/join.hpp boost/algorithm/string/predicate.hpp boost/algorithm/string/replace.hpp boost/algorithm/string/split.hpp + boost/algorithm/string/trim.hpp boost/assert.hpp boost/assign/list_of.hpp boost/assign/std/vector.hpp