mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Merge pull request #7238 from stejbac/grind-for-low-r-signatures
Produce exclusively low-R signatures from wallet keys
This commit is contained in:
commit
7dab669cfb
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
105
core/src/main/java/bisq/core/btc/wallet/BisqWallet.java
Normal file
105
core/src/main/java/bisq/core/btc/wallet/BisqWallet.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
66
core/src/main/java/bisq/core/crypto/LowRSigningKey.java
Normal file
66
core/src/main/java/bisq/core/crypto/LowRSigningKey.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
177
core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java
Normal file
177
core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user