From e7635a70a75003ef0e37b65a3402567233a56f22 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 2 Sep 2024 15:34:08 -0700 Subject: [PATCH 1/2] Remove dependency on JDK 17 HexFormat Add ByteArray, ByteUtils, and HexFormat classes to do it. --- secp-api/src/main/java/module-info.java | 1 + .../java/org/bitcoinj/secp/api/ByteArray.java | 60 +++++++++++++++++ .../bitcoinj/secp/api/P256K1XOnlyPubKey.java | 4 +- .../org/bitcoinj/secp/api/P256k1PubKey.java | 3 - .../org/bitcoinj/secp/api/SignatureData.java | 4 +- .../bitcoinj/secp/api/internal/ByteUtils.java | 47 +++++++++++++ .../bitcoinj/secp/api/internal/HexFormat.java | 66 +++++++++++++++++++ .../bitcoinj/secp/bouncy/BouncyPubKey.java | 3 +- .../org/bitcoinj/secp/ffm/SignaturePojo.java | 1 + 9 files changed, 180 insertions(+), 9 deletions(-) create mode 100644 secp-api/src/main/java/org/bitcoinj/secp/api/ByteArray.java create mode 100644 secp-api/src/main/java/org/bitcoinj/secp/api/internal/ByteUtils.java create mode 100644 secp-api/src/main/java/org/bitcoinj/secp/api/internal/HexFormat.java diff --git a/secp-api/src/main/java/module-info.java b/secp-api/src/main/java/module-info.java index f460bc7..ce8736a 100644 --- a/secp-api/src/main/java/module-info.java +++ b/secp-api/src/main/java/module-info.java @@ -18,6 +18,7 @@ requires org.jspecify; exports org.bitcoinj.secp.api; + exports org.bitcoinj.secp.api.internal; /* TEMPORARY */ uses org.bitcoinj.secp.api.Secp256k1Provider; } diff --git a/secp-api/src/main/java/org/bitcoinj/secp/api/ByteArray.java b/secp-api/src/main/java/org/bitcoinj/secp/api/ByteArray.java new file mode 100644 index 0000000..cb42758 --- /dev/null +++ b/secp-api/src/main/java/org/bitcoinj/secp/api/ByteArray.java @@ -0,0 +1,60 @@ +/* + * Copyright 2023-2024 secp256k1-jdk Developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bitcoinj.secp.api; + +import org.bitcoinj.secp.api.internal.ByteUtils; +import org.bitcoinj.secp.api.internal.HexFormat; + +import java.util.Arrays; + +/** + * An effectively-immutable byte array. + */ +public interface ByteArray extends Comparable { + HexFormat HEX_FORMAT = new HexFormat(); + + /** + * @return the bytes as an array + */ + byte[] bytes(); + + /** + * @return the bytes as a hex-formatted string + */ + default String formatHex() { + return HEX_FORMAT.formatHex(bytes()); + } + + /** + * {@inheritDoc} + *

For {@link ByteArray} this is a byte-by-byte, unsigned comparison. + * @param o {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + default int compareTo(ByteArray o) { + return ByteUtils.arrayUnsignedComparator().compare(bytes(), o.bytes()); + } + + /** + * Utility method to format hex bytes as string + * @param bytes bytes to format + * @return hex-formatted String + */ + static String toHexString(byte[] bytes) { + return HEX_FORMAT.formatHex(bytes); + } +} diff --git a/secp-api/src/main/java/org/bitcoinj/secp/api/P256K1XOnlyPubKey.java b/secp-api/src/main/java/org/bitcoinj/secp/api/P256K1XOnlyPubKey.java index 5352012..8a235b3 100644 --- a/secp-api/src/main/java/org/bitcoinj/secp/api/P256K1XOnlyPubKey.java +++ b/secp-api/src/main/java/org/bitcoinj/secp/api/P256K1XOnlyPubKey.java @@ -16,7 +16,6 @@ package org.bitcoinj.secp.api; import java.math.BigInteger; -import java.util.HexFormat; /** * @@ -56,7 +55,6 @@ static P256K1XOnlyPubKey of(BigInteger x) { * Default implementation. Currently used by all known implementations */ class P256K1XOnlyPubKeyImpl implements P256K1XOnlyPubKey { - private static final HexFormat formatter = HexFormat.of(); private final BigInteger x; public P256K1XOnlyPubKeyImpl(P256k1PubKey pubKey) { @@ -86,7 +84,7 @@ public byte[] getSerialized() { */ @Override public String toString() { - return formatter.formatHex(getSerialized()); + return ByteArray.toHexString(getSerialized()); } } } diff --git a/secp-api/src/main/java/org/bitcoinj/secp/api/P256k1PubKey.java b/secp-api/src/main/java/org/bitcoinj/secp/api/P256k1PubKey.java index 949737a..0ce098e 100644 --- a/secp-api/src/main/java/org/bitcoinj/secp/api/P256k1PubKey.java +++ b/secp-api/src/main/java/org/bitcoinj/secp/api/P256k1PubKey.java @@ -19,14 +19,11 @@ import java.security.interfaces.ECPublicKey; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; -import java.util.HexFormat; /** * */ public interface P256k1PubKey extends ECPublicKey { - HexFormat hf = HexFormat.of(); - @Override default String getAlgorithm() { return "Secp256k1"; diff --git a/secp-api/src/main/java/org/bitcoinj/secp/api/SignatureData.java b/secp-api/src/main/java/org/bitcoinj/secp/api/SignatureData.java index fd06d71..4473752 100644 --- a/secp-api/src/main/java/org/bitcoinj/secp/api/SignatureData.java +++ b/secp-api/src/main/java/org/bitcoinj/secp/api/SignatureData.java @@ -18,6 +18,6 @@ /** * */ -public interface SignatureData { - public byte[] bytes(); +public interface SignatureData extends ByteArray { + byte[] bytes(); } diff --git a/secp-api/src/main/java/org/bitcoinj/secp/api/internal/ByteUtils.java b/secp-api/src/main/java/org/bitcoinj/secp/api/internal/ByteUtils.java new file mode 100644 index 0000000..22873a8 --- /dev/null +++ b/secp-api/src/main/java/org/bitcoinj/secp/api/internal/ByteUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023-2024 secp256k1-jdk Developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bitcoinj.secp.api.internal; + +import java.util.Comparator; + +/** + * + */ +public class ByteUtils { + /** + * Provides a byte array comparator. + * @return A comparator for byte[] + */ + public static Comparator arrayUnsignedComparator() { + return ARRAY_UNSIGNED_COMPARATOR; + } + + // In Java 9, this can be replaced with Arrays.compareUnsigned() + private static final Comparator ARRAY_UNSIGNED_COMPARATOR = (a, b) -> { + int minLength = Math.min(a.length, b.length); + for (int i = 0; i < minLength; i++) { + int result = compareUnsigned(a[i], b[i]); + if (result != 0) { + return result; + } + } + return a.length - b.length; + }; + + private static int compareUnsigned(byte a, byte b) { + return Byte.toUnsignedInt(a) - Byte.toUnsignedInt(b); + } +} diff --git a/secp-api/src/main/java/org/bitcoinj/secp/api/internal/HexFormat.java b/secp-api/src/main/java/org/bitcoinj/secp/api/internal/HexFormat.java new file mode 100644 index 0000000..b93a45c --- /dev/null +++ b/secp-api/src/main/java/org/bitcoinj/secp/api/internal/HexFormat.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023-2024 secp256k1-jdk Developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bitcoinj.secp.api.internal; + +/** + * This class implements a subset of the functionality of the {@code HexFormat} class available in Java 17 and later. + * Its behavior is the same as an instance of Java 17 {@code HexFormat} created with {@code HexFormat.of()}. + * It is an internal class and may be removed in the future when and if we have a Java 17 baseline. This is a copy of + * HexFormat.java in `bitcoinj-base/org.bitcoinj.base.internal`. + *

Thanks to this Baeldung article. + */ +public class HexFormat { + public String formatHex(byte[] bytes) { + StringBuilder stringBuilder = new StringBuilder(bytes.length * 2); + for (byte aByte : bytes) { + stringBuilder.append(byteToHex(aByte)); + } + return stringBuilder.toString(); + } + + public byte[] parseHex(String hexString) { + if (hexString.length() % 2 == 1) { + throw new IllegalArgumentException("Invalid hexadecimal String supplied."); + } + + byte[] bytes = new byte[hexString.length() / 2]; + for (int i = 0; i < hexString.length(); i += 2) { + bytes[i / 2] = hexToByte(hexString.substring(i, i + 2)); + } + return bytes; + } + private String byteToHex(byte num) { + char[] hexDigits = new char[2]; + hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16); + hexDigits[1] = Character.forDigit((num & 0xF), 16); + return new String(hexDigits); + } + + private byte hexToByte(String hexString) { + int firstDigit = toDigit(hexString.charAt(0)); + int secondDigit = toDigit(hexString.charAt(1)); + return (byte) ((firstDigit << 4) + secondDigit); + } + + private int toDigit(char hexChar) { + int digit = Character.digit(hexChar, 16); + if (digit == -1) { + throw new IllegalArgumentException("Invalid Hexadecimal Character: "+ hexChar); + } + return digit; + } +} diff --git a/secp-bouncy/src/main/java/org/bitcoinj/secp/bouncy/BouncyPubKey.java b/secp-bouncy/src/main/java/org/bitcoinj/secp/bouncy/BouncyPubKey.java index 1536ad1..cee78cc 100644 --- a/secp-bouncy/src/main/java/org/bitcoinj/secp/bouncy/BouncyPubKey.java +++ b/secp-bouncy/src/main/java/org/bitcoinj/secp/bouncy/BouncyPubKey.java @@ -16,6 +16,7 @@ package org.bitcoinj.secp.bouncy; +import org.bitcoinj.secp.api.ByteArray; import org.bitcoinj.secp.api.P256k1PubKey; import org.bouncycastle.math.ec.ECPoint; @@ -49,7 +50,7 @@ public byte[] getEncoded() { @Override public String toString() { - return hf.formatHex(bytes()) ; + return ByteArray.HEX_FORMAT.formatHex(bytes()); } @Override diff --git a/secp-ffm/src/main/java/org/bitcoinj/secp/ffm/SignaturePojo.java b/secp-ffm/src/main/java/org/bitcoinj/secp/ffm/SignaturePojo.java index 4e46e74..e2d4f6f 100644 --- a/secp-ffm/src/main/java/org/bitcoinj/secp/ffm/SignaturePojo.java +++ b/secp-ffm/src/main/java/org/bitcoinj/secp/ffm/SignaturePojo.java @@ -15,6 +15,7 @@ */ package org.bitcoinj.secp.ffm; +import org.bitcoinj.secp.api.ByteArray; import org.bitcoinj.secp.api.SignatureData; import java.lang.foreign.MemorySegment; From b6b9494066c2871c01d8cb5e5dfd8687a22c4792 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 2 Sep 2024 15:53:29 -0700 Subject: [PATCH 2/2] secp-api, secp-bouncy: compile as Java 9 --- secp-api/build.gradle | 2 +- secp-bouncy/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/secp-api/build.gradle b/secp-api/build.gradle index 84ceb21..52f21af 100644 --- a/secp-api/build.gradle +++ b/secp-api/build.gradle @@ -3,7 +3,7 @@ plugins { } tasks.withType(JavaCompile).configureEach { - options.release = 17 + options.release = 9 } ext.moduleName = 'org.bitcoinj.secp.api' diff --git a/secp-bouncy/build.gradle b/secp-bouncy/build.gradle index e274869..96e0224 100644 --- a/secp-bouncy/build.gradle +++ b/secp-bouncy/build.gradle @@ -3,7 +3,7 @@ plugins { } tasks.withType(JavaCompile).configureEach { - options.release = 17 + options.release = 9 } ext.moduleName = 'org.bitcoinj.secp.bouncy'