From 347042974669b0f8728df5270e0c566afc1236cb Mon Sep 17 00:00:00 2001 From: Steven Barclay Date: Thu, 21 Jan 2021 23:57:57 +0000 Subject: [PATCH] Allow use of bech32 BSQ addresses Remove the restriction to base58 (P2SH & P2PKH) addresses when parsing, formatting & validating BSQ addresses, by replacing o.b.c.LegacyAddress with its superclass o.b.c.Address throughout. Also remove restriction to LegacyAddress in BsqTxListItem and BsqTransfer(Service|Model). The bech32 BSQ addresses follow the same format as the old base58 BSQ addresses, namely 'B' + . --- .../apitest/method/wallet/BsqWalletTest.java | 4 ++-- assets/src/main/java/bisq/asset/coins/BSQ.java | 6 +++--- .../core/api/CorePaymentAccountsService.java | 2 +- .../java/bisq/core/api/CoreWalletsService.java | 17 ++++++++--------- .../bisq/core/btc/model/BsqTransferModel.java | 6 +++--- .../core/btc/wallet/BsqTransferService.java | 4 ++-- .../bisq/core/btc/wallet/BsqWalletService.java | 17 ++++++----------- .../governance/proposal/IssuanceProposal.java | 12 +++++++++++- .../model/governance/CompensationProposal.java | 11 +---------- .../model/governance/ReimbursementProposal.java | 11 +---------- .../java/bisq/core/util/coin/BsqFormatter.java | 10 +++++----- .../main/dao/wallet/tx/BsqTxListItem.java | 11 ++++------- .../util/validation/BsqAddressValidator.java | 2 +- 13 files changed, 48 insertions(+), 65 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java b/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java index 6be9dd66543..6c5ce8ec22a 100644 --- a/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java +++ b/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java @@ -2,7 +2,7 @@ import bisq.proto.grpc.BsqBalanceInfo; -import org.bitcoinj.core.LegacyAddress; +import org.bitcoinj.core.Address; import org.bitcoinj.core.NetworkParameters; import lombok.extern.slf4j.Slf4j; @@ -64,7 +64,7 @@ public void testGetUnusedBsqAddress() { assertFalse(address.isEmpty()); assertTrue(address.startsWith("B")); - NetworkParameters networkParameters = LegacyAddress.getParametersFromAddress(address.substring(1)); + NetworkParameters networkParameters = Address.fromString(null, address.substring(1)).getParameters(); String addressNetwork = networkParameters.getPaymentProtocolId(); assertNotEquals(PAYMENT_PROTOCOL_ID_MAINNET, addressNetwork); // TODO Fix bug causing the regtest bsq address network to be evaluated as 'testnet' here. diff --git a/assets/src/main/java/bisq/asset/coins/BSQ.java b/assets/src/main/java/bisq/asset/coins/BSQ.java index f769a582e38..4f9b6d7a3b6 100644 --- a/assets/src/main/java/bisq/asset/coins/BSQ.java +++ b/assets/src/main/java/bisq/asset/coins/BSQ.java @@ -18,7 +18,7 @@ package bisq.asset.coins; import bisq.asset.AddressValidationResult; -import bisq.asset.Base58AddressValidator; +import bisq.asset.BitcoinAddressValidator; import bisq.asset.Coin; import org.bitcoinj.core.NetworkParameters; @@ -57,7 +57,7 @@ public Regtest() { } - public static class BSQAddressValidator extends Base58AddressValidator { + public static class BSQAddressValidator extends BitcoinAddressValidator { public BSQAddressValidator(NetworkParameters networkParameters) { super(networkParameters); @@ -68,7 +68,7 @@ public AddressValidationResult validate(String address) { if (!address.startsWith("B")) return AddressValidationResult.invalidAddress("BSQ address must start with 'B'"); - String addressAsBtc = address.substring(1, address.length()); + String addressAsBtc = address.substring(1); return super.validate(addressAsBtc); } diff --git a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java index 0843e20ab76..220205fbee3 100644 --- a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java +++ b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java @@ -108,7 +108,7 @@ PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts"); // Validate the BSQ address string but ignore the return value. - coreWalletsService.getValidBsqLegacyAddress(address); + coreWalletsService.getValidBsqAddress(address); var cryptoCurrencyAccount = tradeInstant ? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT) diff --git a/core/src/main/java/bisq/core/api/CoreWalletsService.java b/core/src/main/java/bisq/core/api/CoreWalletsService.java index 4af8c3dd12d..78d71ccb082 100644 --- a/core/src/main/java/bisq/core/api/CoreWalletsService.java +++ b/core/src/main/java/bisq/core/api/CoreWalletsService.java @@ -51,7 +51,6 @@ import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.InsufficientMoneyException; -import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionConfidence; @@ -226,7 +225,7 @@ String getUnusedBsqAddress() { return bsqWalletService.getUnusedBsqAddressAsString(); } - void sendBsq(String address, + void sendBsq(String addressStr, String amount, String txFeeRate, TxBroadcaster.Callback callback) { @@ -234,10 +233,10 @@ void sendBsq(String address, verifyEncryptedWalletIsUnlocked(); try { - LegacyAddress legacyAddress = getValidBsqLegacyAddress(address); + Address address = getValidBsqAddress(addressStr); Coin receiverAmount = getValidTransferAmount(amount, bsqFormatter); Coin txFeePerVbyte = getTxFeeRateFromParamOrPreferenceOrFeeService(txFeeRate); - BsqTransferModel model = bsqTransferService.getBsqTransferModel(legacyAddress, + BsqTransferModel model = bsqTransferService.getBsqTransferModel(address, receiverAmount, txFeePerVbyte); log.info("Sending {} BSQ to {} with tx fee rate {} sats/byte.", @@ -313,7 +312,7 @@ void sendBtc(String address, } boolean verifyBsqSentToAddress(String address, String amount) { - Address receiverAddress = getValidBsqLegacyAddress(address); + Address receiverAddress = getValidBsqAddress(address); NetworkParameters networkParameters = getNetworkParameters(); Predicate isTxOutputAddressMatch = (txOut) -> txOut.getScriptPubKey().getToAddress(networkParameters).equals(receiverAddress); @@ -553,12 +552,12 @@ void verifyApplicationIsFullyInitialized() { throw new IllegalStateException("server is not fully initialized"); } - // Returns a LegacyAddress for the string, or a RuntimeException if invalid. - LegacyAddress getValidBsqLegacyAddress(String address) { + // Returns an Address for the string, or a RuntimeException if invalid. + Address getValidBsqAddress(String address) { try { return bsqFormatter.getAddressFromBsqAddress(address); - } catch (Throwable t) { - log.error("", t); + } catch (RuntimeException e) { + log.error("", e); throw new IllegalStateException(format("%s is not a valid bsq address", address)); } } diff --git a/core/src/main/java/bisq/core/btc/model/BsqTransferModel.java b/core/src/main/java/bisq/core/btc/model/BsqTransferModel.java index 4ee77f24aff..3b31991ef75 100644 --- a/core/src/main/java/bisq/core/btc/model/BsqTransferModel.java +++ b/core/src/main/java/bisq/core/btc/model/BsqTransferModel.java @@ -2,8 +2,8 @@ import bisq.core.dao.state.model.blockchain.TxType; +import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.Transaction; import lombok.Getter; @@ -11,7 +11,7 @@ @Getter public final class BsqTransferModel { - private final LegacyAddress receiverAddress; + private final Address receiverAddress; private final Coin receiverAmount; private final Transaction preparedSendTx; private final Transaction txWithBtcFee; @@ -20,7 +20,7 @@ public final class BsqTransferModel { private final int txSize; private final TxType txType; - public BsqTransferModel(LegacyAddress receiverAddress, + public BsqTransferModel(Address receiverAddress, Coin receiverAmount, Transaction preparedSendTx, Transaction txWithBtcFee, diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java b/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java index 4558b0acf74..5639004c70f 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BsqTransferService.java @@ -5,9 +5,9 @@ import bisq.core.btc.exceptions.WalletException; import bisq.core.btc.model.BsqTransferModel; +import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.InsufficientMoneyException; -import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.Transaction; import javax.inject.Inject; @@ -32,7 +32,7 @@ public BsqTransferService(WalletsManager walletsManager, this.btcWalletService = btcWalletService; } - public BsqTransferModel getBsqTransferModel(LegacyAddress address, + public BsqTransferModel getBsqTransferModel(Address address, Coin receiverAmount, Coin txFeePerVbyte) throws TransactionVerificationException, diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java index e8e1afa98cc..945ecd08ed1 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java @@ -527,7 +527,7 @@ public void commitTx(Transaction tx, TxType txType) { public Transaction getPreparedSendBsqTx(String receiverAddress, Coin receiverAmount) throws AddressFormatException, InsufficientBsqException, WalletException, TransactionVerificationException, BsqChangeBelowDustException { - return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector, false); + return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector); } public Transaction getPreparedSendBsqTx(String receiverAddress, @@ -538,7 +538,7 @@ public Transaction getPreparedSendBsqTx(String receiverAddress, if (utxoCandidates != null) { bsqCoinSelector.setUtxoCandidates(utxoCandidates); } - return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector, false); + return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -548,7 +548,7 @@ public Transaction getPreparedSendBsqTx(String receiverAddress, public Transaction getPreparedSendBtcTx(String receiverAddress, Coin receiverAmount) throws AddressFormatException, InsufficientBsqException, WalletException, TransactionVerificationException, BsqChangeBelowDustException { - return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector, true); + return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector); } public Transaction getPreparedSendBtcTx(String receiverAddress, @@ -559,21 +559,16 @@ public Transaction getPreparedSendBtcTx(String receiverAddress, if (utxoCandidates != null) { nonBsqCoinSelector.setUtxoCandidates(utxoCandidates); } - return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector, true); + return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector); } - private Transaction getPreparedSendTx(String receiverAddress, Coin receiverAmount, CoinSelector coinSelector, - boolean allowSegwitOuput) + private Transaction getPreparedSendTx(String receiverAddress, Coin receiverAmount, CoinSelector coinSelector) throws AddressFormatException, InsufficientBsqException, WalletException, TransactionVerificationException, BsqChangeBelowDustException { daoKillSwitch.assertDaoIsNotDisabled(); Transaction tx = new Transaction(params); checkArgument(Restrictions.isAboveDust(receiverAmount), "The amount is too low (dust limit)."); - if (allowSegwitOuput) { - tx.addOutput(receiverAmount, Address.fromString(params, receiverAddress)); - } else { - tx.addOutput(receiverAmount, LegacyAddress.fromBase58(params, receiverAddress)); - } + tx.addOutput(receiverAmount, Address.fromString(params, receiverAddress)); SendRequest sendRequest = SendRequest.forTx(tx); sendRequest.fee = Coin.ZERO; sendRequest.feePerKb = Coin.ZERO; diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/IssuanceProposal.java b/core/src/main/java/bisq/core/dao/governance/proposal/IssuanceProposal.java index a466b999651..926ce6d48fd 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/IssuanceProposal.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/IssuanceProposal.java @@ -17,10 +17,14 @@ package bisq.core.dao.governance.proposal; +import bisq.common.config.Config; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; /** - * Marker interface for proposals which can lead to new BSQ issuance + * Interface for proposals which can lead to new BSQ issuance */ public interface IssuanceProposal { Coin getRequestedBsq(); @@ -28,4 +32,10 @@ public interface IssuanceProposal { String getBsqAddress(); String getTxId(); + + default Address getAddress() throws AddressFormatException { + // Remove leading 'B' + String underlyingBtcAddress = getBsqAddress().substring(1); + return Address.fromString(Config.baseCurrencyNetworkParameters(), underlyingBtcAddress); + } } diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/CompensationProposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/CompensationProposal.java index d23249b6c4f..4ed48d694b9 100644 --- a/core/src/main/java/bisq/core/dao/state/model/governance/CompensationProposal.java +++ b/core/src/main/java/bisq/core/dao/state/model/governance/CompensationProposal.java @@ -24,12 +24,9 @@ import bisq.core.dao.state.model.blockchain.TxType; import bisq.common.app.Version; -import bisq.common.config.Config; import bisq.common.util.CollectionUtils; -import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.LegacyAddress; import java.util.Date; import java.util.Map; @@ -113,17 +110,11 @@ public static CompensationProposal fromProto(protobuf.Proposal proto) { // Getters /////////////////////////////////////////////////////////////////////////////////////////// + @Override public Coin getRequestedBsq() { return Coin.valueOf(requestedBsq); } - public LegacyAddress getAddress() throws AddressFormatException { - // Remove leading 'B' - String underlyingBtcAddress = bsqAddress.substring(1, bsqAddress.length()); - return LegacyAddress.fromBase58(Config.baseCurrencyNetworkParameters(), underlyingBtcAddress); - } - - @Override public ProposalType getType() { return ProposalType.COMPENSATION_REQUEST; diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/ReimbursementProposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/ReimbursementProposal.java index fb83ab23d51..4e7d63f5c8d 100644 --- a/core/src/main/java/bisq/core/dao/state/model/governance/ReimbursementProposal.java +++ b/core/src/main/java/bisq/core/dao/state/model/governance/ReimbursementProposal.java @@ -24,12 +24,9 @@ import bisq.core.dao.state.model.blockchain.TxType; import bisq.common.app.Version; -import bisq.common.config.Config; import bisq.common.util.CollectionUtils; -import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.LegacyAddress; import java.util.Date; import java.util.Map; @@ -113,17 +110,11 @@ public static ReimbursementProposal fromProto(protobuf.Proposal proto) { // Getters /////////////////////////////////////////////////////////////////////////////////////////// + @Override public Coin getRequestedBsq() { return Coin.valueOf(requestedBsq); } - public LegacyAddress getAddress() throws AddressFormatException { - // Remove leading 'B' - String underlyingBtcAddress = bsqAddress.substring(1, bsqAddress.length()); - return LegacyAddress.fromBase58(Config.baseCurrencyNetworkParameters(), underlyingBtcAddress); - } - - @Override public ProposalType getType() { return ProposalType.REIMBURSEMENT_REQUEST; diff --git a/core/src/main/java/bisq/core/util/coin/BsqFormatter.java b/core/src/main/java/bisq/core/util/coin/BsqFormatter.java index ab476097bed..cd0accdb972 100644 --- a/core/src/main/java/bisq/core/util/coin/BsqFormatter.java +++ b/core/src/main/java/bisq/core/util/coin/BsqFormatter.java @@ -31,9 +31,9 @@ import bisq.common.config.Config; import bisq.common.util.MathUtils; +import org.bitcoinj.core.Address; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.utils.MonetaryFormat; import javax.inject.Inject; @@ -94,7 +94,7 @@ private void switchLocale(Locale locale) { * Returns the base-58 encoded String representation of this * object, including version and checksum bytes. */ - public String getBsqAddressStringFromAddress(LegacyAddress address) { + public String getBsqAddressStringFromAddress(Address address) { final String addressString = address.toString(); if (useBsqAddressFormat) return prefix + addressString; @@ -103,13 +103,13 @@ public String getBsqAddressStringFromAddress(LegacyAddress address) { } - public LegacyAddress getAddressFromBsqAddress(String encoded) { + public Address getAddressFromBsqAddress(String encoded) { String maybeUpdatedEncoded = encoded; if (useBsqAddressFormat) - maybeUpdatedEncoded = encoded.substring(prefix.length(), encoded.length()); + maybeUpdatedEncoded = encoded.substring(prefix.length()); try { - return LegacyAddress.fromBase58(Config.baseCurrencyNetworkParameters(), maybeUpdatedEncoded); + return Address.fromString(Config.baseCurrencyNetworkParameters(), maybeUpdatedEncoded); } catch (AddressFormatException e) { throw new RuntimeException(e); } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxListItem.java index 43d0b2a9b4a..a3d2bdd7834 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxListItem.java @@ -30,7 +30,6 @@ import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; @@ -103,10 +102,9 @@ class BsqTxListItem extends TxConfidenceListItem { WalletService.isOutputScriptConvertibleToAddress(output)) { // We don't support send txs with multiple outputs to multiple receivers, so we can // assume that only one output is not from our own wallets. - // We ignore segwit outputs Address addressFromOutput = WalletService.getAddressFromOutput(output); - if (addressFromOutput instanceof LegacyAddress) { - sendToAddress = bsqFormatter.getBsqAddressStringFromAddress((LegacyAddress) addressFromOutput); + if (addressFromOutput != null) { + sendToAddress = bsqFormatter.getBsqAddressStringFromAddress(addressFromOutput); break; } } @@ -118,9 +116,8 @@ class BsqTxListItem extends TxConfidenceListItem { for (TransactionOutput output : transaction.getOutputs()) { if (WalletService.isOutputScriptConvertibleToAddress(output)) { Address addressFromOutput = WalletService.getAddressFromOutput(output); - // We ignore segwit outputs - if (addressFromOutput instanceof LegacyAddress) { - receivedWithAddress = bsqFormatter.getBsqAddressStringFromAddress((LegacyAddress) addressFromOutput); + if (addressFromOutput != null) { + receivedWithAddress = bsqFormatter.getBsqAddressStringFromAddress(addressFromOutput); break; } } diff --git a/desktop/src/main/java/bisq/desktop/util/validation/BsqAddressValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/BsqAddressValidator.java index c4f441b5f0d..b8069684026 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/BsqAddressValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/BsqAddressValidator.java @@ -46,7 +46,7 @@ private ValidationResult validateBsqAddress(String input) { try { bsqFormatter.getAddressFromBsqAddress(input); return new ValidationResult(true); - } catch (Throwable e) { + } catch (RuntimeException e) { return new ValidationResult(false, Res.get("validation.bsq.invalidFormat")); } }