Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add segwit support to the BTC wallet #4568

Merged
merged 30 commits into from Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f13a7b1
Create a P2WPKH keychain for new btc wallets
oscarguindzberg Sep 14, 2020
cd28660
Add a P2WPKH keychain for existing wallets
oscarguindzberg Sep 14, 2020
ced357c
AddressEntry: Add boolean segwit flag
oscarguindzberg Sep 14, 2020
e85c66b
Stop using LegacyAddress for btc addresses
oscarguindzberg Sep 14, 2020
0c7f345
Fix log msg in BtcCoinSelector
oscarguindzberg Sep 14, 2020
0f4c66f
Comment out segwit BSQ account path
oscarguindzberg Sep 29, 2020
2551571
TradeWalletService: adapt to segwit wallet
oscarguindzberg Sep 29, 2020
d8b7557
WalletService: adapt to segwit wallet
oscarguindzberg Sep 29, 2020
a370848
New AddressEntry: use different script types
oscarguindzberg Sep 25, 2020
a9cc28f
AddressEntryList: arbitrator entry use P2PKH
oscarguindzberg Sep 25, 2020
f9f5d92
Add segwit/legacy checbox for address creation
oscarguindzberg Sep 25, 2020
1f3c585
Serialize tx without segwit
oscarguindzberg Sep 28, 2020
58afc00
Don't create an extra address at startup
oscarguindzberg Sep 28, 2020
e2f74f0
Don't create a wallet address when not needed
oscarguindzberg Sep 28, 2020
d1aeedd
Remove unused WalletService.findKeyFromPubKeyHash()
oscarguindzberg Sep 24, 2020
78a2a43
Remove unused import
oscarguindzberg Sep 14, 2020
01455d2
Enable reusing unused AVAILABLE entries
oscarguindzberg Oct 1, 2020
4a2c0ad
Make codacy happy
oscarguindzberg Oct 1, 2020
86ddd06
Validate AddressEntry.segwit
oscarguindzberg Oct 5, 2020
b9e404f
Make it clear segwit is not used for the trade protocol yet.
oscarguindzberg Oct 5, 2020
5524ba3
BtcWalletService.getFreshAddressEntry(): code clean up
oscarguindzberg Oct 5, 2020
d1aaf3e
Construct dummy outputs with LegacyAddress
oscarguindzberg Oct 5, 2020
3554e19
setWitness(): Code clean up
oscarguindzberg Oct 5, 2020
499d7b7
Use try-with-resources
oscarguindzberg Oct 5, 2020
1d82c01
Improve error handling for P2WPKH
oscarguindzberg Oct 5, 2020
694446c
Switch back to LegacyAddress for fee estimation
oscarguindzberg Oct 5, 2020
a747e83
Fix add segwit keychain for encrypted wallet
oscarguindzberg Oct 7, 2020
417daf5
Use bitcoinj 0.15.8 (commit a733034)
oscarguindzberg Oct 8, 2020
87da2ae
Do a backup of the wallet before segwit migration
oscarguindzberg Oct 8, 2020
261e0ec
Check migratedWalletToSegwit is true
oscarguindzberg Oct 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Expand Up @@ -28,7 +28,7 @@ configure(subprojects) {

ext { // in alphabetical order
bcVersion = '1.63'
bitcoinjVersion = '44ddbdc'
bitcoinjVersion = 'a733034'
btcdCli4jVersion = '27b94333'
codecVersion = '1.13'
easybindVersion = '1.0.3'
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/bisq/core/app/BisqSetup.java
Expand Up @@ -389,6 +389,8 @@ private void initWallet() {
if (requestWalletPasswordHandler != null) {
requestWalletPasswordHandler.accept(aesKey -> {
walletsManager.setAesKey(aesKey);
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(),
aesKey);
if (preferences.isResyncSpvRequested()) {
if (showFirstPopupIfResyncSPVRequestedHandler != null)
showFirstPopupIfResyncSPVRequestedHandler.run();
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/bisq/core/app/WalletAppSetup.java
Expand Up @@ -105,7 +105,7 @@ void init(@Nullable Consumer<String> chainFileLockedExceptionHandler,
Runnable downloadCompleteHandler,
Runnable walletInitializedHandler) {
log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}",
VersionMessage.BITCOINJ_VERSION, "44ddbdc");
VersionMessage.BITCOINJ_VERSION, "a733034");

ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(),
Expand Down
30 changes: 22 additions & 8 deletions core/src/main/java/bisq/core/btc/model/AddressEntry.java
Expand Up @@ -26,8 +26,8 @@

import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.script.Script;

import java.util.Optional;

Expand Down Expand Up @@ -74,6 +74,9 @@ public enum Context {

private long coinLockedInMultiSig;

@Getter
private boolean segwit;

@Nullable
transient private DeterministicKey keyPair;
@Nullable
Expand All @@ -86,18 +89,24 @@ public enum Context {
// Constructor, initialization
///////////////////////////////////////////////////////////////////////////////////////////

public AddressEntry(DeterministicKey keyPair, Context context) {
this(keyPair, context, null);
public AddressEntry(DeterministicKey keyPair, Context context, boolean segwit) {
this(keyPair, context, null, segwit);
}

public AddressEntry(@NotNull DeterministicKey keyPair,
Context context,
@Nullable String offerId) {
@Nullable String offerId,
boolean segwit) {
if (segwit && (!Context.AVAILABLE.equals(context) || offerId != null)) {
throw new IllegalArgumentException("Segwit addresses are only allowed for " +
"AVAILABLE entries without an offerId");
}
this.keyPair = keyPair;
this.context = context;
this.offerId = offerId;
pubKey = keyPair.getPubKey();
pubKeyHash = keyPair.getPubKeyHash();
this.segwit = segwit;
}


Expand All @@ -109,20 +118,23 @@ private AddressEntry(byte[] pubKey,
byte[] pubKeyHash,
Context context,
@Nullable String offerId,
Coin coinLockedInMultiSig) {
Coin coinLockedInMultiSig,
boolean segwit) {
this.pubKey = pubKey;
this.pubKeyHash = pubKeyHash;
this.context = context;
this.offerId = offerId;
this.coinLockedInMultiSig = coinLockedInMultiSig.value;
this.segwit = segwit;
}

public static AddressEntry fromProto(protobuf.AddressEntry proto) {
return new AddressEntry(proto.getPubKey().toByteArray(),
proto.getPubKeyHash().toByteArray(),
ProtoUtil.enumFromProto(AddressEntry.Context.class, proto.getContext().name()),
ProtoUtil.stringOrNullFromProto(proto.getOfferId()),
Coin.valueOf(proto.getCoinLockedInMultiSig()));
Coin.valueOf(proto.getCoinLockedInMultiSig()),
proto.getSegwit());
}

@Override
Expand All @@ -131,7 +143,8 @@ public protobuf.AddressEntry toProtoMessage() {
.setPubKey(ByteString.copyFrom(pubKey))
.setPubKeyHash(ByteString.copyFrom(pubKeyHash))
.setContext(protobuf.AddressEntry.Context.valueOf(context.name()))
.setCoinLockedInMultiSig(coinLockedInMultiSig);
.setCoinLockedInMultiSig(coinLockedInMultiSig)
.setSegwit(segwit);
Optional.ofNullable(offerId).ifPresent(builder::setOfferId);
return builder.build();
}
Expand Down Expand Up @@ -175,7 +188,7 @@ public String getAddressString() {
@Nullable
public Address getAddress() {
if (address == null && keyPair != null)
address = LegacyAddress.fromKey(Config.baseCurrencyNetworkParameters(), keyPair);
address = Address.fromKey(Config.baseCurrencyNetworkParameters(), keyPair, segwit ? Script.ScriptType.P2WPKH : Script.ScriptType.P2PKH);
return address;
}

Expand All @@ -198,6 +211,7 @@ public String toString() {
", context=" + context +
", offerId='" + offerId + '\'' +
", coinLockedInMultiSig=" + coinLockedInMultiSig +
", segwit=" + segwit +
"}";
}
}
30 changes: 18 additions & 12 deletions core/src/main/java/bisq/core/btc/model/AddressEntryList.java
Expand Up @@ -25,7 +25,7 @@
import com.google.protobuf.Message;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.SegwitAddress;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.script.Script;
Expand All @@ -35,6 +35,8 @@

import com.google.common.collect.ImmutableList;

import org.apache.commons.lang3.tuple.Pair;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -107,11 +109,13 @@ public void onWalletReady(Wallet wallet) {
if (!entrySet.isEmpty()) {
Set<AddressEntry> toBeRemoved = new HashSet<>();
entrySet.forEach(addressEntry -> {
Script.ScriptType scriptType = addressEntry.isSegwit() ? Script.ScriptType.P2WPKH
: Script.ScriptType.P2PKH;
DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubKeyHash(
addressEntry.getPubKeyHash(),
Script.ScriptType.P2PKH);
addressEntry.getPubKeyHash(), scriptType);
if (keyFromPubHash != null) {
Address addressFromKey = LegacyAddress.fromKey(Config.baseCurrencyNetworkParameters(), keyFromPubHash);
Address addressFromKey = Address.fromKey(Config.baseCurrencyNetworkParameters(), keyFromPubHash,
scriptType);
// We want to ensure key and address matches in case we have address in entry available already
if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) {
addressEntry.setDeterministicKey(keyFromPubHash);
Expand All @@ -133,7 +137,8 @@ public void onWalletReady(Wallet wallet) {
toBeRemoved.forEach(entrySet::remove);
} else {
// As long the old arbitration domain is not removed from the code base we still support it here.
entrySet.add(new AddressEntry(wallet.freshReceiveKey(), AddressEntry.Context.ARBITRATOR));
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2PKH));
entrySet.add(new AddressEntry(key, AddressEntry.Context.ARBITRATOR, false));
}

// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
Expand All @@ -147,7 +152,7 @@ public void onWalletReady(Wallet wallet) {
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(address);
if (key != null) {
// Address will be derived from key in getAddress method
entrySet.add(new AddressEntry(key, AddressEntry.Context.AVAILABLE));
entrySet.add(new AddressEntry(key, AddressEntry.Context.AVAILABLE, address instanceof SegwitAddress));
}
});
}
Expand Down Expand Up @@ -192,7 +197,8 @@ public void addAddressEntry(AddressEntry addressEntry) {
public void swapToAvailable(AddressEntry addressEntry) {
boolean setChangedByRemove = entrySet.remove(addressEntry);
boolean setChangedByAdd = entrySet.add(new AddressEntry(addressEntry.getKeyPair(),
AddressEntry.Context.AVAILABLE));
AddressEntry.Context.AVAILABLE,
addressEntry.isSegwit()));
if (setChangedByRemove || setChangedByAdd) {
requestPersistence();
}
Expand All @@ -202,7 +208,7 @@ public AddressEntry swapAvailableToAddressEntryWithOfferId(AddressEntry addressE
AddressEntry.Context context,
String offerId) {
boolean setChangedByRemove = entrySet.remove(addressEntry);
final AddressEntry newAddressEntry = new AddressEntry(addressEntry.getKeyPair(), context, offerId);
final AddressEntry newAddressEntry = new AddressEntry(addressEntry.getKeyPair(), context, offerId, addressEntry.isSegwit());
boolean setChangedByAdd = entrySet.add(newAddressEntry);
if (setChangedByRemove || setChangedByAdd)
requestPersistence();
Expand All @@ -225,10 +231,10 @@ private void maybeAddNewAddressEntry(Transaction tx) {
.map(output -> output.getScriptPubKey().getToAddress(wallet.getNetworkParameters()))
.filter(Objects::nonNull)
.filter(this::isAddressNotInEntries)
.map(address -> (DeterministicKey) wallet.findKeyFromPubKeyHash(address.getHash(),
Script.ScriptType.P2PKH))
.filter(Objects::nonNull)
.map(deterministicKey -> new AddressEntry(deterministicKey, AddressEntry.Context.AVAILABLE))
.map(address -> Pair.of(address, (DeterministicKey) wallet.findKeyFromAddress(address)))
.filter(pair -> pair.getRight() != null)
.map(pair -> new AddressEntry(pair.getRight(), AddressEntry.Context.AVAILABLE,
pair.getLeft() instanceof SegwitAddress))
.forEach(this::addAddressEntry);
}

Expand Down
Expand Up @@ -47,10 +47,11 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
new ChildNumber(142, true),
ChildNumber.ZERO_HARDENED);

public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
new ChildNumber(44, true),
new ChildNumber(142, true),
ChildNumber.ONE_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);

private boolean isBsqWallet;

Expand All @@ -71,7 +72,8 @@ else if (outputScriptType == Script.ScriptType.P2WPKH)
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;
//return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
throw new IllegalArgumentException(outputScriptType.toString());
else
throw new IllegalArgumentException(outputScriptType.toString());
}
Expand Down