Perform segwit BSQ wallet migration upon startup

Uncomment & enable the m/44'/142'/1' native segwit BSQ account path and
add code to migrate the user's BSQ wallet to segwit upon startup, along
the same lines as the existing BTC wallet segwit migration logic. That
is, set P2WPKH as the default output type, add a native segwit key chain
(set to active) to the BSQ wallet and back up the old 'bisq_BSQ.wallet'
file to 'pre_segwit_bisq_BSQ.wallet.backup'.

Also filter out legacy addresses coming from the original keychain from
BsqWalletService.get(Unused|Change)Address.
This commit is contained in:
Steven Barclay 2021-01-23 12:49:38 +00:00
parent 3470429746
commit 5f1e32a226
No known key found for this signature in database
GPG key ID: 9FED6BF1176D500B
6 changed files with 54 additions and 34 deletions

View file

@ -581,7 +581,8 @@ class CoreWalletsService {
if (btcWalletService.getAesKey() == null || bsqWalletService.getAesKey() == null) {
KeyParameter aesKey = new KeyParameter(tempAesKey.getKey());
walletsManager.setAesKey(aesKey);
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(), aesKey);
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(), aesKey, false);
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().bsqWallet(), aesKey, true);
}
}

View file

@ -424,7 +424,9 @@ public class BisqSetup {
requestWalletPasswordHandler.accept(aesKey -> {
walletsManager.setAesKey(aesKey);
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(),
aesKey);
aesKey, false);
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().bsqWallet(),
aesKey, true);
if (getResyncSpvSemaphore()) {
if (showFirstPopupIfResyncSPVRequestedHandler != null)
showFirstPopupIfResyncSPVRequestedHandler.run();

View file

@ -47,15 +47,14 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
new ChildNumber(142, true),
ChildNumber.ZERO_HARDENED);
// We don't use segwit for BSQ
// public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
// new ChildNumber(44, true),
// new ChildNumber(142, true),
// ChildNumber.ONE_HARDENED);
public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
new ChildNumber(44, true),
new ChildNumber(142, true),
ChildNumber.ONE_HARDENED);
private boolean isBsqWallet;
private final boolean isBsqWallet;
public BisqKeyChainGroupStructure (boolean isBsqWallet) {
public BisqKeyChainGroupStructure(boolean isBsqWallet) {
this.isBsqWallet = isBsqWallet;
}
@ -72,8 +71,7 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
return BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH;
else if (outputScriptType == Script.ScriptType.P2WPKH)
//return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
throw new IllegalArgumentException(outputScriptType.toString());
return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
else
throw new IllegalArgumentException(outputScriptType.toString());
}

View file

@ -57,6 +57,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -143,7 +144,11 @@ public class WalletConfig extends AbstractIdleService {
@Setter
private int minBroadcastConnections;
@Getter
private BooleanProperty migratedWalletToSegwit = new SimpleBooleanProperty(false);
private BooleanProperty migratedWalletToBtcSegwit = new SimpleBooleanProperty(false);
@Getter
private BooleanProperty migratedWalletToBsqSegwit = new SimpleBooleanProperty(false);
@Getter
private BooleanExpression migratedWalletToSegwit = migratedWalletToBtcSegwit.and(migratedWalletToBsqSegwit);
/**
* Creates a new WalletConfig, with a newly created {@link Context}. Files will be stored in the given directory.
@ -406,15 +411,13 @@ public class WalletConfig extends AbstractIdleService {
wallet = serializer.readWallet(params, extArray, proto);
if (shouldReplayWallet)
wallet.reset();
if (!isBsqWallet) {
maybeAddSegwitKeychain(wallet, null);
}
maybeAddSegwitKeychain(wallet, null, isBsqWallet);
}
return wallet;
}
protected Wallet createWallet(boolean isBsqWallet) {
Script.ScriptType preferredOutputScriptType = isBsqWallet ? Script.ScriptType.P2PKH : Script.ScriptType.P2WPKH;
Script.ScriptType preferredOutputScriptType = Script.ScriptType.P2WPKH;
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure(isBsqWallet);
KeyChainGroup.Builder kcgBuilder = KeyChainGroup.builder(params, structure);
if (restoreFromSeed != null) {
@ -545,21 +548,29 @@ public class WalletConfig extends AbstractIdleService {
return directory;
}
public void maybeAddSegwitKeychain(Wallet wallet, KeyParameter aesKey) {
if (BisqKeyChainGroupStructure.BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH.equals(wallet.getActiveKeyChain().getAccountPath())) {
public void maybeAddSegwitKeychain(Wallet wallet, KeyParameter aesKey, boolean isBsqWallet) {
var nonSegwitAccountPath = isBsqWallet
? BisqKeyChainGroupStructure.BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH
: BisqKeyChainGroupStructure.BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH;
var preSegwitBackupFilename = isBsqWallet
? WalletsSetup.PRE_SEGWIT_BSQ_WALLET_BACKUP
: WalletsSetup.PRE_SEGWIT_BTC_WALLET_BACKUP;
var walletFilename = isBsqWallet ? "bisq_BSQ.wallet" : "bisq_BTC.wallet";
if (nonSegwitAccountPath.equals(wallet.getActiveKeyChain().getAccountPath())) {
if (wallet.isEncrypted() && aesKey == null) {
// wait for the aesKey to be set and this method to be invoked again.
return;
}
// Do a backup of the wallet
File backup = new File(directory, WalletsSetup.PRE_SEGWIT_WALLET_BACKUP);
File backup = new File(directory, preSegwitBackupFilename);
try {
FileUtil.copyFile(new File(directory, "bisq_BTC.wallet"), backup);
FileUtil.copyFile(new File(directory, walletFilename), backup);
} catch (IOException e) {
log.error(e.toString(), e);
}
// Btc wallet does not have a native segwit keychain, we should add one.
// Wallet does not have a native segwit keychain, we should add one.
DeterministicSeed seed = wallet.getKeyChainSeed();
if (aesKey != null) {
// If wallet is encrypted, decrypt the seed.
@ -568,7 +579,7 @@ public class WalletConfig extends AbstractIdleService {
}
DeterministicKeyChain nativeSegwitKeyChain = DeterministicKeyChain.builder().seed(seed)
.outputScriptType(Script.ScriptType.P2WPKH)
.accountPath(new BisqKeyChainGroupStructure(false).accountPathFor(Script.ScriptType.P2WPKH)).build();
.accountPath(new BisqKeyChainGroupStructure(isBsqWallet).accountPathFor(Script.ScriptType.P2WPKH)).build();
if (aesKey != null) {
// If wallet is encrypted, encrypt the new keychain.
KeyCrypter keyCrypter = wallet.getKeyCrypter();
@ -576,7 +587,11 @@ public class WalletConfig extends AbstractIdleService {
}
wallet.addAndActivateHDChain(nativeSegwitKeyChain);
}
migratedWalletToSegwit.set(true);
if (isBsqWallet) {
migratedWalletToBsqSegwit.set(true);
} else {
migratedWalletToBtcSegwit.set(true);
}
}
public boolean stateStartingOrRunning() {

View file

@ -109,7 +109,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class WalletsSetup {
public static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_bisq_BTC.wallet.backup";
public static final String PRE_SEGWIT_BTC_WALLET_BACKUP = "pre_segwit_bisq_BTC.wallet.backup";
public static final String PRE_SEGWIT_BSQ_WALLET_BACKUP = "pre_segwit_bisq_BSQ.wallet.backup";
@Getter
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
@ -427,12 +428,14 @@ public class WalletsSetup {
e.printStackTrace();
}
File segwitBackup = new File(walletDir, PRE_SEGWIT_WALLET_BACKUP);
try {
FileUtil.deleteFileIfExists(segwitBackup);
} catch (IOException e) {
log.error(e.toString(), e);
}
List.of(PRE_SEGWIT_BTC_WALLET_BACKUP, PRE_SEGWIT_BSQ_WALLET_BACKUP).forEach(filename -> {
File segwitBackup = new File(walletDir, filename);
try {
FileUtil.deleteFileIfExists(segwitBackup);
} catch (IOException e) {
log.error(e.toString(), e);
}
});
}

View file

@ -42,7 +42,6 @@ import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
@ -50,6 +49,7 @@ import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.wallet.CoinSelection;
import org.bitcoinj.wallet.CoinSelector;
@ -807,12 +807,13 @@ public class BsqWalletService extends WalletService implements DaoStateListener
// Addresses
///////////////////////////////////////////////////////////////////////////////////////////
private LegacyAddress getChangeAddress() {
private Address getChangeAddress() {
return getUnusedAddress();
}
public LegacyAddress getUnusedAddress() {
return (LegacyAddress) wallet.getIssuedReceiveAddresses().stream()
public Address getUnusedAddress() {
return wallet.getIssuedReceiveAddresses().stream()
.filter(address -> Script.ScriptType.P2WPKH.equals(address.getOutputScriptType()))
.filter(this::isAddressUnused)
.findAny()
.orElse(wallet.freshReceiveAddress());