Merge pull request #822 from litecoin-project/fix-upgraded-wallets
v0.21.2.1
This commit is contained in:
commit
32a108600d
@ -2,7 +2,7 @@ AC_PREREQ([2.69])
|
||||
define(_CLIENT_VERSION_MAJOR, 0)
|
||||
define(_CLIENT_VERSION_MINOR, 21)
|
||||
define(_CLIENT_VERSION_REVISION, 2)
|
||||
define(_CLIENT_VERSION_BUILD, 0)
|
||||
define(_CLIENT_VERSION_BUILD, 1)
|
||||
define(_CLIENT_VERSION_RC, 0)
|
||||
define(_CLIENT_VERSION_IS_RELEASE, true)
|
||||
define(_COPYRIGHT_YEAR, 2022)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2014-2017 Wladimir J. van der Laan
|
||||
# Copyright (c) 2014-2021 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
'''
|
||||
@ -13,19 +13,14 @@ argument:
|
||||
|
||||
These files must consist of lines in the format
|
||||
|
||||
<ip>
|
||||
<ip>:<port>
|
||||
[<ipv6>]
|
||||
[<ipv6>]:<port>
|
||||
<onion>.onion
|
||||
0xDDBBCCAA (IPv4 little-endian old pnSeeds format)
|
||||
<onion>.onion:<port>
|
||||
<i2p>.b32.i2p:<port>
|
||||
|
||||
The output will be two data structures with the peers in binary format:
|
||||
|
||||
static SeedSpec6 pnSeed6_main[]={
|
||||
...
|
||||
}
|
||||
static SeedSpec6 pnSeed6_test[]={
|
||||
static const uint8_t chainparams_seed_{main,test}[]={
|
||||
...
|
||||
}
|
||||
|
||||
@ -33,25 +28,40 @@ These should be pasted into `src/chainparamsseeds.h`.
|
||||
'''
|
||||
|
||||
from base64 import b32decode
|
||||
from binascii import a2b_hex
|
||||
from enum import Enum
|
||||
import struct
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
# ipv4 in ipv6 prefix
|
||||
pchIPv4 = bytearray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff])
|
||||
# tor-specific ipv6 prefix
|
||||
pchOnionCat = bytearray([0xFD,0x87,0xD8,0x7E,0xEB,0x43])
|
||||
class BIP155Network(Enum):
|
||||
IPV4 = 1
|
||||
IPV6 = 2
|
||||
TORV2 = 3 # no longer supported
|
||||
TORV3 = 4
|
||||
I2P = 5
|
||||
CJDNS = 6
|
||||
|
||||
def name_to_ipv6(addr):
|
||||
if len(addr)>6 and addr.endswith('.onion'):
|
||||
def name_to_bip155(addr):
|
||||
'''Convert address string to BIP155 (networkID, addr) tuple.'''
|
||||
if addr.endswith('.onion'):
|
||||
vchAddr = b32decode(addr[0:-6], True)
|
||||
if len(vchAddr) != 16-len(pchOnionCat):
|
||||
if len(vchAddr) == 35:
|
||||
assert vchAddr[34] == 3
|
||||
return (BIP155Network.TORV3, vchAddr[:32])
|
||||
elif len(vchAddr) == 10:
|
||||
return (BIP155Network.TORV2, vchAddr)
|
||||
else:
|
||||
raise ValueError('Invalid onion %s' % vchAddr)
|
||||
return pchOnionCat + vchAddr
|
||||
elif addr.endswith('.b32.i2p'):
|
||||
vchAddr = b32decode(addr[0:-8] + '====', True)
|
||||
if len(vchAddr) == 32:
|
||||
return (BIP155Network.I2P, vchAddr)
|
||||
else:
|
||||
raise ValueError(f'Invalid I2P {vchAddr}')
|
||||
elif '.' in addr: # IPv4
|
||||
return pchIPv4 + bytearray((int(x) for x in addr.split('.')))
|
||||
elif ':' in addr: # IPv6
|
||||
return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.'))))
|
||||
elif ':' in addr: # IPv6 or CJDNS
|
||||
sub = [[], []] # prefix, suffix
|
||||
x = 0
|
||||
addr = addr.split(':')
|
||||
@ -67,13 +77,19 @@ def name_to_ipv6(addr):
|
||||
sub[x].append(val & 0xff)
|
||||
nullbytes = 16 - len(sub[0]) - len(sub[1])
|
||||
assert((x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0))
|
||||
return bytearray(sub[0] + ([0] * nullbytes) + sub[1])
|
||||
elif addr.startswith('0x'): # IPv4-in-little-endian
|
||||
return pchIPv4 + bytearray(reversed(a2b_hex(addr[2:])))
|
||||
addr_bytes = bytes(sub[0] + ([0] * nullbytes) + sub[1])
|
||||
if addr_bytes[0] == 0xfc:
|
||||
# Assume that seeds with fc00::/8 addresses belong to CJDNS,
|
||||
# not to the publicly unroutable "Unique Local Unicast" network, see
|
||||
# RFC4193: https://datatracker.ietf.org/doc/html/rfc4193#section-8
|
||||
return (BIP155Network.CJDNS, addr_bytes)
|
||||
else:
|
||||
return (BIP155Network.IPV6, addr_bytes)
|
||||
else:
|
||||
raise ValueError('Could not parse address %s' % addr)
|
||||
|
||||
def parse_spec(s, defaultport):
|
||||
def parse_spec(s):
|
||||
'''Convert endpoint string to BIP155 (networkID, addr, port) tuple.'''
|
||||
match = re.match(r'\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s)
|
||||
if match: # ipv6
|
||||
host = match.group(1)
|
||||
@ -85,17 +101,42 @@ def parse_spec(s, defaultport):
|
||||
(host,_,port) = s.partition(':')
|
||||
|
||||
if not port:
|
||||
port = defaultport
|
||||
port = 0
|
||||
else:
|
||||
port = int(port)
|
||||
|
||||
host = name_to_ipv6(host)
|
||||
host = name_to_bip155(host)
|
||||
|
||||
return (host,port)
|
||||
if host[0] == BIP155Network.TORV2:
|
||||
return None # TORV2 is no longer supported, so we ignore it
|
||||
else:
|
||||
return host + (port, )
|
||||
|
||||
def process_nodes(g, f, structname, defaultport):
|
||||
g.write('static SeedSpec6 %s[] = {\n' % structname)
|
||||
first = True
|
||||
def ser_compact_size(l):
|
||||
r = b""
|
||||
if l < 253:
|
||||
r = struct.pack("B", l)
|
||||
elif l < 0x10000:
|
||||
r = struct.pack("<BH", 253, l)
|
||||
elif l < 0x100000000:
|
||||
r = struct.pack("<BI", 254, l)
|
||||
else:
|
||||
r = struct.pack("<BQ", 255, l)
|
||||
return r
|
||||
|
||||
def bip155_serialize(spec):
|
||||
'''
|
||||
Serialize (networkID, addr, port) tuple to BIP155 binary format.
|
||||
'''
|
||||
r = b""
|
||||
r += struct.pack('B', spec[0].value)
|
||||
r += ser_compact_size(len(spec[1]))
|
||||
r += spec[1]
|
||||
r += struct.pack('>H', spec[2])
|
||||
return r
|
||||
|
||||
def process_nodes(g, f, structname):
|
||||
g.write('static const uint8_t %s[] = {\n' % structname)
|
||||
for line in f:
|
||||
comment = line.find('#')
|
||||
if comment != -1:
|
||||
@ -103,14 +144,14 @@ def process_nodes(g, f, structname, defaultport):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if not first:
|
||||
g.write(',\n')
|
||||
first = False
|
||||
|
||||
(host,port) = parse_spec(line, defaultport)
|
||||
hoststr = ','.join(('0x%02x' % b) for b in host)
|
||||
g.write(' {{%s}, %i}' % (hoststr, port))
|
||||
g.write('\n};\n')
|
||||
spec = parse_spec(line)
|
||||
if spec is None: # ignore this entry (e.g. no longer supported addresses like TORV2)
|
||||
continue
|
||||
blob = bip155_serialize(spec)
|
||||
hoststr = ','.join(('0x%02x' % b) for b in blob)
|
||||
g.write(f' {hoststr},\n')
|
||||
g.write('};\n')
|
||||
|
||||
def main():
|
||||
if len(sys.argv)<2:
|
||||
@ -121,17 +162,16 @@ def main():
|
||||
g.write('#ifndef BITCOIN_CHAINPARAMSSEEDS_H\n')
|
||||
g.write('#define BITCOIN_CHAINPARAMSSEEDS_H\n')
|
||||
g.write('/**\n')
|
||||
g.write(' * List of fixed seed nodes for the litecoin network\n')
|
||||
g.write(' * List of fixed seed nodes for the bitcoin network\n')
|
||||
g.write(' * AUTOGENERATED by contrib/seeds/generate-seeds.py\n')
|
||||
g.write(' *\n')
|
||||
g.write(' * Each line contains a 16-byte IPv6 address and a port.\n')
|
||||
g.write(' * IPv4 as well as onion addresses are wrapped inside an IPv6 address accordingly.\n')
|
||||
g.write(' * Each line contains a BIP155 serialized (networkID, addr, port) tuple.\n')
|
||||
g.write(' */\n')
|
||||
with open(os.path.join(indir,'nodes_main.txt'), 'r', encoding="utf8") as f:
|
||||
process_nodes(g, f, 'pnSeed6_main', 9333)
|
||||
process_nodes(g, f, 'chainparams_seed_main')
|
||||
g.write('\n')
|
||||
with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f:
|
||||
process_nodes(g, f, 'pnSeed6_test', 19335)
|
||||
process_nodes(g, f, 'chainparams_seed_test')
|
||||
g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -12,4 +12,6 @@
|
||||
154.22.123.138:9333
|
||||
174.62.70.234:9333
|
||||
178.62.46.195:9333
|
||||
213.244.192.149:9333
|
||||
213.244.192.149:9333
|
||||
xtmsopiy2ssu2vumfmnjq3nyflnq2pkpcxndi6rxgwnkljmzijafozqd.onion:9333
|
||||
37am23y5vro3iorqlmjjty7ctsgz6myoaushxrfqct4ngakl7d7idgid.onion:9333
|
||||
@ -27,3 +27,5 @@
|
||||
202.238.193.15:19335
|
||||
203.216.0.105:19335
|
||||
212.83.174.255:19335
|
||||
mxk7vkfxvtap2aikbjqcx7b4eex2yiag5v5vhuljnvurpybv4a6q5cqd.onion:19335
|
||||
octnr6yzgosiupuuivgla67p3ml6pc5qpozqn7zpmpbxge6pq5iq4had.onion:19335
|
||||
@ -19,7 +19,7 @@ Then install [Homebrew](https://brew.sh).
|
||||
|
||||
## Dependencies
|
||||
```shell
|
||||
brew install automake libtool boost miniupnpc pkg-config python qt libevent qrencode
|
||||
brew install automake libtool boost miniupnpc pkg-config python qt libevent qrencode fmt
|
||||
```
|
||||
|
||||
If you run into issues, check [Homebrew's troubleshooting page](https://docs.brew.sh/Troubleshooting).
|
||||
|
||||
@ -81,7 +81,7 @@ Build requirements:
|
||||
|
||||
Now, you can either build from self-compiled [depends](/depends/README.md) or install the required dependencies:
|
||||
|
||||
sudo apt-get install libevent-dev libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev
|
||||
sudo apt-get install libevent-dev libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libfmt-dev
|
||||
|
||||
BerkeleyDB is required for the wallet.
|
||||
|
||||
@ -131,7 +131,7 @@ built by default.
|
||||
|
||||
Build requirements:
|
||||
|
||||
sudo dnf install gcc-c++ libtool make autoconf automake libevent-devel boost-devel libdb4-devel libdb4-cxx-devel python3
|
||||
sudo dnf install gcc-c++ libtool make autoconf automake libevent-devel boost-devel libdb4-devel libdb4-cxx-devel python3 fmt
|
||||
|
||||
Optional (see `--with-miniupnpc` and `--enable-upnp-default`):
|
||||
|
||||
|
||||
21
doc/litecoin-release-notes/release-notes-0.21.2.1.md
Normal file
21
doc/litecoin-release-notes/release-notes-0.21.2.1.md
Normal file
@ -0,0 +1,21 @@
|
||||
Litecoin Core version 0.21.2.1 is now available from:
|
||||
|
||||
<https://download.litecoin.org/litecoin-0.21.2.1/>.
|
||||
|
||||
This includes a critical bug fix for upgraded wallets to receive via MWEB.
|
||||
|
||||
Please report bugs using the issue tracker at GitHub:
|
||||
|
||||
<https://github.com/litecoin-project/litecoin/issues>
|
||||
|
||||
To receive security and update notifications, please subscribe to:
|
||||
|
||||
<https://groups.google.com/forum/#!forum/litecoin-dev>
|
||||
|
||||
Notable changes
|
||||
===============
|
||||
|
||||
An issue with MWEB key generation for older wallets that were upgraded was solved.
|
||||
Keys are now generated from the appropriate keypools, and coins sent to previously generated stealth addresses are recoverable.
|
||||
Use `rescanblockchain` after upgrading to recover any missing MWEB coins.
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
Litecoin Core version 0.21.2 is now available from:
|
||||
Litecoin Core version 0.21.2.1 is now available from:
|
||||
|
||||
<https://download.litecoin.org/litecoin-0.21.2/>.
|
||||
<https://download.litecoin.org/litecoin-0.21.2.1/>.
|
||||
|
||||
This is the largest update ever, providing full node, wallet, and mining support for MWEB.
|
||||
This includes a critical bug fix for upgraded wallets to receive via MWEB.
|
||||
|
||||
Please report bugs using the issue tracker at GitHub:
|
||||
|
||||
@ -12,109 +12,10 @@ To receive security and update notifications, please subscribe to:
|
||||
|
||||
<https://groups.google.com/forum/#!forum/litecoin-dev>
|
||||
|
||||
|
||||
How to upgrade:
|
||||
==============
|
||||
|
||||
Firstly, thank you for running Litecoin Core and helping secure the network!
|
||||
|
||||
As you’re running an older version of Litecoin Core, shut it down. Wait until it’s completely shut down - which might take a few minutes for older versions - then follow these simple steps:
|
||||
For Windows: simply run the installer
|
||||
For Mac: copy over to `/Applications/Litecoin-Qt`
|
||||
For Linux: copy cover `litecoind`/`litecoin-qt`.
|
||||
|
||||
NB: upgrading directly from an ‘end of life’ version of Litecoin Core is possible, but it might take a while if the data directory needs to be migrated. Old wallet versions of Litecoin Core are generally supported.
|
||||
|
||||
|
||||
Compatibility:
|
||||
==============
|
||||
|
||||
Litecoin Core is supported and extensively tested on operating systems using the Linux kernel, macOS 10.10+, Windows 7 and newer. It’s not recommended to use Litecoin Core on unsupported systems.
|
||||
|
||||
Litecoin Core should also work on most other Unix-like systems, but is not as frequently tested on them.
|
||||
|
||||
MWEB fields added to BlockIndex, and block serialization format has changed. Downgrading to older versions is unsafe.
|
||||
|
||||
If upgrading to 0.21.2 *after* MWEB has activated, you must resync to download MWEB blocks.
|
||||
|
||||
Notable changes
|
||||
===============
|
||||
|
||||
Consensus changes
|
||||
-----------------
|
||||
An issue with MWEB key generation for older wallets that were upgraded was solved.
|
||||
Keys are now generated from the appropriate keypools, and coins sent to previously generated stealth addresses are recoverable.
|
||||
Use `rescanblockchain` after upgrading to recover any missing MWEB coins.
|
||||
|
||||
- This release implements the proposed MWEB consensus rules
|
||||
([LIP002](https://github.com/litecoin-project/lips/blob/master/lip-0002.mediawiki),
|
||||
[LIP003](https://github.com/litecoin-project/lips/blob/master/lip-0003.mediawiki), and
|
||||
[LIP004](https://github.com/litecoin-project/lips/blob/master/lip-0004.mediawiki))
|
||||
|
||||
P2P and network changes
|
||||
-----------------------
|
||||
|
||||
- A new service flag, NODE_MWEB (1 << 24), was added to signal to peers that the node supports MWEB.
|
||||
When connected peers both advertise this capability, they are expected to provide all MWEB data when
|
||||
sharing transactions, blocks, and compact blocks with each other.
|
||||
|
||||
- Nodes now announce compact block version 3 support, informing peers that they can provide MWEB data
|
||||
in compact blocks.
|
||||
|
||||
|
||||
Updated RPCs
|
||||
------------
|
||||
|
||||
- `getblockheader` now returns an additional `mweb_header` field containing
|
||||
all of the MWEB header data, and an `mweb_amount` field containing the total
|
||||
number of coins pegged-in to the MWEB after applying the block.
|
||||
|
||||
- `getblock` now returns an additional `mweb` field containing MWEB header info,
|
||||
and all of the inputs, outputs, and kernels in the MWEB block.
|
||||
|
||||
- Added `mwebweight`, `descendantmwebweight`, `ancestormwebweight`, and `mweb`
|
||||
fields to `getrawmempool`, `getmempoolancestors`, `getmempooldescendants`,
|
||||
and `getmempoolentry`.
|
||||
|
||||
- Added new fields to describe MWEB transaction inputs, outputs, and kernels
|
||||
for `getrawtransaction`.
|
||||
|
||||
Changes to Wallet or GUI related RPCs can be found in the GUI or Wallet section below.
|
||||
|
||||
New settings
|
||||
------------
|
||||
|
||||
- Added "fMWEBFeatures" option for enabling the new "Advanced MWEB Features"
|
||||
control.
|
||||
|
||||
Wallet Database
|
||||
---------------
|
||||
|
||||
- Added "mweb_coin" type which stores MWEB coins and their derived keys.
|
||||
|
||||
- Added CHDChain version 4 which includes an MWEB key index counter and
|
||||
the stealth address scan key.
|
||||
|
||||
- Added CKeyMetadata version 14 which includes the MWEB key index.
|
||||
|
||||
- Added FEATURE_MWEB = 210000 minimum database version.
|
||||
|
||||
Wallet RPC changes
|
||||
------------------
|
||||
|
||||
- Added 'listwallettransactions' which matches the transaction list display values.
|
||||
|
||||
GUI changes
|
||||
-----------
|
||||
|
||||
- Added an "Advanced MWEB Features" control for testing. It’s only available
|
||||
when the "-debug" argument is supplied, and the option is turned on in the
|
||||
settings dialog.
|
||||
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Thanks to everyone who directly contributed to this release:
|
||||
|
||||
- [The Bitcoin Core Developers](https://github.com/bitcoin/bitcoin/tree/master/doc/release-notes)
|
||||
- DavidBurkett
|
||||
- hectorchu
|
||||
- losh11
|
||||
@ -139,7 +139,7 @@ public:
|
||||
bech32_hrp = "ltc";
|
||||
mweb_hrp = "ltcmweb";
|
||||
|
||||
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main));
|
||||
vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_main), std::end(chainparams_seed_main));
|
||||
|
||||
fDefaultConsistencyChecks = false;
|
||||
fRequireStandard = true;
|
||||
@ -249,7 +249,7 @@ public:
|
||||
bech32_hrp = "tltc";
|
||||
mweb_hrp = "tmweb";
|
||||
|
||||
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));
|
||||
vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_test), std::end(chainparams_seed_test));
|
||||
|
||||
fDefaultConsistencyChecks = false;
|
||||
fRequireStandard = false;
|
||||
@ -260,6 +260,7 @@ public:
|
||||
{
|
||||
{300, uint256S("54e6075affe658d6574e04c9245a7920ad94dc5af8f5b37fd9a094e317769740")},
|
||||
{2056, uint256S("17748a31ba97afdc9a4f86837a39d287e3e7c7290a08a1d816c5969c78a83289")},
|
||||
{2352616, uint256S("7540437e7bf7831fa872ba8cfae85951a1e5dbb04c201b6f5def934d9299f3c2")}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@ public:
|
||||
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
|
||||
const std::string& Bech32HRP() const { return bech32_hrp; }
|
||||
const std::string& MWEB_HRP() const { return mweb_hrp; }
|
||||
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
|
||||
const std::vector<uint8_t>& FixedSeeds() const { return vFixedSeeds; }
|
||||
const CCheckpointData& Checkpoints() const { return checkpointData; }
|
||||
const ChainTxData& TxData() const { return chainTxData; }
|
||||
protected:
|
||||
@ -108,7 +108,7 @@ protected:
|
||||
std::string mweb_hrp;
|
||||
std::string strNetworkID;
|
||||
CBlock genesis;
|
||||
std::vector<SeedSpec6> vFixedSeeds;
|
||||
std::vector<uint8_t> vFixedSeeds;
|
||||
bool fDefaultConsistencyChecks;
|
||||
bool fRequireStandard;
|
||||
bool m_is_test_chain;
|
||||
|
||||
@ -1,59 +1,62 @@
|
||||
#ifndef BITCOIN_CHAINPARAMSSEEDS_H
|
||||
#define BITCOIN_CHAINPARAMSSEEDS_H
|
||||
/**
|
||||
* List of fixed seed nodes for the litecoin network
|
||||
* List of fixed seed nodes for the bitcoin network
|
||||
* AUTOGENERATED by contrib/seeds/generate-seeds.py
|
||||
*
|
||||
* Each line contains a 16-byte IPv6 address and a port.
|
||||
* IPv4 as well as onion addresses are wrapped inside an IPv6 address accordingly.
|
||||
* Each line contains a BIP155 serialized (networkID, addr, port) tuple.
|
||||
*/
|
||||
static SeedSpec6 pnSeed6_main[] = {
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x08,0xd6,0x9a,0x19}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x27,0x68,0xa3,0xc8}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x2f,0x5a,0x89,0x79}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x2f,0xfd,0x39,0xd7}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x3e,0xd7,0x7f,0x49}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x45,0xa4,0xc4,0xef}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x46,0x3f,0xaa,0x56}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x55,0x02,0x44,0xfa}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x56,0x0a,0x6e,0x8f}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x68,0x03,0x26,0xf7}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x8f,0xb2,0x39,0x03}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x9a,0x16,0x7b,0x8a}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xae,0x3e,0x46,0xea}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb2,0x3e,0x2e,0xc3}, 9333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xd5,0xf4,0xc0,0x95}, 9333}
|
||||
static const uint8_t chainparams_seed_main[] = {
|
||||
0x01,0x04,0x08,0xd6,0x9a,0x19,0x24,0x75,
|
||||
0x01,0x04,0x27,0x68,0xa3,0xc8,0x24,0x75,
|
||||
0x01,0x04,0x2f,0x5a,0x89,0x79,0x24,0x75,
|
||||
0x01,0x04,0x2f,0xfd,0x39,0xd7,0x24,0x75,
|
||||
0x01,0x04,0x3e,0xd7,0x7f,0x49,0x24,0x75,
|
||||
0x01,0x04,0x45,0xa4,0xc4,0xef,0x24,0x75,
|
||||
0x01,0x04,0x46,0x3f,0xaa,0x56,0x24,0x75,
|
||||
0x01,0x04,0x55,0x02,0x44,0xfa,0x24,0x75,
|
||||
0x01,0x04,0x56,0x0a,0x6e,0x8f,0x24,0x75,
|
||||
0x01,0x04,0x68,0x03,0x26,0xf7,0x24,0x75,
|
||||
0x01,0x04,0x8f,0xb2,0x39,0x03,0x24,0x75,
|
||||
0x01,0x04,0x9a,0x16,0x7b,0x8a,0x24,0x75,
|
||||
0x01,0x04,0xae,0x3e,0x46,0xea,0x24,0x75,
|
||||
0x01,0x04,0xb2,0x3e,0x2e,0xc3,0x24,0x75,
|
||||
0x01,0x04,0xd5,0xf4,0xc0,0x95,0x24,0x75,
|
||||
0x04,0x20,0xbc,0xd9,0x27,0x3d,0x18,0xd4,0xa5,0x4d,0x56,0x8c,0x2b,0x1a,0x98,0x6d,0xb8,0x2a,0xdb,0x0d,0x3d,0x4f,0x15,0xda,0x34,0x7a,0x37,0x35,0x9a,0xa5,0xa5,0x99,0x42,0x40,0x24,0x75,
|
||||
0x04,0x20,0xdf,0xc0,0xcd,0x6f,0x1d,0xac,0x5d,0xb4,0x3a,0x30,0x5b,0x12,0x99,0xe3,0xe2,0x9c,0x8d,0x9f,0x33,0x0e,0x05,0x24,0x7b,0xc4,0xb0,0x14,0xf8,0xd3,0x01,0x4b,0xf8,0xfe,0x24,0x75,
|
||||
};
|
||||
|
||||
static SeedSpec6 pnSeed6_test[] = {
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x03,0x89,0x44,0x8c}, 49333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x05,0x09,0x96,0x70}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x25,0x3b,0x39,0x60}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x27,0x64,0x63,0x74}, 30008},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x33,0x0f,0x73,0x61}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x34,0x0d,0x2a,0x7d}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x36,0xff,0x80,0x6e}, 19333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x3e,0xab,0xa1,0xcb}, 19333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x40,0xe3,0x13,0x5c}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x4e,0x1c,0xe1,0xa0}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x4f,0x62,0x9f,0x07}, 19333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x54,0x26,0x03,0xc7}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x59,0x27,0x68,0xa7}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x59,0xa0,0x9f,0x2d}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x5e,0x4f,0x37,0x1c}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x5f,0xd8,0x4c,0xe0}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x64,0x19,0x78,0x9a}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x67,0xe7,0xbf,0x07}, 19333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x68,0xed,0x83,0x8a}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x8e,0x5d,0xc6,0x68}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x93,0x87,0x0b,0x7c}, 49333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xad,0xd1,0x28,0x3d}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xad,0xd1,0x2a,0x07}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb2,0x3e,0x2e,0xc3}, 19333},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0xb4,0xdd,0xc9}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xc6,0x3a,0x66,0x12}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xca,0xee,0xc1,0x0f}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xcb,0xd8,0x00,0x69}, 19335},
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xd4,0x53,0xae,0xff}, 19335}
|
||||
static const uint8_t chainparams_seed_test[] = {
|
||||
0x01,0x04,0x03,0x89,0x44,0x8c,0xc0,0xb5,
|
||||
0x01,0x04,0x05,0x09,0x96,0x70,0x4b,0x87,
|
||||
0x01,0x04,0x25,0x3b,0x39,0x60,0x4b,0x87,
|
||||
0x01,0x04,0x27,0x64,0x63,0x74,0x75,0x38,
|
||||
0x01,0x04,0x33,0x0f,0x73,0x61,0x4b,0x87,
|
||||
0x01,0x04,0x34,0x0d,0x2a,0x7d,0x4b,0x87,
|
||||
0x01,0x04,0x36,0xff,0x80,0x6e,0x4b,0x85,
|
||||
0x01,0x04,0x3e,0xab,0xa1,0xcb,0x4b,0x85,
|
||||
0x01,0x04,0x40,0xe3,0x13,0x5c,0x4b,0x87,
|
||||
0x01,0x04,0x4e,0x1c,0xe1,0xa0,0x4b,0x87,
|
||||
0x01,0x04,0x4f,0x62,0x9f,0x07,0x4b,0x85,
|
||||
0x01,0x04,0x54,0x26,0x03,0xc7,0x4b,0x87,
|
||||
0x01,0x04,0x59,0x27,0x68,0xa7,0x4b,0x87,
|
||||
0x01,0x04,0x59,0xa0,0x9f,0x2d,0x4b,0x87,
|
||||
0x01,0x04,0x5e,0x4f,0x37,0x1c,0x4b,0x87,
|
||||
0x01,0x04,0x5f,0xd8,0x4c,0xe0,0x4b,0x87,
|
||||
0x01,0x04,0x64,0x19,0x78,0x9a,0x4b,0x87,
|
||||
0x01,0x04,0x67,0xe7,0xbf,0x07,0x4b,0x85,
|
||||
0x01,0x04,0x68,0xed,0x83,0x8a,0x4b,0x87,
|
||||
0x01,0x04,0x8e,0x5d,0xc6,0x68,0x4b,0x87,
|
||||
0x01,0x04,0x93,0x87,0x0b,0x7c,0xc0,0xb5,
|
||||
0x01,0x04,0xad,0xd1,0x28,0x3d,0x4b,0x87,
|
||||
0x01,0x04,0xad,0xd1,0x2a,0x07,0x4b,0x87,
|
||||
0x01,0x04,0xb2,0x3e,0x2e,0xc3,0x4b,0x85,
|
||||
0x01,0x04,0xb9,0xb4,0xdd,0xc9,0x4b,0x87,
|
||||
0x01,0x04,0xc6,0x3a,0x66,0x12,0x4b,0x87,
|
||||
0x01,0x04,0xca,0xee,0xc1,0x0f,0x4b,0x87,
|
||||
0x01,0x04,0xcb,0xd8,0x00,0x69,0x4b,0x87,
|
||||
0x01,0x04,0xd4,0x53,0xae,0xff,0x4b,0x87,
|
||||
0x04,0x20,0x65,0xd5,0xfa,0xa8,0xb7,0xac,0xc0,0xfd,0x01,0x0a,0x0a,0x60,0x2b,0xfc,0x3c,0x21,0x2f,0xac,0x20,0x06,0xed,0x7b,0x53,0xd1,0x69,0x6d,0x69,0x17,0xe0,0x35,0xe0,0x3d,0x4b,0x87,
|
||||
0x04,0x20,0x70,0xa6,0xd8,0xfb,0x19,0x33,0xa4,0x8a,0x3e,0x94,0x45,0x4c,0xb0,0x7b,0xef,0xdb,0x17,0xe7,0x8b,0xb0,0x7b,0xb3,0x06,0xff,0x2f,0x63,0xc3,0x73,0x13,0xcf,0x87,0x51,0x4b,0x87,
|
||||
};
|
||||
#endif // BITCOIN_CHAINPARAMSSEEDS_H
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
#include <mw/common/Traits.h>
|
||||
#include <mw/models/crypto/BigInteger.h>
|
||||
#include <mw/models/crypto/SecretKey.h>
|
||||
#include <pubkey.h>
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
|
||||
class PublicKey :
|
||||
@ -45,6 +47,7 @@ public:
|
||||
static PublicKey Random();
|
||||
|
||||
const BigInt<33>& GetBigInt() const { return m_compressed; }
|
||||
CKeyID GetID() const { return CPubKey(vec()).GetID(); }
|
||||
std::array<uint8_t, 33> array() const { return m_compressed.ToArray(); }
|
||||
const std::vector<uint8_t>& vec() const { return m_compressed.vec(); }
|
||||
const uint8_t& operator[](const size_t x) const { return m_compressed[x]; }
|
||||
|
||||
@ -20,6 +20,13 @@ static constexpr uint32_t CHANGE_INDEX{0};
|
||||
/// </summary>
|
||||
static constexpr uint32_t PEGIN_INDEX{1};
|
||||
|
||||
/// <summary>
|
||||
/// Outputs sent to a stealth address whose spend key was not generated using the MWEB
|
||||
/// keychain won't have an address index. We use 0xfffffffe to represent this.
|
||||
/// In that case, we must lookup the secret key in the wallet DB, rather than the MWEB keychain.
|
||||
/// </summary>
|
||||
static constexpr uint32_t CUSTOM_KEY{std::numeric_limits<uint32_t>::max() - 1};
|
||||
|
||||
/// <summary>
|
||||
/// Outputs sent to others will be marked with an address_index of 0xffffffff.
|
||||
/// </summary>
|
||||
@ -35,7 +42,8 @@ struct Coin : public Traits::ISerializable {
|
||||
uint32_t address_index{UNKNOWN_INDEX};
|
||||
|
||||
// The private key needed in order to spend the coin.
|
||||
// May be empty for watch-only wallets.
|
||||
// Will be empty for watch-only wallets.
|
||||
// May be empty for locked wallets. Upon unlock, spend_key will get populated.
|
||||
boost::optional<SecretKey> spend_key;
|
||||
|
||||
// The blinding factor of the coin's output.
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#include <memory>
|
||||
|
||||
// Forward Declarations
|
||||
class ScriptPubKeyMan;
|
||||
class LegacyScriptPubKeyMan;
|
||||
|
||||
MW_NAMESPACE
|
||||
|
||||
@ -16,7 +16,7 @@ class Keychain
|
||||
public:
|
||||
using Ptr = std::shared_ptr<Keychain>;
|
||||
|
||||
Keychain(const ScriptPubKeyMan& spk_man, SecretKey scan_secret, SecretKey spend_secret)
|
||||
Keychain(const LegacyScriptPubKeyMan& spk_man, SecretKey scan_secret, SecretKey spend_secret)
|
||||
: m_spk_man(spk_man),
|
||||
m_scanSecret(std::move(scan_secret)),
|
||||
m_spendSecret(std::move(spend_secret)) { }
|
||||
@ -28,9 +28,11 @@ public:
|
||||
// used to calculate the spend key when the wallet becomes unlocked.
|
||||
bool RewindOutput(const Output& output, mw::Coin& coin) const;
|
||||
|
||||
// Calculates the output spend key for the address index and shared secret.
|
||||
// Requires that keychain be unlocked and not watch-only.
|
||||
SecretKey CalculateOutputKey(const uint32_t index, const SecretKey& shared_secret) const;
|
||||
// Calculates the output secret key for the given coin.
|
||||
// If the address index is known, it calculates from the keychain's master spend key.
|
||||
// If not, it attempts to lookup the spend key in the database.
|
||||
// Returns boost::empty when keychain is locked or watch-only.
|
||||
boost::optional<SecretKey> CalculateOutputKey(const mw::Coin& coin) const;
|
||||
|
||||
// Calculates the StealthAddress at the given index.
|
||||
// Requires that keychain be unlocked and not watch-only.
|
||||
@ -42,6 +44,8 @@ public:
|
||||
const SecretKey& GetScanSecret() const noexcept { return m_scanSecret; }
|
||||
const SecretKey& GetSpendSecret() const noexcept { return m_spendSecret; }
|
||||
|
||||
bool HasSpendSecret() const noexcept { return !m_spendSecret.IsNull(); }
|
||||
|
||||
// Clears the spend secret from memory, effectively making this a watch-only keychain.
|
||||
void Lock() { m_spendSecret = SecretKey::Null(); }
|
||||
|
||||
@ -49,7 +53,7 @@ public:
|
||||
void Unlock(const SecretKey& spend_secret) { m_spendSecret = spend_secret; }
|
||||
|
||||
private:
|
||||
const ScriptPubKeyMan& m_spk_man;
|
||||
const LegacyScriptPubKeyMan& m_spk_man;
|
||||
SecretKey m_scanSecret;
|
||||
SecretKey m_spendSecret;
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <mw/crypto/SecretKeys.h>
|
||||
#include <mw/models/tx/OutputMask.h>
|
||||
#include <wallet/scriptpubkeyman.h>
|
||||
#include <key_io.h>
|
||||
|
||||
MW_NAMESPACE
|
||||
|
||||
@ -25,12 +26,10 @@ bool Keychain::RewindOutput(const Output& output, mw::Coin& coin) const
|
||||
// Check if B_i belongs to wallet
|
||||
StealthAddress address(B_i.Mul(m_scanSecret), B_i);
|
||||
auto pMetadata = m_spk_man.GetMetadata(address);
|
||||
if (!pMetadata || !pMetadata->mweb_index) {
|
||||
if (!pMetadata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t index = *pMetadata->mweb_index;
|
||||
|
||||
// Calc blinding factor and unmask nonce and amount.
|
||||
OutputMask mask = OutputMask::FromShared(t);
|
||||
uint64_t value = mask.MaskValue(output.GetMaskedValue());
|
||||
@ -51,28 +50,53 @@ bool Keychain::RewindOutput(const Output& output, mw::Coin& coin) const
|
||||
return false;
|
||||
}
|
||||
|
||||
// Spend secret will be null for locked or watch-only wallets.
|
||||
if (!GetSpendSecret().IsNull()) {
|
||||
coin.spend_key = boost::make_optional(CalculateOutputKey(index, t));
|
||||
}
|
||||
|
||||
coin.address_index = index;
|
||||
// v0.21.2 incorrectly generated MWEB keys from the pre-split keypool for upgraded wallets.
|
||||
// These keys will not have an mweb_index, so we set the address_index as CUSTOM_KEY.
|
||||
coin.address_index = pMetadata->mweb_index.get_value_or(CUSTOM_KEY);
|
||||
coin.blind = boost::make_optional(mask.GetRawBlind());
|
||||
coin.amount = value;
|
||||
coin.output_id = output.GetOutputID();
|
||||
coin.address = address;
|
||||
coin.shared_secret = boost::make_optional(std::move(t));
|
||||
coin.spend_key = CalculateOutputKey(coin);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SecretKey Keychain::CalculateOutputKey(const uint32_t index, const SecretKey& shared_secret) const
|
||||
boost::optional<SecretKey> Keychain::CalculateOutputKey(const mw::Coin& coin) const
|
||||
{
|
||||
assert(!m_spendSecret.IsNull());
|
||||
// If we already calculated the spend key, there's no need to calculate it again.
|
||||
if (coin.HasSpendKey()) {
|
||||
return coin.spend_key;
|
||||
}
|
||||
|
||||
return SecretKeys::From(GetSpendKey(index))
|
||||
.Mul(Hashed(EHashTag::OUT_KEY, shared_secret))
|
||||
.Total();
|
||||
// Watch-only or locked wallets will not have the spend secret.
|
||||
if (!HasSpendSecret() || !coin.HasSharedSecret() || !coin.IsMine()) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
auto derive_output_key = [](const SecretKey& spend_key, const SecretKey& shared_secret) -> SecretKey {
|
||||
return SecretKeys::From(spend_key)
|
||||
.Mul(Hashed(EHashTag::OUT_KEY, shared_secret))
|
||||
.Total();
|
||||
};
|
||||
|
||||
// An address_index of CUSTOM_KEY means the spend key was not generated from the MWEB keychain.
|
||||
// We should lookup the secret key in the wallet DB, instead of calculating by index.
|
||||
if (coin.address_index == CUSTOM_KEY) {
|
||||
if (!coin.HasAddress()) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
CKey key;
|
||||
if (!m_spk_man.GetKey(coin.address->B().GetID(), key)) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
return derive_output_key(SecretKey(key.begin()), *coin.shared_secret);
|
||||
}
|
||||
|
||||
return derive_output_key(GetSpendKey(coin.address_index), *coin.shared_secret);
|
||||
}
|
||||
|
||||
StealthAddress Keychain::GetStealthAddress(const uint32_t index) const
|
||||
|
||||
@ -36,6 +36,11 @@ struct Block {
|
||||
return IsNull() ? 0 : m_block->GetSupplyChange();
|
||||
}
|
||||
|
||||
mw::Hash GetHash() const noexcept
|
||||
{
|
||||
return IsNull() ? mw::Hash{} : m_block->GetHeader()->GetHash();
|
||||
}
|
||||
|
||||
mw::Header::CPtr GetMWEBHeader() const noexcept
|
||||
{
|
||||
return IsNull() ? mw::Header::CPtr{nullptr} : m_block->GetHeader();
|
||||
|
||||
@ -8,7 +8,7 @@ using namespace MWEB;
|
||||
bool Wallet::UpgradeCoins()
|
||||
{
|
||||
mw::Keychain::Ptr keychain = GetKeychain();
|
||||
if (!keychain || keychain->GetSpendSecret().IsNull()) {
|
||||
if (!keychain || !keychain->HasSpendSecret()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -19,14 +19,17 @@ bool Wallet::UpgradeCoins()
|
||||
|
||||
if (wtx->mweb_wtx_info && wtx->mweb_wtx_info->received_coin) {
|
||||
mw::Coin& coin = *wtx->mweb_wtx_info->received_coin;
|
||||
if (!coin.HasSpendKey() && coin.HasSharedSecret()) {
|
||||
coin.spend_key = keychain->CalculateOutputKey(coin.address_index, *coin.shared_secret);
|
||||
if (!coin.HasSpendKey()) {
|
||||
coin.spend_key = keychain->CalculateOutputKey(coin);
|
||||
|
||||
m_coins[coin.output_id] = coin;
|
||||
// If spend key was populated, update the database and m_coins map.
|
||||
if (coin.HasSpendKey()) {
|
||||
m_coins[coin.output_id] = coin;
|
||||
|
||||
WalletBatch batch(m_pWallet->GetDatabase());
|
||||
batch.WriteMWEBCoin(coin);
|
||||
batch.WriteTx(*wtx);
|
||||
WalletBatch batch(m_pWallet->GetDatabase());
|
||||
batch.WriteMWEBCoin(coin);
|
||||
batch.WriteTx(*wtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,7 +60,7 @@ bool Wallet::RewindOutput(const Output& output, mw::Coin& coin)
|
||||
if (GetCoin(output.GetOutputID(), coin) && coin.IsMine()) {
|
||||
// If the coin has the spend key, it's fully rewound.
|
||||
// If not, try rewinding further if we have the master spend key (i.e. wallet is unlocked).
|
||||
if (coin.HasSpendKey() || !keychain || keychain->GetSpendSecret().IsNull()) {
|
||||
if (coin.HasSpendKey() || !keychain || !keychain->HasSpendSecret()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -90,7 +93,7 @@ bool Wallet::GetStealthAddress(const mw::Coin& coin, StealthAddress& address) co
|
||||
bool Wallet::GetStealthAddress(const uint32_t index, StealthAddress& address) const
|
||||
{
|
||||
mw::Keychain::Ptr keychain = GetKeychain();
|
||||
if (!keychain || index == mw::UNKNOWN_INDEX) {
|
||||
if (!keychain || index == mw::UNKNOWN_INDEX || index == mw::CUSTOM_KEY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
19
src/net.cpp
19
src/net.cpp
@ -157,22 +157,23 @@ bool GetLocal(CService& addr, const CNetAddr *paddrPeer)
|
||||
return nBestScore >= 0;
|
||||
}
|
||||
|
||||
//! Convert the pnSeed6 array into usable address objects.
|
||||
static std::vector<CAddress> convertSeed6(const std::vector<SeedSpec6> &vSeedsIn)
|
||||
//! Convert the serialized seeds into usable address objects.
|
||||
static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t>& vSeedsIn)
|
||||
{
|
||||
// It'll only connect to one or two seed nodes because once it connects,
|
||||
// it'll get a pile of addresses with newer timestamps.
|
||||
// Seed nodes are given a random 'last seen time' of between one and two
|
||||
// weeks ago.
|
||||
const int64_t nOneWeek = 7*24*60*60;
|
||||
const int64_t nOneWeek = 7 * 24 * 60 * 60;
|
||||
std::vector<CAddress> vSeedsOut;
|
||||
vSeedsOut.reserve(vSeedsIn.size());
|
||||
FastRandomContext rng;
|
||||
for (const auto& seed_in : vSeedsIn) {
|
||||
struct in6_addr ip;
|
||||
memcpy(&ip, seed_in.addr, sizeof(ip));
|
||||
CAddress addr(CService(ip, seed_in.port), GetDesirableServiceFlags(NODE_NONE));
|
||||
CDataStream s(vSeedsIn, SER_NETWORK, PROTOCOL_VERSION | ADDRV2_FORMAT);
|
||||
while (!s.eof()) {
|
||||
CService endpoint;
|
||||
s >> endpoint;
|
||||
CAddress addr{endpoint, GetDesirableServiceFlags(NODE_NONE)};
|
||||
addr.nTime = GetTime() - rng.randrange(nOneWeek) - nOneWeek;
|
||||
LogPrint(BCLog::NET, "Added hardcoded seed: %s\n", addr.ToString());
|
||||
vSeedsOut.push_back(addr);
|
||||
}
|
||||
return vSeedsOut;
|
||||
@ -1906,7 +1907,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
|
||||
LogPrintf("Adding fixed seed nodes as DNS doesn't seem to be available.\n");
|
||||
CNetAddr local;
|
||||
local.SetInternal("fixedseeds");
|
||||
addrman.Add(convertSeed6(Params().FixedSeeds()), local);
|
||||
addrman.Add(ConvertSeeds(Params().FixedSeeds()), local);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,7 +196,7 @@ CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination&
|
||||
}
|
||||
}
|
||||
if (auto stealth_address = boost::get<StealthAddress>(&dest)) {
|
||||
return CPubKey(stealth_address->B().vec()).GetID();
|
||||
return stealth_address->B().GetID();
|
||||
}
|
||||
return CKeyID();
|
||||
}
|
||||
|
||||
@ -13,9 +13,11 @@ bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool inter
|
||||
m_spk_man->TopUp();
|
||||
|
||||
CKeyPool keypool;
|
||||
if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) {
|
||||
int64_t reserved_index;
|
||||
if (!m_spk_man->GetReservedDestination(type, internal, address, reserved_index, keypool)) {
|
||||
return false;
|
||||
}
|
||||
nIndex = reserved_index;
|
||||
fInternal = keypool.fInternal;
|
||||
}
|
||||
dest = address;
|
||||
|
||||
@ -244,8 +244,7 @@ isminetype LegacyScriptPubKeyMan::IsMine(const DestinationAddr& script) const
|
||||
return ISMINE_NO;
|
||||
}
|
||||
|
||||
CPubKey pubkey(mweb_address.GetSpendPubKey().vec());
|
||||
return HaveKey(pubkey.GetID()) ? ISMINE_SPENDABLE : ISMINE_NO;
|
||||
return HaveKey(mweb_address.GetSpendPubKey().GetID()) ? ISMINE_SPENDABLE : ISMINE_NO;
|
||||
}
|
||||
|
||||
switch (IsMineInner(*this, script.GetScript(), IsMineSigVersion::TOP)) {
|
||||
@ -1442,13 +1441,14 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key
|
||||
bool fReturningInternal = (purpose == KeyPurpose::INTERNAL);
|
||||
fReturningInternal &= (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||
bool fMWEB = (purpose == KeyPurpose::MWEB) && IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT);
|
||||
bool use_pre_split = !fMWEB && !set_pre_split_keypool.empty();
|
||||
|
||||
auto fn_get_keypool = [this](const bool internal, const bool mweb) -> std::set<int64_t>& {
|
||||
if (!set_pre_split_keypool.empty()) {
|
||||
return set_pre_split_keypool;
|
||||
} else if (mweb) {
|
||||
if (mweb) {
|
||||
return set_mweb_keypool;
|
||||
} else if (internal) {
|
||||
} else if (!set_pre_split_keypool.empty()) {
|
||||
return set_pre_split_keypool;
|
||||
} else if (internal) {
|
||||
return setInternalKeyPool;
|
||||
}
|
||||
|
||||
@ -1474,8 +1474,11 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key
|
||||
if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) {
|
||||
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
|
||||
}
|
||||
if (keypool.fMWEB != fMWEB) {
|
||||
throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified");
|
||||
}
|
||||
// If the key was pre-split keypool, we don't care about what type it is
|
||||
if (set_pre_split_keypool.empty() && (keypool.fInternal != fReturningInternal || keypool.fMWEB != fMWEB)) {
|
||||
if (!use_pre_split && keypool.fInternal != fReturningInternal) {
|
||||
throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified");
|
||||
}
|
||||
if (!keypool.vchPubKey.IsValid()) {
|
||||
@ -1548,8 +1551,7 @@ void LegacyScriptPubKeyMan::MarkReserveKeysAsUsed(int64_t keypool_id)
|
||||
std::vector<CKeyID> GetAffectedKeys(const DestinationAddr& spk, const SigningProvider& provider)
|
||||
{
|
||||
if (spk.IsMWEB()) {
|
||||
CPubKey spend_pubkey(spk.GetMWEBAddress().GetSpendPubKey().vec());
|
||||
return std::vector<CKeyID>{spend_pubkey.GetID()};
|
||||
return std::vector<CKeyID>{spk.GetMWEBAddress().GetSpendPubKey().GetID()};
|
||||
}
|
||||
|
||||
std::vector<DestinationAddr> dummy;
|
||||
|
||||
@ -66,24 +66,21 @@ BOOST_AUTO_TEST_CASE(StealthAddresses)
|
||||
StealthAddress change_address = mweb_keychain->GetStealthAddress(0);
|
||||
BOOST_CHECK(EncodeDestination(change_address) == "ltcmweb1qq20e2arnhvxw97katjkmsd35agw3capxjkrkh7dk8d30rczm8ypxuq329nwh2twmchhqn3jqh7ua4ps539f6aazh79jy76urqht4qa59ts3at6gf");
|
||||
BOOST_CHECK(keyman.IsMine(change_address) == ISMINE_SPENDABLE);
|
||||
CPubKey change_pubkey(change_address.B().vec());
|
||||
BOOST_CHECK(keyman.GetAllReserveKeys().find(change_pubkey.GetID()) == keyman.GetAllReserveKeys().end());
|
||||
BOOST_CHECK(keyman.GetAllReserveKeys().find(change_address.B().GetID()) == keyman.GetAllReserveKeys().end());
|
||||
BOOST_CHECK(*keyman.GetMetadata(change_address)->mweb_index == 0);
|
||||
|
||||
// Check "peg-in" (idx=1) address is USED
|
||||
StealthAddress pegin_address = mweb_keychain->GetStealthAddress(1);
|
||||
BOOST_CHECK(EncodeDestination(pegin_address) == "ltcmweb1qqg5hddkl4uhspjwg9tkmatxa4s6gswdaq9swl8vsg5xxznmye7phcqatzc62mzkg788tsrfcuegxe9q3agf5cplw7ztqdusqf7x3n2tl55x4gvyt");
|
||||
BOOST_CHECK(keyman.IsMine(pegin_address) == ISMINE_SPENDABLE);
|
||||
CPubKey pegin_pubkey(pegin_address.B().vec());
|
||||
BOOST_CHECK(keyman.GetAllReserveKeys().find(pegin_pubkey.GetID()) == keyman.GetAllReserveKeys().end());
|
||||
BOOST_CHECK(keyman.GetAllReserveKeys().find(pegin_address.B().GetID()) == keyman.GetAllReserveKeys().end());
|
||||
BOOST_CHECK(*keyman.GetMetadata(pegin_address)->mweb_index == 1);
|
||||
|
||||
// Check first receive (idx=2) address is UNUSED
|
||||
StealthAddress receive_address = mweb_keychain->GetStealthAddress(2);
|
||||
BOOST_CHECK(EncodeDestination(receive_address) == "ltcmweb1qq0yq03ewm830ugmkkvrvjmyyeslcpwk8ayd7k27qx63sryy6kx3ksqm3k6jd24ld3r5dp5lzx7rm7uyxfujf8sn7v4nlxeqwrcq6k6xxwqdc6tl3");
|
||||
BOOST_CHECK(keyman.IsMine(receive_address) == ISMINE_SPENDABLE);
|
||||
CPubKey receive_pubkey(receive_address.B().vec());
|
||||
BOOST_CHECK(keyman.GetAllReserveKeys().find(receive_pubkey.GetID()) != keyman.GetAllReserveKeys().end());
|
||||
BOOST_CHECK(keyman.GetAllReserveKeys().find(receive_address.B().GetID()) != keyman.GetAllReserveKeys().end());
|
||||
BOOST_CHECK(*keyman.GetMetadata(receive_address)->mweb_index == 2);
|
||||
|
||||
BOOST_CHECK(keyman.GetHDChain().nMWEBIndexCounter == 1002);
|
||||
|
||||
97
test/functional/mweb_wallet_upgrade.py
Normal file
97
test/functional/mweb_wallet_upgrade.py
Normal file
@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2021 The Litecoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Tests that non-HD wallets that are upgraded are able to receive via MWEB"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.ltc_util import create_non_hd_wallet, setup_mweb_chain
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
class MWEBWalletUpgradeTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
self.num_nodes = 2
|
||||
self.extra_args = [['-whitelist=noban@127.0.0.1'],[]] # immediate tx relay
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_previous_releases()
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
node0 = self.nodes[0]
|
||||
node1 = self.nodes[1]
|
||||
|
||||
#
|
||||
# Mine until MWEB is activated
|
||||
#
|
||||
self.log.info("Setting up MWEB chain")
|
||||
setup_mweb_chain(node0)
|
||||
self.sync_all()
|
||||
|
||||
#
|
||||
# Create a non-HD wallet using an older litecoin core version
|
||||
#
|
||||
self.log.info("Creating non-hd wallet")
|
||||
nonhd_wallet_dat = create_non_hd_wallet(self.chain, self.options)
|
||||
|
||||
#
|
||||
# Replace node1's wallet with the non-HD wallet.dat
|
||||
#
|
||||
self.log.info("Replacing wallet with non-hd wallet.dat")
|
||||
node1.get_wallet_rpc(self.default_wallet_name).unloadwallet()
|
||||
upgrade_wallet_dir = os.path.join(node1.datadir, "regtest", "wallets", self.default_wallet_name)
|
||||
shutil.rmtree(upgrade_wallet_dir)
|
||||
os.mkdir(upgrade_wallet_dir)
|
||||
shutil.copy(nonhd_wallet_dat, upgrade_wallet_dir)
|
||||
node1.loadwallet(self.default_wallet_name)
|
||||
|
||||
#
|
||||
# Upgrade node1's non-HD wallet to the latest version
|
||||
#
|
||||
self.log.info("Upgrading wallet")
|
||||
node1.upgradewallet()
|
||||
|
||||
#
|
||||
# Send to MWEB address of upgraded wallet (node1)
|
||||
#
|
||||
self.log.info("Send to upgraded wallet's mweb address")
|
||||
mweb_addr = node1.getnewaddress(address_type='mweb')
|
||||
tx1_id = node0.sendtoaddress(mweb_addr, 25)
|
||||
self.sync_mempools()
|
||||
|
||||
#
|
||||
# Verify transaction is received by upgraded wallet (node1)
|
||||
#
|
||||
self.log.info("Verify upgraded wallet lists the transaction")
|
||||
tx1 = node1.gettransaction(txid=tx1_id)
|
||||
assert_equal(tx1['confirmations'], 0)
|
||||
assert_equal(tx1['amount'], 25)
|
||||
assert_equal(tx1['details'][0]['address'], mweb_addr)
|
||||
|
||||
node0.generate(1)
|
||||
self.sync_all()
|
||||
|
||||
#
|
||||
# Verify that MWEB coins can be spent by upgraded wallet (node1)
|
||||
#
|
||||
self.log.info("Spending MWEB coins")
|
||||
mining_mweb_addr = node0.getnewaddress(address_type='mweb')
|
||||
tx2_id = node1.sendtoaddress(mining_mweb_addr, 10)
|
||||
self.sync_mempools()
|
||||
|
||||
#
|
||||
# Mine 1 block and verify transaction confirms
|
||||
#
|
||||
self.log.info("Mining block to verify it confirms")
|
||||
node0.generate(1)
|
||||
tx2 = node0.gettransaction(txid=tx2_id)
|
||||
assert_equal(tx2['confirmations'], 1)
|
||||
assert_equal(tx2['amount'], 10)
|
||||
assert_equal(tx2['details'][0]['address'], mining_mweb_addr)
|
||||
|
||||
if __name__ == '__main__':
|
||||
MWEBWalletUpgradeTest().main()
|
||||
@ -4,9 +4,12 @@
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Random assortment of utility functions"""
|
||||
|
||||
import os
|
||||
|
||||
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, MWEBHeader
|
||||
from test_framework.util import satoshi_round
|
||||
from test_framework.util import get_datadir_path, initialize_datadir, satoshi_round
|
||||
from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, hogaddr_script
|
||||
from test_framework.test_node import TestNode
|
||||
|
||||
"""Create a txout with a given amount and scriptPubKey
|
||||
|
||||
@ -91,5 +94,43 @@ def create_hogex(node, mweb_hash):
|
||||
tx.vout = [CTxOut(int(hog_addr['value'] * COIN), hogaddr_script(mweb_hash))]
|
||||
tx.hogex = True
|
||||
tx.rehash()
|
||||
return tx
|
||||
|
||||
return tx
|
||||
""" Create a non-HD wallet from a temporary v15.1.0 node.
|
||||
|
||||
Returns the path of the wallet.dat.
|
||||
"""
|
||||
def create_non_hd_wallet(chain, options):
|
||||
version = 150100
|
||||
bin_dir = os.path.join(options.previous_releases_path, 'v0.15.1', 'bin')
|
||||
initialize_datadir(options.tmpdir, 10, chain)
|
||||
data_dir = get_datadir_path(options.tmpdir, 10)
|
||||
|
||||
# adjust conf for pre 17
|
||||
conf_file = os.path.join(data_dir, 'litecoin.conf')
|
||||
with open(conf_file, 'r', encoding='utf8') as conf:
|
||||
conf_data = conf.read()
|
||||
with open(conf_file, 'w', encoding='utf8') as conf:
|
||||
conf.write(conf_data.replace('[regtest]', ''))
|
||||
|
||||
v15_node = TestNode(
|
||||
i=10,
|
||||
datadir=data_dir,
|
||||
chain=chain,
|
||||
rpchost=None,
|
||||
timewait=60,
|
||||
timeout_factor=1.0,
|
||||
bitcoind=os.path.join(bin_dir, 'litecoind'),
|
||||
bitcoin_cli=os.path.join(bin_dir, 'litecoin-cli'),
|
||||
version=version,
|
||||
coverage_dir=None,
|
||||
cwd=options.tmpdir,
|
||||
extra_args=["-usehd=0"],
|
||||
)
|
||||
v15_node.start()
|
||||
v15_node.wait_for_cookie_credentials() # ensure cookie file is available to avoid race condition
|
||||
v15_node.wait_for_rpc_connection()
|
||||
v15_node.stop_node(wait=0)
|
||||
v15_node.wait_until_stopped()
|
||||
|
||||
return os.path.join(v15_node.datadir, chain, "wallet.dat")
|
||||
@ -252,6 +252,7 @@ BASE_SCRIPTS = [
|
||||
'mweb_node_compatibility.py',
|
||||
'mweb_wallet_address.py',
|
||||
'mweb_wallet_basic.py',
|
||||
'mweb_wallet_upgrade.py',
|
||||
'wallet_listwallettransactions.py',
|
||||
'rpc_uptime.py',
|
||||
'wallet_resendwallettransactions.py',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user