Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions qa/pull-tester/rpc-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
#Tests
testScripts = [
'wallet.py',
'hdwallet.py',
'listtransactions.py',
'receivedby.py',
'mempool_resurrect_test.py',
Expand Down
129 changes: 129 additions & 0 deletions qa/rpc-tests/hdwallet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env python2
# Copyright (c) 2014 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.


from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *

class HDWalletTest (BitcoinTestFramework):

def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)

def setup_network(self, split=False):


self.nodes = start_nodes(3, self.options.tmpdir)

#connect to a local machine for debugging
# url = "http://bitcoinrpc:DP6DvqZtqXarpeNWyN3LZTFchCCyCUuHwNF7E8pX99x1@%s:%d" % ('127.0.0.1', 18332)
# proxy = AuthServiceProxy(url)
# proxy.url = url # store URL on proxy for info
# self.nodes.append(proxy)

connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)
self.is_network_split=False
self.sync_all()

def run_test (self):
print "Mining blocks..."

encrypt = True
self.nodes[0].generate(1)

walletinfo = self.nodes[0].getwalletinfo()
assert_equal(walletinfo['immature_balance'], 50)
assert_equal(walletinfo['balance'], 0)
self.nodes[0].generate(100)
self.sync_all()

self.nodes[2].hdaddchain('default', 'tprv8ZgxMBicQKsPePWBxbX4F1arnkRyTvM3kVWgGJV2oNJ3abnwgWRhW1q9ruAaW2Y5Ffgak1PRemKd9LgJCrV2vWKeixAvrAAUtyktAMLv4YE');
adr = self.nodes[2].hdgetaddress()
assert_equal(adr['address'], "msXnguyqxBFdd7Y2zrsZTU3pKL6fpPCpzX");
assert_equal(adr['chainpath'], "m/44'/0'/0'/0/0");

self.nodes[2].hdaddchain("m/101/10'/c", 'tprv8ZgxMBicQKsPfJt4aGm5uB6STj5nCjLCH24rxgnpfusp38cHmcFNoTUan37ndbHCYcQMj544jjNJekSZcET4NoaVGA8s6atuzUHPQBG6mAp');
adr = self.nodes[2].hdgetaddress()
assert_equal(adr['address'], "mnvAsVFCiUXh9Sm86JV4EVLfwP9TRz6Yqf");
assert_equal(adr['chainpath'], "m/101/10'/0/0");

self.nodes[2].hdaddchain('default', 'f81a7a4efdc29e54dcc739df87315a756038d0b68fbc4880ffbbbef222152e6a')
adr = self.nodes[2].hdgetaddress()
assert_equal(adr['address'], "n1hBoYyGjqkbC8kdKNAejuaNR19eoYCSoi");
assert_equal(adr['chainpath'], "m/44'/0'/0'/0/0");

adr2 = self.nodes[2].hdgetaddress()
assert_equal(adr2['address'], "mvFePVSFGELgCDyLYrTJJ3tijnyeB9UF6p");
assert_equal(adr2['chainpath'], "m/44'/0'/0'/0/1");

self.nodes[0].sendtoaddress(adr['address'], 11);
self.sync_all()
self.nodes[1].generate(1)
self.sync_all()
walletinfo = self.nodes[2].getwalletinfo()
assert_equal(walletinfo['balance'], 11)

stop_node(self.nodes[0], 0)
stop_node(self.nodes[1], 1)
stop_node(self.nodes[2], 2)

#try to recover over master seed
os.remove(self.options.tmpdir + "/node2/regtest/wallet.dat")
self.nodes[2] = start_node(2, self.options.tmpdir)

if encrypt:
print "encrypt wallet"
self.nodes[2].encryptwallet("test")
bitcoind_processes[2].wait()
del bitcoind_processes[2]

self.nodes[2] = start_node(2, self.options.tmpdir)
self.nodes[2].walletpassphrase("test", 100)

self.nodes[2].hdaddchain('default', 'f81a7a4efdc29e54dcc739df87315a756038d0b68fbc4880ffbbbef222152e6a')
#generate address
adr = self.nodes[2].hdgetaddress()
assert_equal(adr['address'], "n1hBoYyGjqkbC8kdKNAejuaNR19eoYCSoi"); #must be deterministic
walletinfo = self.nodes[2].getwalletinfo()
assert_equal(walletinfo['balance'], 0) #balance should be o beause we need to rescan first

stop_node(self.nodes[2], 2)
self.nodes[0] = start_node(0, self.options.tmpdir)
self.nodes[1] = start_node(1, self.options.tmpdir)
self.nodes[2] = start_node(2, self.options.tmpdir, ['-rescan=1'])
connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)

walletinfo = self.nodes[2].getwalletinfo()
assert_equal(walletinfo['balance'], 11) #after rescan we should have detected the spendable coins

walletinfo = self.nodes[0].getwalletinfo()
balanceOld = walletinfo['balance'];

if encrypt:
self.nodes[2].walletpassphrase("test", 100)

self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 2.0); #try to send (sign) with HD keymaterial
self.sync_all()
self.nodes[1].generate(1)
self.sync_all()
walletinfo = self.nodes[0].getwalletinfo()
assert_equal(walletinfo['balance'], balanceOld+Decimal('52.00000000'))

self.nodes[2].hdaddchain('m/ch', '9886e45b8435b488a4cb753121db41a07f66a6a73e0a705ce24cee3a3bce87db')
adr0 = self.nodes[2].hdgetaddress()
assert_equal(adr0['address'], "mhr4GkkutTAVQ2RQvqSYdrFyt7vtmrgJ6S");
assert_equal(adr0['chainpath'], "m/0'/0'");

adr1 = self.nodes[2].hdgetaddress()
assert_equal(adr1['address'], "mzSuRQocScfhoYufYE5Uc1E5JW8BJtnZFr");
assert_equal(adr1['chainpath'], "m/0'/1'");

if __name__ == '__main__':
HDWalletTest ().main ()
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ BITCOIN_CORE_H = \
version.h \
wallet/crypter.h \
wallet/db.h \
wallet/hdkeystore.h \
wallet/wallet.h \
wallet/wallet_ismine.h \
wallet/walletdb.h \
Expand Down Expand Up @@ -234,6 +235,7 @@ libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_wallet_a_SOURCES = \
wallet/crypter.cpp \
wallet/db.cpp \
wallet/hdkeystore.cpp \
wallet/rpcdump.cpp \
wallet/rpcwallet.cpp \
wallet/wallet.cpp \
Expand Down
4 changes: 2 additions & 2 deletions src/base58.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ template<typename K, int Size, CChainParams::Base58Type Type> class CBitcoinExtK
CBitcoinExtKeyBase() {}
};

typedef CBitcoinExtKeyBase<CExtKey, 74, CChainParams::EXT_SECRET_KEY> CBitcoinExtKey;
typedef CBitcoinExtKeyBase<CExtPubKey, 74, CChainParams::EXT_PUBLIC_KEY> CBitcoinExtPubKey;
typedef CBitcoinExtKeyBase<CExtKey, BIP32_EXTKEY_SIZE, CChainParams::EXT_SECRET_KEY> CBitcoinExtKey;
typedef CBitcoinExtKeyBase<CExtPubKey, BIP32_EXTKEY_SIZE, CChainParams::EXT_PUBLIC_KEY> CBitcoinExtPubKey;

#endif // BITCOIN_BASE58_H
4 changes: 2 additions & 2 deletions src/key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ CExtPubKey CExtKey::Neuter() const {
return ret;
}

void CExtKey::Encode(unsigned char code[74]) const {
void CExtKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const {
code[0] = nDepth;
memcpy(code+1, vchFingerprint, 4);
code[5] = (nChild >> 24) & 0xFF; code[6] = (nChild >> 16) & 0xFF;
Expand All @@ -286,7 +286,7 @@ void CExtKey::Encode(unsigned char code[74]) const {
memcpy(code+42, key.begin(), 32);
}

void CExtKey::Decode(const unsigned char code[74]) {
void CExtKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) {
nDepth = code[0];
memcpy(vchFingerprint, code+1, 4);
nChild = (code[5] << 24) | (code[6] << 16) | (code[7] << 8) | code[8];
Expand Down
4 changes: 2 additions & 2 deletions src/key.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ struct CExtKey {
a.chaincode == b.chaincode && a.key == b.key;
}

void Encode(unsigned char code[74]) const;
void Decode(const unsigned char code[74]);
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
bool Derive(CExtKey& out, unsigned int nChild) const;
CExtPubKey Neuter() const;
void SetMaster(const unsigned char* seed, unsigned int nSeedLen);
Expand Down
6 changes: 3 additions & 3 deletions src/pubkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChi
return true;
}

void CExtPubKey::Encode(unsigned char code[74]) const {
void CExtPubKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const {
code[0] = nDepth;
memcpy(code+1, vchFingerprint, 4);
code[5] = (nChild >> 24) & 0xFF; code[6] = (nChild >> 16) & 0xFF;
Expand All @@ -256,12 +256,12 @@ void CExtPubKey::Encode(unsigned char code[74]) const {
memcpy(code+41, pubkey.begin(), 33);
}

void CExtPubKey::Decode(const unsigned char code[74]) {
void CExtPubKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) {
nDepth = code[0];
memcpy(vchFingerprint, code+1, 4);
nChild = (code[5] << 24) | (code[6] << 16) | (code[7] << 8) | code[8];
memcpy(chaincode.begin(), code+9, 32);
pubkey.Set(code+41, code+74);
pubkey.Set(code+41, code+BIP32_EXTKEY_SIZE);
}

bool CExtPubKey::Derive(CExtPubKey &out, unsigned int nChild) const {
Expand Down
28 changes: 26 additions & 2 deletions src/pubkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
* script supports up to 75 for single byte push
*/

const unsigned int BIP32_EXTKEY_SIZE = 74;

/** A reference to a CKey: the Hash160 of its serialized public key */
class CKeyID : public uint160
{
Expand Down Expand Up @@ -205,9 +207,31 @@ struct CExtPubKey {
a.chaincode == b.chaincode && a.pubkey == b.pubkey;
}

void Encode(unsigned char code[74]) const;
void Decode(const unsigned char code[74]);
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
bool Derive(CExtPubKey& out, unsigned int nChild) const;

unsigned int GetSerializeSize(int nType, int nVersion) const
{
return BIP32_EXTKEY_SIZE+1; //add one byte for the size (compact int)
}
template <typename Stream>
void Serialize(Stream& s, int nType, int nVersion) const
{
unsigned int len = BIP32_EXTKEY_SIZE;
::WriteCompactSize(s, len);
unsigned char code[BIP32_EXTKEY_SIZE];
Encode(code);
s.write((const char *)&code[0], len);
}
template <typename Stream>
void Unserialize(Stream& s, int nType, int nVersion)
{
unsigned int len = ::ReadCompactSize(s);
unsigned char code[BIP32_EXTKEY_SIZE];
s.read((char *)&code[0], len);
Decode(code);
}
};

/** Users of this module must hold an ECCVerifyHandle. The constructor and
Expand Down
2 changes: 2 additions & 0 deletions src/rpcclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "prioritisetransaction", 2 },
{ "setban", 2 },
{ "setban", 3 },
{ "hdsendtoaddress", 1 },
{ "hdsendtoaddress", 4 },
};

class CRPCConvertTable
Expand Down
5 changes: 5 additions & 0 deletions src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ static const CRPCCommand vRPCCommands[] =
{ "wallet", "walletlock", &walletlock, true },
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, true },
{ "wallet", "walletpassphrase", &walletpassphrase, true },
{ "wallet", "hdaddchain", &hdaddchain, true },
{ "wallet", "hdgetaddress", &hdgetaddress, true },
{ "wallet", "hdsendtoaddress", &hdsendtoaddress, false },
{ "wallet", "hdsetchain", &hdsetchain, false },
{ "wallet", "hdgetinfo", &hdgetinfo, false },
#endif // ENABLE_WALLET
};

Expand Down
5 changes: 5 additions & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ extern UniValue getblockchaininfo(const UniValue& params, bool fHelp);
extern UniValue getnetworkinfo(const UniValue& params, bool fHelp);
extern UniValue setmocktime(const UniValue& params, bool fHelp);
extern UniValue resendwallettransactions(const UniValue& params, bool fHelp);
extern UniValue hdaddchain(const UniValue& params, bool fHelp);
extern UniValue hdgetaddress(const UniValue& params, bool fHelp);
extern UniValue hdsendtoaddress(const UniValue& params, bool fHelp);
extern UniValue hdsetchain(const UniValue& params, bool fHelp);
extern UniValue hdgetinfo(const UniValue& params, bool fHelp);

extern UniValue getrawtransaction(const UniValue& params, bool fHelp); // in rcprawtransaction.cpp
extern UniValue listunspent(const UniValue& params, bool fHelp);
Expand Down
5 changes: 4 additions & 1 deletion src/test/bip32_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ void RunTest(const TestVector &test) {
unsigned char data[74];
key.Encode(data);
pubkey.Encode(data);

// Test private key
CBitcoinExtKey b58key; b58key.SetKey(key);
BOOST_CHECK(b58key.ToString() == derive.prv);
Expand Down Expand Up @@ -117,6 +116,10 @@ void RunTest(const TestVector &test) {
}
key = keyNew;
pubkey = pubkeyNew;

CDataStream ss(SER_DISK, CLIENT_VERSION);
ss << pubkeyNew;
BOOST_CHECK(ss.size() == 75);
}
}

Expand Down
20 changes: 20 additions & 0 deletions src/wallet/crypter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,23 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
}
return true;
}

bool CCryptoKeyStore::EncryptSeed(const CKeyingMaterial& seedIn, const uint256& seedPubHash, std::vector<unsigned char> &vchCiphertext) const
{
LOCK(cs_KeyStore);

if (!EncryptSecret(vMasterKey, seedIn, seedPubHash, vchCiphertext))
return false;

return true;
}

bool CCryptoKeyStore::DecryptSeed(const std::vector<unsigned char>& vchCiphertextIn, const uint256& seedPubHash, CKeyingMaterial& seedOut) const
{
LOCK(cs_KeyStore);

if (!DecryptSecret(vMasterKey, vchCiphertextIn, seedPubHash, seedOut))
return false;

return true;
}
3 changes: 3 additions & 0 deletions src/wallet/crypter.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ class CCryptoKeyStore : public CBasicKeyStore
* Note: Called without locks held.
*/
boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged;

bool EncryptSeed(const CKeyingMaterial& seedIn, const uint256& seedPubHash, std::vector<unsigned char> &vchCiphertext) const;
bool DecryptSeed(const std::vector<unsigned char>& vchCiphertextIn, const uint256& seedPubHash, CKeyingMaterial& seedOut) const;
};

#endif // BITCOIN_WALLET_CRYPTER_H
Loading