diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java index 498d8e7bba..a270b743c5 100644 --- a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java +++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java @@ -18,6 +18,7 @@ package bisq.core.account.sign; import bisq.core.account.witness.AccountAgeWitness; +import bisq.core.crypto.LowRSigningKey; import bisq.core.filter.FilterManager; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.user.User; @@ -280,7 +281,7 @@ public class SignedWitnessService { } String accountAgeWitnessHashAsHex = Utilities.encodeToHex(accountAgeWitness.getHash()); - String signatureBase64 = key.signMessage(accountAgeWitnessHashAsHex); + String signatureBase64 = LowRSigningKey.from(key).signMessage(accountAgeWitnessHashAsHex); SignedWitness signedWitness = new SignedWitness(SignedWitness.VerificationMethod.ARBITRATOR, accountAgeWitness.getHash(), signatureBase64.getBytes(Charsets.UTF_8), @@ -289,7 +290,7 @@ public class SignedWitnessService { time, tradeAmount.value); publishSignedWitness(signedWitness); - log.info("Arbitrator signed witness {}", signedWitness.toString()); + log.info("Arbitrator signed witness {}", signedWitness); return ""; } @@ -322,7 +323,7 @@ public class SignedWitnessService { new Date().getTime(), tradeAmount.value); publishSignedWitness(signedWitness); - log.info("Trader signed witness {}", signedWitness.toString()); + log.info("Trader signed witness {}", signedWitness); return Optional.of(signedWitness); } @@ -537,7 +538,7 @@ public class SignedWitnessService { private void publishSignedWitness(SignedWitness signedWitness) { if (!signedWitnessMap.containsKey(signedWitness.getHashAsByteArray())) { - log.info("broadcast signed witness {}", signedWitness.toString()); + log.info("broadcast signed witness {}", signedWitness); // We set reBroadcast to true to achieve better resilience. p2PService.addPersistableNetworkPayload(signedWitness, true); addToMap(signedWitness); diff --git a/core/src/main/java/bisq/core/alert/AlertManager.java b/core/src/main/java/bisq/core/alert/AlertManager.java index efedf78787..961057c633 100644 --- a/core/src/main/java/bisq/core/alert/AlertManager.java +++ b/core/src/main/java/bisq/core/alert/AlertManager.java @@ -17,6 +17,7 @@ package bisq.core.alert; +import bisq.core.crypto.LowRSigningKey; import bisq.core.user.User; import bisq.network.p2p.P2PService; @@ -160,7 +161,7 @@ public class AlertManager { private void signAndAddSignatureToAlertMessage(Alert alert) { String alertMessageAsHex = Utils.HEX.encode(alert.getMessage().getBytes(Charsets.UTF_8)); - String signatureAsBase64 = alertSigningKey.signMessage(alertMessageAsHex); + String signatureAsBase64 = LowRSigningKey.from(alertSigningKey).signMessage(alertMessageAsHex); alert.setSigAndPubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic()); } diff --git a/core/src/main/java/bisq/core/alert/PrivateNotificationManager.java b/core/src/main/java/bisq/core/alert/PrivateNotificationManager.java index c37f5c6a9d..b8ce186bbe 100644 --- a/core/src/main/java/bisq/core/alert/PrivateNotificationManager.java +++ b/core/src/main/java/bisq/core/alert/PrivateNotificationManager.java @@ -17,6 +17,8 @@ package bisq.core.alert; +import bisq.core.crypto.LowRSigningKey; + import bisq.network.p2p.DecryptedMessageWithPubKey; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; @@ -175,7 +177,7 @@ public class PrivateNotificationManager implements MessageListener { private void signAndAddSignatureToPrivateNotificationMessage(PrivateNotificationPayload privateNotification) { String privateNotificationMessageAsHex = Utils.HEX.encode(privateNotification.getMessage().getBytes(Charsets.UTF_8)); - String signatureAsBase64 = privateNotificationSigningKey.signMessage(privateNotificationMessageAsHex); + String signatureAsBase64 = LowRSigningKey.from(privateNotificationSigningKey).signMessage(privateNotificationMessageAsHex); privateNotification.setSigAndPubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic()); } diff --git a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java index afa52f8482..c08597ee61 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java @@ -20,6 +20,7 @@ package bisq.core.btc.setup; import bisq.core.btc.nodes.LocalBitcoinNode; import bisq.core.btc.nodes.ProxySocketFactory; import bisq.core.btc.wallet.BisqRiskAnalysis; +import bisq.core.btc.wallet.BisqWallet; import bisq.common.config.Config; import bisq.common.file.FileUtil; @@ -405,7 +406,7 @@ public class WalletConfig extends AbstractIdleService { WalletExtension[] extArray = new WalletExtension[]{}; Protos.Wallet proto = WalletProtobufSerializer.parseToProto(walletStream); final WalletProtobufSerializer serializer; - serializer = new WalletProtobufSerializer(); + serializer = new WalletProtobufSerializer(BisqWallet::new); // Hack to convert bitcoinj 0.14 wallets to bitcoinj 0.15 format serializer.setKeyChainFactory(new BisqKeyChainFactory(isBsqWallet)); wallet = serializer.readWallet(params, extArray, proto); @@ -432,7 +433,7 @@ public class WalletConfig extends AbstractIdleService { kcgBuilder.fromSeed(vBtcWallet.getKeyChainSeed(), preferredOutputScriptType); } } - return new Wallet(params, kcgBuilder.build()); + return new BisqWallet(params, kcgBuilder.build()); } private void maybeMoveOldWalletOutOfTheWay(File walletFile) { diff --git a/core/src/main/java/bisq/core/btc/wallet/BisqWallet.java b/core/src/main/java/bisq/core/btc/wallet/BisqWallet.java new file mode 100644 index 0000000000..e7afb5a5c1 --- /dev/null +++ b/core/src/main/java/bisq/core/btc/wallet/BisqWallet.java @@ -0,0 +1,105 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.btc.wallet; + +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionInput; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptException; +import org.bitcoinj.signers.MissingSigResolutionSigner; +import org.bitcoinj.signers.TransactionSigner; +import org.bitcoinj.wallet.DecryptingKeyBag; +import org.bitcoinj.wallet.KeyBag; +import org.bitcoinj.wallet.KeyChainGroup; +import org.bitcoinj.wallet.RedeemData; +import org.bitcoinj.wallet.SendRequest; +import org.bitcoinj.wallet.Wallet; + +import java.util.List; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +@Slf4j +public class BisqWallet extends Wallet { + public BisqWallet(NetworkParameters params, KeyChainGroup keyChainGroup) { + super(params, keyChainGroup); + } + + // Largely copied from super, but modified to supply instances of LowRSigningKey to the tx signers, + // instead of deterministic keys, so that low-R signatures always result when using the SendRequest + // API. This likely breaks the keychain marrying functionality of bitcoinj, used for multisig inputs, + // but we don't use that anywhere in Bisq. + @Override + public void signTransaction(SendRequest req) { + lock.lock(); + try { + Transaction tx = req.tx; + List inputs = tx.getInputs(); + List outputs = tx.getOutputs(); + checkState(inputs.size() > 0); + checkState(outputs.size() > 0); + + KeyBag maybeDecryptingKeyBag = new LowRSigningKeyBag(new DecryptingKeyBag(this, req.aesKey)); + + int numInputs = tx.getInputs().size(); + for (int i = 0; i < numInputs; i++) { + TransactionInput txIn = tx.getInput(i); + TransactionOutput connectedOutput = txIn.getConnectedOutput(); + if (connectedOutput == null) { + // Missing connected output, assuming already signed. + continue; + } + Script scriptPubKey = connectedOutput.getScriptPubKey(); + + try { + // We assume if its already signed, its 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 + // standard output types or a way to get processed signatures out of script execution) + txIn.getScriptSig().correctlySpends(tx, i, txIn.getWitness(), connectedOutput.getValue(), + connectedOutput.getScriptPubKey(), Script.ALL_VERIFY_FLAGS); + log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", i); + continue; + } catch (ScriptException e) { + log.debug("Input contained an incorrect signature", e); + // Expected. + } + + RedeemData redeemData = txIn.getConnectedRedeemData(maybeDecryptingKeyBag); + checkNotNull(redeemData, "Transaction exists in wallet that we cannot redeem: %s", txIn.getOutpoint().getHash()); + txIn.setScriptSig(scriptPubKey.createEmptyInputScript(redeemData.keys.get(0), redeemData.redeemScript)); + txIn.setWitness(scriptPubKey.createEmptyWitness(redeemData.keys.get(0))); + } + + TransactionSigner.ProposedTransaction proposal = new TransactionSigner.ProposedTransaction(tx); + for (TransactionSigner signer : getTransactionSigners()) { + if (!signer.signInputs(proposal, maybeDecryptingKeyBag)) + log.info("{} returned false for the tx", signer.getClass().getName()); + } + + // resolve missing sigs if any + new MissingSigResolutionSigner(req.missingSigsMode).signInputs(proposal, maybeDecryptingKeyBag); + } finally { + lock.unlock(); + } + } +} diff --git a/core/src/main/java/bisq/core/btc/wallet/LowRSigningKeyBag.java b/core/src/main/java/bisq/core/btc/wallet/LowRSigningKeyBag.java new file mode 100644 index 0000000000..d112cc5395 --- /dev/null +++ b/core/src/main/java/bisq/core/btc/wallet/LowRSigningKeyBag.java @@ -0,0 +1,61 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.btc.wallet; + +import bisq.core.crypto.LowRSigningKey; + +import org.bitcoinj.core.ECKey; +import org.bitcoinj.script.Script; +import org.bitcoinj.wallet.KeyBag; +import org.bitcoinj.wallet.RedeemData; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +public class LowRSigningKeyBag implements KeyBag { + private final KeyBag target; + + public LowRSigningKeyBag(KeyBag target) { + this.target = target; + } + + @Nullable + @Override + public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash, @Nullable Script.ScriptType scriptType) { + return LowRSigningKey.from(target.findKeyFromPubKeyHash(pubKeyHash, scriptType)); + } + + @Nullable + @Override + public ECKey findKeyFromPubKey(byte[] pubKey) { + return LowRSigningKey.from(target.findKeyFromPubKey(pubKey)); + } + + @Nullable + @Override + public RedeemData findRedeemDataFromScriptHash(byte[] scriptHash) { + RedeemData redeemData = target.findRedeemDataFromScriptHash(scriptHash); + if (redeemData == null) { + return null; + } + List lowRKeys = redeemData.keys.stream().map(LowRSigningKey::from).collect(Collectors.toList()); + return RedeemData.of(lowRKeys, redeemData.redeemScript); + } +} diff --git a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java index 29bda0b41e..24dac384a6 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -26,6 +26,7 @@ import bisq.core.btc.model.PreparedDepositTxAndMakerInputs; import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.setup.WalletConfig; import bisq.core.btc.setup.WalletsSetup; +import bisq.core.crypto.LowRSigningKey; import bisq.core.locale.Res; import bisq.core.user.Preferences; @@ -744,7 +745,7 @@ public class TradeWalletService { checkNotNull(aesKey); } - ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised(); + ECKey.ECDSASignature mySignature = LowRSigningKey.from(myMultiSigKeyPair).sign(sigHash, aesKey); WalletService.printTx("delayedPayoutTx for sig creation", delayedPayoutTx); WalletService.verifyTransaction(delayedPayoutTx); return mySignature.encodeToDER(); @@ -841,7 +842,7 @@ public class TradeWalletService { if (multiSigKeyPair.isEncrypted()) { checkNotNull(aesKey); } - ECKey.ECDSASignature buyerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised(); + ECKey.ECDSASignature buyerSignature = LowRSigningKey.from(multiSigKeyPair).sign(sigHash, aesKey); WalletService.printTx("prepared payoutTx", preparedPayoutTx); WalletService.verifyTransaction(preparedPayoutTx); return buyerSignature.encodeToDER(); @@ -893,7 +894,7 @@ public class TradeWalletService { if (multiSigKeyPair.isEncrypted()) { checkNotNull(aesKey); } - ECKey.ECDSASignature sellerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised(); + ECKey.ECDSASignature sellerSignature = LowRSigningKey.from(multiSigKeyPair).sign(sigHash, aesKey); TransactionSignature buyerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(buyerSignature), Transaction.SigHash.ALL, false); TransactionSignature sellerTxSig = new TransactionSignature(sellerSignature, Transaction.SigHash.ALL, false); @@ -948,7 +949,7 @@ public class TradeWalletService { if (myMultiSigKeyPair.isEncrypted()) { checkNotNull(aesKey); } - ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised(); + ECKey.ECDSASignature mySignature = LowRSigningKey.from(myMultiSigKeyPair).sign(sigHash, aesKey); WalletService.printTx("prepared mediated payoutTx for sig creation", preparedPayoutTx); WalletService.verifyTransaction(preparedPayoutTx); return mySignature.encodeToDER(); @@ -1058,7 +1059,7 @@ public class TradeWalletService { if (tradersMultiSigKeyPair.isEncrypted()) { checkNotNull(aesKey); } - ECKey.ECDSASignature tradersSignature = tradersMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised(); + ECKey.ECDSASignature tradersSignature = LowRSigningKey.from(tradersMultiSigKeyPair).sign(sigHash, aesKey); TransactionSignature tradersTxSig = new TransactionSignature(tradersSignature, Transaction.SigHash.ALL, false); TransactionSignature arbitratorTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(arbitratorSignature), Transaction.SigHash.ALL, false); @@ -1137,7 +1138,7 @@ public class TradeWalletService { ECKey myPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(myPrivKeyAsHex)); checkNotNull(myPrivateKey, "key must not be null"); - ECKey.ECDSASignature myECDSASignature = myPrivateKey.sign(sigHash, aesKey).toCanonicalised(); + ECKey.ECDSASignature myECDSASignature = LowRSigningKey.from(myPrivateKey).sign(sigHash, aesKey); TransactionSignature myTxSig = new TransactionSignature(myECDSASignature, Transaction.SigHash.ALL, false); return Utils.HEX.encode(myTxSig.encodeToBitcoin()); } @@ -1409,9 +1410,8 @@ public class TradeWalletService { private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException { checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null"); Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); - ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); - checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" + - input.getOutpoint().toString()); + ECKey sigKey = LowRSigningKey.from(input.getOutpoint().getConnectedKey(wallet)); + checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=%s", input.getOutpoint()); if (sigKey.isEncrypted()) { checkNotNull(aesKey); } diff --git a/core/src/main/java/bisq/core/btc/wallet/WalletService.java b/core/src/main/java/bisq/core/btc/wallet/WalletService.java index 7b0566cb3c..72a670df88 100644 --- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java @@ -24,6 +24,7 @@ import bisq.core.btc.listeners.BalanceListener; import bisq.core.btc.listeners.TxConfidenceListener; import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster; +import bisq.core.crypto.LowRSigningKey; import bisq.core.provider.fee.FeeService; import bisq.core.user.Preferences; @@ -359,8 +360,8 @@ public abstract class WalletService { if (pubKey instanceof DeterministicKey) propTx.keyPaths.put(scriptPubKey, (((DeterministicKey) pubKey).getPath())); - ECKey key; - if ((key = redeemData.getFullKey()) == null) { + ECKey key = LowRSigningKey.from(redeemData.getFullKey()); + if (key == null) { log.warn("No local key found for input {}", index); return; } diff --git a/core/src/main/java/bisq/core/crypto/CountingHMacDSAKCalculator.java b/core/src/main/java/bisq/core/crypto/CountingHMacDSAKCalculator.java new file mode 100644 index 0000000000..342f6ec7eb --- /dev/null +++ b/core/src/main/java/bisq/core/crypto/CountingHMacDSAKCalculator.java @@ -0,0 +1,80 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.crypto; + +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.prng.EntropySource; +import org.bouncycastle.crypto.prng.drbg.HMacSP800DRBG; +import org.bouncycastle.crypto.prng.drbg.SP80090DRBG; + +import java.math.BigInteger; + +import java.util.Arrays; + +public class CountingHMacDSAKCalculator extends DeterministicDSAKCalculator { + private final HMac hMac = new HMac(new SHA256Digest()); + private SP80090DRBG drbg; + private BigInteger n; + private int counter; + + @Override + void init(BigInteger n, BigInteger d, BigInteger e) { + int len = (n.bitLength() + 7) / 8; + int securityStrength = Math.min(n.bitLength(), 256); + byte[] dBytes = toByteArrayBE(d, len); + byte[] eBytes = toByteArrayBE(e, len); + byte[] additionalData = counter != 0 ? toByteArrayLE(counter, hMac.getMacSize()) : null; + drbg = new HMacSP800DRBG(hMac, securityStrength, getEntropySource(dBytes), additionalData, eBytes); + this.n = n; + } + + @Override + public BigInteger nextK() { + byte[] kBytes = new byte[(n.bitLength() + 7) / 8]; + BigInteger k; + do { + drbg.generate(kBytes, null, false); + } while (!kRange.contains(k = toScalar(kBytes, n))); + counter++; + return k; + } + + private static byte[] toByteArrayLE(int i, int len) { + return Arrays.copyOf(new byte[]{(byte) i, (byte) (i >> 8), (byte) (i >> 16), (byte) (i >> 24)}, len); + } + + private EntropySource getEntropySource(byte[] dBytes) { + return new EntropySource() { + @Override + public boolean isPredictionResistant() { + return false; + } + + @Override + public byte[] getEntropy() { + return dBytes; + } + + @Override + public int entropySize() { + return dBytes.length * 8; + } + }; + } +} diff --git a/core/src/main/java/bisq/core/crypto/DeterministicDSAKCalculator.java b/core/src/main/java/bisq/core/crypto/DeterministicDSAKCalculator.java new file mode 100644 index 0000000000..7b43296d80 --- /dev/null +++ b/core/src/main/java/bisq/core/crypto/DeterministicDSAKCalculator.java @@ -0,0 +1,59 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.crypto; + +import com.google.common.collect.Range; + +import org.bouncycastle.crypto.signers.DSAKCalculator; + +import java.security.SecureRandom; + +import java.math.BigInteger; + +import java.util.Arrays; + +public abstract class DeterministicDSAKCalculator implements DSAKCalculator { + Range kRange; + + @Override + public boolean isDeterministic() { + return true; + } + + @Override + public void init(BigInteger n, SecureRandom random) { + throw new IllegalStateException("Operation not supported"); + } + + @Override + public void init(BigInteger n, BigInteger d, byte[] message) { + kRange = Range.closedOpen(BigInteger.ONE, n); + init(n, d, toScalar(message, n).mod(n)); + } + + abstract void init(BigInteger n, BigInteger d, BigInteger e); + + static byte[] toByteArrayBE(BigInteger k, int len) { + byte[] guardedKBytes = k.or(BigInteger.ONE.shiftLeft(len * 8)).toByteArray(); + return Arrays.copyOfRange(guardedKBytes, guardedKBytes.length - len, guardedKBytes.length); + } + + static BigInteger toScalar(byte[] kBytes, BigInteger n) { + return new BigInteger(1, kBytes).shiftRight(Math.max(kBytes.length * 8 - n.bitLength(), 0)); + } +} diff --git a/core/src/main/java/bisq/core/crypto/LowRSigningKey.java b/core/src/main/java/bisq/core/crypto/LowRSigningKey.java new file mode 100644 index 0000000000..498c93241c --- /dev/null +++ b/core/src/main/java/bisq/core/crypto/LowRSigningKey.java @@ -0,0 +1,66 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.crypto; + +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.crypto.KeyCrypterException; +import org.bitcoinj.crypto.LazyECPoint; + +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.signers.DSAKCalculator; +import org.bouncycastle.crypto.signers.ECDSASigner; + +import java.math.BigInteger; + +public class LowRSigningKey extends ECKey { + private final ECKey originalKey; + + protected LowRSigningKey(ECKey key) { + super(key.hasPrivKey() ? key.getPrivKey() : null, new LazyECPoint(CURVE.getCurve(), key.getPubKey())); + this.keyCrypter = key.getKeyCrypter(); + this.encryptedPrivateKey = key.getEncryptedPrivateKey(); + originalKey = key; + } + + public static LowRSigningKey from(ECKey key) { + return key != null ? key instanceof LowRSigningKey ? (LowRSigningKey) key : new LowRSigningKey(key) : null; + } + + @Override + public LowRSigningKey decrypt(KeyParameter aesKey) throws KeyCrypterException { + return new LowRSigningKey(originalKey.decrypt(aesKey)); + } + + @Override + protected ECDSASignature doSign(Sha256Hash input, BigInteger privateKeyForSigning) { + return doSign(input, privateKeyForSigning, new CountingHMacDSAKCalculator()); + } + + protected ECDSASignature doSign(Sha256Hash input, BigInteger privateKeyForSigning, DSAKCalculator kCalculator) { + ECDSASigner signer = new ECDSASigner(kCalculator); + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, CURVE); + signer.init(true, privKey); + BigInteger[] components; + do { + components = signer.generateSignature(input.getBytes()); + } while (components[0].bitLength() >= 256); + return new ECDSASignature(components[0], components[1]).toCanonicalised(); + } +} diff --git a/core/src/main/java/bisq/core/dao/SignVerifyService.java b/core/src/main/java/bisq/core/dao/SignVerifyService.java index cc41a0a529..1d7501d807 100644 --- a/core/src/main/java/bisq/core/dao/SignVerifyService.java +++ b/core/src/main/java/bisq/core/dao/SignVerifyService.java @@ -18,6 +18,7 @@ package bisq.core.dao; import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.crypto.LowRSigningKey; import bisq.core.dao.state.DaoStateService; import bisq.core.dao.state.model.blockchain.Tx; @@ -76,7 +77,7 @@ public class SignVerifyService { public Optional sign(String txId, String message) { byte[] pubKey = getPubKey(txId); - ECKey key = bsqWalletService.findKeyFromPubKey(pubKey); + ECKey key = LowRSigningKey.from(bsqWalletService.findKeyFromPubKey(pubKey)); if (key == null) return Optional.empty(); diff --git a/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNode.java b/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNode.java index 782eda9f38..2e9d71a78e 100644 --- a/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNode.java +++ b/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNode.java @@ -17,6 +17,7 @@ package bisq.core.dao.burningman.accounting.node; +import bisq.core.crypto.LowRSigningKey; import bisq.core.dao.DaoSetupService; import bisq.core.dao.burningman.accounting.BurningManAccountingService; import bisq.core.dao.burningman.accounting.blockchain.AccountingBlock; @@ -80,7 +81,7 @@ public abstract class AccountingNode implements DaoSetupService, DaoStateListene } public static byte[] getSignature(Sha256Hash sha256Hash, ECKey privKey) { - ECKey.ECDSASignature ecdsaSignature = privKey.sign(sha256Hash); + ECKey.ECDSASignature ecdsaSignature = LowRSigningKey.from(privKey).sign(sha256Hash); return ecdsaSignature.encodeToDER(); } diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java index 5bf858eca6..9c5c1aded2 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java @@ -24,6 +24,7 @@ import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.btc.wallet.WalletsManager; +import bisq.core.crypto.LowRSigningKey; import bisq.core.dao.DaoSetupService; import bisq.core.dao.exceptions.PublishToP2PNetworkException; import bisq.core.dao.governance.ballot.BallotListService; @@ -61,7 +62,6 @@ import org.bitcoinj.core.ECKey; import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; -import org.bitcoinj.crypto.DeterministicKey; import javax.inject.Inject; @@ -228,7 +228,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen publishTx(resultHandler, exceptionHandler, blindVoteTx); } catch (CryptoException | TransactionVerificationException | InsufficientMoneyException | - WalletException | IOException exception) { + WalletException | IOException exception) { log.error(exception.toString()); exception.printStackTrace(); exceptionHandler.handleException(exception); @@ -299,7 +299,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen return null; } - DeterministicKey key = bsqWalletService.findKeyFromPubKey(Utilities.decodeFromHex(pubKey)); + ECKey key = LowRSigningKey.from(bsqWalletService.findKeyFromPubKey(Utilities.decodeFromHex(pubKey))); if (key == null) { // Maybe add exception log.error("We did not find the key for our compensation request. txId={}", @@ -318,7 +318,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen ECKey.ECDSASignature signature = bsqWalletService.isEncrypted() ? key.sign(Sha256Hash.wrap(blindVoteTxId), bsqWalletService.getAesKey()) : key.sign(Sha256Hash.wrap(blindVoteTxId)); - signatureAsBytes = signature.toCanonicalised().encodeToDER(); + signatureAsBytes = signature.encodeToDER(); } else { // In case we use it for requesting the currently available merit we don't apply a signature signatureAsBytes = new byte[0]; diff --git a/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java index b8f41358c7..f35a5abc62 100644 --- a/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java +++ b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java @@ -24,6 +24,7 @@ import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.btc.wallet.WalletsManager; +import bisq.core.crypto.LowRSigningKey; import bisq.core.dao.DaoSetupService; import bisq.core.dao.governance.proposal.TxException; import bisq.core.dao.state.DaoStateListener; @@ -73,7 +74,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener { private final DaoStateService daoStateService; @Getter - private IntegerProperty updateFlag = new SimpleIntegerProperty(0); + private final IntegerProperty updateFlag = new SimpleIntegerProperty(0); @Getter private final List proofOfBurnTxList = new ArrayList<>(); @@ -194,7 +195,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener { public Optional sign(String proofOfBurnTxId, String message) { byte[] pubKey = getPubKey(proofOfBurnTxId); - ECKey key = bsqWalletService.findKeyFromPubKey(pubKey); + ECKey key = LowRSigningKey.from(bsqWalletService.findKeyFromPubKey(pubKey)); if (key == null) return Optional.empty(); diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java index 77c58391a5..7d553916d6 100644 --- a/core/src/main/java/bisq/core/filter/FilterManager.java +++ b/core/src/main/java/bisq/core/filter/FilterManager.java @@ -18,6 +18,7 @@ package bisq.core.filter; import bisq.core.btc.nodes.BtcNodes; +import bisq.core.crypto.LowRSigningKey; import bisq.core.locale.Res; import bisq.core.offer.Offer; import bisq.core.payment.payload.PaymentAccountPayload; @@ -654,7 +655,7 @@ public class FilterManager { private String getSignature(Filter filterWithoutSig) { Sha256Hash hash = getSha256Hash(filterWithoutSig); - ECKey.ECDSASignature ecdsaSignature = filterSigningKey.sign(hash); + ECKey.ECDSASignature ecdsaSignature = LowRSigningKey.from(filterSigningKey).sign(hash); byte[] encodeToDER = ecdsaSignature.encodeToDER(); return new String(Base64.encode(encodeToDER), StandardCharsets.UTF_8); } diff --git a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java index a725927ab4..6fbd3a5207 100644 --- a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java +++ b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java @@ -17,6 +17,7 @@ package bisq.core.support.dispute.agent; +import bisq.core.crypto.LowRSigningKey; import bisq.core.filter.FilterManager; import bisq.core.user.User; @@ -257,7 +258,7 @@ public abstract class DisputeAgentManager { // Other users will check the signature with the list of public keys hardcoded in the app. public String signStorageSignaturePubKey(ECKey key) { String keyToSignAsHex = Utils.HEX.encode(keyRing.getPubKeyRing().getSignaturePubKey().getEncoded()); - return key.signMessage(keyToSignAsHex); + return LowRSigningKey.from(key).signMessage(keyToSignAsHex); } @Nullable diff --git a/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java b/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java index 4e372321dd..45a807640e 100644 --- a/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java +++ b/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java @@ -19,6 +19,7 @@ package bisq.core.account.sign; import bisq.core.account.witness.AccountAgeWitness; +import bisq.core.crypto.LowRSigningKey; import bisq.core.filter.FilterManager; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; @@ -104,7 +105,7 @@ public class SignedWitnessServiceTest { aew1 = new AccountAgeWitness(account1DataHash, account1CreationTime); aew2 = new AccountAgeWitness(account2DataHash, account2CreationTime); aew3 = new AccountAgeWitness(account3DataHash, account3CreationTime); - arbitrator1Key = new ECKey(); + arbitrator1Key = LowRSigningKey.from(new ECKey()); peer1KeyPair = Sig.generateKeyPair(); peer2KeyPair = Sig.generateKeyPair(); peer3KeyPair = Sig.generateKeyPair(); @@ -320,7 +321,7 @@ public class SignedWitnessServiceTest { byte[] signerPubKey; if (i == 0) { // use arbitrator key - ECKey arbitratorKey = new ECKey(); + ECKey arbitratorKey = LowRSigningKey.from(new ECKey()); signedKeyPair = Sig.generateKeyPair(); String signature1String = arbitratorKey.signMessage(accountDataHashAsHexString); signature = signature1String.getBytes(Charsets.UTF_8); diff --git a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java index e0abc989a8..9796c3b1c9 100644 --- a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java +++ b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java @@ -19,6 +19,7 @@ package bisq.core.account.witness; import bisq.core.account.sign.SignedWitness; import bisq.core.account.sign.SignedWitnessService; +import bisq.core.crypto.LowRSigningKey; import bisq.core.filter.FilterManager; import bisq.core.locale.CountryUtil; import bisq.core.offer.bisq_v1.OfferPayload; @@ -236,7 +237,7 @@ public class AccountAgeWitnessServiceTest { assertEquals(2, items.size()); // Setup a mocked arbitrator key - ECKey arbitratorKey = mock(ECKey.class); + ECKey arbitratorKey = mock(LowRSigningKey.class); when(arbitratorKey.signMessage(any())).thenReturn("1"); when(arbitratorKey.signMessage(any())).thenReturn("2"); when(arbitratorKey.getPubKey()).thenReturn("1".getBytes()); @@ -250,14 +251,14 @@ public class AccountAgeWitnessServiceTest { // Check that both accountAgeWitnesses are signed SignedWitness foundBuyerSignedWitness = signedWitnessService.getSignedWitnessSetByOwnerPubKey( - buyerPubKeyRing.getSignaturePubKeyBytes()).stream() + buyerPubKeyRing.getSignaturePubKeyBytes()).stream() .findFirst() .orElse(null); assert foundBuyerSignedWitness != null; assertEquals(Utilities.bytesAsHexString(foundBuyerSignedWitness.getAccountAgeWitnessHash()), Utilities.bytesAsHexString(buyerAccountAgeWitness.getHash())); SignedWitness foundSellerSignedWitness = signedWitnessService.getSignedWitnessSetByOwnerPubKey( - sellerPubKeyRing.getSignaturePubKeyBytes()).stream() + sellerPubKeyRing.getSignaturePubKeyBytes()).stream() .findFirst() .orElse(null); assert foundSellerSignedWitness != null; diff --git a/core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java b/core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java new file mode 100644 index 0000000000..7ff1d796c2 --- /dev/null +++ b/core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java @@ -0,0 +1,110 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.crypto; + +import org.bitcoinj.core.Sha256Hash; + +import java.nio.charset.StandardCharsets; + +import java.math.BigInteger; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CountingHMacDSAKCalculatorTest { + // Taken from https://www.rfc-editor.org/rfc/rfc6979: + private static final String[][] RFC_6979_SHA_256_ECDSA_TEST_VECTORS = { + // NIST P-192 + {"ffffffffffffffffffffffff99def836146bc9b1b4d22831", "6fab034934e4c0fc9ae67f5b5659a9d7d1fefd187ee09fd4", "sample", "32b1b6d7d42a05cb449065727a84804fb1a3e34d8f261496"}, + {"ffffffffffffffffffffffff99def836146bc9b1b4d22831", "6fab034934e4c0fc9ae67f5b5659a9d7d1fefd187ee09fd4", "test", "5c4ce89cf56d9e7c77c8585339b006b97b5f0680b4306c6c"}, + // NIST P-224 + {"ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", "f220266e1105bfe3083e03ec7a3a654651f45e37167e88600bf257c1", "sample", "ad3029e0278f80643de33917ce6908c70a8ff50a411f06e41dedfcdc"}, + {"ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", "f220266e1105bfe3083e03ec7a3a654651f45e37167e88600bf257c1", "test", "ff86f57924da248d6e44e8154eb69f0ae2aebaee9931d0b5a969f904"}, + // NIST p-256 + {"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", "sample", "a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60"}, + {"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", "test", "d16b6ae827f17175e040871a1c7ec3500192c4c92677336ec2537acaee0008e0"}, + // NIST P-384 + {"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", "6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d896d5724e4c70a825f872c9ea60d2edf5", "sample", "180ae9f9aec5438a44bc159a1fcb277c7be54fa20e7cf404b490650a8acc414e375572342863c899f9f2edf9747a9b60"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", "6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d896d5724e4c70a825f872c9ea60d2edf5", "test", "0cfac37587532347dc3389fdc98286bba8c73807285b184c83e62e26c401c0faa48dd070ba79921a3457abff2d630ad7"}, + // NIST P-521 + {"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", "0fad06daa62ba3b25d2fb40133da757205de67f5bb0018fee8c86e1b68c7e75caa896eb32f1f47c70855836a6d16fcc1466f6d8fbec67db89ec0c08b0e996b83538", "sample", "0edf38afcaaecab4383358b34d67c9f2216c8382aaea44a3dad5fdc9c32575761793fef24eb0fc276dfc4f6e3ec476752f043cf01415387470bcbd8678ed2c7e1a0"}, + {"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", "0fad06daa62ba3b25d2fb40133da757205de67f5bb0018fee8c86e1b68c7e75caa896eb32f1f47c70855836a6d16fcc1466f6d8fbec67db89ec0c08b0e996b83538", "test", "01de74955efaabc4c4f17f8e84d881d1310b5392d7700275f82f145c61e843841af09035bf7a6210f5a431a6a9e81c9323354a9e69135d44ebd2fcaa7731b909258"}, + // NIST K-163 + {"4000000000000000000020108a2e0cc0d99f8a5ef", "09a4d6792295a7f730fc3f2b49cbc0f62e862272f", "sample", "23af4074c90a02b3fe61d286d5c87f425e6bdd81b"}, + {"4000000000000000000020108a2e0cc0d99f8a5ef", "09a4d6792295a7f730fc3f2b49cbc0f62e862272f", "test", "193649ce51f0cff0784cfc47628f4fa854a93f7a2"}, + // NIST K-233 + {"8000000000000000000000000000069d5bb915bcd46efb1ad5f173abdf", "103b2142bdc2a3c3b55080d09df1808f79336da2399f5ca7171d1be9b0", "sample", "73552f9cac5774f74f485fa253871f2109a0c86040552eaa67dba92dc9"}, + {"8000000000000000000000000000069d5bb915bcd46efb1ad5f173abdf", "103b2142bdc2a3c3b55080d09df1808f79336da2399f5ca7171d1be9b0", "test", "2ce5aedc155acc0ddc5e679ebacfd21308362e5efc05c5e99b2557a8d7"}, + // NIST K-283 + {"1ffffffffffffffffffffffffffffffffffe9ae2ed07577265dff7f94451e061e163c61", "06a0777356e87b89ba1ed3a3d845357be332173c8f7a65bdc7db4fab3c4cc79acc8194e", "sample", "1ceb9e8e0dff53ce687deb81339aca3c98e7a657d5a9499ef779f887a934408ecbe5a38"}, + {"1ffffffffffffffffffffffffffffffffffe9ae2ed07577265dff7f94451e061e163c61", "06a0777356e87b89ba1ed3a3d845357be332173c8f7a65bdc7db4fab3c4cc79acc8194e", "test", "0b585a7a68f51089691d6ede2b43fc4451f66c10e65f134b963d4cbd4eb844b0e1469a6"}, + // NIST K-409 + {"7ffffffffffffffffffffffffffffffffffffffffffffffffffe5f83b2d4ea20400ec4557d5ed3e3e7ca5b4b5c83b8e01e5fcf", "29c16768f01d1b8a89fda85e2efd73a09558b92a178a2931f359e4d70ad853e569cdaf16daa569758fb4e73089e4525d8bbfcf", "sample", "782385f18baf5a36a588637a76dfab05739a14163bf723a4417b74bd1469d37ac9e8cce6aec8ff63f37b815aaf14a876eed962"}, + {"7ffffffffffffffffffffffffffffffffffffffffffffffffffe5f83b2d4ea20400ec4557d5ed3e3e7ca5b4b5c83b8e01e5fcf", "29c16768f01d1b8a89fda85e2efd73a09558b92a178a2931f359e4d70ad853e569cdaf16daa569758fb4e73089e4525d8bbfcf", "test", "251e32dee10ed5ea4ad7370df3eff091e467d5531ca59de3aa791763715e1169ab5e18c2a11cd473b0044fb45308e8542f2eb0"}, + // NIST K-571 + {"20000000000000000000000000000000000000000000000000000000000000000000000131850e1f19a63e4b391a8db917f4138b630d84be5d639381e91deb45cfe778f637c1001", "0c16f58550d824ed7b95569d4445375d3a490bc7e0194c41a39deb732c29396cdf1d66de02dd1460a816606f3bec0f32202c7bd18a32d87506466aa92032f1314ed7b19762b0d22", "sample", "0f79d53e63d89fb87f4d9e6dc5949f5d9388bcfe9ebcb4c2f7ce497814cf40e845705f8f18dbf0f860de0b1cc4a433ef74a5741f3202e958c082e0b76e16ecd5866aa0f5f3df300"}, + {"20000000000000000000000000000000000000000000000000000000000000000000000131850e1f19a63e4b391a8db917f4138b630d84be5d639381e91deb45cfe778f637c1001", "0c16f58550d824ed7b95569d4445375d3a490bc7e0194c41a39deb732c29396cdf1d66de02dd1460a816606f3bec0f32202c7bd18a32d87506466aa92032f1314ed7b19762b0d22", "test", "04ddd0707e81bb56ea2d1d45d7fafdbdd56912cae224086802fea1018db306c4fb8d93338dbf6841ce6c6ab1506e9a848d2c0463e0889268843dee4acb552cffcb858784ed116b2"}, + // NIST B-163 + {"40000000000000000000292fe77e70c12a4234c33", "35318fc447d48d7e6bc93b48617dddedf26aa658f", "sample", "3d7086a59e6981064a9cdb684653f3a81b6ec0f0b"}, + {"40000000000000000000292fe77e70c12a4234c33", "35318fc447d48d7e6bc93b48617dddedf26aa658f", "test", "38145e3ffca94e4ddacc20ad6e0997bd0e3b669d2"}, + // NIST B-233 + {"1000000000000000000000000000013e974e72f8a6922031d2603cfe0d7", "07adc13dd5bf34d1ddeeb50b2ce23b5f5e6d18067306d60c5f6ff11e5d3", "sample", "034a53897b0bbdb484302e19bf3f9b34a2abfed639d109a388dc52006b5"}, + {"1000000000000000000000000000013e974e72f8a6922031d2603cfe0d7", "07adc13dd5bf34d1ddeeb50b2ce23b5f5e6d18067306d60c5f6ff11e5d3", "test", "00376886e89013f7ff4b5214d56a30d49c99f53f211a3afe01aa2bde12d"}, + // NIST B-283 + {"3ffffffffffffffffffffffffffffffffffef90399660fc938a90165b042a7cefadb307", "14510d4bc44f2d26f4553942c98073c1bd35545ceabb5cc138853c5158d2729ea408836", "sample", "38c9d662188982943e080b794a4cfb0732dba37c6f40d5b8cfaded6ff31c5452ba3f877"}, + {"3ffffffffffffffffffffffffffffffffffef90399660fc938a90165b042a7cefadb307", "14510d4bc44f2d26f4553942c98073c1bd35545ceabb5cc138853c5158d2729ea408836", "test", "018a7d44f2b4341fefe68f6bd8894960f97e08124aab92c1ffbbe90450fcc9356c9aaa5"}, + // NIST B-409 + {"10000000000000000000000000000000000000000000000000001e2aad6a612f33307be5fa47c3c9e052f838164cd37d9a21173", "0494994cc325b08e7b4ce038bd9436f90b5e59a2c13c3140cd3ae07c04a01fc489f572ce0569a6db7b8060393de76330c624177", "sample", "08ec42d13a3909a20c41bebd2dfed8cacce56c7a7d1251df43f3e9e289dae00e239f6960924ac451e125b784cb687c7f23283fd"}, + {"10000000000000000000000000000000000000000000000000001e2aad6a612f33307be5fa47c3c9e052f838164cd37d9a21173", "0494994cc325b08e7b4ce038bd9436f90b5e59a2c13c3140cd3ae07c04a01fc489f572ce0569a6db7b8060393de76330c624177", "test", "06eba3d58d0e0dfc406d67fc72ef0c943624cf40019d1e48c3b54ccab0594afd5dee30aebaa22e693dbcfecad1a85d774313dad"}, + // NIST B-571 + {"3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe661ce18ff55987308059b186823851ec7dd9ca1161de93d5174d66e8382e9bb2fe84e47", "028a04857f24c1c082df0d909c0e72f453f2e2340ccb071f0e389bca2575da19124198c57174929ad26e348cf63f78d28021ef5a9bf2d5cbeaf6b7ccb6c4da824dd5c82cfb24e11", "sample", "15c2c6b7d1a070274484774e558b69fdfa193bdb7a23f27c2cd24298ce1b22a6cc9b7fb8cabfd6cf7c6b1cf3251e5a1cddd16fbfed28de79935bb2c631b8b8ea9cc4bcc937e669e"}, + {"3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe661ce18ff55987308059b186823851ec7dd9ca1161de93d5174d66e8382e9bb2fe84e47", "028a04857f24c1c082df0d909c0e72f453f2e2340ccb071f0e389bca2575da19124198c57174929ad26e348cf63f78d28021ef5a9bf2d5cbeaf6b7ccb6c4da824dd5c82cfb24e11", "test", "328e02cf07c7b5b6d3749d8302f1ae5bfaa8f239398459af4a2c859c7727a8123a7fe9be8b228413fc8dc0e9de16af3f8f43005107f9989a5d97a5c4455da895e81336710a3fb2c"} + }; + + static Stream rfc6979TestVectors() { + return Stream.of(RFC_6979_SHA_256_ECDSA_TEST_VECTORS) + .map(args -> new Object[]{new BigInteger(args[0], 16), new BigInteger(args[1], 16), args[2], + new BigInteger(args[3], 16)}); + } + + @MethodSource("rfc6979TestVectors") + @ParameterizedTest(name = "[{index}] n={0}, d={1}, message={2}, expectedK={3}") + public void testKCalculator(BigInteger n, BigInteger d, String message, BigInteger expectedK) { + byte[] messageHash = Sha256Hash.hash(message.getBytes(StandardCharsets.UTF_8)); + var kCalculator = new CountingHMacDSAKCalculator(); + + // First usage of the k-calculator gives a nonce matching that in the RFC 6979 test vector. + kCalculator.init(n, d, messageHash); + assertEquals(expectedK, kCalculator.nextK()); + + // Further invocations result in distinct nonces, as the internal counter increments. + Set retries = new HashSet<>(); + for (int i = 0; i < 4; i++) { + kCalculator.init(n, d, messageHash); + retries.add(kCalculator.nextK()); + } + retries.add(expectedK); + assertEquals(5, retries.size()); + } +} diff --git a/core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java b/core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java new file mode 100644 index 0000000000..70fa8a92c8 --- /dev/null +++ b/core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java @@ -0,0 +1,177 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.crypto; + +import bisq.common.util.Utilities; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.crypto.TransactionSignature; +import org.bitcoinj.params.MainNetParams; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptBuilder; + +import com.google.common.base.Strings; +import com.google.common.util.concurrent.Futures; + +import java.nio.charset.StandardCharsets; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import java.math.BigInteger; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LowRSigningKeyTest { + /** + * Deterministic low-R signature test vectors of the form {privKey, sigHash, rComponent, sComponent}, generated with + * the aid of the Bitcoin Core tx creation and editing command: 'bitcoin-tx'. The signed txs and subsequent test + * vectors here match those produced by Core/Knots version 27.1 (and likely other post-2018 versions). + * @see TestVectorGeneration + */ + private static final String[][] GENERATED_TEST_VECTORS = { + {"02", "546876d08f9f06f12c168f96079cd0af996ce22d89bbc58eced2631918a9bc9a", "45cc5d5e4fad81f2bc89f16cb37575da3ae13677f707a73ca5ca1e2787e3d311", "25829e0ad206dbd53cd637b75eedbde3273548673b2d08de3f61dcbe723409e2"}, + {"02", "2cca2f04e6e654226483f5af39fbfebb883e301bd32553a6a0fb176b0d172b3a", "2bdcafead8f6db212228e52f061e894db8bdc2133c6c81a5b54883ef5648ae6d", "007bdcc92effb2931b4c7c5c834bf7aa847d3e0e0e1d9c7e41ed3981821eb830"}, + {"02", "d78c70bc5e7f9a74ce4e3000369592a037a8898a93b07a5bdc2d88ed78462521", "1f283dfd1ba17e69dbbe2fe261c3984569316efa781b1a4e846fa7978f0fe918", "180187c9cc33c2849b4770260a1ee379e5c4292ad46bd700c45a108bb4c6f346"}, + {"02", "c65a47a4d448b60cff8bbcf190492a1d5ef6beb217465c69bd358c4460ab84fb", "216301d61b337ca80c62047d349fa85c04b05451586ab0a2034d0855b09209fe", "228d09fe3e5a9a90501def65a8e467a5172c9fdd510c8a5db3a6cdbea000b1cc"}, + {"02", "d5bde8603bada85600e24ae82c9dee32671ac5038f6842558610f5cf3129f21e", "50b0b26bbfe72cacee2c8e1612e057c1855da8320232965137dd67f9eab77523", "26f990be93e0b8ce5ddbe468d0d242c51686c2ad9608ee44b17875b991e23e82"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "340b1fe486af2cd6b0ef8786d1c747fc3d785c5499d35faa25fbde57f9bf70ae", "2c5bc7104c059e2db8cda7b500424d2438ae2635b6cddcc51695a11e7ec95cc7", "62bc8969bfb02cac905f0739cecadc456bf48539a9b268b2c2725d3c2680be88"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "a39e0f59a4d8448ab4a614edb998e3f315d3a4c855855386afdfe664a42a0864", "752066dbf4e862d634440649014ec1fc64fdcea1127320acc09b223b1e7fbc59", "2a613585d5ac1741080380bdf7b151e559ccb37c5521674a77b999c9de0f21e8"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "df0a60d25576d441cdd76f5a3d63fe1308860121e70e832098abac5b6d5ff715", "65f8ccf807faeb46c0a69d1b1774a53081ec11c5e0ffb854620bcb2f2c8098bb", "32feb22792926929f01d51439814ec1bc414c2ab52b36a7c675e50ea37310b78"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "79df48383bdf5d295e510f4aadcc3a3b2ed0c3d454e1b1e4315de7c3f1ea86ab", "588ae9380e85c8c2c565321957aa459a4ae0080331d41145b7dea5214bf91377", "7207f2c4969e19ef5de08d088ea7dcd4c1ba67574cc0ac023dc9e7c48abee8b6"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "44adec39be60bf10994596a0248dcafa78a45ca30472190f0de61523e90ec131", "1bef291f3aa99c551ef5d96d9fd06d945092e185cebe2cda75351e2f27ca3ea2", "2024fda909bfb9b9079f072dbe005ae850a54f16d1b59bc46afc6db2ef5461fe"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "e48ae59f8a200c946c827ab8992f8774dc0d5742d50baaa9f26b39a91f471eda", "39af0095c3cd45cba48891f29e2a69c3ba421c97bbc3181aa1f3699e3deb5234", "19ad75c9f49f42648338ddfac0b3597145db0864b897212d3da0a88b796d09f8"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "9e1a0043b4e941352f8cd957041244bed7f4a616c254ab8ba1a8ce7fba8a798a", "33b8197cfdefd2d5715139770256037224454ccc21d25bebb91100bcead5aba9", "45857b3c9cea024f663afb580dc284db387627fbe701f620715cdd7bed0ad074"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "bda8eafc615336f3db072cb6a77804f284fb89d7ae1ff90c7542278cfb25f1f0", "546033ea22ec13216b202af72d6cc434cdcfad74ef2a745d69adb5a568443118", "45d87e494c695b956fb5f5ec7d7af72ff35642f5b684654dcca72bbbbcf5bfa2"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "c8fd5e849b13582c9eb71a98b82fc23597cf85ddeac7f83f1c773be2e73021eb", "3cc7ed384b7f64ed6e50d0ae93617761573571e6b15ae421d957ccb6f7b91d45", "404fd888b72db73a355fe0b5924f699103ae607c70b9f64c40e1667c61e7a80c"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "597d869fc78e5ba7eaa27b6dd418d57d90915f9e8212125002187f715510928b", "65f5ae18a30d1c36f4a7af7e75372e47a39e6fdde2ab33156da74bd263eae039", "47fa066b272a29653b9fc8111be55ef5b5de73109fd94211a0484ba9ec29fc59"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "abed54431dfcd125916d9c0ebd8a2be9e871300fec5b664cb895acb4bc3e9535", "14aa272497fda7a11be7d3f93c528b5b2decacce8b652a7f07351b6297850586", "51fe6c6165880c91a945601c8b2d161c9f35243989d436b61d0369c2b3281ddd"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "ecf4f4f0cc3bcf6d828ccb237a6d80fcf84b55380cd37f8927af2516cde1c4e4", "398e2b26fbb01485d8fcf351249a49023db08db8d89df7cba4b8317df428b3ad", "6c1cb45174d97b6c1197022757155d2d3b33143853d341a2c7054f0f8dfdbaf6"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "a85c398d193cd603a4960a90232354baea46918a96c421ab53a972cd7a0892d6", "09269f59ff0423a0289e891cafffbde8fba9eec528f9c27cf4c4670cc1130e4e", "37d383ea040a6d41d7740ddad71b38b160ab61a28bd6f4985c7b0507ac8079c1"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "5be2ab78309b5cf85d41d035b42b8c10d7fd69aab5d496af84c0b4760bda639f", "7804f6ca0c17986184affe96ae8df3c163bfa4661fbf3f9208586e30768b99a2", "546c91e23074e3b38b6f4cf9c72b041968f56d06f6bf2e18f03eb02423369d28"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "14c2ff5e669fcf329d5a6bafbfd8b2a60b173ea565cdc8f39fc8c8a4564367d3", "5c12e5a06f942446ec0bdd470d13e871acbae8083678b2bcc365e53314d432f7", "3f856d7cac724daa898030dac14835b3702e8f484425e736b51752e6939ddc4d"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "f0f2fd0d2c8af9b1204c8320bf4453d381934afdca9ceaaa2f3fab24783b433a", "0cdfeff3615c787f74c01b0d40a69c75ac7f3e3aa4dd6024c570a19f0af08623", "04a78302a97985b7a7fcfc000d794f885c7bba0631a5f4c86c657e3993434403"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "7defe5ad8b5e636e8667bea1fc19c4619a326091201b3839b65c0339550e8df5", "62513a14aeac3a222b9805d198cf252667eb955348814d922ed33b705fdcaf30", "4f1ec4de4ab1e7ecd942b4a1c23be28a2f108090ee1494a4dd8c56b00560e577"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "c499af12e92bf421a0968aaeb766d140d240bd7b3ed021efc55e69707e95ba09", "78d6ee5e9b9e757671edf8023e38e1f5df9be261a498b9aec8cdaacb7e191b2c", "54448228d3b5e021224847f1706d31e69bc41ada1dc9dde93da0b770f653d8b9"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "22cbd20a30c91f0ad022a185b1e3999fe1c35f64f29572ddd00781c12ea0e024", "74c653169f9296952fec948a92e574365b40a74596913875c6ca0ac2d864a533", "168bed1a949d205b9358326fcffc7ac3318fd21a1c340da727995d3c2642daff"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "9339f4b9ca9ef2c9459462b329261895b5ed2952a1a0d26223fbaea88f60960e", "31b5b2fbaad2e3696cb4492aad8044f264f3ae991dde02c590fe254587d96e0c", "183ae3a641b2056ce425adec1bea9454574a1af367071d9aa28bcdc148ba425d"} + }; + + static Stream generatedTestVectors() { + return Stream.of(GENERATED_TEST_VECTORS) + .map(args -> new Object[]{new BigInteger(args[0], 16), Sha256Hash.wrap(args[1]), + new BigInteger(args[2], 16), new BigInteger(args[3], 16)}); + } + + @MethodSource("generatedTestVectors") + @ParameterizedTest(name = "[{index}] privKey={0}, messageHash={1}, expectedR={2}, expectedS={3}") + public void testSign(BigInteger privKey, Sha256Hash messageHash, BigInteger expectedR, BigInteger expectedS) { + var key = LowRSigningKey.from(ECKey.fromPrivate(privKey)); + var signature = key.sign(messageHash); + + // Signature is valid and low-R. + assertTrue(key.verify(messageHash, signature)); + assertTrue(signature.r.bitLength() < 256); + + // Signature matches that produced by Bitcoin Core/Knots with the same message & private key, + // which shows that the same nonce k was chosen, at least up to a sign (mod N, the curve order). + assertEquals(expectedR, signature.r); + assertEquals(expectedS, signature.s); + } + + @Disabled + public static class TestVectorGeneration { + private static final String BITCOIN_TX_COMMAND_FMT_STR = "bitcoin-tx -create " + + "in=00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff:0 " + + "set=privatekeys:[\"%s\"] " + ( + "set=prevtxs:[{" + + "\"txid\":\"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff\"," + + "\"vout\":0," + + "\"amount\":\"0.001\"," + + "\"scriptPubKey\":\"%s\"}] ") + + "outaddr=%s:193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7 " + + "sign=ALL"; + + private static final String[] PRIVATE_KEYS = { + "02", "110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "0123456789abcdef00000000000000000123456789abcdef0000000000000000", + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140" // = -1 (mod N) + }; + + private static final String[] COIN_OUTPUT_AMOUNTS = { + "0.0001", "0.0003", "0.0005", "0.0007", "0.0009" + }; + + @Test + public void preparedTestVectorsMatch() { + assertArrayEquals(GENERATED_TEST_VECTORS, Arrays.stream(PRIVATE_KEYS) + .flatMap(privKeyHex -> Arrays.stream(COIN_OUTPUT_AMOUNTS) + .map(coinOutputAmount -> assertDoesNotThrow(() -> generateTestVector(privKeyHex, coinOutputAmount)))) +// .forEach(vector -> System.out.printf("{\"%s\", \"%s\", \"%s\", \"%s\"},%n", (Object[]) vector)); + .toArray(String[][]::new)); + } + + private static String[] generateTestVector(String privKeyHex, String coinOutputAmount) throws Exception { + ECKey key = ECKey.fromPrivate(new BigInteger(privKeyHex, 16)); + Script scriptCode = ScriptBuilder.createP2PKHOutputScript(key); + Script scriptPubKey = ScriptBuilder.createP2WPKHOutputScript(key); + String scriptPubKeyHex = Utilities.encodeToHex(scriptPubKey.getProgram()); + String wif = key.getPrivateKeyAsWiF(MainNetParams.get()); + + String txHex = callBitcoinTx(wif, scriptPubKeyHex, coinOutputAmount); + + var tx = new Transaction(MainNetParams.get(), Utilities.decodeFromHex(txHex)); + var witness = tx.getInput(0).getWitness(); + var sigHash = tx.hashForWitnessSignature(0, scriptCode, Coin.MILLICOIN, Transaction.SigHash.ALL, false); + var signature = TransactionSignature.decodeFromBitcoin(witness.getPush(0), true, true); + + assertTrue(key.verify(sigHash, signature)); + return new String[]{privKeyHex, sigHash.toString(), + Strings.padStart(signature.r.toString(16), 64, '0'), + Strings.padStart(signature.s.toString(16), 64, '0')}; + } + + private static String callBitcoinTx(String wif, + String scriptPubKeyHex, + String coinOutputAmount) throws IOException { + String cmd = String.format(BITCOIN_TX_COMMAND_FMT_STR, wif, scriptPubKeyHex, coinOutputAmount); + Process process = new ProcessBuilder(cmd.split(" ")).start(); + var outputStream = new ByteArrayOutputStream(); + process.getInputStream().transferTo(outputStream); + + int exitCode = Futures.getUnchecked(process.onExit().thenApply(Process::exitValue)); + String output = outputStream.toString(StandardCharsets.UTF_8).trim(); + + assertEquals(0, exitCode); + return output; + } + } +} diff --git a/core/src/test/java/bisq/core/filter/TestFilter.java b/core/src/test/java/bisq/core/filter/TestFilter.java index ff2c041f0c..1627565b4d 100644 --- a/core/src/test/java/bisq/core/filter/TestFilter.java +++ b/core/src/test/java/bisq/core/filter/TestFilter.java @@ -17,6 +17,8 @@ package bisq.core.filter; +import bisq.core.crypto.LowRSigningKey; + import bisq.network.p2p.storage.payload.ProtectedStorageEntry; import org.bitcoinj.core.ECKey; @@ -114,7 +116,7 @@ public class TestFilter { byte[] filterData = unsignedFilter.serializeForHash(); Sha256Hash hash = Sha256Hash.of(filterData); - ECKey.ECDSASignature ecdsaSignature = signerKey.sign(hash); + ECKey.ECDSASignature ecdsaSignature = LowRSigningKey.from(signerKey).sign(hash); byte[] encodeToDER = ecdsaSignature.encodeToDER(); String signatureAsBase64 = new String(Base64.encode(encodeToDER), StandardCharsets.UTF_8); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/supporttool/SignVerifyPane.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/supporttool/SignVerifyPane.java index ea6d3ed0ef..e9e64914df 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/supporttool/SignVerifyPane.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/supporttool/SignVerifyPane.java @@ -23,6 +23,7 @@ import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.popups.Popup; import bisq.core.btc.wallet.WalletsManager; +import bisq.core.crypto.LowRSigningKey; import bisq.common.config.Config; @@ -80,7 +81,7 @@ public class SignVerifyPane extends CommonPane { new Popup().information("Key not found in wallet").show(); } else { ECKey myPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(privKeyHex)); - String signatureBase64 = myPrivateKey.signMessage(messageText.getText()); + String signatureBase64 = LowRSigningKey.from(myPrivateKey).signMessage(messageText.getText()); messageSig.setText(signatureBase64); } });