Merge pull request #7238 from stejbac/grind-for-low-r-signatures

Produce exclusively low-R signatures from wallet keys
This commit is contained in:
Alejandro García 2024-09-08 21:24:53 +00:00 committed by GitHub
commit 7dab669cfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 710 additions and 36 deletions

View File

@ -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);

View File

@ -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());
}

View File

@ -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());
}

View File

@ -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) {

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<TransactionInput> inputs = tx.getInputs();
List<TransactionOutput> 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();
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<ECKey> lowRKeys = redeemData.keys.stream().map(LowRSigningKey::from).collect(Collectors.toList());
return RedeemData.of(lowRKeys, redeemData.redeemScript);
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
};
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<BigInteger> 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));
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -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<String> 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();

View File

@ -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();
}

View File

@ -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];

View File

@ -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<Tx> proofOfBurnTxList = new ArrayList<>();
@ -194,7 +195,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener {
public Optional<String> 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();

View File

@ -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);
}

View File

@ -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<T extends DisputeAgent> {
// 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

View File

@ -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);

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Object[]> 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<BigInteger> 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());
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Object[]> 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;
}
}
}

View File

@ -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);

View File

@ -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);
}
});