From 18a0c32708e28484be0f2e66b207a9d9d92fcb10 Mon Sep 17 00:00:00 2001 From: Steven Barclay Date: Wed, 4 Sep 2024 14:14:23 +0800 Subject: [PATCH 1/3] Add subclass of ECKey which grinds for low-R signatures Implement low-R nonce grinding with the class 'LowRSigningKey', which always produces low-R signatures, consistent with the behaviour of Bitcoin Core/Knots (post-2018), Sparrow, Coldcard and possibly other wallets with recent updates, for hopefully improved privacy and slightly lower and more predictable tx fees. Canonical DER-encoded signatures are usually either 71 or 70 bytes (starting with hex 3045 or 3044 resp.), with roughly 50-50 odds, depending on whether they are high-R or low-R. (Less than 1% of the time, they will be shorter than 70 bytes, because of the variable length bigint R & S encodings.) So trying different nonces for low-R saves half-a-byte on average, at the cost of doubling the average signing time. To this end, provide the class 'CountingHMacDSAKCalculator' to supply a custom nonce to 'o.b.c.s.ECDSASigner'. The first invocation of the k-calculator instance matches the output of the RFC 6979 compliant Bouncy Castle version, but subsequent invocations increment an internal counter supplied as additional data/entropy to the HMAC, as mentioned in section 3.6 of the RFC, until a low-R signature results. In this way, a deterministic signing algorithm exactly matching that of (post-2018 versions of) Bitcoin Core results. Also add unit tests, with test vectors taken from the RFC (which only covers the NIST curves, unfortunately, not secp256k1), and test vectors generated from signed txs created by the 'bitcoin-tx' command. --- .../crypto/CountingHMacDSAKCalculator.java | 80 ++++++++ .../crypto/DeterministicDSAKCalculator.java | 59 ++++++ .../java/bisq/core/crypto/LowRSigningKey.java | 66 +++++++ .../CountingHMacDSAKCalculatorTest.java | 110 +++++++++++ .../bisq/core/crypto/LowRSigningKeyTest.java | 177 ++++++++++++++++++ 5 files changed, 492 insertions(+) create mode 100644 core/src/main/java/bisq/core/crypto/CountingHMacDSAKCalculator.java create mode 100644 core/src/main/java/bisq/core/crypto/DeterministicDSAKCalculator.java create mode 100644 core/src/main/java/bisq/core/crypto/LowRSigningKey.java create mode 100644 core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java create mode 100644 core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java 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 00000000000..342f6ec7ebc --- /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 00000000000..7b43296d80d --- /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 00000000000..c907863db6f --- /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 ? 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/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java b/core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java new file mode 100644 index 00000000000..7ff1d796c24 --- /dev/null +++ b/core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java @@ -0,0 +1,110 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.crypto; + +import org.bitcoinj.core.Sha256Hash; + +import java.nio.charset.StandardCharsets; + +import java.math.BigInteger; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CountingHMacDSAKCalculatorTest { + // Taken from https://www.rfc-editor.org/rfc/rfc6979: + private static final String[][] RFC_6979_SHA_256_ECDSA_TEST_VECTORS = { + // NIST P-192 + {"ffffffffffffffffffffffff99def836146bc9b1b4d22831", "6fab034934e4c0fc9ae67f5b5659a9d7d1fefd187ee09fd4", "sample", "32b1b6d7d42a05cb449065727a84804fb1a3e34d8f261496"}, + {"ffffffffffffffffffffffff99def836146bc9b1b4d22831", "6fab034934e4c0fc9ae67f5b5659a9d7d1fefd187ee09fd4", "test", "5c4ce89cf56d9e7c77c8585339b006b97b5f0680b4306c6c"}, + // NIST P-224 + {"ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", "f220266e1105bfe3083e03ec7a3a654651f45e37167e88600bf257c1", "sample", "ad3029e0278f80643de33917ce6908c70a8ff50a411f06e41dedfcdc"}, + {"ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", "f220266e1105bfe3083e03ec7a3a654651f45e37167e88600bf257c1", "test", "ff86f57924da248d6e44e8154eb69f0ae2aebaee9931d0b5a969f904"}, + // NIST p-256 + {"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", "sample", "a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60"}, + {"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", "test", "d16b6ae827f17175e040871a1c7ec3500192c4c92677336ec2537acaee0008e0"}, + // NIST P-384 + {"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", "6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d896d5724e4c70a825f872c9ea60d2edf5", "sample", "180ae9f9aec5438a44bc159a1fcb277c7be54fa20e7cf404b490650a8acc414e375572342863c899f9f2edf9747a9b60"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", "6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d896d5724e4c70a825f872c9ea60d2edf5", "test", "0cfac37587532347dc3389fdc98286bba8c73807285b184c83e62e26c401c0faa48dd070ba79921a3457abff2d630ad7"}, + // NIST P-521 + {"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", "0fad06daa62ba3b25d2fb40133da757205de67f5bb0018fee8c86e1b68c7e75caa896eb32f1f47c70855836a6d16fcc1466f6d8fbec67db89ec0c08b0e996b83538", "sample", "0edf38afcaaecab4383358b34d67c9f2216c8382aaea44a3dad5fdc9c32575761793fef24eb0fc276dfc4f6e3ec476752f043cf01415387470bcbd8678ed2c7e1a0"}, + {"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", "0fad06daa62ba3b25d2fb40133da757205de67f5bb0018fee8c86e1b68c7e75caa896eb32f1f47c70855836a6d16fcc1466f6d8fbec67db89ec0c08b0e996b83538", "test", "01de74955efaabc4c4f17f8e84d881d1310b5392d7700275f82f145c61e843841af09035bf7a6210f5a431a6a9e81c9323354a9e69135d44ebd2fcaa7731b909258"}, + // NIST K-163 + {"4000000000000000000020108a2e0cc0d99f8a5ef", "09a4d6792295a7f730fc3f2b49cbc0f62e862272f", "sample", "23af4074c90a02b3fe61d286d5c87f425e6bdd81b"}, + {"4000000000000000000020108a2e0cc0d99f8a5ef", "09a4d6792295a7f730fc3f2b49cbc0f62e862272f", "test", "193649ce51f0cff0784cfc47628f4fa854a93f7a2"}, + // NIST K-233 + {"8000000000000000000000000000069d5bb915bcd46efb1ad5f173abdf", "103b2142bdc2a3c3b55080d09df1808f79336da2399f5ca7171d1be9b0", "sample", "73552f9cac5774f74f485fa253871f2109a0c86040552eaa67dba92dc9"}, + {"8000000000000000000000000000069d5bb915bcd46efb1ad5f173abdf", "103b2142bdc2a3c3b55080d09df1808f79336da2399f5ca7171d1be9b0", "test", "2ce5aedc155acc0ddc5e679ebacfd21308362e5efc05c5e99b2557a8d7"}, + // NIST K-283 + {"1ffffffffffffffffffffffffffffffffffe9ae2ed07577265dff7f94451e061e163c61", "06a0777356e87b89ba1ed3a3d845357be332173c8f7a65bdc7db4fab3c4cc79acc8194e", "sample", "1ceb9e8e0dff53ce687deb81339aca3c98e7a657d5a9499ef779f887a934408ecbe5a38"}, + {"1ffffffffffffffffffffffffffffffffffe9ae2ed07577265dff7f94451e061e163c61", "06a0777356e87b89ba1ed3a3d845357be332173c8f7a65bdc7db4fab3c4cc79acc8194e", "test", "0b585a7a68f51089691d6ede2b43fc4451f66c10e65f134b963d4cbd4eb844b0e1469a6"}, + // NIST K-409 + {"7ffffffffffffffffffffffffffffffffffffffffffffffffffe5f83b2d4ea20400ec4557d5ed3e3e7ca5b4b5c83b8e01e5fcf", "29c16768f01d1b8a89fda85e2efd73a09558b92a178a2931f359e4d70ad853e569cdaf16daa569758fb4e73089e4525d8bbfcf", "sample", "782385f18baf5a36a588637a76dfab05739a14163bf723a4417b74bd1469d37ac9e8cce6aec8ff63f37b815aaf14a876eed962"}, + {"7ffffffffffffffffffffffffffffffffffffffffffffffffffe5f83b2d4ea20400ec4557d5ed3e3e7ca5b4b5c83b8e01e5fcf", "29c16768f01d1b8a89fda85e2efd73a09558b92a178a2931f359e4d70ad853e569cdaf16daa569758fb4e73089e4525d8bbfcf", "test", "251e32dee10ed5ea4ad7370df3eff091e467d5531ca59de3aa791763715e1169ab5e18c2a11cd473b0044fb45308e8542f2eb0"}, + // NIST K-571 + {"20000000000000000000000000000000000000000000000000000000000000000000000131850e1f19a63e4b391a8db917f4138b630d84be5d639381e91deb45cfe778f637c1001", "0c16f58550d824ed7b95569d4445375d3a490bc7e0194c41a39deb732c29396cdf1d66de02dd1460a816606f3bec0f32202c7bd18a32d87506466aa92032f1314ed7b19762b0d22", "sample", "0f79d53e63d89fb87f4d9e6dc5949f5d9388bcfe9ebcb4c2f7ce497814cf40e845705f8f18dbf0f860de0b1cc4a433ef74a5741f3202e958c082e0b76e16ecd5866aa0f5f3df300"}, + {"20000000000000000000000000000000000000000000000000000000000000000000000131850e1f19a63e4b391a8db917f4138b630d84be5d639381e91deb45cfe778f637c1001", "0c16f58550d824ed7b95569d4445375d3a490bc7e0194c41a39deb732c29396cdf1d66de02dd1460a816606f3bec0f32202c7bd18a32d87506466aa92032f1314ed7b19762b0d22", "test", "04ddd0707e81bb56ea2d1d45d7fafdbdd56912cae224086802fea1018db306c4fb8d93338dbf6841ce6c6ab1506e9a848d2c0463e0889268843dee4acb552cffcb858784ed116b2"}, + // NIST B-163 + {"40000000000000000000292fe77e70c12a4234c33", "35318fc447d48d7e6bc93b48617dddedf26aa658f", "sample", "3d7086a59e6981064a9cdb684653f3a81b6ec0f0b"}, + {"40000000000000000000292fe77e70c12a4234c33", "35318fc447d48d7e6bc93b48617dddedf26aa658f", "test", "38145e3ffca94e4ddacc20ad6e0997bd0e3b669d2"}, + // NIST B-233 + {"1000000000000000000000000000013e974e72f8a6922031d2603cfe0d7", "07adc13dd5bf34d1ddeeb50b2ce23b5f5e6d18067306d60c5f6ff11e5d3", "sample", "034a53897b0bbdb484302e19bf3f9b34a2abfed639d109a388dc52006b5"}, + {"1000000000000000000000000000013e974e72f8a6922031d2603cfe0d7", "07adc13dd5bf34d1ddeeb50b2ce23b5f5e6d18067306d60c5f6ff11e5d3", "test", "00376886e89013f7ff4b5214d56a30d49c99f53f211a3afe01aa2bde12d"}, + // NIST B-283 + {"3ffffffffffffffffffffffffffffffffffef90399660fc938a90165b042a7cefadb307", "14510d4bc44f2d26f4553942c98073c1bd35545ceabb5cc138853c5158d2729ea408836", "sample", "38c9d662188982943e080b794a4cfb0732dba37c6f40d5b8cfaded6ff31c5452ba3f877"}, + {"3ffffffffffffffffffffffffffffffffffef90399660fc938a90165b042a7cefadb307", "14510d4bc44f2d26f4553942c98073c1bd35545ceabb5cc138853c5158d2729ea408836", "test", "018a7d44f2b4341fefe68f6bd8894960f97e08124aab92c1ffbbe90450fcc9356c9aaa5"}, + // NIST B-409 + {"10000000000000000000000000000000000000000000000000001e2aad6a612f33307be5fa47c3c9e052f838164cd37d9a21173", "0494994cc325b08e7b4ce038bd9436f90b5e59a2c13c3140cd3ae07c04a01fc489f572ce0569a6db7b8060393de76330c624177", "sample", "08ec42d13a3909a20c41bebd2dfed8cacce56c7a7d1251df43f3e9e289dae00e239f6960924ac451e125b784cb687c7f23283fd"}, + {"10000000000000000000000000000000000000000000000000001e2aad6a612f33307be5fa47c3c9e052f838164cd37d9a21173", "0494994cc325b08e7b4ce038bd9436f90b5e59a2c13c3140cd3ae07c04a01fc489f572ce0569a6db7b8060393de76330c624177", "test", "06eba3d58d0e0dfc406d67fc72ef0c943624cf40019d1e48c3b54ccab0594afd5dee30aebaa22e693dbcfecad1a85d774313dad"}, + // NIST B-571 + {"3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe661ce18ff55987308059b186823851ec7dd9ca1161de93d5174d66e8382e9bb2fe84e47", "028a04857f24c1c082df0d909c0e72f453f2e2340ccb071f0e389bca2575da19124198c57174929ad26e348cf63f78d28021ef5a9bf2d5cbeaf6b7ccb6c4da824dd5c82cfb24e11", "sample", "15c2c6b7d1a070274484774e558b69fdfa193bdb7a23f27c2cd24298ce1b22a6cc9b7fb8cabfd6cf7c6b1cf3251e5a1cddd16fbfed28de79935bb2c631b8b8ea9cc4bcc937e669e"}, + {"3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe661ce18ff55987308059b186823851ec7dd9ca1161de93d5174d66e8382e9bb2fe84e47", "028a04857f24c1c082df0d909c0e72f453f2e2340ccb071f0e389bca2575da19124198c57174929ad26e348cf63f78d28021ef5a9bf2d5cbeaf6b7ccb6c4da824dd5c82cfb24e11", "test", "328e02cf07c7b5b6d3749d8302f1ae5bfaa8f239398459af4a2c859c7727a8123a7fe9be8b228413fc8dc0e9de16af3f8f43005107f9989a5d97a5c4455da895e81336710a3fb2c"} + }; + + static Stream rfc6979TestVectors() { + return Stream.of(RFC_6979_SHA_256_ECDSA_TEST_VECTORS) + .map(args -> new Object[]{new BigInteger(args[0], 16), new BigInteger(args[1], 16), args[2], + new BigInteger(args[3], 16)}); + } + + @MethodSource("rfc6979TestVectors") + @ParameterizedTest(name = "[{index}] n={0}, d={1}, message={2}, expectedK={3}") + public void testKCalculator(BigInteger n, BigInteger d, String message, BigInteger expectedK) { + byte[] messageHash = Sha256Hash.hash(message.getBytes(StandardCharsets.UTF_8)); + var kCalculator = new CountingHMacDSAKCalculator(); + + // First usage of the k-calculator gives a nonce matching that in the RFC 6979 test vector. + kCalculator.init(n, d, messageHash); + assertEquals(expectedK, kCalculator.nextK()); + + // Further invocations result in distinct nonces, as the internal counter increments. + Set retries = new HashSet<>(); + for (int i = 0; i < 4; i++) { + kCalculator.init(n, d, messageHash); + retries.add(kCalculator.nextK()); + } + retries.add(expectedK); + assertEquals(5, retries.size()); + } +} diff --git a/core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java b/core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java new file mode 100644 index 00000000000..70fa8a92c8b --- /dev/null +++ b/core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java @@ -0,0 +1,177 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.crypto; + +import bisq.common.util.Utilities; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.crypto.TransactionSignature; +import org.bitcoinj.params.MainNetParams; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptBuilder; + +import com.google.common.base.Strings; +import com.google.common.util.concurrent.Futures; + +import java.nio.charset.StandardCharsets; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import java.math.BigInteger; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LowRSigningKeyTest { + /** + * Deterministic low-R signature test vectors of the form {privKey, sigHash, rComponent, sComponent}, generated with + * the aid of the Bitcoin Core tx creation and editing command: 'bitcoin-tx'. The signed txs and subsequent test + * vectors here match those produced by Core/Knots version 27.1 (and likely other post-2018 versions). + * @see TestVectorGeneration + */ + private static final String[][] GENERATED_TEST_VECTORS = { + {"02", "546876d08f9f06f12c168f96079cd0af996ce22d89bbc58eced2631918a9bc9a", "45cc5d5e4fad81f2bc89f16cb37575da3ae13677f707a73ca5ca1e2787e3d311", "25829e0ad206dbd53cd637b75eedbde3273548673b2d08de3f61dcbe723409e2"}, + {"02", "2cca2f04e6e654226483f5af39fbfebb883e301bd32553a6a0fb176b0d172b3a", "2bdcafead8f6db212228e52f061e894db8bdc2133c6c81a5b54883ef5648ae6d", "007bdcc92effb2931b4c7c5c834bf7aa847d3e0e0e1d9c7e41ed3981821eb830"}, + {"02", "d78c70bc5e7f9a74ce4e3000369592a037a8898a93b07a5bdc2d88ed78462521", "1f283dfd1ba17e69dbbe2fe261c3984569316efa781b1a4e846fa7978f0fe918", "180187c9cc33c2849b4770260a1ee379e5c4292ad46bd700c45a108bb4c6f346"}, + {"02", "c65a47a4d448b60cff8bbcf190492a1d5ef6beb217465c69bd358c4460ab84fb", "216301d61b337ca80c62047d349fa85c04b05451586ab0a2034d0855b09209fe", "228d09fe3e5a9a90501def65a8e467a5172c9fdd510c8a5db3a6cdbea000b1cc"}, + {"02", "d5bde8603bada85600e24ae82c9dee32671ac5038f6842558610f5cf3129f21e", "50b0b26bbfe72cacee2c8e1612e057c1855da8320232965137dd67f9eab77523", "26f990be93e0b8ce5ddbe468d0d242c51686c2ad9608ee44b17875b991e23e82"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "340b1fe486af2cd6b0ef8786d1c747fc3d785c5499d35faa25fbde57f9bf70ae", "2c5bc7104c059e2db8cda7b500424d2438ae2635b6cddcc51695a11e7ec95cc7", "62bc8969bfb02cac905f0739cecadc456bf48539a9b268b2c2725d3c2680be88"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "a39e0f59a4d8448ab4a614edb998e3f315d3a4c855855386afdfe664a42a0864", "752066dbf4e862d634440649014ec1fc64fdcea1127320acc09b223b1e7fbc59", "2a613585d5ac1741080380bdf7b151e559ccb37c5521674a77b999c9de0f21e8"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "df0a60d25576d441cdd76f5a3d63fe1308860121e70e832098abac5b6d5ff715", "65f8ccf807faeb46c0a69d1b1774a53081ec11c5e0ffb854620bcb2f2c8098bb", "32feb22792926929f01d51439814ec1bc414c2ab52b36a7c675e50ea37310b78"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "79df48383bdf5d295e510f4aadcc3a3b2ed0c3d454e1b1e4315de7c3f1ea86ab", "588ae9380e85c8c2c565321957aa459a4ae0080331d41145b7dea5214bf91377", "7207f2c4969e19ef5de08d088ea7dcd4c1ba67574cc0ac023dc9e7c48abee8b6"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "44adec39be60bf10994596a0248dcafa78a45ca30472190f0de61523e90ec131", "1bef291f3aa99c551ef5d96d9fd06d945092e185cebe2cda75351e2f27ca3ea2", "2024fda909bfb9b9079f072dbe005ae850a54f16d1b59bc46afc6db2ef5461fe"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "e48ae59f8a200c946c827ab8992f8774dc0d5742d50baaa9f26b39a91f471eda", "39af0095c3cd45cba48891f29e2a69c3ba421c97bbc3181aa1f3699e3deb5234", "19ad75c9f49f42648338ddfac0b3597145db0864b897212d3da0a88b796d09f8"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "9e1a0043b4e941352f8cd957041244bed7f4a616c254ab8ba1a8ce7fba8a798a", "33b8197cfdefd2d5715139770256037224454ccc21d25bebb91100bcead5aba9", "45857b3c9cea024f663afb580dc284db387627fbe701f620715cdd7bed0ad074"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "bda8eafc615336f3db072cb6a77804f284fb89d7ae1ff90c7542278cfb25f1f0", "546033ea22ec13216b202af72d6cc434cdcfad74ef2a745d69adb5a568443118", "45d87e494c695b956fb5f5ec7d7af72ff35642f5b684654dcca72bbbbcf5bfa2"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "c8fd5e849b13582c9eb71a98b82fc23597cf85ddeac7f83f1c773be2e73021eb", "3cc7ed384b7f64ed6e50d0ae93617761573571e6b15ae421d957ccb6f7b91d45", "404fd888b72db73a355fe0b5924f699103ae607c70b9f64c40e1667c61e7a80c"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "597d869fc78e5ba7eaa27b6dd418d57d90915f9e8212125002187f715510928b", "65f5ae18a30d1c36f4a7af7e75372e47a39e6fdde2ab33156da74bd263eae039", "47fa066b272a29653b9fc8111be55ef5b5de73109fd94211a0484ba9ec29fc59"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "abed54431dfcd125916d9c0ebd8a2be9e871300fec5b664cb895acb4bc3e9535", "14aa272497fda7a11be7d3f93c528b5b2decacce8b652a7f07351b6297850586", "51fe6c6165880c91a945601c8b2d161c9f35243989d436b61d0369c2b3281ddd"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "ecf4f4f0cc3bcf6d828ccb237a6d80fcf84b55380cd37f8927af2516cde1c4e4", "398e2b26fbb01485d8fcf351249a49023db08db8d89df7cba4b8317df428b3ad", "6c1cb45174d97b6c1197022757155d2d3b33143853d341a2c7054f0f8dfdbaf6"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "a85c398d193cd603a4960a90232354baea46918a96c421ab53a972cd7a0892d6", "09269f59ff0423a0289e891cafffbde8fba9eec528f9c27cf4c4670cc1130e4e", "37d383ea040a6d41d7740ddad71b38b160ab61a28bd6f4985c7b0507ac8079c1"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "5be2ab78309b5cf85d41d035b42b8c10d7fd69aab5d496af84c0b4760bda639f", "7804f6ca0c17986184affe96ae8df3c163bfa4661fbf3f9208586e30768b99a2", "546c91e23074e3b38b6f4cf9c72b041968f56d06f6bf2e18f03eb02423369d28"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "14c2ff5e669fcf329d5a6bafbfd8b2a60b173ea565cdc8f39fc8c8a4564367d3", "5c12e5a06f942446ec0bdd470d13e871acbae8083678b2bcc365e53314d432f7", "3f856d7cac724daa898030dac14835b3702e8f484425e736b51752e6939ddc4d"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "f0f2fd0d2c8af9b1204c8320bf4453d381934afdca9ceaaa2f3fab24783b433a", "0cdfeff3615c787f74c01b0d40a69c75ac7f3e3aa4dd6024c570a19f0af08623", "04a78302a97985b7a7fcfc000d794f885c7bba0631a5f4c86c657e3993434403"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "7defe5ad8b5e636e8667bea1fc19c4619a326091201b3839b65c0339550e8df5", "62513a14aeac3a222b9805d198cf252667eb955348814d922ed33b705fdcaf30", "4f1ec4de4ab1e7ecd942b4a1c23be28a2f108090ee1494a4dd8c56b00560e577"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "c499af12e92bf421a0968aaeb766d140d240bd7b3ed021efc55e69707e95ba09", "78d6ee5e9b9e757671edf8023e38e1f5df9be261a498b9aec8cdaacb7e191b2c", "54448228d3b5e021224847f1706d31e69bc41ada1dc9dde93da0b770f653d8b9"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "22cbd20a30c91f0ad022a185b1e3999fe1c35f64f29572ddd00781c12ea0e024", "74c653169f9296952fec948a92e574365b40a74596913875c6ca0ac2d864a533", "168bed1a949d205b9358326fcffc7ac3318fd21a1c340da727995d3c2642daff"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "9339f4b9ca9ef2c9459462b329261895b5ed2952a1a0d26223fbaea88f60960e", "31b5b2fbaad2e3696cb4492aad8044f264f3ae991dde02c590fe254587d96e0c", "183ae3a641b2056ce425adec1bea9454574a1af367071d9aa28bcdc148ba425d"} + }; + + static Stream generatedTestVectors() { + return Stream.of(GENERATED_TEST_VECTORS) + .map(args -> new Object[]{new BigInteger(args[0], 16), Sha256Hash.wrap(args[1]), + new BigInteger(args[2], 16), new BigInteger(args[3], 16)}); + } + + @MethodSource("generatedTestVectors") + @ParameterizedTest(name = "[{index}] privKey={0}, messageHash={1}, expectedR={2}, expectedS={3}") + public void testSign(BigInteger privKey, Sha256Hash messageHash, BigInteger expectedR, BigInteger expectedS) { + var key = LowRSigningKey.from(ECKey.fromPrivate(privKey)); + var signature = key.sign(messageHash); + + // Signature is valid and low-R. + assertTrue(key.verify(messageHash, signature)); + assertTrue(signature.r.bitLength() < 256); + + // Signature matches that produced by Bitcoin Core/Knots with the same message & private key, + // which shows that the same nonce k was chosen, at least up to a sign (mod N, the curve order). + assertEquals(expectedR, signature.r); + assertEquals(expectedS, signature.s); + } + + @Disabled + public static class TestVectorGeneration { + private static final String BITCOIN_TX_COMMAND_FMT_STR = "bitcoin-tx -create " + + "in=00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff:0 " + + "set=privatekeys:[\"%s\"] " + ( + "set=prevtxs:[{" + + "\"txid\":\"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff\"," + + "\"vout\":0," + + "\"amount\":\"0.001\"," + + "\"scriptPubKey\":\"%s\"}] ") + + "outaddr=%s:193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7 " + + "sign=ALL"; + + private static final String[] PRIVATE_KEYS = { + "02", "110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "0123456789abcdef00000000000000000123456789abcdef0000000000000000", + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140" // = -1 (mod N) + }; + + private static final String[] COIN_OUTPUT_AMOUNTS = { + "0.0001", "0.0003", "0.0005", "0.0007", "0.0009" + }; + + @Test + public void preparedTestVectorsMatch() { + assertArrayEquals(GENERATED_TEST_VECTORS, Arrays.stream(PRIVATE_KEYS) + .flatMap(privKeyHex -> Arrays.stream(COIN_OUTPUT_AMOUNTS) + .map(coinOutputAmount -> assertDoesNotThrow(() -> generateTestVector(privKeyHex, coinOutputAmount)))) +// .forEach(vector -> System.out.printf("{\"%s\", \"%s\", \"%s\", \"%s\"},%n", (Object[]) vector)); + .toArray(String[][]::new)); + } + + private static String[] generateTestVector(String privKeyHex, String coinOutputAmount) throws Exception { + ECKey key = ECKey.fromPrivate(new BigInteger(privKeyHex, 16)); + Script scriptCode = ScriptBuilder.createP2PKHOutputScript(key); + Script scriptPubKey = ScriptBuilder.createP2WPKHOutputScript(key); + String scriptPubKeyHex = Utilities.encodeToHex(scriptPubKey.getProgram()); + String wif = key.getPrivateKeyAsWiF(MainNetParams.get()); + + String txHex = callBitcoinTx(wif, scriptPubKeyHex, coinOutputAmount); + + var tx = new Transaction(MainNetParams.get(), Utilities.decodeFromHex(txHex)); + var witness = tx.getInput(0).getWitness(); + var sigHash = tx.hashForWitnessSignature(0, scriptCode, Coin.MILLICOIN, Transaction.SigHash.ALL, false); + var signature = TransactionSignature.decodeFromBitcoin(witness.getPush(0), true, true); + + assertTrue(key.verify(sigHash, signature)); + return new String[]{privKeyHex, sigHash.toString(), + Strings.padStart(signature.r.toString(16), 64, '0'), + Strings.padStart(signature.s.toString(16), 64, '0')}; + } + + private static String callBitcoinTx(String wif, + String scriptPubKeyHex, + String coinOutputAmount) throws IOException { + String cmd = String.format(BITCOIN_TX_COMMAND_FMT_STR, wif, scriptPubKeyHex, coinOutputAmount); + Process process = new ProcessBuilder(cmd.split(" ")).start(); + var outputStream = new ByteArrayOutputStream(); + process.getInputStream().transferTo(outputStream); + + int exitCode = Futures.getUnchecked(process.onExit().thenApply(Process::exitValue)); + String output = outputStream.toString(StandardCharsets.UTF_8).trim(); + + assertEquals(0, exitCode); + return output; + } + } +} From 61b06e716d4b7124a8c0822373f680083f338d43 Mon Sep 17 00:00:00 2001 From: Steven Barclay Date: Wed, 4 Sep 2024 16:32:53 +0800 Subject: [PATCH 2/3] Use LowRSigningKey for all BTC & BSQ tx signing Provide a 'BisqWallet' subclass of 'o.b.w.Wallet', in order to override the 'signTransaction' method used internally by the bitcoinj SendRequest API, so that it always produces txs with low-R signatures. Also modify 'WalletService.signTransactionInput' to likewise wrap wallet keys, so that the provided tx input is given a low-R signature. Finally, modify the manual signing logic in TradeWalletService to wrap multisig & deposit input keys, which should cover all tx signing in the Bisq application. --- .../bisq/core/btc/setup/WalletConfig.java | 5 +- .../java/bisq/core/btc/wallet/BisqWallet.java | 105 ++++++++++++++++++ .../core/btc/wallet/LowRSigningKeyBag.java | 61 ++++++++++ .../core/btc/wallet/TradeWalletService.java | 18 +-- .../bisq/core/btc/wallet/WalletService.java | 5 +- 5 files changed, 181 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/bisq/core/btc/wallet/BisqWallet.java create mode 100644 core/src/main/java/bisq/core/btc/wallet/LowRSigningKeyBag.java 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 afa52f8482a..c08597ee616 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 @@ 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 @@ private Wallet loadWallet(boolean shouldReplayWallet, File walletFile, boolean i 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 @@ protected Wallet createWallet(boolean isBsqWallet) { 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 00000000000..e7afb5a5c1a --- /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 00000000000..d112cc53955 --- /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 29bda0b41e3..24dac384a69 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.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 byte[] signDelayedPayoutTx(Transaction delayedPayoutTx, 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 byte[] buyerSignsPayoutTx(Transaction depositTx, 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 Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx, 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 byte[] signMediatedPayoutTx(Transaction depositTx, 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 Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali 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 String emergencyGenerateSignature(String rawTxHex, 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 @@ private Transaction createPayoutTx(Transaction depositTx, 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 7b0566cb3c1..72a670df88b 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.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 static void signTransactionInput(Wallet wallet, 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; } From 408f902d9c3b99ad9d03f2f0c6e2034ed902832f Mon Sep 17 00:00:00 2001 From: Steven Barclay Date: Wed, 4 Sep 2024 19:10:10 +0800 Subject: [PATCH 3/3] Use LowRSigningKey elsewhere ECKeys are used for signing To slightly save storage & bandwidth, create low-R signatures in places besides tx ScriptSigs & witnesses, such as merit lists, proofs of burn, filter, alert & accounting authentication data. (Also, to make setup of mock keys a little easier, bypass wrapping of keys that are already instances of 'LowRSigningKey' in the factory method, 'LowRSigningKey.from(ECKey)'.) --- .../bisq/core/account/sign/SignedWitnessService.java | 9 +++++---- core/src/main/java/bisq/core/alert/AlertManager.java | 3 ++- .../java/bisq/core/alert/PrivateNotificationManager.java | 4 +++- core/src/main/java/bisq/core/crypto/LowRSigningKey.java | 2 +- core/src/main/java/bisq/core/dao/SignVerifyService.java | 3 ++- .../dao/burningman/accounting/node/AccountingNode.java | 3 ++- .../dao/governance/blindvote/MyBlindVoteListService.java | 8 ++++---- .../dao/governance/proofofburn/ProofOfBurnService.java | 5 +++-- core/src/main/java/bisq/core/filter/FilterManager.java | 3 ++- .../core/support/dispute/agent/DisputeAgentManager.java | 3 ++- .../bisq/core/account/sign/SignedWitnessServiceTest.java | 5 +++-- .../account/witness/AccountAgeWitnessServiceTest.java | 7 ++++--- core/src/test/java/bisq/core/filter/TestFilter.java | 4 +++- .../overlays/windows/supporttool/SignVerifyPane.java | 3 ++- 14 files changed, 38 insertions(+), 24 deletions(-) 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 498d8e7bbae..a270b743c57 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 @@ private String signAndPublishAccountAgeWitness(Coin tradeAmount, } 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 @@ private String signAndPublishAccountAgeWitness(Coin tradeAmount, time, tradeAmount.value); publishSignedWitness(signedWitness); - log.info("Arbitrator signed witness {}", signedWitness.toString()); + log.info("Arbitrator signed witness {}", signedWitness); return ""; } @@ -322,7 +323,7 @@ public Optional signAndPublishAccountAgeWitness(Coin tradeAmount, 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 void addToMap(SignedWitness signedWitness) { 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 efedf78787f..961057c6332 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 @@ private boolean isKeyValid(String privKeyString) { 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 c37f5c6a9de..b8ce186bbe4 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 @@ private boolean isKeyValid(String privKeyString) { 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/crypto/LowRSigningKey.java b/core/src/main/java/bisq/core/crypto/LowRSigningKey.java index c907863db6f..498c93241c2 100644 --- a/core/src/main/java/bisq/core/crypto/LowRSigningKey.java +++ b/core/src/main/java/bisq/core/crypto/LowRSigningKey.java @@ -40,7 +40,7 @@ protected LowRSigningKey(ECKey key) { } public static LowRSigningKey from(ECKey key) { - return key != null ? new LowRSigningKey(key) : null; + return key != null ? key instanceof LowRSigningKey ? (LowRSigningKey) key : new LowRSigningKey(key) : null; } @Override diff --git a/core/src/main/java/bisq/core/dao/SignVerifyService.java b/core/src/main/java/bisq/core/dao/SignVerifyService.java index cc41a0a5295..1d7501d807c 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 String getPubKeyAsHex(String txId) { 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 782eda9f38a..2e9d71a78e8 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 static Sha256Hash getSha256Hash(Collection blocks) { } 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 5bf858eca6f..9c5c1aded2a 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.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.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 void publishBlindVote(Coin stake, ResultHandler resultHandler, ExceptionH 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 MeritList getMerits(@Nullable String blindVoteTxId) { 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 MeritList getMerits(@Nullable String blindVoteTxId) { 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 b8f41358c75..f35a5abc624 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.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 String getPubKeyAsHex(String proofOfBurnTxId) { 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 25c4d363548..d10ba5120e1 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 @@ private void setFilterSigningKey(String privKeyString) { 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 a725927ab45..6fbd3a52071 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 ObservableMap getObservableMap() { // 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 4e372321dd5..45a807640e8 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 @@ 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 void setup() throws Exception { 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 void testIsValidAccountAgeWitnessLongLoop() throws Exception { 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 e0abc989a87..9796c3b1c9d 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 @@ 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 void testArbitratorSignWitness() { 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 void testArbitratorSignWitness() { // 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/filter/TestFilter.java b/core/src/test/java/bisq/core/filter/TestFilter.java index ff2c041f0c3..1627565b4df 100644 --- a/core/src/test/java/bisq/core/filter/TestFilter.java +++ b/core/src/test/java/bisq/core/filter/TestFilter.java @@ -17,6 +17,8 @@ package bisq.core.filter; +import bisq.core.crypto.LowRSigningKey; + import bisq.network.p2p.storage.payload.ProtectedStorageEntry; import org.bitcoinj.core.ECKey; @@ -114,7 +116,7 @@ public static Filter signFilter(Filter unsignedFilter, ECKey signerKey) { byte[] filterData = unsignedFilter.serializeForHash(); Sha256Hash hash = Sha256Hash.of(filterData); - ECKey.ECDSASignature ecdsaSignature = signerKey.sign(hash); + ECKey.ECDSASignature ecdsaSignature = LowRSigningKey.from(signerKey).sign(hash); byte[] encodeToDER = ecdsaSignature.encodeToDER(); String signatureAsBase64 = new String(Base64.encode(encodeToDER), StandardCharsets.UTF_8); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/supporttool/SignVerifyPane.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/supporttool/SignVerifyPane.java index ea6d3ed0ef6..e9e64914df7 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/supporttool/SignVerifyPane.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/supporttool/SignVerifyPane.java @@ -23,6 +23,7 @@ import bisq.desktop.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); } });