mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 18:03:12 +01:00
Merge pull request #4568 from oscarguindzberg/segwitWallet
Add segwit support to the BTC wallet
This commit is contained in:
commit
35e0c34c74
@ -28,7 +28,7 @@ configure(subprojects) {
|
|||||||
|
|
||||||
ext { // in alphabetical order
|
ext { // in alphabetical order
|
||||||
bcVersion = '1.63'
|
bcVersion = '1.63'
|
||||||
bitcoinjVersion = '44ddbdc'
|
bitcoinjVersion = 'a733034'
|
||||||
btcdCli4jVersion = '27b94333'
|
btcdCli4jVersion = '27b94333'
|
||||||
codecVersion = '1.13'
|
codecVersion = '1.13'
|
||||||
easybindVersion = '1.0.3'
|
easybindVersion = '1.0.3'
|
||||||
|
@ -389,6 +389,8 @@ public class BisqSetup {
|
|||||||
if (requestWalletPasswordHandler != null) {
|
if (requestWalletPasswordHandler != null) {
|
||||||
requestWalletPasswordHandler.accept(aesKey -> {
|
requestWalletPasswordHandler.accept(aesKey -> {
|
||||||
walletsManager.setAesKey(aesKey);
|
walletsManager.setAesKey(aesKey);
|
||||||
|
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(),
|
||||||
|
aesKey);
|
||||||
if (preferences.isResyncSpvRequested()) {
|
if (preferences.isResyncSpvRequested()) {
|
||||||
if (showFirstPopupIfResyncSPVRequestedHandler != null)
|
if (showFirstPopupIfResyncSPVRequestedHandler != null)
|
||||||
showFirstPopupIfResyncSPVRequestedHandler.run();
|
showFirstPopupIfResyncSPVRequestedHandler.run();
|
||||||
|
@ -105,7 +105,7 @@ public class WalletAppSetup {
|
|||||||
Runnable downloadCompleteHandler,
|
Runnable downloadCompleteHandler,
|
||||||
Runnable walletInitializedHandler) {
|
Runnable walletInitializedHandler) {
|
||||||
log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}",
|
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<>();
|
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
|
||||||
btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(),
|
btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(),
|
||||||
|
@ -26,8 +26,8 @@ import com.google.protobuf.ByteString;
|
|||||||
|
|
||||||
import org.bitcoinj.core.Address;
|
import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
|
||||||
import org.bitcoinj.crypto.DeterministicKey;
|
import org.bitcoinj.crypto.DeterministicKey;
|
||||||
|
import org.bitcoinj.script.Script;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -74,6 +74,9 @@ public final class AddressEntry implements PersistablePayload {
|
|||||||
|
|
||||||
private long coinLockedInMultiSig;
|
private long coinLockedInMultiSig;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private boolean segwit;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
transient private DeterministicKey keyPair;
|
transient private DeterministicKey keyPair;
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -86,18 +89,24 @@ public final class AddressEntry implements PersistablePayload {
|
|||||||
// Constructor, initialization
|
// Constructor, initialization
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public AddressEntry(DeterministicKey keyPair, Context context) {
|
public AddressEntry(DeterministicKey keyPair, Context context, boolean segwit) {
|
||||||
this(keyPair, context, null);
|
this(keyPair, context, null, segwit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddressEntry(@NotNull DeterministicKey keyPair,
|
public AddressEntry(@NotNull DeterministicKey keyPair,
|
||||||
Context context,
|
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.keyPair = keyPair;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.offerId = offerId;
|
this.offerId = offerId;
|
||||||
pubKey = keyPair.getPubKey();
|
pubKey = keyPair.getPubKey();
|
||||||
pubKeyHash = keyPair.getPubKeyHash();
|
pubKeyHash = keyPair.getPubKeyHash();
|
||||||
|
this.segwit = segwit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -109,12 +118,14 @@ public final class AddressEntry implements PersistablePayload {
|
|||||||
byte[] pubKeyHash,
|
byte[] pubKeyHash,
|
||||||
Context context,
|
Context context,
|
||||||
@Nullable String offerId,
|
@Nullable String offerId,
|
||||||
Coin coinLockedInMultiSig) {
|
Coin coinLockedInMultiSig,
|
||||||
|
boolean segwit) {
|
||||||
this.pubKey = pubKey;
|
this.pubKey = pubKey;
|
||||||
this.pubKeyHash = pubKeyHash;
|
this.pubKeyHash = pubKeyHash;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.offerId = offerId;
|
this.offerId = offerId;
|
||||||
this.coinLockedInMultiSig = coinLockedInMultiSig.value;
|
this.coinLockedInMultiSig = coinLockedInMultiSig.value;
|
||||||
|
this.segwit = segwit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AddressEntry fromProto(protobuf.AddressEntry proto) {
|
public static AddressEntry fromProto(protobuf.AddressEntry proto) {
|
||||||
@ -122,7 +133,8 @@ public final class AddressEntry implements PersistablePayload {
|
|||||||
proto.getPubKeyHash().toByteArray(),
|
proto.getPubKeyHash().toByteArray(),
|
||||||
ProtoUtil.enumFromProto(AddressEntry.Context.class, proto.getContext().name()),
|
ProtoUtil.enumFromProto(AddressEntry.Context.class, proto.getContext().name()),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getOfferId()),
|
ProtoUtil.stringOrNullFromProto(proto.getOfferId()),
|
||||||
Coin.valueOf(proto.getCoinLockedInMultiSig()));
|
Coin.valueOf(proto.getCoinLockedInMultiSig()),
|
||||||
|
proto.getSegwit());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -131,7 +143,8 @@ public final class AddressEntry implements PersistablePayload {
|
|||||||
.setPubKey(ByteString.copyFrom(pubKey))
|
.setPubKey(ByteString.copyFrom(pubKey))
|
||||||
.setPubKeyHash(ByteString.copyFrom(pubKeyHash))
|
.setPubKeyHash(ByteString.copyFrom(pubKeyHash))
|
||||||
.setContext(protobuf.AddressEntry.Context.valueOf(context.name()))
|
.setContext(protobuf.AddressEntry.Context.valueOf(context.name()))
|
||||||
.setCoinLockedInMultiSig(coinLockedInMultiSig);
|
.setCoinLockedInMultiSig(coinLockedInMultiSig)
|
||||||
|
.setSegwit(segwit);
|
||||||
Optional.ofNullable(offerId).ifPresent(builder::setOfferId);
|
Optional.ofNullable(offerId).ifPresent(builder::setOfferId);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
@ -175,7 +188,7 @@ public final class AddressEntry implements PersistablePayload {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public Address getAddress() {
|
public Address getAddress() {
|
||||||
if (address == null && keyPair != null)
|
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;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,6 +211,7 @@ public final class AddressEntry implements PersistablePayload {
|
|||||||
", context=" + context +
|
", context=" + context +
|
||||||
", offerId='" + offerId + '\'' +
|
", offerId='" + offerId + '\'' +
|
||||||
", coinLockedInMultiSig=" + coinLockedInMultiSig +
|
", coinLockedInMultiSig=" + coinLockedInMultiSig +
|
||||||
|
", segwit=" + segwit +
|
||||||
"}";
|
"}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import bisq.common.proto.persistable.PersistedDataHost;
|
|||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
|
|
||||||
import org.bitcoinj.core.Address;
|
import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
import org.bitcoinj.core.SegwitAddress;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
import org.bitcoinj.crypto.DeterministicKey;
|
import org.bitcoinj.crypto.DeterministicKey;
|
||||||
import org.bitcoinj.script.Script;
|
import org.bitcoinj.script.Script;
|
||||||
@ -35,6 +35,8 @@ import com.google.inject.Inject;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -107,11 +109,13 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
|||||||
if (!entrySet.isEmpty()) {
|
if (!entrySet.isEmpty()) {
|
||||||
Set<AddressEntry> toBeRemoved = new HashSet<>();
|
Set<AddressEntry> toBeRemoved = new HashSet<>();
|
||||||
entrySet.forEach(addressEntry -> {
|
entrySet.forEach(addressEntry -> {
|
||||||
|
Script.ScriptType scriptType = addressEntry.isSegwit() ? Script.ScriptType.P2WPKH
|
||||||
|
: Script.ScriptType.P2PKH;
|
||||||
DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubKeyHash(
|
DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubKeyHash(
|
||||||
addressEntry.getPubKeyHash(),
|
addressEntry.getPubKeyHash(), scriptType);
|
||||||
Script.ScriptType.P2PKH);
|
|
||||||
if (keyFromPubHash != null) {
|
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
|
// 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())) {
|
if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) {
|
||||||
addressEntry.setDeterministicKey(keyFromPubHash);
|
addressEntry.setDeterministicKey(keyFromPubHash);
|
||||||
@ -133,7 +137,8 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
|||||||
toBeRemoved.forEach(entrySet::remove);
|
toBeRemoved.forEach(entrySet::remove);
|
||||||
} else {
|
} else {
|
||||||
// As long the old arbitration domain is not removed from the code base we still support it here.
|
// 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.
|
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
|
||||||
@ -147,7 +152,7 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
|||||||
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(address);
|
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(address);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
// Address will be derived from key in getAddress method
|
// 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));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -192,7 +197,8 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
|||||||
public void swapToAvailable(AddressEntry addressEntry) {
|
public void swapToAvailable(AddressEntry addressEntry) {
|
||||||
boolean setChangedByRemove = entrySet.remove(addressEntry);
|
boolean setChangedByRemove = entrySet.remove(addressEntry);
|
||||||
boolean setChangedByAdd = entrySet.add(new AddressEntry(addressEntry.getKeyPair(),
|
boolean setChangedByAdd = entrySet.add(new AddressEntry(addressEntry.getKeyPair(),
|
||||||
AddressEntry.Context.AVAILABLE));
|
AddressEntry.Context.AVAILABLE,
|
||||||
|
addressEntry.isSegwit()));
|
||||||
if (setChangedByRemove || setChangedByAdd) {
|
if (setChangedByRemove || setChangedByAdd) {
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
@ -202,7 +208,7 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
|||||||
AddressEntry.Context context,
|
AddressEntry.Context context,
|
||||||
String offerId) {
|
String offerId) {
|
||||||
boolean setChangedByRemove = entrySet.remove(addressEntry);
|
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);
|
boolean setChangedByAdd = entrySet.add(newAddressEntry);
|
||||||
if (setChangedByRemove || setChangedByAdd)
|
if (setChangedByRemove || setChangedByAdd)
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
@ -225,10 +231,10 @@ public final class AddressEntryList implements PersistableEnvelope, PersistedDat
|
|||||||
.map(output -> output.getScriptPubKey().getToAddress(wallet.getNetworkParameters()))
|
.map(output -> output.getScriptPubKey().getToAddress(wallet.getNetworkParameters()))
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.filter(this::isAddressNotInEntries)
|
.filter(this::isAddressNotInEntries)
|
||||||
.map(address -> (DeterministicKey) wallet.findKeyFromPubKeyHash(address.getHash(),
|
.map(address -> Pair.of(address, (DeterministicKey) wallet.findKeyFromAddress(address)))
|
||||||
Script.ScriptType.P2PKH))
|
.filter(pair -> pair.getRight() != null)
|
||||||
.filter(Objects::nonNull)
|
.map(pair -> new AddressEntry(pair.getRight(), AddressEntry.Context.AVAILABLE,
|
||||||
.map(deterministicKey -> new AddressEntry(deterministicKey, AddressEntry.Context.AVAILABLE))
|
pair.getLeft() instanceof SegwitAddress))
|
||||||
.forEach(this::addAddressEntry);
|
.forEach(this::addAddressEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,10 +47,11 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
|
|||||||
new ChildNumber(142, true),
|
new ChildNumber(142, true),
|
||||||
ChildNumber.ZERO_HARDENED);
|
ChildNumber.ZERO_HARDENED);
|
||||||
|
|
||||||
public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
|
// We don't use segwit for BSQ
|
||||||
new ChildNumber(44, true),
|
// public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
|
||||||
new ChildNumber(142, true),
|
// new ChildNumber(44, true),
|
||||||
ChildNumber.ONE_HARDENED);
|
// new ChildNumber(142, true),
|
||||||
|
// ChildNumber.ONE_HARDENED);
|
||||||
|
|
||||||
private boolean isBsqWallet;
|
private boolean isBsqWallet;
|
||||||
|
|
||||||
@ -71,7 +72,8 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
|
|||||||
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
|
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
|
||||||
return BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH;
|
return BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH;
|
||||||
else if (outputScriptType == Script.ScriptType.P2WPKH)
|
else if (outputScriptType == Script.ScriptType.P2WPKH)
|
||||||
return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
|
//return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
|
||||||
|
throw new IllegalArgumentException(outputScriptType.toString());
|
||||||
else
|
else
|
||||||
throw new IllegalArgumentException(outputScriptType.toString());
|
throw new IllegalArgumentException(outputScriptType.toString());
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,13 @@ import bisq.core.btc.nodes.ProxySocketFactory;
|
|||||||
import bisq.core.btc.wallet.BisqRiskAnalysis;
|
import bisq.core.btc.wallet.BisqRiskAnalysis;
|
||||||
|
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
|
import bisq.common.file.FileUtil;
|
||||||
|
|
||||||
import com.google.common.io.Closeables;
|
import com.google.common.io.Closeables;
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.*;
|
||||||
import org.bitcoinj.core.listeners.*;
|
import org.bitcoinj.core.listeners.*;
|
||||||
import org.bitcoinj.core.*;
|
import org.bitcoinj.core.*;
|
||||||
|
import org.bitcoinj.crypto.KeyCrypter;
|
||||||
import org.bitcoinj.net.BlockingClientManager;
|
import org.bitcoinj.net.BlockingClientManager;
|
||||||
import org.bitcoinj.net.discovery.*;
|
import org.bitcoinj.net.discovery.*;
|
||||||
import org.bitcoinj.script.Script;
|
import org.bitcoinj.script.Script;
|
||||||
@ -35,6 +37,11 @@ import org.bitcoinj.wallet.*;
|
|||||||
|
|
||||||
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
||||||
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import org.slf4j.*;
|
import org.slf4j.*;
|
||||||
|
|
||||||
import javax.annotation.*;
|
import javax.annotation.*;
|
||||||
@ -103,6 +110,8 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private int minBroadcastConnections;
|
private int minBroadcastConnections;
|
||||||
|
@Getter
|
||||||
|
private BooleanProperty migratedWalletToSegwit = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new WalletConfig, with a newly created {@link Context}. Files will be stored in the given directory.
|
* Creates a new WalletConfig, with a newly created {@link Context}. Files will be stored in the given directory.
|
||||||
@ -293,6 +302,22 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
vPeerGroup.addWallet(vBsqWallet);
|
vPeerGroup.addWallet(vBsqWallet);
|
||||||
onSetupCompleted();
|
onSetupCompleted();
|
||||||
|
|
||||||
|
if (migratedWalletToSegwit.get()) {
|
||||||
|
startPeerGroup();
|
||||||
|
} else {
|
||||||
|
migratedWalletToSegwit.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue) {
|
||||||
|
startPeerGroup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (BlockStoreException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startPeerGroup() {
|
||||||
Futures.addCallback((ListenableFuture<?>) vPeerGroup.startAsync(), new FutureCallback<Object>() {
|
Futures.addCallback((ListenableFuture<?>) vPeerGroup.startAsync(), new FutureCallback<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(@Nullable Object result) {
|
public void onSuccess(@Nullable Object result) {
|
||||||
@ -307,9 +332,6 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}, MoreExecutors.directExecutor());
|
}, MoreExecutors.directExecutor());
|
||||||
} catch (BlockStoreException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Wallet createOrLoadWallet(boolean shouldReplayWallet, File walletFile, boolean isBsqWallet) throws Exception {
|
private Wallet createOrLoadWallet(boolean shouldReplayWallet, File walletFile, boolean isBsqWallet) throws Exception {
|
||||||
@ -321,7 +343,7 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
wallet = loadWallet(shouldReplayWallet, walletFile, isBsqWallet);
|
wallet = loadWallet(shouldReplayWallet, walletFile, isBsqWallet);
|
||||||
} else {
|
} else {
|
||||||
wallet = createWallet(isBsqWallet);
|
wallet = createWallet(isBsqWallet);
|
||||||
wallet.freshReceiveKey();
|
//wallet.freshReceiveKey();
|
||||||
|
|
||||||
// Currently the only way we can be sure that an extension is aware of its containing wallet is by
|
// Currently the only way we can be sure that an extension is aware of its containing wallet is by
|
||||||
// deserializing the extension (see WalletExtension#deserializeWalletExtension(Wallet, byte[]))
|
// deserializing the extension (see WalletExtension#deserializeWalletExtension(Wallet, byte[]))
|
||||||
@ -341,8 +363,7 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
|
|
||||||
private Wallet loadWallet(boolean shouldReplayWallet, File walletFile, boolean isBsqWallet) throws Exception {
|
private Wallet loadWallet(boolean shouldReplayWallet, File walletFile, boolean isBsqWallet) throws Exception {
|
||||||
Wallet wallet;
|
Wallet wallet;
|
||||||
FileInputStream walletStream = new FileInputStream(walletFile);
|
try (FileInputStream walletStream = new FileInputStream(walletFile)) {
|
||||||
try {
|
|
||||||
WalletExtension[] extArray = new WalletExtension[]{};
|
WalletExtension[] extArray = new WalletExtension[]{};
|
||||||
Protos.Wallet proto = WalletProtobufSerializer.parseToProto(walletStream);
|
Protos.Wallet proto = WalletProtobufSerializer.parseToProto(walletStream);
|
||||||
final WalletProtobufSerializer serializer;
|
final WalletProtobufSerializer serializer;
|
||||||
@ -352,16 +373,15 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
wallet = serializer.readWallet(params, extArray, proto);
|
wallet = serializer.readWallet(params, extArray, proto);
|
||||||
if (shouldReplayWallet)
|
if (shouldReplayWallet)
|
||||||
wallet.reset();
|
wallet.reset();
|
||||||
} finally {
|
if (!isBsqWallet) {
|
||||||
walletStream.close();
|
maybeAddSegwitKeychain(wallet, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Wallet createWallet(boolean isBsqWallet) {
|
protected Wallet createWallet(boolean isBsqWallet) {
|
||||||
// Change preferredOutputScriptType of btc wallet to P2WPKH to start using segwit
|
Script.ScriptType preferredOutputScriptType = isBsqWallet ? Script.ScriptType.P2PKH : Script.ScriptType.P2WPKH;
|
||||||
// Script.ScriptType preferredOutputScriptType = isBsqWallet ? Script.ScriptType.P2PKH : Script.ScriptType.P2WPKH;
|
|
||||||
Script.ScriptType preferredOutputScriptType = Script.ScriptType.P2PKH;
|
|
||||||
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure(isBsqWallet);
|
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure(isBsqWallet);
|
||||||
KeyChainGroup.Builder kcgBuilder = KeyChainGroup.builder(params, structure);
|
KeyChainGroup.Builder kcgBuilder = KeyChainGroup.builder(params, structure);
|
||||||
if (restoreFromSeed != null) {
|
if (restoreFromSeed != null) {
|
||||||
@ -488,4 +508,38 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
public File directory() {
|
public File directory() {
|
||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void maybeAddSegwitKeychain(Wallet wallet, KeyParameter aesKey) {
|
||||||
|
if (BisqKeyChainGroupStructure.BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH.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);
|
||||||
|
try {
|
||||||
|
FileUtil.copyFile(new File(directory, "bisq_BTC.wallet"), backup);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.toString(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Btc 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.
|
||||||
|
KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
||||||
|
seed = seed.decrypt(keyCrypter, DeterministicKeyChain.DEFAULT_PASSPHRASE_FOR_MNEMONIC, aesKey);
|
||||||
|
}
|
||||||
|
DeterministicKeyChain nativeSegwitKeyChain = DeterministicKeyChain.builder().seed(seed)
|
||||||
|
.outputScriptType(Script.ScriptType.P2WPKH)
|
||||||
|
.accountPath(new BisqKeyChainGroupStructure(false).accountPathFor(Script.ScriptType.P2WPKH)).build();
|
||||||
|
if (aesKey != null) {
|
||||||
|
// If wallet is encrypted, encrypt the new keychain.
|
||||||
|
KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
||||||
|
nativeSegwitKeyChain = nativeSegwitKeyChain.toEncrypted(keyCrypter, aesKey);
|
||||||
|
}
|
||||||
|
wallet.addAndActivateHDChain(nativeSegwitKeyChain);
|
||||||
|
}
|
||||||
|
migratedWalletToSegwit.set(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class WalletsSetup {
|
public class WalletsSetup {
|
||||||
|
|
||||||
|
public static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_bisq_BTC.wallet.backup";
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
|
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
|
||||||
|
|
||||||
@ -421,6 +423,13 @@ public class WalletsSetup {
|
|||||||
log.error("Could not delete directory " + e.getMessage());
|
log.error("Could not delete directory " + e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File segwitBackup = new File(walletDir, PRE_SEGWIT_WALLET_BACKUP);
|
||||||
|
try {
|
||||||
|
FileUtil.deleteFileIfExists(segwitBackup);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.toString(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ class BtcCoinSelector extends BisqDefaultCoinSelector {
|
|||||||
Address address = WalletService.getAddressFromOutput(output);
|
Address address = WalletService.getAddressFromOutput(output);
|
||||||
return addresses.contains(address);
|
return addresses.contains(address);
|
||||||
} else {
|
} else {
|
||||||
log.warn("transactionOutput.getScriptPubKey() not isSentToAddress or isPayToScriptHash");
|
log.warn("transactionOutput.getScriptPubKey() is not P2PKH nor P2SH nor P2WH");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import bisq.common.handlers.ErrorMessageHandler;
|
|||||||
import org.bitcoinj.core.Address;
|
import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.AddressFormatException;
|
import org.bitcoinj.core.AddressFormatException;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.bitcoinj.core.ECKey;
|
||||||
import org.bitcoinj.core.InsufficientMoneyException;
|
import org.bitcoinj.core.InsufficientMoneyException;
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
import org.bitcoinj.core.LegacyAddress;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
@ -41,6 +42,7 @@ import org.bitcoinj.core.TransactionOutPoint;
|
|||||||
import org.bitcoinj.core.TransactionOutput;
|
import org.bitcoinj.core.TransactionOutput;
|
||||||
import org.bitcoinj.crypto.DeterministicKey;
|
import org.bitcoinj.crypto.DeterministicKey;
|
||||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||||
|
import org.bitcoinj.script.Script;
|
||||||
import org.bitcoinj.script.ScriptBuilder;
|
import org.bitcoinj.script.ScriptBuilder;
|
||||||
import org.bitcoinj.wallet.SendRequest;
|
import org.bitcoinj.wallet.SendRequest;
|
||||||
import org.bitcoinj.wallet.Wallet;
|
import org.bitcoinj.wallet.Wallet;
|
||||||
@ -576,46 +578,49 @@ public class BtcWalletService extends WalletService {
|
|||||||
if (addressEntry.isPresent()) {
|
if (addressEntry.isPresent()) {
|
||||||
return addressEntry.get();
|
return addressEntry.get();
|
||||||
} else {
|
} else {
|
||||||
|
// We still use non-segwit addresses for the trade protocol.
|
||||||
// We try to use available and not yet used entries
|
// We try to use available and not yet used entries
|
||||||
Optional<AddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream()
|
Optional<AddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream()
|
||||||
.filter(e -> AddressEntry.Context.AVAILABLE == e.getContext())
|
.filter(e -> AddressEntry.Context.AVAILABLE == e.getContext())
|
||||||
.filter(e -> isAddressUnused(e.getAddress()))
|
.filter(e -> isAddressUnused(e.getAddress()))
|
||||||
|
.filter(e -> Script.ScriptType.P2PKH.equals(e.getAddress().getOutputScriptType()))
|
||||||
.findAny();
|
.findAny();
|
||||||
if (emptyAvailableAddressEntry.isPresent()) {
|
if (emptyAvailableAddressEntry.isPresent()) {
|
||||||
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
|
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
|
||||||
} else {
|
} else {
|
||||||
AddressEntry entry = new AddressEntry(wallet.freshReceiveKey(), context, offerId);
|
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2PKH));
|
||||||
|
AddressEntry entry = new AddressEntry(key, context, offerId, false);
|
||||||
addressEntryList.addAddressEntry(entry);
|
addressEntryList.addAddressEntry(entry);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry) {
|
|
||||||
if (addressEntry.isPresent()) {
|
|
||||||
return addressEntry.get();
|
|
||||||
} else {
|
|
||||||
AddressEntry entry = new AddressEntry(wallet.freshReceiveKey(), context);
|
|
||||||
addressEntryList.addAddressEntry(entry);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressEntry getArbitratorAddressEntry() {
|
public AddressEntry getArbitratorAddressEntry() {
|
||||||
AddressEntry.Context context = AddressEntry.Context.ARBITRATOR;
|
AddressEntry.Context context = AddressEntry.Context.ARBITRATOR;
|
||||||
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
||||||
.filter(e -> context == e.getContext())
|
.filter(e -> context == e.getContext())
|
||||||
.findAny();
|
.findAny();
|
||||||
return getOrCreateAddressEntry(context, addressEntry);
|
return getOrCreateAddressEntry(context, addressEntry, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddressEntry getFreshAddressEntry() {
|
public AddressEntry getFreshAddressEntry() {
|
||||||
|
return getFreshAddressEntry(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressEntry getFreshAddressEntry(boolean segwit) {
|
||||||
AddressEntry.Context context = AddressEntry.Context.AVAILABLE;
|
AddressEntry.Context context = AddressEntry.Context.AVAILABLE;
|
||||||
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
||||||
.filter(e -> context == e.getContext())
|
.filter(e -> context == e.getContext())
|
||||||
.filter(e -> isAddressUnused(e.getAddress()))
|
.filter(e -> isAddressUnused(e.getAddress()))
|
||||||
|
.filter(e -> {
|
||||||
|
boolean isSegwitOutputScriptType = Script.ScriptType.P2WPKH.equals(e.getAddress().getOutputScriptType());
|
||||||
|
// We need to ensure that we take only addressEntries which matches our segWit flag
|
||||||
|
boolean isMatchingOutputScriptType = isSegwitOutputScriptType == segwit;
|
||||||
|
return isMatchingOutputScriptType;
|
||||||
|
})
|
||||||
.findAny();
|
.findAny();
|
||||||
return getOrCreateAddressEntry(context, addressEntry);
|
return getOrCreateAddressEntry(context, addressEntry, segwit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void recoverAddressEntry(String offerId, String address, AddressEntry.Context context) {
|
public void recoverAddressEntry(String offerId, String address, AddressEntry.Context context) {
|
||||||
@ -623,6 +628,22 @@ public class BtcWalletService extends WalletService {
|
|||||||
addressEntryList.swapAvailableToAddressEntryWithOfferId(addressEntry, context, offerId));
|
addressEntryList.swapAvailableToAddressEntryWithOfferId(addressEntry, context, offerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry, boolean segwit) {
|
||||||
|
if (addressEntry.isPresent()) {
|
||||||
|
return addressEntry.get();
|
||||||
|
} else {
|
||||||
|
DeterministicKey key;
|
||||||
|
if (segwit) {
|
||||||
|
key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2WPKH));
|
||||||
|
} else {
|
||||||
|
key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2PKH));
|
||||||
|
}
|
||||||
|
AddressEntry entry = new AddressEntry(key, context, segwit);
|
||||||
|
addressEntryList.addAddressEntry(entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<AddressEntry> findAddressEntry(String address, AddressEntry.Context context) {
|
private Optional<AddressEntry> findAddressEntry(String address, AddressEntry.Context context) {
|
||||||
return getAddressEntryListAsImmutableList().stream()
|
return getAddressEntryListAsImmutableList().stream()
|
||||||
.filter(e -> address.equals(e.getAddressString()))
|
.filter(e -> address.equals(e.getAddressString()))
|
||||||
@ -969,7 +990,7 @@ public class BtcWalletService extends WalletService {
|
|||||||
counter++;
|
counter++;
|
||||||
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
||||||
// We use a dummy address for the output
|
// We use a dummy address for the output
|
||||||
final String dummyReceiver = getFreshAddressEntry().getAddressString();
|
final String dummyReceiver = LegacyAddress.fromKey(params, new ECKey()).toBase58();
|
||||||
SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, dummyReceiver, amount, fee, null, aesKey);
|
SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, dummyReceiver, amount, fee, null, aesKey);
|
||||||
wallet.completeTx(sendRequest);
|
wallet.completeTx(sendRequest);
|
||||||
tx = sendRequest.tx;
|
tx = sendRequest.tx;
|
||||||
@ -998,7 +1019,7 @@ public class BtcWalletService extends WalletService {
|
|||||||
public int getEstimatedFeeTxSize(List<Coin> outputValues, Coin txFee)
|
public int getEstimatedFeeTxSize(List<Coin> outputValues, Coin txFee)
|
||||||
throws InsufficientMoneyException, AddressFormatException {
|
throws InsufficientMoneyException, AddressFormatException {
|
||||||
Transaction transaction = new Transaction(params);
|
Transaction transaction = new Transaction(params);
|
||||||
Address dummyAddress = LegacyAddress.fromKey(params, wallet.currentReceiveKey());
|
Address dummyAddress = LegacyAddress.fromKey(params, new ECKey());
|
||||||
outputValues.forEach(outputValue -> transaction.addOutput(outputValue, dummyAddress));
|
outputValues.forEach(outputValue -> transaction.addOutput(outputValue, dummyAddress));
|
||||||
|
|
||||||
SendRequest sendRequest = SendRequest.forTx(transaction);
|
SendRequest sendRequest = SendRequest.forTx(transaction);
|
||||||
|
@ -44,6 +44,7 @@ import org.bitcoinj.core.Transaction;
|
|||||||
import org.bitcoinj.core.TransactionInput;
|
import org.bitcoinj.core.TransactionInput;
|
||||||
import org.bitcoinj.core.TransactionOutPoint;
|
import org.bitcoinj.core.TransactionOutPoint;
|
||||||
import org.bitcoinj.core.TransactionOutput;
|
import org.bitcoinj.core.TransactionOutput;
|
||||||
|
import org.bitcoinj.core.TransactionWitness;
|
||||||
import org.bitcoinj.core.Utils;
|
import org.bitcoinj.core.Utils;
|
||||||
import org.bitcoinj.crypto.DeterministicKey;
|
import org.bitcoinj.crypto.DeterministicKey;
|
||||||
import org.bitcoinj.crypto.TransactionSignature;
|
import org.bitcoinj.crypto.TransactionSignature;
|
||||||
@ -153,7 +154,7 @@ public class TradeWalletService {
|
|||||||
Transaction tradingFeeTx = new Transaction(params);
|
Transaction tradingFeeTx = new Transaction(params);
|
||||||
SendRequest sendRequest = null;
|
SendRequest sendRequest = null;
|
||||||
try {
|
try {
|
||||||
tradingFeeTx.addOutput(tradingFee, LegacyAddress.fromBase58(params, feeReceiverAddress));
|
tradingFeeTx.addOutput(tradingFee, Address.fromString(params, feeReceiverAddress));
|
||||||
// the reserved amount we need for the trade we send to our trade reservedForTradeAddress
|
// the reserved amount we need for the trade we send to our trade reservedForTradeAddress
|
||||||
tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
|
tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
|
||||||
|
|
||||||
@ -516,7 +517,7 @@ public class TradeWalletService {
|
|||||||
TransactionOutput takerTransactionOutput = null;
|
TransactionOutput takerTransactionOutput = null;
|
||||||
if (takerChangeOutputValue > 0 && takerChangeAddressString != null) {
|
if (takerChangeOutputValue > 0 && takerChangeAddressString != null) {
|
||||||
takerTransactionOutput = new TransactionOutput(params, preparedDepositTx, Coin.valueOf(takerChangeOutputValue),
|
takerTransactionOutput = new TransactionOutput(params, preparedDepositTx, Coin.valueOf(takerChangeOutputValue),
|
||||||
LegacyAddress.fromBase58(params, takerChangeAddressString));
|
Address.fromString(params, takerChangeAddressString));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (makerIsBuyer) {
|
if (makerIsBuyer) {
|
||||||
@ -599,8 +600,13 @@ public class TradeWalletService {
|
|||||||
// Add buyer inputs and apply signature
|
// Add buyer inputs and apply signature
|
||||||
// We grab the signature from the makersDepositTx and apply it to the new tx input
|
// We grab the signature from the makersDepositTx and apply it to the new tx input
|
||||||
for (int i = 0; i < buyerInputs.size(); i++) {
|
for (int i = 0; i < buyerInputs.size(); i++) {
|
||||||
TransactionInput transactionInput = makersDepositTx.getInputs().get(i);
|
TransactionInput makersInput = makersDepositTx.getInputs().get(i);
|
||||||
depositTx.addInput(getTransactionInput(depositTx, getMakersScriptSigProgram(transactionInput), buyerInputs.get(i)));
|
byte[] makersScriptSigProgram = getMakersScriptSigProgram(makersInput);
|
||||||
|
TransactionInput input = getTransactionInput(depositTx, makersScriptSigProgram, buyerInputs.get(i));
|
||||||
|
if (!TransactionWitness.EMPTY.equals(makersInput.getWitness())) {
|
||||||
|
input.setWitness(makersInput.getWitness());
|
||||||
|
}
|
||||||
|
depositTx.addInput(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add seller inputs
|
// Add seller inputs
|
||||||
@ -662,9 +668,14 @@ public class TradeWalletService {
|
|||||||
|
|
||||||
// We add takers signature from his inputs and add it to out tx which was already signed earlier.
|
// We add takers signature from his inputs and add it to out tx which was already signed earlier.
|
||||||
for (int i = 0; i < numTakersInputs; i++) {
|
for (int i = 0; i < numTakersInputs; i++) {
|
||||||
TransactionInput input = takersDepositTx.getInput(i);
|
TransactionInput takersInput = takersDepositTx.getInput(i);
|
||||||
Script scriptSig = input.getScriptSig();
|
Script takersScriptSig = takersInput.getScriptSig();
|
||||||
myDepositTx.getInput(i).setScriptSig(scriptSig);
|
TransactionInput txInput = myDepositTx.getInput(i);
|
||||||
|
txInput.setScriptSig(takersScriptSig);
|
||||||
|
TransactionWitness witness = takersInput.getWitness();
|
||||||
|
if (!TransactionWitness.EMPTY.equals(witness)) {
|
||||||
|
txInput.setWitness(witness);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletService.printTx("sellerAsMakerFinalizesDepositTx", myDepositTx);
|
WalletService.printTx("sellerAsMakerFinalizesDepositTx", myDepositTx);
|
||||||
@ -686,7 +697,7 @@ public class TradeWalletService {
|
|||||||
delayedPayoutTx.addInput(p2SHMultiSigOutput);
|
delayedPayoutTx.addInput(p2SHMultiSigOutput);
|
||||||
applyLockTime(lockTime, delayedPayoutTx);
|
applyLockTime(lockTime, delayedPayoutTx);
|
||||||
Coin outputAmount = p2SHMultiSigOutput.getValue().subtract(minerFee);
|
Coin outputAmount = p2SHMultiSigOutput.getValue().subtract(minerFee);
|
||||||
delayedPayoutTx.addOutput(outputAmount, LegacyAddress.fromBase58(params, donationAddressString));
|
delayedPayoutTx.addOutput(outputAmount, Address.fromString(params, donationAddressString));
|
||||||
WalletService.printTx("Unsigned delayedPayoutTx ToDonationAddress", delayedPayoutTx);
|
WalletService.printTx("Unsigned delayedPayoutTx ToDonationAddress", delayedPayoutTx);
|
||||||
WalletService.verifyTransaction(delayedPayoutTx);
|
WalletService.verifyTransaction(delayedPayoutTx);
|
||||||
return delayedPayoutTx;
|
return delayedPayoutTx;
|
||||||
@ -938,10 +949,10 @@ public class TradeWalletService {
|
|||||||
Transaction payoutTx = new Transaction(params);
|
Transaction payoutTx = new Transaction(params);
|
||||||
payoutTx.addInput(p2SHMultiSigOutput);
|
payoutTx.addInput(p2SHMultiSigOutput);
|
||||||
if (buyerPayoutAmount.isPositive()) {
|
if (buyerPayoutAmount.isPositive()) {
|
||||||
payoutTx.addOutput(buyerPayoutAmount, LegacyAddress.fromBase58(params, buyerAddressString));
|
payoutTx.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString));
|
||||||
}
|
}
|
||||||
if (sellerPayoutAmount.isPositive()) {
|
if (sellerPayoutAmount.isPositive()) {
|
||||||
payoutTx.addOutput(sellerPayoutAmount, LegacyAddress.fromBase58(params, sellerAddressString));
|
payoutTx.addOutput(sellerPayoutAmount, Address.fromString(params, sellerAddressString));
|
||||||
}
|
}
|
||||||
|
|
||||||
// take care of sorting!
|
// take care of sorting!
|
||||||
@ -1001,10 +1012,10 @@ public class TradeWalletService {
|
|||||||
payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput));
|
payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput));
|
||||||
|
|
||||||
if (buyerPayoutAmount.isPositive()) {
|
if (buyerPayoutAmount.isPositive()) {
|
||||||
payoutTx.addOutput(buyerPayoutAmount, LegacyAddress.fromBase58(params, buyerAddressString));
|
payoutTx.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString));
|
||||||
}
|
}
|
||||||
if (sellerPayoutAmount.isPositive()) {
|
if (sellerPayoutAmount.isPositive()) {
|
||||||
payoutTx.addOutput(sellerPayoutAmount, LegacyAddress.fromBase58(params, sellerAddressString));
|
payoutTx.addOutput(sellerPayoutAmount, Address.fromString(params, sellerAddressString));
|
||||||
}
|
}
|
||||||
|
|
||||||
// take care of sorting!
|
// take care of sorting!
|
||||||
@ -1082,7 +1093,7 @@ public class TradeWalletService {
|
|||||||
checkNotNull(input.getValue(), "input.getValue() must not be null");
|
checkNotNull(input.getValue(), "input.getValue() must not be null");
|
||||||
|
|
||||||
return new RawTransactionInput(input.getOutpoint().getIndex(),
|
return new RawTransactionInput(input.getOutpoint().getIndex(),
|
||||||
input.getConnectedOutput().getParentTransaction().bitcoinSerialize(),
|
input.getConnectedOutput().getParentTransaction().bitcoinSerialize(false),
|
||||||
input.getValue().value);
|
input.getValue().value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1146,10 +1157,10 @@ public class TradeWalletService {
|
|||||||
Transaction transaction = new Transaction(params);
|
Transaction transaction = new Transaction(params);
|
||||||
transaction.addInput(p2SHMultiSigOutput);
|
transaction.addInput(p2SHMultiSigOutput);
|
||||||
if (buyerPayoutAmount.isPositive()) {
|
if (buyerPayoutAmount.isPositive()) {
|
||||||
transaction.addOutput(buyerPayoutAmount, LegacyAddress.fromBase58(params, buyerAddressString));
|
transaction.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString));
|
||||||
}
|
}
|
||||||
if (sellerPayoutAmount.isPositive()) {
|
if (sellerPayoutAmount.isPositive()) {
|
||||||
transaction.addOutput(sellerPayoutAmount, LegacyAddress.fromBase58(params, sellerAddressString));
|
transaction.addOutput(sellerPayoutAmount, Address.fromString(params, sellerAddressString));
|
||||||
}
|
}
|
||||||
checkArgument(transaction.getOutputs().size() >= 1, "We need at least one output.");
|
checkArgument(transaction.getOutputs().size() >= 1, "We need at least one output.");
|
||||||
return transaction;
|
return transaction;
|
||||||
@ -1165,6 +1176,8 @@ public class TradeWalletService {
|
|||||||
if (sigKey.isEncrypted()) {
|
if (sigKey.isEncrypted()) {
|
||||||
checkNotNull(aesKey);
|
checkNotNull(aesKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ScriptPattern.isP2PK(scriptPubKey) || ScriptPattern.isP2PKH(scriptPubKey)) {
|
||||||
Sha256Hash hash = transaction.hashForSignature(inputIndex, scriptPubKey, Transaction.SigHash.ALL, false);
|
Sha256Hash hash = transaction.hashForSignature(inputIndex, scriptPubKey, Transaction.SigHash.ALL, false);
|
||||||
ECKey.ECDSASignature signature = sigKey.sign(hash, aesKey);
|
ECKey.ECDSASignature signature = sigKey.sign(hash, aesKey);
|
||||||
TransactionSignature txSig = new TransactionSignature(signature, Transaction.SigHash.ALL, false);
|
TransactionSignature txSig = new TransactionSignature(signature, Transaction.SigHash.ALL, false);
|
||||||
@ -1172,6 +1185,18 @@ public class TradeWalletService {
|
|||||||
input.setScriptSig(ScriptBuilder.createInputScript(txSig));
|
input.setScriptSig(ScriptBuilder.createInputScript(txSig));
|
||||||
} else if (ScriptPattern.isP2PKH(scriptPubKey)) {
|
} else if (ScriptPattern.isP2PKH(scriptPubKey)) {
|
||||||
input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey));
|
input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey));
|
||||||
|
}
|
||||||
|
} else if (ScriptPattern.isP2WPKH(scriptPubKey)) {
|
||||||
|
// TODO: Consider using this alternative way to build the scriptCode (taken from bitcoinj master)
|
||||||
|
// Script scriptCode = ScriptBuilder.createP2PKHOutputScript(sigKey)
|
||||||
|
Script scriptCode = new ScriptBuilder().data(
|
||||||
|
ScriptBuilder.createOutputScript(LegacyAddress.fromKey(transaction.getParams(), sigKey)).getProgram())
|
||||||
|
.build();
|
||||||
|
Coin value = input.getValue();
|
||||||
|
TransactionSignature txSig = transaction.calculateWitnessSignature(inputIndex, sigKey, scriptCode, value,
|
||||||
|
Transaction.SigHash.ALL, false);
|
||||||
|
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||||
|
input.setWitness(TransactionWitness.redeemP2WPKH(txSig, sigKey));
|
||||||
} else {
|
} else {
|
||||||
throw new SigningException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey);
|
throw new SigningException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey);
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,14 @@ import org.bitcoinj.core.Coin;
|
|||||||
import org.bitcoinj.core.Context;
|
import org.bitcoinj.core.Context;
|
||||||
import org.bitcoinj.core.ECKey;
|
import org.bitcoinj.core.ECKey;
|
||||||
import org.bitcoinj.core.InsufficientMoneyException;
|
import org.bitcoinj.core.InsufficientMoneyException;
|
||||||
|
import org.bitcoinj.core.LegacyAddress;
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
import org.bitcoinj.core.Sha256Hash;
|
import org.bitcoinj.core.Sha256Hash;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
import org.bitcoinj.core.TransactionConfidence;
|
import org.bitcoinj.core.TransactionConfidence;
|
||||||
import org.bitcoinj.core.TransactionInput;
|
import org.bitcoinj.core.TransactionInput;
|
||||||
import org.bitcoinj.core.TransactionOutput;
|
import org.bitcoinj.core.TransactionOutput;
|
||||||
|
import org.bitcoinj.core.TransactionWitness;
|
||||||
import org.bitcoinj.core.VerificationException;
|
import org.bitcoinj.core.VerificationException;
|
||||||
import org.bitcoinj.core.listeners.NewBestBlockListener;
|
import org.bitcoinj.core.listeners.NewBestBlockListener;
|
||||||
import org.bitcoinj.core.listeners.TransactionConfidenceEventListener;
|
import org.bitcoinj.core.listeners.TransactionConfidenceEventListener;
|
||||||
@ -51,6 +53,7 @@ import org.bitcoinj.crypto.KeyCrypter;
|
|||||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||||
import org.bitcoinj.crypto.TransactionSignature;
|
import org.bitcoinj.crypto.TransactionSignature;
|
||||||
import org.bitcoinj.script.Script;
|
import org.bitcoinj.script.Script;
|
||||||
|
import org.bitcoinj.script.ScriptBuilder;
|
||||||
import org.bitcoinj.script.ScriptChunk;
|
import org.bitcoinj.script.ScriptChunk;
|
||||||
import org.bitcoinj.script.ScriptException;
|
import org.bitcoinj.script.ScriptException;
|
||||||
import org.bitcoinj.script.ScriptPattern;
|
import org.bitcoinj.script.ScriptPattern;
|
||||||
@ -241,7 +244,7 @@ public abstract class WalletService {
|
|||||||
int inputIndex) throws TransactionVerificationException {
|
int inputIndex) throws TransactionVerificationException {
|
||||||
try {
|
try {
|
||||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||||
input.getScriptSig().correctlySpends(transaction, inputIndex, input.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
input.getScriptSig().correctlySpends(transaction, inputIndex, input.getWitness(), input.getValue(), input.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
log.error(t.getMessage());
|
log.error(t.getMessage());
|
||||||
@ -265,7 +268,7 @@ public abstract class WalletService {
|
|||||||
// We assume if it's already signed, it's hopefully got a SIGHASH type that will not invalidate when
|
// We assume if it's already signed, it's hopefully got a SIGHASH type that will not invalidate when
|
||||||
// we sign missing pieces (to check this would require either assuming any signatures are signing
|
// we sign missing pieces (to check this would require either assuming any signatures are signing
|
||||||
// standard output types or a way to get processed signatures out of script execution)
|
// standard output types or a way to get processed signatures out of script execution)
|
||||||
txIn.getScriptSig().correctlySpends(tx, index, txIn.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
txIn.getScriptSig().correctlySpends(tx, index, txIn.getWitness(), txIn.getValue(), txIn.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||||
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", index);
|
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", index);
|
||||||
return;
|
return;
|
||||||
} catch (ScriptException e) {
|
} catch (ScriptException e) {
|
||||||
@ -288,7 +291,7 @@ public abstract class WalletService {
|
|||||||
// We assume if it's already signed, it's hopefully got a SIGHASH type that will not invalidate when
|
// We assume if it's already signed, it's hopefully got a SIGHASH type that will not invalidate when
|
||||||
// we sign missing pieces (to check this would require either assuming any signatures are signing
|
// we sign missing pieces (to check this would require either assuming any signatures are signing
|
||||||
// standard output types or a way to get processed signatures out of script execution)
|
// standard output types or a way to get processed signatures out of script execution)
|
||||||
txIn.getScriptSig().correctlySpends(tx, index, txIn.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
txIn.getScriptSig().correctlySpends(tx, index, txIn.getWitness(), txIn.getValue(), txIn.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
|
||||||
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", index);
|
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", index);
|
||||||
return;
|
return;
|
||||||
} catch (ScriptException e) {
|
} catch (ScriptException e) {
|
||||||
@ -312,6 +315,8 @@ public abstract class WalletService {
|
|||||||
|
|
||||||
Script inputScript = txIn.getScriptSig();
|
Script inputScript = txIn.getScriptSig();
|
||||||
byte[] script = redeemData.redeemScript.getProgram();
|
byte[] script = redeemData.redeemScript.getProgram();
|
||||||
|
|
||||||
|
if (ScriptPattern.isP2PK(scriptPubKey) || ScriptPattern.isP2PKH(scriptPubKey)) {
|
||||||
try {
|
try {
|
||||||
TransactionSignature signature = partialTx.calculateSignature(index, key, script, Transaction.SigHash.ALL, false);
|
TransactionSignature signature = partialTx.calculateSignature(index, key, script, Transaction.SigHash.ALL, false);
|
||||||
inputScript = scriptPubKey.getScriptSigWithSignature(inputScript, signature.encodeToBitcoin(), 0);
|
inputScript = scriptPubKey.getScriptSigWithSignature(inputScript, signature.encodeToBitcoin(), 0);
|
||||||
@ -321,6 +326,27 @@ public abstract class WalletService {
|
|||||||
} catch (ECKey.MissingPrivateKeyException e1) {
|
} catch (ECKey.MissingPrivateKeyException e1) {
|
||||||
log.warn("No private key in keypair for input {}", index);
|
log.warn("No private key in keypair for input {}", index);
|
||||||
}
|
}
|
||||||
|
} else if (ScriptPattern.isP2WPKH(scriptPubKey)) {
|
||||||
|
try {
|
||||||
|
// TODO: Consider using this alternative way to build the scriptCode (taken from bitcoinj master)
|
||||||
|
// Script scriptCode = ScriptBuilder.createP2PKHOutputScript(key);
|
||||||
|
Script scriptCode = new ScriptBuilder().data(
|
||||||
|
ScriptBuilder.createOutputScript(LegacyAddress.fromKey(tx.getParams(), key)).getProgram())
|
||||||
|
.build();
|
||||||
|
Coin value = txIn.getValue();
|
||||||
|
TransactionSignature txSig = tx.calculateWitnessSignature(index, key, scriptCode, value,
|
||||||
|
Transaction.SigHash.ALL, false);
|
||||||
|
txIn.setScriptSig(ScriptBuilder.createEmpty());
|
||||||
|
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key));
|
||||||
|
} catch (ECKey.KeyIsEncryptedException e1) {
|
||||||
|
throw e1;
|
||||||
|
} catch (ECKey.MissingPrivateKeyException e1) {
|
||||||
|
log.warn("No private key in keypair for input {}", index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// log.error("Unexpected script type.");
|
||||||
|
throw new RuntimeException("Unexpected script type.");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn("Missing connected output, assuming input {} is already signed.", index);
|
log.warn("Missing connected output, assuming input {} is already signed.", index);
|
||||||
}
|
}
|
||||||
@ -585,14 +611,9 @@ public abstract class WalletService {
|
|||||||
return wallet.checkAESKey(aesKey);
|
return wallet.checkAESKey(aesKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public DeterministicKey findKeyFromPubKeyHash(byte[] pubKeyHash) {
|
|
||||||
return wallet.getActiveKeyChain().findKeyFromPubHash(pubKeyHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public DeterministicKey findKeyFromPubKey(byte[] pubKey) {
|
public DeterministicKey findKeyFromPubKey(byte[] pubKey) {
|
||||||
return wallet.getActiveKeyChain().findKeyFromPubKey(pubKey);
|
return (DeterministicKey) wallet.findKeyFromPubKey(pubKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEncrypted() {
|
public boolean isEncrypted() {
|
||||||
|
@ -992,6 +992,7 @@ funds.deposit.fundWallet=Fund your wallet
|
|||||||
funds.deposit.withdrawFromWallet=Send funds from wallet
|
funds.deposit.withdrawFromWallet=Send funds from wallet
|
||||||
funds.deposit.amount=Amount in BTC (optional)
|
funds.deposit.amount=Amount in BTC (optional)
|
||||||
funds.deposit.generateAddress=Generate new address
|
funds.deposit.generateAddress=Generate new address
|
||||||
|
funds.deposit.generateAddressSegwit=Native segwit format (Bech32)
|
||||||
funds.deposit.selectUnused=Please select an unused address from the table above rather than generating a new one.
|
funds.deposit.selectUnused=Please select an unused address from the table above rather than generating a new one.
|
||||||
|
|
||||||
funds.withdrawal.arbitrationFee=Arbitration fee
|
funds.withdrawal.arbitrationFee=Arbitration fee
|
||||||
|
@ -41,8 +41,12 @@ import bisq.core.util.coin.CoinFormatter;
|
|||||||
|
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.app.DevEnv;
|
import bisq.common.app.DevEnv;
|
||||||
|
import bisq.common.config.Config;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
|
import org.bitcoinj.core.SegwitAddress;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
|
|
||||||
import net.glxn.qrgen.QRCode;
|
import net.glxn.qrgen.QRCode;
|
||||||
@ -54,6 +58,7 @@ import javax.inject.Named;
|
|||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.TableCell;
|
import javafx.scene.control.TableCell;
|
||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
@ -85,10 +90,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import static bisq.desktop.util.FormBuilder.addAddressTextField;
|
import static bisq.desktop.util.FormBuilder.*;
|
||||||
import static bisq.desktop.util.FormBuilder.addButton;
|
|
||||||
import static bisq.desktop.util.FormBuilder.addInputTextField;
|
|
||||||
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
|
|
||||||
|
|
||||||
@FxmlView
|
@FxmlView
|
||||||
public class DepositView extends ActivatableView<VBox, Void> {
|
public class DepositView extends ActivatableView<VBox, Void> {
|
||||||
@ -102,6 +104,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
|||||||
private ImageView qrCodeImageView;
|
private ImageView qrCodeImageView;
|
||||||
private AddressTextField addressTextField;
|
private AddressTextField addressTextField;
|
||||||
private Button generateNewAddressButton;
|
private Button generateNewAddressButton;
|
||||||
|
private CheckBox generateNewAddressSegwitCheckbox;
|
||||||
private TitledGroupBg titledGroupBg;
|
private TitledGroupBg titledGroupBg;
|
||||||
private InputTextField amountTextField;
|
private InputTextField amountTextField;
|
||||||
|
|
||||||
@ -195,16 +198,26 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
|||||||
addressTextField.setManaged(false);
|
addressTextField.setManaged(false);
|
||||||
amountTextField.setManaged(false);
|
amountTextField.setManaged(false);
|
||||||
|
|
||||||
|
generateNewAddressSegwitCheckbox = addCheckBox(gridPane, ++gridRow,
|
||||||
|
Res.get("funds.deposit.generateAddressSegwit"), -20);
|
||||||
|
generateNewAddressSegwitCheckbox.setAllowIndeterminate(false);
|
||||||
|
generateNewAddressSegwitCheckbox.setSelected(true);
|
||||||
|
GridPane.setColumnIndex(generateNewAddressSegwitCheckbox, 0);
|
||||||
|
GridPane.setHalignment(generateNewAddressSegwitCheckbox, HPos.LEFT);
|
||||||
|
|
||||||
generateNewAddressButton = addButton(gridPane, ++gridRow, Res.get("funds.deposit.generateAddress"), -20);
|
generateNewAddressButton = addButton(gridPane, ++gridRow, Res.get("funds.deposit.generateAddress"), -20);
|
||||||
GridPane.setColumnIndex(generateNewAddressButton, 0);
|
GridPane.setColumnIndex(generateNewAddressButton, 0);
|
||||||
GridPane.setHalignment(generateNewAddressButton, HPos.LEFT);
|
GridPane.setHalignment(generateNewAddressButton, HPos.LEFT);
|
||||||
|
|
||||||
generateNewAddressButton.setOnAction(event -> {
|
generateNewAddressButton.setOnAction(event -> {
|
||||||
boolean hasUnUsedAddress = observableList.stream().anyMatch(e -> e.getNumTxOutputs() == 0);
|
boolean segwit = generateNewAddressSegwitCheckbox.isSelected();
|
||||||
|
NetworkParameters params = Config.baseCurrencyNetworkParameters();
|
||||||
|
boolean hasUnUsedAddress = observableList.stream().anyMatch(e -> e.getNumTxOutputs() == 0
|
||||||
|
&& (Address.fromString(params, e.getAddressString()) instanceof SegwitAddress) == segwit);
|
||||||
if (hasUnUsedAddress) {
|
if (hasUnUsedAddress) {
|
||||||
new Popup().warning(Res.get("funds.deposit.selectUnused")).show();
|
new Popup().warning(Res.get("funds.deposit.selectUnused")).show();
|
||||||
} else {
|
} else {
|
||||||
AddressEntry newSavingsAddressEntry = walletService.getFreshAddressEntry();
|
AddressEntry newSavingsAddressEntry = walletService.getFreshAddressEntry(segwit);
|
||||||
updateList();
|
updateList();
|
||||||
observableList.stream()
|
observableList.stream()
|
||||||
.filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString()))
|
.filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString()))
|
||||||
|
@ -20,7 +20,7 @@ dependencyVerification {
|
|||||||
'com.fasterxml.jackson.core:jackson-core:39a74610521d7fb9eb3f437bb8739bbf47f6435be12d17bf954c731a0c6352bb',
|
'com.fasterxml.jackson.core:jackson-core:39a74610521d7fb9eb3f437bb8739bbf47f6435be12d17bf954c731a0c6352bb',
|
||||||
'com.fasterxml.jackson.core:jackson-databind:fcf3c2b0c332f5f54604f7e27fa7ee502378a2cc5df6a944bbfae391872c32ff',
|
'com.fasterxml.jackson.core:jackson-databind:fcf3c2b0c332f5f54604f7e27fa7ee502378a2cc5df6a944bbfae391872c32ff',
|
||||||
'com.github.JesusMcCloud:jtorctl:389d61b1b5a85eb2f23c582c3913ede49f80c9f2b553e4762382c836270e57e5',
|
'com.github.JesusMcCloud:jtorctl:389d61b1b5a85eb2f23c582c3913ede49f80c9f2b553e4762382c836270e57e5',
|
||||||
'com.github.bisq-network:bitcoinj:85d609e9bbaa93de0a9ca1ab436f578c14f7cfa1876b50878046d9f624b48a6b',
|
'com.github.bisq-network:bitcoinj:b8b6e4b8010f2b8d4aac7141c0809dea6d102c3ff3c06ceba78c2626d531b0af',
|
||||||
'com.github.cd2357.netlayer:tor.external:7c70846d36465279c2664f147a0f2d47202c5d67c6a2075225194779c3fbe122',
|
'com.github.cd2357.netlayer:tor.external:7c70846d36465279c2664f147a0f2d47202c5d67c6a2075225194779c3fbe122',
|
||||||
'com.github.cd2357.netlayer:tor.native:84b449191d535a3c2187f7f7f3bb9bcb7d1097f07c6bf8c4f2b3331c20107d9a',
|
'com.github.cd2357.netlayer:tor.native:84b449191d535a3c2187f7f7f3bb9bcb7d1097f07c6bf8c4f2b3331c20107d9a',
|
||||||
'com.github.cd2357.netlayer:tor:ff92e4a7b59d1b480e0427fcfcf3f82a6fd69be68eec91c6360774d599e3c2e0',
|
'com.github.cd2357.netlayer:tor:ff92e4a7b59d1b480e0427fcfcf3f82a6fd69be68eec91c6360774d599e3c2e0',
|
||||||
|
@ -1239,6 +1239,7 @@ message AddressEntry {
|
|||||||
bytes pub_key = 9;
|
bytes pub_key = 9;
|
||||||
bytes pub_key_hash = 10;
|
bytes pub_key_hash = 10;
|
||||||
int64 coin_locked_in_multi_sig = 11;
|
int64 coin_locked_in_multi_sig = 11;
|
||||||
|
bool segwit = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message NavigationPath {
|
message NavigationPath {
|
||||||
|
Loading…
Reference in New Issue
Block a user