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..c907863db6 --- /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 0000000000..7ff1d796c2 --- /dev/null +++ b/core/src/test/java/bisq/core/crypto/CountingHMacDSAKCalculatorTest.java @@ -0,0 +1,110 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.crypto; + +import org.bitcoinj.core.Sha256Hash; + +import java.nio.charset.StandardCharsets; + +import java.math.BigInteger; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CountingHMacDSAKCalculatorTest { + // Taken from https://www.rfc-editor.org/rfc/rfc6979: + private static final String[][] RFC_6979_SHA_256_ECDSA_TEST_VECTORS = { + // NIST P-192 + {"ffffffffffffffffffffffff99def836146bc9b1b4d22831", "6fab034934e4c0fc9ae67f5b5659a9d7d1fefd187ee09fd4", "sample", "32b1b6d7d42a05cb449065727a84804fb1a3e34d8f261496"}, + {"ffffffffffffffffffffffff99def836146bc9b1b4d22831", "6fab034934e4c0fc9ae67f5b5659a9d7d1fefd187ee09fd4", "test", "5c4ce89cf56d9e7c77c8585339b006b97b5f0680b4306c6c"}, + // NIST P-224 + {"ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", "f220266e1105bfe3083e03ec7a3a654651f45e37167e88600bf257c1", "sample", "ad3029e0278f80643de33917ce6908c70a8ff50a411f06e41dedfcdc"}, + {"ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", "f220266e1105bfe3083e03ec7a3a654651f45e37167e88600bf257c1", "test", "ff86f57924da248d6e44e8154eb69f0ae2aebaee9931d0b5a969f904"}, + // NIST p-256 + {"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", "sample", "a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60"}, + {"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721", "test", "d16b6ae827f17175e040871a1c7ec3500192c4c92677336ec2537acaee0008e0"}, + // NIST P-384 + {"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", "6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d896d5724e4c70a825f872c9ea60d2edf5", "sample", "180ae9f9aec5438a44bc159a1fcb277c7be54fa20e7cf404b490650a8acc414e375572342863c899f9f2edf9747a9b60"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", "6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d896d5724e4c70a825f872c9ea60d2edf5", "test", "0cfac37587532347dc3389fdc98286bba8c73807285b184c83e62e26c401c0faa48dd070ba79921a3457abff2d630ad7"}, + // NIST P-521 + {"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", "0fad06daa62ba3b25d2fb40133da757205de67f5bb0018fee8c86e1b68c7e75caa896eb32f1f47c70855836a6d16fcc1466f6d8fbec67db89ec0c08b0e996b83538", "sample", "0edf38afcaaecab4383358b34d67c9f2216c8382aaea44a3dad5fdc9c32575761793fef24eb0fc276dfc4f6e3ec476752f043cf01415387470bcbd8678ed2c7e1a0"}, + {"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", "0fad06daa62ba3b25d2fb40133da757205de67f5bb0018fee8c86e1b68c7e75caa896eb32f1f47c70855836a6d16fcc1466f6d8fbec67db89ec0c08b0e996b83538", "test", "01de74955efaabc4c4f17f8e84d881d1310b5392d7700275f82f145c61e843841af09035bf7a6210f5a431a6a9e81c9323354a9e69135d44ebd2fcaa7731b909258"}, + // NIST K-163 + {"4000000000000000000020108a2e0cc0d99f8a5ef", "09a4d6792295a7f730fc3f2b49cbc0f62e862272f", "sample", "23af4074c90a02b3fe61d286d5c87f425e6bdd81b"}, + {"4000000000000000000020108a2e0cc0d99f8a5ef", "09a4d6792295a7f730fc3f2b49cbc0f62e862272f", "test", "193649ce51f0cff0784cfc47628f4fa854a93f7a2"}, + // NIST K-233 + {"8000000000000000000000000000069d5bb915bcd46efb1ad5f173abdf", "103b2142bdc2a3c3b55080d09df1808f79336da2399f5ca7171d1be9b0", "sample", "73552f9cac5774f74f485fa253871f2109a0c86040552eaa67dba92dc9"}, + {"8000000000000000000000000000069d5bb915bcd46efb1ad5f173abdf", "103b2142bdc2a3c3b55080d09df1808f79336da2399f5ca7171d1be9b0", "test", "2ce5aedc155acc0ddc5e679ebacfd21308362e5efc05c5e99b2557a8d7"}, + // NIST K-283 + {"1ffffffffffffffffffffffffffffffffffe9ae2ed07577265dff7f94451e061e163c61", "06a0777356e87b89ba1ed3a3d845357be332173c8f7a65bdc7db4fab3c4cc79acc8194e", "sample", "1ceb9e8e0dff53ce687deb81339aca3c98e7a657d5a9499ef779f887a934408ecbe5a38"}, + {"1ffffffffffffffffffffffffffffffffffe9ae2ed07577265dff7f94451e061e163c61", "06a0777356e87b89ba1ed3a3d845357be332173c8f7a65bdc7db4fab3c4cc79acc8194e", "test", "0b585a7a68f51089691d6ede2b43fc4451f66c10e65f134b963d4cbd4eb844b0e1469a6"}, + // NIST K-409 + {"7ffffffffffffffffffffffffffffffffffffffffffffffffffe5f83b2d4ea20400ec4557d5ed3e3e7ca5b4b5c83b8e01e5fcf", "29c16768f01d1b8a89fda85e2efd73a09558b92a178a2931f359e4d70ad853e569cdaf16daa569758fb4e73089e4525d8bbfcf", "sample", "782385f18baf5a36a588637a76dfab05739a14163bf723a4417b74bd1469d37ac9e8cce6aec8ff63f37b815aaf14a876eed962"}, + {"7ffffffffffffffffffffffffffffffffffffffffffffffffffe5f83b2d4ea20400ec4557d5ed3e3e7ca5b4b5c83b8e01e5fcf", "29c16768f01d1b8a89fda85e2efd73a09558b92a178a2931f359e4d70ad853e569cdaf16daa569758fb4e73089e4525d8bbfcf", "test", "251e32dee10ed5ea4ad7370df3eff091e467d5531ca59de3aa791763715e1169ab5e18c2a11cd473b0044fb45308e8542f2eb0"}, + // NIST K-571 + {"20000000000000000000000000000000000000000000000000000000000000000000000131850e1f19a63e4b391a8db917f4138b630d84be5d639381e91deb45cfe778f637c1001", "0c16f58550d824ed7b95569d4445375d3a490bc7e0194c41a39deb732c29396cdf1d66de02dd1460a816606f3bec0f32202c7bd18a32d87506466aa92032f1314ed7b19762b0d22", "sample", "0f79d53e63d89fb87f4d9e6dc5949f5d9388bcfe9ebcb4c2f7ce497814cf40e845705f8f18dbf0f860de0b1cc4a433ef74a5741f3202e958c082e0b76e16ecd5866aa0f5f3df300"}, + {"20000000000000000000000000000000000000000000000000000000000000000000000131850e1f19a63e4b391a8db917f4138b630d84be5d639381e91deb45cfe778f637c1001", "0c16f58550d824ed7b95569d4445375d3a490bc7e0194c41a39deb732c29396cdf1d66de02dd1460a816606f3bec0f32202c7bd18a32d87506466aa92032f1314ed7b19762b0d22", "test", "04ddd0707e81bb56ea2d1d45d7fafdbdd56912cae224086802fea1018db306c4fb8d93338dbf6841ce6c6ab1506e9a848d2c0463e0889268843dee4acb552cffcb858784ed116b2"}, + // NIST B-163 + {"40000000000000000000292fe77e70c12a4234c33", "35318fc447d48d7e6bc93b48617dddedf26aa658f", "sample", "3d7086a59e6981064a9cdb684653f3a81b6ec0f0b"}, + {"40000000000000000000292fe77e70c12a4234c33", "35318fc447d48d7e6bc93b48617dddedf26aa658f", "test", "38145e3ffca94e4ddacc20ad6e0997bd0e3b669d2"}, + // NIST B-233 + {"1000000000000000000000000000013e974e72f8a6922031d2603cfe0d7", "07adc13dd5bf34d1ddeeb50b2ce23b5f5e6d18067306d60c5f6ff11e5d3", "sample", "034a53897b0bbdb484302e19bf3f9b34a2abfed639d109a388dc52006b5"}, + {"1000000000000000000000000000013e974e72f8a6922031d2603cfe0d7", "07adc13dd5bf34d1ddeeb50b2ce23b5f5e6d18067306d60c5f6ff11e5d3", "test", "00376886e89013f7ff4b5214d56a30d49c99f53f211a3afe01aa2bde12d"}, + // NIST B-283 + {"3ffffffffffffffffffffffffffffffffffef90399660fc938a90165b042a7cefadb307", "14510d4bc44f2d26f4553942c98073c1bd35545ceabb5cc138853c5158d2729ea408836", "sample", "38c9d662188982943e080b794a4cfb0732dba37c6f40d5b8cfaded6ff31c5452ba3f877"}, + {"3ffffffffffffffffffffffffffffffffffef90399660fc938a90165b042a7cefadb307", "14510d4bc44f2d26f4553942c98073c1bd35545ceabb5cc138853c5158d2729ea408836", "test", "018a7d44f2b4341fefe68f6bd8894960f97e08124aab92c1ffbbe90450fcc9356c9aaa5"}, + // NIST B-409 + {"10000000000000000000000000000000000000000000000000001e2aad6a612f33307be5fa47c3c9e052f838164cd37d9a21173", "0494994cc325b08e7b4ce038bd9436f90b5e59a2c13c3140cd3ae07c04a01fc489f572ce0569a6db7b8060393de76330c624177", "sample", "08ec42d13a3909a20c41bebd2dfed8cacce56c7a7d1251df43f3e9e289dae00e239f6960924ac451e125b784cb687c7f23283fd"}, + {"10000000000000000000000000000000000000000000000000001e2aad6a612f33307be5fa47c3c9e052f838164cd37d9a21173", "0494994cc325b08e7b4ce038bd9436f90b5e59a2c13c3140cd3ae07c04a01fc489f572ce0569a6db7b8060393de76330c624177", "test", "06eba3d58d0e0dfc406d67fc72ef0c943624cf40019d1e48c3b54ccab0594afd5dee30aebaa22e693dbcfecad1a85d774313dad"}, + // NIST B-571 + {"3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe661ce18ff55987308059b186823851ec7dd9ca1161de93d5174d66e8382e9bb2fe84e47", "028a04857f24c1c082df0d909c0e72f453f2e2340ccb071f0e389bca2575da19124198c57174929ad26e348cf63f78d28021ef5a9bf2d5cbeaf6b7ccb6c4da824dd5c82cfb24e11", "sample", "15c2c6b7d1a070274484774e558b69fdfa193bdb7a23f27c2cd24298ce1b22a6cc9b7fb8cabfd6cf7c6b1cf3251e5a1cddd16fbfed28de79935bb2c631b8b8ea9cc4bcc937e669e"}, + {"3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe661ce18ff55987308059b186823851ec7dd9ca1161de93d5174d66e8382e9bb2fe84e47", "028a04857f24c1c082df0d909c0e72f453f2e2340ccb071f0e389bca2575da19124198c57174929ad26e348cf63f78d28021ef5a9bf2d5cbeaf6b7ccb6c4da824dd5c82cfb24e11", "test", "328e02cf07c7b5b6d3749d8302f1ae5bfaa8f239398459af4a2c859c7727a8123a7fe9be8b228413fc8dc0e9de16af3f8f43005107f9989a5d97a5c4455da895e81336710a3fb2c"} + }; + + static Stream rfc6979TestVectors() { + return Stream.of(RFC_6979_SHA_256_ECDSA_TEST_VECTORS) + .map(args -> new Object[]{new BigInteger(args[0], 16), new BigInteger(args[1], 16), args[2], + new BigInteger(args[3], 16)}); + } + + @MethodSource("rfc6979TestVectors") + @ParameterizedTest(name = "[{index}] n={0}, d={1}, message={2}, expectedK={3}") + public void testKCalculator(BigInteger n, BigInteger d, String message, BigInteger expectedK) { + byte[] messageHash = Sha256Hash.hash(message.getBytes(StandardCharsets.UTF_8)); + var kCalculator = new CountingHMacDSAKCalculator(); + + // First usage of the k-calculator gives a nonce matching that in the RFC 6979 test vector. + kCalculator.init(n, d, messageHash); + assertEquals(expectedK, kCalculator.nextK()); + + // Further invocations result in distinct nonces, as the internal counter increments. + Set retries = new HashSet<>(); + for (int i = 0; i < 4; i++) { + kCalculator.init(n, d, messageHash); + retries.add(kCalculator.nextK()); + } + retries.add(expectedK); + assertEquals(5, retries.size()); + } +} diff --git a/core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java b/core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java new file mode 100644 index 0000000000..70fa8a92c8 --- /dev/null +++ b/core/src/test/java/bisq/core/crypto/LowRSigningKeyTest.java @@ -0,0 +1,177 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.crypto; + +import bisq.common.util.Utilities; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.crypto.TransactionSignature; +import org.bitcoinj.params.MainNetParams; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptBuilder; + +import com.google.common.base.Strings; +import com.google.common.util.concurrent.Futures; + +import java.nio.charset.StandardCharsets; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import java.math.BigInteger; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LowRSigningKeyTest { + /** + * Deterministic low-R signature test vectors of the form {privKey, sigHash, rComponent, sComponent}, generated with + * the aid of the Bitcoin Core tx creation and editing command: 'bitcoin-tx'. The signed txs and subsequent test + * vectors here match those produced by Core/Knots version 27.1 (and likely other post-2018 versions). + * @see TestVectorGeneration + */ + private static final String[][] GENERATED_TEST_VECTORS = { + {"02", "546876d08f9f06f12c168f96079cd0af996ce22d89bbc58eced2631918a9bc9a", "45cc5d5e4fad81f2bc89f16cb37575da3ae13677f707a73ca5ca1e2787e3d311", "25829e0ad206dbd53cd637b75eedbde3273548673b2d08de3f61dcbe723409e2"}, + {"02", "2cca2f04e6e654226483f5af39fbfebb883e301bd32553a6a0fb176b0d172b3a", "2bdcafead8f6db212228e52f061e894db8bdc2133c6c81a5b54883ef5648ae6d", "007bdcc92effb2931b4c7c5c834bf7aa847d3e0e0e1d9c7e41ed3981821eb830"}, + {"02", "d78c70bc5e7f9a74ce4e3000369592a037a8898a93b07a5bdc2d88ed78462521", "1f283dfd1ba17e69dbbe2fe261c3984569316efa781b1a4e846fa7978f0fe918", "180187c9cc33c2849b4770260a1ee379e5c4292ad46bd700c45a108bb4c6f346"}, + {"02", "c65a47a4d448b60cff8bbcf190492a1d5ef6beb217465c69bd358c4460ab84fb", "216301d61b337ca80c62047d349fa85c04b05451586ab0a2034d0855b09209fe", "228d09fe3e5a9a90501def65a8e467a5172c9fdd510c8a5db3a6cdbea000b1cc"}, + {"02", "d5bde8603bada85600e24ae82c9dee32671ac5038f6842558610f5cf3129f21e", "50b0b26bbfe72cacee2c8e1612e057c1855da8320232965137dd67f9eab77523", "26f990be93e0b8ce5ddbe468d0d242c51686c2ad9608ee44b17875b991e23e82"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "340b1fe486af2cd6b0ef8786d1c747fc3d785c5499d35faa25fbde57f9bf70ae", "2c5bc7104c059e2db8cda7b500424d2438ae2635b6cddcc51695a11e7ec95cc7", "62bc8969bfb02cac905f0739cecadc456bf48539a9b268b2c2725d3c2680be88"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "a39e0f59a4d8448ab4a614edb998e3f315d3a4c855855386afdfe664a42a0864", "752066dbf4e862d634440649014ec1fc64fdcea1127320acc09b223b1e7fbc59", "2a613585d5ac1741080380bdf7b151e559ccb37c5521674a77b999c9de0f21e8"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "df0a60d25576d441cdd76f5a3d63fe1308860121e70e832098abac5b6d5ff715", "65f8ccf807faeb46c0a69d1b1774a53081ec11c5e0ffb854620bcb2f2c8098bb", "32feb22792926929f01d51439814ec1bc414c2ab52b36a7c675e50ea37310b78"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "79df48383bdf5d295e510f4aadcc3a3b2ed0c3d454e1b1e4315de7c3f1ea86ab", "588ae9380e85c8c2c565321957aa459a4ae0080331d41145b7dea5214bf91377", "7207f2c4969e19ef5de08d088ea7dcd4c1ba67574cc0ac023dc9e7c48abee8b6"}, + {"110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "44adec39be60bf10994596a0248dcafa78a45ca30472190f0de61523e90ec131", "1bef291f3aa99c551ef5d96d9fd06d945092e185cebe2cda75351e2f27ca3ea2", "2024fda909bfb9b9079f072dbe005ae850a54f16d1b59bc46afc6db2ef5461fe"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "e48ae59f8a200c946c827ab8992f8774dc0d5742d50baaa9f26b39a91f471eda", "39af0095c3cd45cba48891f29e2a69c3ba421c97bbc3181aa1f3699e3deb5234", "19ad75c9f49f42648338ddfac0b3597145db0864b897212d3da0a88b796d09f8"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "9e1a0043b4e941352f8cd957041244bed7f4a616c254ab8ba1a8ce7fba8a798a", "33b8197cfdefd2d5715139770256037224454ccc21d25bebb91100bcead5aba9", "45857b3c9cea024f663afb580dc284db387627fbe701f620715cdd7bed0ad074"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "bda8eafc615336f3db072cb6a77804f284fb89d7ae1ff90c7542278cfb25f1f0", "546033ea22ec13216b202af72d6cc434cdcfad74ef2a745d69adb5a568443118", "45d87e494c695b956fb5f5ec7d7af72ff35642f5b684654dcca72bbbbcf5bfa2"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "c8fd5e849b13582c9eb71a98b82fc23597cf85ddeac7f83f1c773be2e73021eb", "3cc7ed384b7f64ed6e50d0ae93617761573571e6b15ae421d957ccb6f7b91d45", "404fd888b72db73a355fe0b5924f699103ae607c70b9f64c40e1667c61e7a80c"}, + {"0123456789abcdef00000000000000000123456789abcdef0000000000000000", "597d869fc78e5ba7eaa27b6dd418d57d90915f9e8212125002187f715510928b", "65f5ae18a30d1c36f4a7af7e75372e47a39e6fdde2ab33156da74bd263eae039", "47fa066b272a29653b9fc8111be55ef5b5de73109fd94211a0484ba9ec29fc59"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "abed54431dfcd125916d9c0ebd8a2be9e871300fec5b664cb895acb4bc3e9535", "14aa272497fda7a11be7d3f93c528b5b2decacce8b652a7f07351b6297850586", "51fe6c6165880c91a945601c8b2d161c9f35243989d436b61d0369c2b3281ddd"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "ecf4f4f0cc3bcf6d828ccb237a6d80fcf84b55380cd37f8927af2516cde1c4e4", "398e2b26fbb01485d8fcf351249a49023db08db8d89df7cba4b8317df428b3ad", "6c1cb45174d97b6c1197022757155d2d3b33143853d341a2c7054f0f8dfdbaf6"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "a85c398d193cd603a4960a90232354baea46918a96c421ab53a972cd7a0892d6", "09269f59ff0423a0289e891cafffbde8fba9eec528f9c27cf4c4670cc1130e4e", "37d383ea040a6d41d7740ddad71b38b160ab61a28bd6f4985c7b0507ac8079c1"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "5be2ab78309b5cf85d41d035b42b8c10d7fd69aab5d496af84c0b4760bda639f", "7804f6ca0c17986184affe96ae8df3c163bfa4661fbf3f9208586e30768b99a2", "546c91e23074e3b38b6f4cf9c72b041968f56d06f6bf2e18f03eb02423369d28"}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "14c2ff5e669fcf329d5a6bafbfd8b2a60b173ea565cdc8f39fc8c8a4564367d3", "5c12e5a06f942446ec0bdd470d13e871acbae8083678b2bcc365e53314d432f7", "3f856d7cac724daa898030dac14835b3702e8f484425e736b51752e6939ddc4d"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "f0f2fd0d2c8af9b1204c8320bf4453d381934afdca9ceaaa2f3fab24783b433a", "0cdfeff3615c787f74c01b0d40a69c75ac7f3e3aa4dd6024c570a19f0af08623", "04a78302a97985b7a7fcfc000d794f885c7bba0631a5f4c86c657e3993434403"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "7defe5ad8b5e636e8667bea1fc19c4619a326091201b3839b65c0339550e8df5", "62513a14aeac3a222b9805d198cf252667eb955348814d922ed33b705fdcaf30", "4f1ec4de4ab1e7ecd942b4a1c23be28a2f108090ee1494a4dd8c56b00560e577"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "c499af12e92bf421a0968aaeb766d140d240bd7b3ed021efc55e69707e95ba09", "78d6ee5e9b9e757671edf8023e38e1f5df9be261a498b9aec8cdaacb7e191b2c", "54448228d3b5e021224847f1706d31e69bc41ada1dc9dde93da0b770f653d8b9"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "22cbd20a30c91f0ad022a185b1e3999fe1c35f64f29572ddd00781c12ea0e024", "74c653169f9296952fec948a92e574365b40a74596913875c6ca0ac2d864a533", "168bed1a949d205b9358326fcffc7ac3318fd21a1c340da727995d3c2642daff"}, + {"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "9339f4b9ca9ef2c9459462b329261895b5ed2952a1a0d26223fbaea88f60960e", "31b5b2fbaad2e3696cb4492aad8044f264f3ae991dde02c590fe254587d96e0c", "183ae3a641b2056ce425adec1bea9454574a1af367071d9aa28bcdc148ba425d"} + }; + + static Stream generatedTestVectors() { + return Stream.of(GENERATED_TEST_VECTORS) + .map(args -> new Object[]{new BigInteger(args[0], 16), Sha256Hash.wrap(args[1]), + new BigInteger(args[2], 16), new BigInteger(args[3], 16)}); + } + + @MethodSource("generatedTestVectors") + @ParameterizedTest(name = "[{index}] privKey={0}, messageHash={1}, expectedR={2}, expectedS={3}") + public void testSign(BigInteger privKey, Sha256Hash messageHash, BigInteger expectedR, BigInteger expectedS) { + var key = LowRSigningKey.from(ECKey.fromPrivate(privKey)); + var signature = key.sign(messageHash); + + // Signature is valid and low-R. + assertTrue(key.verify(messageHash, signature)); + assertTrue(signature.r.bitLength() < 256); + + // Signature matches that produced by Bitcoin Core/Knots with the same message & private key, + // which shows that the same nonce k was chosen, at least up to a sign (mod N, the curve order). + assertEquals(expectedR, signature.r); + assertEquals(expectedS, signature.s); + } + + @Disabled + public static class TestVectorGeneration { + private static final String BITCOIN_TX_COMMAND_FMT_STR = "bitcoin-tx -create " + + "in=00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff:0 " + + "set=privatekeys:[\"%s\"] " + ( + "set=prevtxs:[{" + + "\"txid\":\"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff\"," + + "\"vout\":0," + + "\"amount\":\"0.001\"," + + "\"scriptPubKey\":\"%s\"}] ") + + "outaddr=%s:193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7 " + + "sign=ALL"; + + private static final String[] PRIVATE_KEYS = { + "02", "110022003300440055006600770088009900aa00bb00cc00dd00ee00ff", "0123456789abcdef00000000000000000123456789abcdef0000000000000000", + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140" // = -1 (mod N) + }; + + private static final String[] COIN_OUTPUT_AMOUNTS = { + "0.0001", "0.0003", "0.0005", "0.0007", "0.0009" + }; + + @Test + public void preparedTestVectorsMatch() { + assertArrayEquals(GENERATED_TEST_VECTORS, Arrays.stream(PRIVATE_KEYS) + .flatMap(privKeyHex -> Arrays.stream(COIN_OUTPUT_AMOUNTS) + .map(coinOutputAmount -> assertDoesNotThrow(() -> generateTestVector(privKeyHex, coinOutputAmount)))) +// .forEach(vector -> System.out.printf("{\"%s\", \"%s\", \"%s\", \"%s\"},%n", (Object[]) vector)); + .toArray(String[][]::new)); + } + + private static String[] generateTestVector(String privKeyHex, String coinOutputAmount) throws Exception { + ECKey key = ECKey.fromPrivate(new BigInteger(privKeyHex, 16)); + Script scriptCode = ScriptBuilder.createP2PKHOutputScript(key); + Script scriptPubKey = ScriptBuilder.createP2WPKHOutputScript(key); + String scriptPubKeyHex = Utilities.encodeToHex(scriptPubKey.getProgram()); + String wif = key.getPrivateKeyAsWiF(MainNetParams.get()); + + String txHex = callBitcoinTx(wif, scriptPubKeyHex, coinOutputAmount); + + var tx = new Transaction(MainNetParams.get(), Utilities.decodeFromHex(txHex)); + var witness = tx.getInput(0).getWitness(); + var sigHash = tx.hashForWitnessSignature(0, scriptCode, Coin.MILLICOIN, Transaction.SigHash.ALL, false); + var signature = TransactionSignature.decodeFromBitcoin(witness.getPush(0), true, true); + + assertTrue(key.verify(sigHash, signature)); + return new String[]{privKeyHex, sigHash.toString(), + Strings.padStart(signature.r.toString(16), 64, '0'), + Strings.padStart(signature.s.toString(16), 64, '0')}; + } + + private static String callBitcoinTx(String wif, + String scriptPubKeyHex, + String coinOutputAmount) throws IOException { + String cmd = String.format(BITCOIN_TX_COMMAND_FMT_STR, wif, scriptPubKeyHex, coinOutputAmount); + Process process = new ProcessBuilder(cmd.split(" ")).start(); + var outputStream = new ByteArrayOutputStream(); + process.getInputStream().transferTo(outputStream); + + int exitCode = Futures.getUnchecked(process.onExit().thenApply(Process::exitValue)); + String output = outputStream.toString(StandardCharsets.UTF_8).trim(); + + assertEquals(0, exitCode); + return output; + } + } +}