diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java
index 498d8e7bba..a270b743c5 100644
--- a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java
+++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java
@@ -18,6 +18,7 @@
package bisq.core.account.sign;
import bisq.core.account.witness.AccountAgeWitness;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.filter.FilterManager;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.user.User;
@@ -280,7 +281,7 @@ public class SignedWitnessService {
}
String accountAgeWitnessHashAsHex = Utilities.encodeToHex(accountAgeWitness.getHash());
- String signatureBase64 = key.signMessage(accountAgeWitnessHashAsHex);
+ String signatureBase64 = LowRSigningKey.from(key).signMessage(accountAgeWitnessHashAsHex);
SignedWitness signedWitness = new SignedWitness(SignedWitness.VerificationMethod.ARBITRATOR,
accountAgeWitness.getHash(),
signatureBase64.getBytes(Charsets.UTF_8),
@@ -289,7 +290,7 @@ public class SignedWitnessService {
time,
tradeAmount.value);
publishSignedWitness(signedWitness);
- log.info("Arbitrator signed witness {}", signedWitness.toString());
+ log.info("Arbitrator signed witness {}", signedWitness);
return "";
}
@@ -322,7 +323,7 @@ public class SignedWitnessService {
new Date().getTime(),
tradeAmount.value);
publishSignedWitness(signedWitness);
- log.info("Trader signed witness {}", signedWitness.toString());
+ log.info("Trader signed witness {}", signedWitness);
return Optional.of(signedWitness);
}
@@ -537,7 +538,7 @@ public class SignedWitnessService {
private void publishSignedWitness(SignedWitness signedWitness) {
if (!signedWitnessMap.containsKey(signedWitness.getHashAsByteArray())) {
- log.info("broadcast signed witness {}", signedWitness.toString());
+ log.info("broadcast signed witness {}", signedWitness);
// We set reBroadcast to true to achieve better resilience.
p2PService.addPersistableNetworkPayload(signedWitness, true);
addToMap(signedWitness);
diff --git a/core/src/main/java/bisq/core/alert/AlertManager.java b/core/src/main/java/bisq/core/alert/AlertManager.java
index efedf78787..961057c633 100644
--- a/core/src/main/java/bisq/core/alert/AlertManager.java
+++ b/core/src/main/java/bisq/core/alert/AlertManager.java
@@ -17,6 +17,7 @@
package bisq.core.alert;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.user.User;
import bisq.network.p2p.P2PService;
@@ -160,7 +161,7 @@ public class AlertManager {
private void signAndAddSignatureToAlertMessage(Alert alert) {
String alertMessageAsHex = Utils.HEX.encode(alert.getMessage().getBytes(Charsets.UTF_8));
- String signatureAsBase64 = alertSigningKey.signMessage(alertMessageAsHex);
+ String signatureAsBase64 = LowRSigningKey.from(alertSigningKey).signMessage(alertMessageAsHex);
alert.setSigAndPubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic());
}
diff --git a/core/src/main/java/bisq/core/alert/PrivateNotificationManager.java b/core/src/main/java/bisq/core/alert/PrivateNotificationManager.java
index c37f5c6a9d..b8ce186bbe 100644
--- a/core/src/main/java/bisq/core/alert/PrivateNotificationManager.java
+++ b/core/src/main/java/bisq/core/alert/PrivateNotificationManager.java
@@ -17,6 +17,8 @@
package bisq.core.alert;
+import bisq.core.crypto.LowRSigningKey;
+
import bisq.network.p2p.DecryptedMessageWithPubKey;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
@@ -175,7 +177,7 @@ public class PrivateNotificationManager implements MessageListener {
private void signAndAddSignatureToPrivateNotificationMessage(PrivateNotificationPayload privateNotification) {
String privateNotificationMessageAsHex = Utils.HEX.encode(privateNotification.getMessage().getBytes(Charsets.UTF_8));
- String signatureAsBase64 = privateNotificationSigningKey.signMessage(privateNotificationMessageAsHex);
+ String signatureAsBase64 = LowRSigningKey.from(privateNotificationSigningKey).signMessage(privateNotificationMessageAsHex);
privateNotification.setSigAndPubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic());
}
diff --git a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java
index afa52f8482..c08597ee61 100644
--- a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java
+++ b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java
@@ -20,6 +20,7 @@ package bisq.core.btc.setup;
import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.nodes.ProxySocketFactory;
import bisq.core.btc.wallet.BisqRiskAnalysis;
+import bisq.core.btc.wallet.BisqWallet;
import bisq.common.config.Config;
import bisq.common.file.FileUtil;
@@ -405,7 +406,7 @@ public class WalletConfig extends AbstractIdleService {
WalletExtension[] extArray = new WalletExtension[]{};
Protos.Wallet proto = WalletProtobufSerializer.parseToProto(walletStream);
final WalletProtobufSerializer serializer;
- serializer = new WalletProtobufSerializer();
+ serializer = new WalletProtobufSerializer(BisqWallet::new);
// Hack to convert bitcoinj 0.14 wallets to bitcoinj 0.15 format
serializer.setKeyChainFactory(new BisqKeyChainFactory(isBsqWallet));
wallet = serializer.readWallet(params, extArray, proto);
@@ -432,7 +433,7 @@ public class WalletConfig extends AbstractIdleService {
kcgBuilder.fromSeed(vBtcWallet.getKeyChainSeed(), preferredOutputScriptType);
}
}
- return new Wallet(params, kcgBuilder.build());
+ return new BisqWallet(params, kcgBuilder.build());
}
private void maybeMoveOldWalletOutOfTheWay(File walletFile) {
diff --git a/core/src/main/java/bisq/core/btc/wallet/BisqWallet.java b/core/src/main/java/bisq/core/btc/wallet/BisqWallet.java
new file mode 100644
index 0000000000..e7afb5a5c1
--- /dev/null
+++ b/core/src/main/java/bisq/core/btc/wallet/BisqWallet.java
@@ -0,0 +1,105 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.btc.wallet;
+
+import org.bitcoinj.core.NetworkParameters;
+import org.bitcoinj.core.Transaction;
+import org.bitcoinj.core.TransactionInput;
+import org.bitcoinj.core.TransactionOutput;
+import org.bitcoinj.script.Script;
+import org.bitcoinj.script.ScriptException;
+import org.bitcoinj.signers.MissingSigResolutionSigner;
+import org.bitcoinj.signers.TransactionSigner;
+import org.bitcoinj.wallet.DecryptingKeyBag;
+import org.bitcoinj.wallet.KeyBag;
+import org.bitcoinj.wallet.KeyChainGroup;
+import org.bitcoinj.wallet.RedeemData;
+import org.bitcoinj.wallet.SendRequest;
+import org.bitcoinj.wallet.Wallet;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+@Slf4j
+public class BisqWallet extends Wallet {
+ public BisqWallet(NetworkParameters params, KeyChainGroup keyChainGroup) {
+ super(params, keyChainGroup);
+ }
+
+ // Largely copied from super, but modified to supply instances of LowRSigningKey to the tx signers,
+ // instead of deterministic keys, so that low-R signatures always result when using the SendRequest
+ // API. This likely breaks the keychain marrying functionality of bitcoinj, used for multisig inputs,
+ // but we don't use that anywhere in Bisq.
+ @Override
+ public void signTransaction(SendRequest req) {
+ lock.lock();
+ try {
+ Transaction tx = req.tx;
+ List inputs = tx.getInputs();
+ List outputs = tx.getOutputs();
+ checkState(inputs.size() > 0);
+ checkState(outputs.size() > 0);
+
+ KeyBag maybeDecryptingKeyBag = new LowRSigningKeyBag(new DecryptingKeyBag(this, req.aesKey));
+
+ int numInputs = tx.getInputs().size();
+ for (int i = 0; i < numInputs; i++) {
+ TransactionInput txIn = tx.getInput(i);
+ TransactionOutput connectedOutput = txIn.getConnectedOutput();
+ if (connectedOutput == null) {
+ // Missing connected output, assuming already signed.
+ continue;
+ }
+ Script scriptPubKey = connectedOutput.getScriptPubKey();
+
+ try {
+ // We assume if its already signed, its hopefully got a SIGHASH type that will not invalidate when
+ // we sign missing pieces (to check this would require either assuming any signatures are signing
+ // standard output types or a way to get processed signatures out of script execution)
+ txIn.getScriptSig().correctlySpends(tx, i, txIn.getWitness(), connectedOutput.getValue(),
+ connectedOutput.getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
+ log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", i);
+ continue;
+ } catch (ScriptException e) {
+ log.debug("Input contained an incorrect signature", e);
+ // Expected.
+ }
+
+ RedeemData redeemData = txIn.getConnectedRedeemData(maybeDecryptingKeyBag);
+ checkNotNull(redeemData, "Transaction exists in wallet that we cannot redeem: %s", txIn.getOutpoint().getHash());
+ txIn.setScriptSig(scriptPubKey.createEmptyInputScript(redeemData.keys.get(0), redeemData.redeemScript));
+ txIn.setWitness(scriptPubKey.createEmptyWitness(redeemData.keys.get(0)));
+ }
+
+ TransactionSigner.ProposedTransaction proposal = new TransactionSigner.ProposedTransaction(tx);
+ for (TransactionSigner signer : getTransactionSigners()) {
+ if (!signer.signInputs(proposal, maybeDecryptingKeyBag))
+ log.info("{} returned false for the tx", signer.getClass().getName());
+ }
+
+ // resolve missing sigs if any
+ new MissingSigResolutionSigner(req.missingSigsMode).signInputs(proposal, maybeDecryptingKeyBag);
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/btc/wallet/LowRSigningKeyBag.java b/core/src/main/java/bisq/core/btc/wallet/LowRSigningKeyBag.java
new file mode 100644
index 0000000000..d112cc5395
--- /dev/null
+++ b/core/src/main/java/bisq/core/btc/wallet/LowRSigningKeyBag.java
@@ -0,0 +1,61 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.btc.wallet;
+
+import bisq.core.crypto.LowRSigningKey;
+
+import org.bitcoinj.core.ECKey;
+import org.bitcoinj.script.Script;
+import org.bitcoinj.wallet.KeyBag;
+import org.bitcoinj.wallet.RedeemData;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+public class LowRSigningKeyBag implements KeyBag {
+ private final KeyBag target;
+
+ public LowRSigningKeyBag(KeyBag target) {
+ this.target = target;
+ }
+
+ @Nullable
+ @Override
+ public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash, @Nullable Script.ScriptType scriptType) {
+ return LowRSigningKey.from(target.findKeyFromPubKeyHash(pubKeyHash, scriptType));
+ }
+
+ @Nullable
+ @Override
+ public ECKey findKeyFromPubKey(byte[] pubKey) {
+ return LowRSigningKey.from(target.findKeyFromPubKey(pubKey));
+ }
+
+ @Nullable
+ @Override
+ public RedeemData findRedeemDataFromScriptHash(byte[] scriptHash) {
+ RedeemData redeemData = target.findRedeemDataFromScriptHash(scriptHash);
+ if (redeemData == null) {
+ return null;
+ }
+ List lowRKeys = redeemData.keys.stream().map(LowRSigningKey::from).collect(Collectors.toList());
+ return RedeemData.of(lowRKeys, redeemData.redeemScript);
+ }
+}
diff --git a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
index 29bda0b41e..24dac384a6 100644
--- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
@@ -26,6 +26,7 @@ import bisq.core.btc.model.PreparedDepositTxAndMakerInputs;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.setup.WalletConfig;
import bisq.core.btc.setup.WalletsSetup;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
@@ -744,7 +745,7 @@ public class TradeWalletService {
checkNotNull(aesKey);
}
- ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
+ ECKey.ECDSASignature mySignature = LowRSigningKey.from(myMultiSigKeyPair).sign(sigHash, aesKey);
WalletService.printTx("delayedPayoutTx for sig creation", delayedPayoutTx);
WalletService.verifyTransaction(delayedPayoutTx);
return mySignature.encodeToDER();
@@ -841,7 +842,7 @@ public class TradeWalletService {
if (multiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
}
- ECKey.ECDSASignature buyerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
+ ECKey.ECDSASignature buyerSignature = LowRSigningKey.from(multiSigKeyPair).sign(sigHash, aesKey);
WalletService.printTx("prepared payoutTx", preparedPayoutTx);
WalletService.verifyTransaction(preparedPayoutTx);
return buyerSignature.encodeToDER();
@@ -893,7 +894,7 @@ public class TradeWalletService {
if (multiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
}
- ECKey.ECDSASignature sellerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
+ ECKey.ECDSASignature sellerSignature = LowRSigningKey.from(multiSigKeyPair).sign(sigHash, aesKey);
TransactionSignature buyerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(buyerSignature),
Transaction.SigHash.ALL, false);
TransactionSignature sellerTxSig = new TransactionSignature(sellerSignature, Transaction.SigHash.ALL, false);
@@ -948,7 +949,7 @@ public class TradeWalletService {
if (myMultiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
}
- ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
+ ECKey.ECDSASignature mySignature = LowRSigningKey.from(myMultiSigKeyPair).sign(sigHash, aesKey);
WalletService.printTx("prepared mediated payoutTx for sig creation", preparedPayoutTx);
WalletService.verifyTransaction(preparedPayoutTx);
return mySignature.encodeToDER();
@@ -1058,7 +1059,7 @@ public class TradeWalletService {
if (tradersMultiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
}
- ECKey.ECDSASignature tradersSignature = tradersMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
+ ECKey.ECDSASignature tradersSignature = LowRSigningKey.from(tradersMultiSigKeyPair).sign(sigHash, aesKey);
TransactionSignature tradersTxSig = new TransactionSignature(tradersSignature, Transaction.SigHash.ALL, false);
TransactionSignature arbitratorTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(arbitratorSignature),
Transaction.SigHash.ALL, false);
@@ -1137,7 +1138,7 @@ public class TradeWalletService {
ECKey myPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(myPrivKeyAsHex));
checkNotNull(myPrivateKey, "key must not be null");
- ECKey.ECDSASignature myECDSASignature = myPrivateKey.sign(sigHash, aesKey).toCanonicalised();
+ ECKey.ECDSASignature myECDSASignature = LowRSigningKey.from(myPrivateKey).sign(sigHash, aesKey);
TransactionSignature myTxSig = new TransactionSignature(myECDSASignature, Transaction.SigHash.ALL, false);
return Utils.HEX.encode(myTxSig.encodeToBitcoin());
}
@@ -1409,9 +1410,8 @@ public class TradeWalletService {
private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException {
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
- ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
- checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" +
- input.getOutpoint().toString());
+ ECKey sigKey = LowRSigningKey.from(input.getOutpoint().getConnectedKey(wallet));
+ checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=%s", input.getOutpoint());
if (sigKey.isEncrypted()) {
checkNotNull(aesKey);
}
diff --git a/core/src/main/java/bisq/core/btc/wallet/WalletService.java b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
index 7b0566cb3c..72a670df88 100644
--- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
@@ -24,6 +24,7 @@ import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.provider.fee.FeeService;
import bisq.core.user.Preferences;
@@ -359,8 +360,8 @@ public abstract class WalletService {
if (pubKey instanceof DeterministicKey)
propTx.keyPaths.put(scriptPubKey, (((DeterministicKey) pubKey).getPath()));
- ECKey key;
- if ((key = redeemData.getFullKey()) == null) {
+ ECKey key = LowRSigningKey.from(redeemData.getFullKey());
+ if (key == null) {
log.warn("No local key found for input {}", index);
return;
}
diff --git a/core/src/main/java/bisq/core/crypto/CountingHMacDSAKCalculator.java b/core/src/main/java/bisq/core/crypto/CountingHMacDSAKCalculator.java
new file mode 100644
index 0000000000..342f6ec7eb
--- /dev/null
+++ b/core/src/main/java/bisq/core/crypto/CountingHMacDSAKCalculator.java
@@ -0,0 +1,80 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.crypto;
+
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.prng.EntropySource;
+import org.bouncycastle.crypto.prng.drbg.HMacSP800DRBG;
+import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
+
+import java.math.BigInteger;
+
+import java.util.Arrays;
+
+public class CountingHMacDSAKCalculator extends DeterministicDSAKCalculator {
+ private final HMac hMac = new HMac(new SHA256Digest());
+ private SP80090DRBG drbg;
+ private BigInteger n;
+ private int counter;
+
+ @Override
+ void init(BigInteger n, BigInteger d, BigInteger e) {
+ int len = (n.bitLength() + 7) / 8;
+ int securityStrength = Math.min(n.bitLength(), 256);
+ byte[] dBytes = toByteArrayBE(d, len);
+ byte[] eBytes = toByteArrayBE(e, len);
+ byte[] additionalData = counter != 0 ? toByteArrayLE(counter, hMac.getMacSize()) : null;
+ drbg = new HMacSP800DRBG(hMac, securityStrength, getEntropySource(dBytes), additionalData, eBytes);
+ this.n = n;
+ }
+
+ @Override
+ public BigInteger nextK() {
+ byte[] kBytes = new byte[(n.bitLength() + 7) / 8];
+ BigInteger k;
+ do {
+ drbg.generate(kBytes, null, false);
+ } while (!kRange.contains(k = toScalar(kBytes, n)));
+ counter++;
+ return k;
+ }
+
+ private static byte[] toByteArrayLE(int i, int len) {
+ return Arrays.copyOf(new byte[]{(byte) i, (byte) (i >> 8), (byte) (i >> 16), (byte) (i >> 24)}, len);
+ }
+
+ private EntropySource getEntropySource(byte[] dBytes) {
+ return new EntropySource() {
+ @Override
+ public boolean isPredictionResistant() {
+ return false;
+ }
+
+ @Override
+ public byte[] getEntropy() {
+ return dBytes;
+ }
+
+ @Override
+ public int entropySize() {
+ return dBytes.length * 8;
+ }
+ };
+ }
+}
diff --git a/core/src/main/java/bisq/core/crypto/DeterministicDSAKCalculator.java b/core/src/main/java/bisq/core/crypto/DeterministicDSAKCalculator.java
new file mode 100644
index 0000000000..7b43296d80
--- /dev/null
+++ b/core/src/main/java/bisq/core/crypto/DeterministicDSAKCalculator.java
@@ -0,0 +1,59 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.crypto;
+
+import com.google.common.collect.Range;
+
+import org.bouncycastle.crypto.signers.DSAKCalculator;
+
+import java.security.SecureRandom;
+
+import java.math.BigInteger;
+
+import java.util.Arrays;
+
+public abstract class DeterministicDSAKCalculator implements DSAKCalculator {
+ Range kRange;
+
+ @Override
+ public boolean isDeterministic() {
+ return true;
+ }
+
+ @Override
+ public void init(BigInteger n, SecureRandom random) {
+ throw new IllegalStateException("Operation not supported");
+ }
+
+ @Override
+ public void init(BigInteger n, BigInteger d, byte[] message) {
+ kRange = Range.closedOpen(BigInteger.ONE, n);
+ init(n, d, toScalar(message, n).mod(n));
+ }
+
+ abstract void init(BigInteger n, BigInteger d, BigInteger e);
+
+ static byte[] toByteArrayBE(BigInteger k, int len) {
+ byte[] guardedKBytes = k.or(BigInteger.ONE.shiftLeft(len * 8)).toByteArray();
+ return Arrays.copyOfRange(guardedKBytes, guardedKBytes.length - len, guardedKBytes.length);
+ }
+
+ static BigInteger toScalar(byte[] kBytes, BigInteger n) {
+ return new BigInteger(1, kBytes).shiftRight(Math.max(kBytes.length * 8 - n.bitLength(), 0));
+ }
+}
diff --git a/core/src/main/java/bisq/core/crypto/LowRSigningKey.java b/core/src/main/java/bisq/core/crypto/LowRSigningKey.java
new file mode 100644
index 0000000000..498c93241c
--- /dev/null
+++ b/core/src/main/java/bisq/core/crypto/LowRSigningKey.java
@@ -0,0 +1,66 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.crypto;
+
+import org.bitcoinj.core.ECKey;
+import org.bitcoinj.core.Sha256Hash;
+import org.bitcoinj.crypto.KeyCrypterException;
+import org.bitcoinj.crypto.LazyECPoint;
+
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.signers.DSAKCalculator;
+import org.bouncycastle.crypto.signers.ECDSASigner;
+
+import java.math.BigInteger;
+
+public class LowRSigningKey extends ECKey {
+ private final ECKey originalKey;
+
+ protected LowRSigningKey(ECKey key) {
+ super(key.hasPrivKey() ? key.getPrivKey() : null, new LazyECPoint(CURVE.getCurve(), key.getPubKey()));
+ this.keyCrypter = key.getKeyCrypter();
+ this.encryptedPrivateKey = key.getEncryptedPrivateKey();
+ originalKey = key;
+ }
+
+ public static LowRSigningKey from(ECKey key) {
+ return key != null ? key instanceof LowRSigningKey ? (LowRSigningKey) key : new LowRSigningKey(key) : null;
+ }
+
+ @Override
+ public LowRSigningKey decrypt(KeyParameter aesKey) throws KeyCrypterException {
+ return new LowRSigningKey(originalKey.decrypt(aesKey));
+ }
+
+ @Override
+ protected ECDSASignature doSign(Sha256Hash input, BigInteger privateKeyForSigning) {
+ return doSign(input, privateKeyForSigning, new CountingHMacDSAKCalculator());
+ }
+
+ protected ECDSASignature doSign(Sha256Hash input, BigInteger privateKeyForSigning, DSAKCalculator kCalculator) {
+ ECDSASigner signer = new ECDSASigner(kCalculator);
+ ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, CURVE);
+ signer.init(true, privKey);
+ BigInteger[] components;
+ do {
+ components = signer.generateSignature(input.getBytes());
+ } while (components[0].bitLength() >= 256);
+ return new ECDSASignature(components[0], components[1]).toCanonicalised();
+ }
+}
diff --git a/core/src/main/java/bisq/core/dao/SignVerifyService.java b/core/src/main/java/bisq/core/dao/SignVerifyService.java
index cc41a0a529..1d7501d807 100644
--- a/core/src/main/java/bisq/core/dao/SignVerifyService.java
+++ b/core/src/main/java/bisq/core/dao/SignVerifyService.java
@@ -18,6 +18,7 @@
package bisq.core.dao;
import bisq.core.btc.wallet.BsqWalletService;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Tx;
@@ -76,7 +77,7 @@ public class SignVerifyService {
public Optional sign(String txId, String message) {
byte[] pubKey = getPubKey(txId);
- ECKey key = bsqWalletService.findKeyFromPubKey(pubKey);
+ ECKey key = LowRSigningKey.from(bsqWalletService.findKeyFromPubKey(pubKey));
if (key == null)
return Optional.empty();
diff --git a/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNode.java b/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNode.java
index 782eda9f38..2e9d71a78e 100644
--- a/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNode.java
+++ b/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNode.java
@@ -17,6 +17,7 @@
package bisq.core.dao.burningman.accounting.node;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.dao.DaoSetupService;
import bisq.core.dao.burningman.accounting.BurningManAccountingService;
import bisq.core.dao.burningman.accounting.blockchain.AccountingBlock;
@@ -80,7 +81,7 @@ public abstract class AccountingNode implements DaoSetupService, DaoStateListene
}
public static byte[] getSignature(Sha256Hash sha256Hash, ECKey privKey) {
- ECKey.ECDSASignature ecdsaSignature = privKey.sign(sha256Hash);
+ ECKey.ECDSASignature ecdsaSignature = LowRSigningKey.from(privKey).sign(sha256Hash);
return ecdsaSignature.encodeToDER();
}
diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java
index 5bf858eca6..9c5c1aded2 100644
--- a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java
+++ b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java
@@ -24,6 +24,7 @@ import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletsManager;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.dao.DaoSetupService;
import bisq.core.dao.exceptions.PublishToP2PNetworkException;
import bisq.core.dao.governance.ballot.BallotListService;
@@ -61,7 +62,6 @@ import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
-import org.bitcoinj.crypto.DeterministicKey;
import javax.inject.Inject;
@@ -228,7 +228,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
publishTx(resultHandler, exceptionHandler, blindVoteTx);
} catch (CryptoException | TransactionVerificationException | InsufficientMoneyException |
- WalletException | IOException exception) {
+ WalletException | IOException exception) {
log.error(exception.toString());
exception.printStackTrace();
exceptionHandler.handleException(exception);
@@ -299,7 +299,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
return null;
}
- DeterministicKey key = bsqWalletService.findKeyFromPubKey(Utilities.decodeFromHex(pubKey));
+ ECKey key = LowRSigningKey.from(bsqWalletService.findKeyFromPubKey(Utilities.decodeFromHex(pubKey)));
if (key == null) {
// Maybe add exception
log.error("We did not find the key for our compensation request. txId={}",
@@ -318,7 +318,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
ECKey.ECDSASignature signature = bsqWalletService.isEncrypted() ?
key.sign(Sha256Hash.wrap(blindVoteTxId), bsqWalletService.getAesKey()) :
key.sign(Sha256Hash.wrap(blindVoteTxId));
- signatureAsBytes = signature.toCanonicalised().encodeToDER();
+ signatureAsBytes = signature.encodeToDER();
} else {
// In case we use it for requesting the currently available merit we don't apply a signature
signatureAsBytes = new byte[0];
diff --git a/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java
index b8f41358c7..f35a5abc62 100644
--- a/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java
+++ b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java
@@ -24,6 +24,7 @@ import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletsManager;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.dao.DaoSetupService;
import bisq.core.dao.governance.proposal.TxException;
import bisq.core.dao.state.DaoStateListener;
@@ -73,7 +74,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener {
private final DaoStateService daoStateService;
@Getter
- private IntegerProperty updateFlag = new SimpleIntegerProperty(0);
+ private final IntegerProperty updateFlag = new SimpleIntegerProperty(0);
@Getter
private final List proofOfBurnTxList = new ArrayList<>();
@@ -194,7 +195,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener {
public Optional sign(String proofOfBurnTxId, String message) {
byte[] pubKey = getPubKey(proofOfBurnTxId);
- ECKey key = bsqWalletService.findKeyFromPubKey(pubKey);
+ ECKey key = LowRSigningKey.from(bsqWalletService.findKeyFromPubKey(pubKey));
if (key == null)
return Optional.empty();
diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java
index 77c58391a5..7d553916d6 100644
--- a/core/src/main/java/bisq/core/filter/FilterManager.java
+++ b/core/src/main/java/bisq/core/filter/FilterManager.java
@@ -18,6 +18,7 @@
package bisq.core.filter;
import bisq.core.btc.nodes.BtcNodes;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentAccountPayload;
@@ -654,7 +655,7 @@ public class FilterManager {
private String getSignature(Filter filterWithoutSig) {
Sha256Hash hash = getSha256Hash(filterWithoutSig);
- ECKey.ECDSASignature ecdsaSignature = filterSigningKey.sign(hash);
+ ECKey.ECDSASignature ecdsaSignature = LowRSigningKey.from(filterSigningKey).sign(hash);
byte[] encodeToDER = ecdsaSignature.encodeToDER();
return new String(Base64.encode(encodeToDER), StandardCharsets.UTF_8);
}
diff --git a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java
index a725927ab4..6fbd3a5207 100644
--- a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java
@@ -17,6 +17,7 @@
package bisq.core.support.dispute.agent;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.filter.FilterManager;
import bisq.core.user.User;
@@ -257,7 +258,7 @@ public abstract class DisputeAgentManager {
// Other users will check the signature with the list of public keys hardcoded in the app.
public String signStorageSignaturePubKey(ECKey key) {
String keyToSignAsHex = Utils.HEX.encode(keyRing.getPubKeyRing().getSignaturePubKey().getEncoded());
- return key.signMessage(keyToSignAsHex);
+ return LowRSigningKey.from(key).signMessage(keyToSignAsHex);
}
@Nullable
diff --git a/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java b/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java
index 4e372321dd..45a807640e 100644
--- a/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java
+++ b/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java
@@ -19,6 +19,7 @@ package bisq.core.account.sign;
import bisq.core.account.witness.AccountAgeWitness;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.filter.FilterManager;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
@@ -104,7 +105,7 @@ public class SignedWitnessServiceTest {
aew1 = new AccountAgeWitness(account1DataHash, account1CreationTime);
aew2 = new AccountAgeWitness(account2DataHash, account2CreationTime);
aew3 = new AccountAgeWitness(account3DataHash, account3CreationTime);
- arbitrator1Key = new ECKey();
+ arbitrator1Key = LowRSigningKey.from(new ECKey());
peer1KeyPair = Sig.generateKeyPair();
peer2KeyPair = Sig.generateKeyPair();
peer3KeyPair = Sig.generateKeyPair();
@@ -320,7 +321,7 @@ public class SignedWitnessServiceTest {
byte[] signerPubKey;
if (i == 0) {
// use arbitrator key
- ECKey arbitratorKey = new ECKey();
+ ECKey arbitratorKey = LowRSigningKey.from(new ECKey());
signedKeyPair = Sig.generateKeyPair();
String signature1String = arbitratorKey.signMessage(accountDataHashAsHexString);
signature = signature1String.getBytes(Charsets.UTF_8);
diff --git a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java
index e0abc989a8..9796c3b1c9 100644
--- a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java
+++ b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java
@@ -19,6 +19,7 @@ package bisq.core.account.witness;
import bisq.core.account.sign.SignedWitness;
import bisq.core.account.sign.SignedWitnessService;
+import bisq.core.crypto.LowRSigningKey;
import bisq.core.filter.FilterManager;
import bisq.core.locale.CountryUtil;
import bisq.core.offer.bisq_v1.OfferPayload;
@@ -236,7 +237,7 @@ public class AccountAgeWitnessServiceTest {
assertEquals(2, items.size());
// Setup a mocked arbitrator key
- ECKey arbitratorKey = mock(ECKey.class);
+ ECKey arbitratorKey = mock(LowRSigningKey.class);
when(arbitratorKey.signMessage(any())).thenReturn("1");
when(arbitratorKey.signMessage(any())).thenReturn("2");
when(arbitratorKey.getPubKey()).thenReturn("1".getBytes());
@@ -250,14 +251,14 @@ public class AccountAgeWitnessServiceTest {
// Check that both accountAgeWitnesses are signed
SignedWitness foundBuyerSignedWitness = signedWitnessService.getSignedWitnessSetByOwnerPubKey(
- buyerPubKeyRing.getSignaturePubKeyBytes()).stream()
+ buyerPubKeyRing.getSignaturePubKeyBytes()).stream()
.findFirst()
.orElse(null);
assert foundBuyerSignedWitness != null;
assertEquals(Utilities.bytesAsHexString(foundBuyerSignedWitness.getAccountAgeWitnessHash()),
Utilities.bytesAsHexString(buyerAccountAgeWitness.getHash()));
SignedWitness foundSellerSignedWitness = signedWitnessService.getSignedWitnessSetByOwnerPubKey(
- sellerPubKeyRing.getSignaturePubKeyBytes()).stream()
+ sellerPubKeyRing.getSignaturePubKeyBytes()).stream()
.findFirst()
.orElse(null);
assert foundSellerSignedWitness != null;
diff --git a/core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java b/core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java
new file mode 100644
index 0000000000..7ff1d796c2
--- /dev/null
+++ b/core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java
@@ -0,0 +1,110 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.crypto;
+
+import org.bitcoinj.core.Sha256Hash;
+
+import java.nio.charset.StandardCharsets;
+
+import java.math.BigInteger;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class CountingHMacDSAKCalculatorTest {
+ // Taken from https://www.rfc-editor.org/rfc/rfc6979:
+ private static final String[][] RFC_6979_SHA_256_ECDSA_TEST_VECTORS = {
+ // NIST P-192
+ {"ffffffffffffffffffffffff99def836146bc9b1b4d22831", "6fab034934e4c0fc9ae67f5b5659a9d7d1fefd187ee09fd4", "sample", "32b1b6d7d42a05cb449065727a84804fb1a3e34d8f261496"},
+ {"ffffffffffffffffffffffff99def836146bc9b1b4d22831", "6fab034934e4c0fc9ae67f5b5659a9d7d1fefd187ee09fd4", "test", "5c4ce89cf56d9e7c77c8585339b006b97b5f0680b4306c6c"},
+ // NIST P-224
+ {"ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", "f220266e1105bfe3083e03ec7a3a654651f45e37167e88600bf257c1", "sample", "ad3029e0278f80643de33917ce6908c70a8ff50a411f06e41dedfcdc"},
+ {"ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", "f220266e1105bfe3083e03ec7a3a654651f45e37167e88600bf257c1", "test", "ff86f57924da248d6e44e8154eb69f0ae2aebaee9931d0b5a969f904"},
+ // NIST p-256
+ {"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", "sample", "a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60"},
+ {"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", "test", "d16b6ae827f17175e040871a1c7ec3500192c4c92677336ec2537acaee0008e0"},
+ // NIST P-384
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", "6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d896d5724e4c70a825f872c9ea60d2edf5", "sample", "180ae9f9aec5438a44bc159a1fcb277c7be54fa20e7cf404b490650a8acc414e375572342863c899f9f2edf9747a9b60"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", "6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d896d5724e4c70a825f872c9ea60d2edf5", "test", "0cfac37587532347dc3389fdc98286bba8c73807285b184c83e62e26c401c0faa48dd070ba79921a3457abff2d630ad7"},
+ // NIST P-521
+ {"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", "0fad06daa62ba3b25d2fb40133da757205de67f5bb0018fee8c86e1b68c7e75caa896eb32f1f47c70855836a6d16fcc1466f6d8fbec67db89ec0c08b0e996b83538", "sample", "0edf38afcaaecab4383358b34d67c9f2216c8382aaea44a3dad5fdc9c32575761793fef24eb0fc276dfc4f6e3ec476752f043cf01415387470bcbd8678ed2c7e1a0"},
+ {"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", "0fad06daa62ba3b25d2fb40133da757205de67f5bb0018fee8c86e1b68c7e75caa896eb32f1f47c70855836a6d16fcc1466f6d8fbec67db89ec0c08b0e996b83538", "test", "01de74955efaabc4c4f17f8e84d881d1310b5392d7700275f82f145c61e843841af09035bf7a6210f5a431a6a9e81c9323354a9e69135d44ebd2fcaa7731b909258"},
+ // NIST K-163
+ {"4000000000000000000020108a2e0cc0d99f8a5ef", "09a4d6792295a7f730fc3f2b49cbc0f62e862272f", "sample", "23af4074c90a02b3fe61d286d5c87f425e6bdd81b"},
+ {"4000000000000000000020108a2e0cc0d99f8a5ef", "09a4d6792295a7f730fc3f2b49cbc0f62e862272f", "test", "193649ce51f0cff0784cfc47628f4fa854a93f7a2"},
+ // NIST K-233
+ {"8000000000000000000000000000069d5bb915bcd46efb1ad5f173abdf", "103b2142bdc2a3c3b55080d09df1808f79336da2399f5ca7171d1be9b0", "sample", "73552f9cac5774f74f485fa253871f2109a0c86040552eaa67dba92dc9"},
+ {"8000000000000000000000000000069d5bb915bcd46efb1ad5f173abdf", "103b2142bdc2a3c3b55080d09df1808f79336da2399f5ca7171d1be9b0", "test", "2ce5aedc155acc0ddc5e679ebacfd21308362e5efc05c5e99b2557a8d7"},
+ // NIST K-283
+ {"1ffffffffffffffffffffffffffffffffffe9ae2ed07577265dff7f94451e061e163c61", "06a0777356e87b89ba1ed3a3d845357be332173c8f7a65bdc7db4fab3c4cc79acc8194e", "sample", "1ceb9e8e0dff53ce687deb81339aca3c98e7a657d5a9499ef779f887a934408ecbe5a38"},
+ {"1ffffffffffffffffffffffffffffffffffe9ae2ed07577265dff7f94451e061e163c61", "06a0777356e87b89ba1ed3a3d845357be332173c8f7a65bdc7db4fab3c4cc79acc8194e", "test", "0b585a7a68f51089691d6ede2b43fc4451f66c10e65f134b963d4cbd4eb844b0e1469a6"},
+ // NIST K-409
+ {"7ffffffffffffffffffffffffffffffffffffffffffffffffffe5f83b2d4ea20400ec4557d5ed3e3e7ca5b4b5c83b8e01e5fcf", "29c16768f01d1b8a89fda85e2efd73a09558b92a178a2931f359e4d70ad853e569cdaf16daa569758fb4e73089e4525d8bbfcf", "sample", "782385f18baf5a36a588637a76dfab05739a14163bf723a4417b74bd1469d37ac9e8cce6aec8ff63f37b815aaf14a876eed962"},
+ {"7ffffffffffffffffffffffffffffffffffffffffffffffffffe5f83b2d4ea20400ec4557d5ed3e3e7ca5b4b5c83b8e01e5fcf", "29c16768f01d1b8a89fda85e2efd73a09558b92a178a2931f359e4d70ad853e569cdaf16daa569758fb4e73089e4525d8bbfcf", "test", "251e32dee10ed5ea4ad7370df3eff091e467d5531ca59de3aa791763715e1169ab5e18c2a11cd473b0044fb45308e8542f2eb0"},
+ // NIST K-571
+ {"20000000000000000000000000000000000000000000000000000000000000000000000131850e1f19a63e4b391a8db917f4138b630d84be5d639381e91deb45cfe778f637c1001", "0c16f58550d824ed7b95569d4445375d3a490bc7e0194c41a39deb732c29396cdf1d66de02dd1460a816606f3bec0f32202c7bd18a32d87506466aa92032f1314ed7b19762b0d22", "sample", "0f79d53e63d89fb87f4d9e6dc5949f5d9388bcfe9ebcb4c2f7ce497814cf40e845705f8f18dbf0f860de0b1cc4a433ef74a5741f3202e958c082e0b76e16ecd5866aa0f5f3df300"},
+ {"20000000000000000000000000000000000000000000000000000000000000000000000131850e1f19a63e4b391a8db917f4138b630d84be5d639381e91deb45cfe778f637c1001", "0c16f58550d824ed7b95569d4445375d3a490bc7e0194c41a39deb732c29396cdf1d66de02dd1460a816606f3bec0f32202c7bd18a32d87506466aa92032f1314ed7b19762b0d22", "test", "04ddd0707e81bb56ea2d1d45d7fafdbdd56912cae224086802fea1018db306c4fb8d93338dbf6841ce6c6ab1506e9a848d2c0463e0889268843dee4acb552cffcb858784ed116b2"},
+ // NIST B-163
+ {"40000000000000000000292fe77e70c12a4234c33", "35318fc447d48d7e6bc93b48617dddedf26aa658f", "sample", "3d7086a59e6981064a9cdb684653f3a81b6ec0f0b"},
+ {"40000000000000000000292fe77e70c12a4234c33", "35318fc447d48d7e6bc93b48617dddedf26aa658f", "test", "38145e3ffca94e4ddacc20ad6e0997bd0e3b669d2"},
+ // NIST B-233
+ {"1000000000000000000000000000013e974e72f8a6922031d2603cfe0d7", "07adc13dd5bf34d1ddeeb50b2ce23b5f5e6d18067306d60c5f6ff11e5d3", "sample", "034a53897b0bbdb484302e19bf3f9b34a2abfed639d109a388dc52006b5"},
+ {"1000000000000000000000000000013e974e72f8a6922031d2603cfe0d7", "07adc13dd5bf34d1ddeeb50b2ce23b5f5e6d18067306d60c5f6ff11e5d3", "test", "00376886e89013f7ff4b5214d56a30d49c99f53f211a3afe01aa2bde12d"},
+ // NIST B-283
+ {"3ffffffffffffffffffffffffffffffffffef90399660fc938a90165b042a7cefadb307", "14510d4bc44f2d26f4553942c98073c1bd35545ceabb5cc138853c5158d2729ea408836", "sample", "38c9d662188982943e080b794a4cfb0732dba37c6f40d5b8cfaded6ff31c5452ba3f877"},
+ {"3ffffffffffffffffffffffffffffffffffef90399660fc938a90165b042a7cefadb307", "14510d4bc44f2d26f4553942c98073c1bd35545ceabb5cc138853c5158d2729ea408836", "test", "018a7d44f2b4341fefe68f6bd8894960f97e08124aab92c1ffbbe90450fcc9356c9aaa5"},
+ // NIST B-409
+ {"10000000000000000000000000000000000000000000000000001e2aad6a612f33307be5fa47c3c9e052f838164cd37d9a21173", "0494994cc325b08e7b4ce038bd9436f90b5e59a2c13c3140cd3ae07c04a01fc489f572ce0569a6db7b8060393de76330c624177", "sample", "08ec42d13a3909a20c41bebd2dfed8cacce56c7a7d1251df43f3e9e289dae00e239f6960924ac451e125b784cb687c7f23283fd"},
+ {"10000000000000000000000000000000000000000000000000001e2aad6a612f33307be5fa47c3c9e052f838164cd37d9a21173", "0494994cc325b08e7b4ce038bd9436f90b5e59a2c13c3140cd3ae07c04a01fc489f572ce0569a6db7b8060393de76330c624177", "test", "06eba3d58d0e0dfc406d67fc72ef0c943624cf40019d1e48c3b54ccab0594afd5dee30aebaa22e693dbcfecad1a85d774313dad"},
+ // NIST B-571
+ {"3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe661ce18ff55987308059b186823851ec7dd9ca1161de93d5174d66e8382e9bb2fe84e47", "028a04857f24c1c082df0d909c0e72f453f2e2340ccb071f0e389bca2575da19124198c57174929ad26e348cf63f78d28021ef5a9bf2d5cbeaf6b7ccb6c4da824dd5c82cfb24e11", "sample", "15c2c6b7d1a070274484774e558b69fdfa193bdb7a23f27c2cd24298ce1b22a6cc9b7fb8cabfd6cf7c6b1cf3251e5a1cddd16fbfed28de79935bb2c631b8b8ea9cc4bcc937e669e"},
+ {"3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe661ce18ff55987308059b186823851ec7dd9ca1161de93d5174d66e8382e9bb2fe84e47", "028a04857f24c1c082df0d909c0e72f453f2e2340ccb071f0e389bca2575da19124198c57174929ad26e348cf63f78d28021ef5a9bf2d5cbeaf6b7ccb6c4da824dd5c82cfb24e11", "test", "328e02cf07c7b5b6d3749d8302f1ae5bfaa8f239398459af4a2c859c7727a8123a7fe9be8b228413fc8dc0e9de16af3f8f43005107f9989a5d97a5c4455da895e81336710a3fb2c"}
+ };
+
+ static Stream