An RLP "value" is fundamentally an {@code Item} defined the following way: + * + *
+ * Item ::= List | Bytes + * List ::= [ Item, ... , Item ] + * Bytes ::= a binary value (comprised of an arbitrary number of bytes). + *+ * + * In other words, RLP encodes binary data organized in arbitrary nested lists. + * + *
A {@link RLPOutput} thus provides methods to write both lists and binary values. A list is + * started by calling {@link #startList()} and ended by {@link #endList()}. Lists can be nested in + * other lists in arbitrary ways. Binary values can be written directly with {@link + * #writeBytes(Bytes)}, but the {@link RLPOutput} interface provides a wealth of convenience methods + * to write specific types of data with a specific encoding. + * + *
Amongst the methods to write binary data, some methods are provided to write "scalar". A + * scalar should simply be understood as a positive integer that is encoded with no leading zeros. + * In other word, if an integer is written with a "Scalar" method variant, that number will be + * encoded with the minimum number of bytes necessary to represent it. + * + *
The {@link RLPOutput} only defines the interface for writing data meant to be RLP encoded. + * Getting the finally encoded output will depend on the concrete implementation, see {@link + * BytesValueRLPOutput} for instance. + */ +public interface RLPOutput { + + /** Starts a new list. */ + void startList(); + + /** + * Ends the current list. + * + * @throws IllegalStateException if no list has been previously started with {@link #startList()} + * (or any started had already be ended). + */ + void endList(); + + /** + * Writes a new value. + * + * @param v The value to write. + */ + void writeBytes(Bytes v); + + /** + * Writes a scalar (encoded with no leading zeroes). + * + * @param v The scalar to write. + */ + default void writeUInt256Scalar(final UInt256Value> v) { + writeBytes(v.trimLeadingZeros()); + } + + /** + * Writes a RLP "null", that is an empty value. + * + *
This is a shortcut for {@code writeBytes(Bytes.EMPTY)}. + */ + default void writeNull() { + writeBytes(Bytes.EMPTY); + } + + /** + * Writes a scalar (encoded with no leading zeroes). + * + * @param v The scalar to write. + * @throws IllegalArgumentException if {@code v < 0}. + */ + default void writeIntScalar(final int v) { + writeLongScalar(v); + } + + /** + * Writes a scalar (encoded with no leading zeroes). + * + * @param v The scalar to write. + * @throws IllegalArgumentException if {@code v < 0}. + */ + default void writeLongScalar(final long v) { + checkArgument(v >= 0, "Invalid negative value %s for scalar encoding", v); + writeBytes(Bytes.minimalBytes(v)); + } + + /** + * Writes a scalar (encoded with no leading zeroes). + * + * @param v The scalar to write. + * @throws IllegalArgumentException if {@code v} is a negative integer ({@code v.signum() < 0}). + */ + default void writeBigIntegerScalar(final BigInteger v) { + checkArgument(v.signum() >= 0, "Invalid negative integer %s for scalar encoding", v); + if (v.equals(BigInteger.ZERO)) { + writeBytes(Bytes.EMPTY); + return; + } + + final byte[] bytes = v.toByteArray(); + // BigInteger will not include leading zeros by contract, but it always include at least one + // bit of sign (a zero here since it's positive). What that mean is that if the first 1 of the + // resulting number is exactly on a byte boundary, then the sign bit constraint will make the + // value include one extra byte, which will be zero. In other words, they can be one zero bytes + // in practice we should ignore, but there should never be more than one. + writeBytes( + bytes.length > 1 && bytes[0] == 0 + ? Bytes.wrap(bytes, 1, bytes.length - 1) + : Bytes.wrap(bytes)); + } + + /** + * Writes a single byte value. + * + * @param b The byte to write. + */ + default void writeByte(final byte b) { + writeBytes(Bytes.of(b)); + } + + /** + * Writes a 2-bytes value. + * + *
Note that this is not a "scalar" write: the value will be encoded with exactly 2 bytes. + * + * @param s The 2-bytes short to write. + */ + default void writeShort(final short s) { + final byte[] res = new byte[2]; + res[0] = (byte) (s >> 8); + res[1] = (byte) s; + writeBytes(Bytes.wrap(res)); + } + + /** + * Writes a 4-bytes value. + * + *
Note that this is not a "scalar" write: the value will be encoded with exactly 4 bytes. + * + * @param i The 4-bytes int to write. + */ + default void writeInt(final int i) { + final MutableBytes v = MutableBytes.create(4); + v.setInt(0, i); + writeBytes(v); + } + + /** + * Writes a 8-bytes value. + * + *
Note that this is not a "scalar" write: the value will be encoded with exactly 8 bytes. + * + * @param l The 8-bytes long to write. + */ + default void writeLong(final long l) { + final MutableBytes v = MutableBytes.create(8); + v.setLong(0, l); + writeBytes(v); + } + + /** + * Writes a single byte value. + * + * @param b A value that must fit an unsigned byte. + * @throws IllegalArgumentException if {@code b} does not fit an unsigned byte, that is if either + * {@code b < 0} or {@code b > 0xFF}. + */ + default void writeUnsignedByte(final int b) { + writeBytes(Bytes.of(b)); + } + + /** + * Writes a 2-bytes value. + * + * @param s A value that must fit an unsigned 2-bytes short. + * @throws IllegalArgumentException if {@code s} does not fit an unsigned 2-bytes short, that is + * if either {@code s < 0} or {@code s > 0xFFFF}. + */ + default void writeUnsignedShort(final int s) { + writeBytes(Bytes.ofUnsignedShort(s)); + } + + /** + * Writes a 4-bytes value. + * + * @param i A value that must fit an unsigned 4-bytes integer. + * @throws IllegalArgumentException if {@code i} does not fit an unsigned 4-bytes int, that is if + * either {@code i < 0} or {@code i > 0xFFFFFFFFL}. + */ + default void writeUnsignedInt(final long i) { + writeBytes(Bytes.ofUnsignedInt(i)); + } + + /** + * Writes the byte representation of an inet address (so either 4 or 16 bytes long). + * + * @param address The address to write. + */ + default void writeInetAddress(final InetAddress address) { + writeBytes(Bytes.wrap(address.getAddress())); + } + + /** + * Writes a list of values of a specific class provided a function to write values of that class + * to an {@link RLPOutput}. + * + *
This is a convenience method whose result is equivalent to doing: + * + *
{@code + * startList(); + * for (T v : values) { + * valueWriter.accept(v, this); + * } + * endList(); + * }+ * + * @param values A list of value of type {@code T}. + * @param valueWriter A method that given a value of type {@code T} and an {@link RLPOutput}, + * writes this value to the output. + * @param
This is a shortcut for doing: + * + *
{@code + * startList(); + * endList(); + * }+ */ + default void writeEmptyList() { + startList(); + endList(); + } + +} diff --git a/src/main/java/irita/sdk/crypto/eth/SECPPublicKey.java b/src/main/java/irita/sdk/crypto/eth/SECPPublicKey.java new file mode 100644 index 0000000..e8d5fa7 --- /dev/null +++ b/src/main/java/irita/sdk/crypto/eth/SECPPublicKey.java @@ -0,0 +1,99 @@ +package irita.sdk.crypto.eth; + + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class SECPPublicKey implements java.security.PublicKey { + + public static final int BYTE_LENGTH = 64; + + private final Bytes encoded; + private final String algorithm; + + public static SECPPublicKey create(final BigInteger key, final String algorithm) { + return create(toBytes64(key.toByteArray()), algorithm); + } + + public static SECPPublicKey create(final Bytes encoded, final String algorithm) { + return new SECPPublicKey(encoded, algorithm); + } + + + private static Bytes toBytes64(final byte[] backing) { + if (backing.length == BYTE_LENGTH) { + return Bytes.wrap(backing); + } else if (backing.length > BYTE_LENGTH) { + return Bytes.wrap(backing, backing.length - BYTE_LENGTH, BYTE_LENGTH); + } else { + final MutableBytes res = MutableBytes.create(BYTE_LENGTH); + Bytes.wrap(backing).copyTo(res, BYTE_LENGTH - backing.length); + return res; + } + } + + private SECPPublicKey(final Bytes encoded, final String algorithm) { + checkNotNull(encoded); + checkNotNull(algorithm); + this.encoded = encoded; + this.algorithm = algorithm; + } + + /** + * Returns this public key as an {@link ECPoint} of Bouncy Castle, to facilitate cryptographic + * operations. + * + * @param curve The elliptic curve (e.g. SECP256K1) represented as its domain parameters + * @return This public key represented as an Elliptic Curve point. + */ + public ECPoint asEcPoint(final ECDomainParameters curve) { + // 0x04 is the prefix for uncompressed keys. + final Bytes val = Bytes.concatenate(Bytes.of(0x04), encoded); + return curve.getCurve().decodePoint(val.toArrayUnsafe()); + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof SECPPublicKey)) { + return false; + } + + final SECPPublicKey that = (SECPPublicKey) other; + return this.encoded.equals(that.encoded) && this.algorithm.equals(that.algorithm); + } + + @Override + public byte[] getEncoded() { + return encoded.toArrayUnsafe(); + } + + public Bytes getEncodedBytes() { + return encoded; + } + + @Override + public String getAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public int hashCode() { + return encoded.hashCode(); + } + + @Override + public String toString() { + return encoded.toString(); + } +} diff --git a/src/main/java/irita/sdk/crypto/eth/SECPSignature.java b/src/main/java/irita/sdk/crypto/eth/SECPSignature.java new file mode 100644 index 0000000..2385158 --- /dev/null +++ b/src/main/java/irita/sdk/crypto/eth/SECPSignature.java @@ -0,0 +1,136 @@ +package irita.sdk.crypto.eth; + + +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; +import org.apache.tuweni.units.bigints.UInt256; + +import java.math.BigInteger; +import java.util.Objects; +import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public class SECPSignature { + + public static final int BYTES_REQUIRED = 65; + /** + * The recovery id to reconstruct the public key used to create the signature. + * + *
The recId is an index from 0 to 3 which indicates which of the 4 possible keys is the
+ * correct one. Because the key recovery operation yields multiple potential keys, the correct key
+ * must either be stored alongside the signature, or you must be willing to try each recId in turn
+ * until you find one that outputs the key you are expecting.
+ */
+ private final byte recId;
+
+ private final BigInteger r;
+ private final BigInteger s;
+
+ public final Supplier Except for test cases, this function should compute some cryptographic hash of the message,
+ * the algorithm, the key and the attempt.
+ */
+ public interface secp256k1_nonce_function extends Callback {
+
+ /**
+ * @param nonce32 (output) Pointer to a 32-byte array to be filled by the function.
+ * @param msg32 The 32-byte message hash being verified (will not be NULL).
+ * @param key32 Pointer to a 32-byte secret key (will not be NULL)
+ * @param algo16 Pointer to a 16-byte array describing the signature * algorithm (will be NULL
+ * for ECDSA for compatibility).
+ * @param data Arbitrary data pointer that is passed through.
+ * @param attempt How many iterations we have tried to find a nonce. This will almost always be
+ * 0, but different attempt values are required to result in a different nonce.
+ * @return 1 if a nonce was successfully generated. 0 will cause signing to fail.
+ */
+ int apply(
+ Pointer nonce32, Pointer msg32, Pointer key32, Pointer algo16, Pointer data, int attempt);
+ }
+
+ /**
+ * Opaque data structure that holds a parsed and valid public key.
+ *
+ * The exact representation of data inside is implementation defined and not guaranteed to be
+ * portable between different platforms or versions. It is however guaranteed to be 64 bytes in
+ * size, and can be safely copied/moved. If you need to convert to a format suitable for storage,
+ * transmission, or comparison, use secp256k1_ec_pubkey_serialize and secp256k1_ec_pubkey_parse.
+ */
+ @FieldOrder({"data"})
+ public static class secp256k1_pubkey extends Structure {
+ public byte[] data = new byte[64];
+ }
+
+ /**
+ * Opaque data structured that holds a parsed ECDSA signature.
+ *
+ * The exact representation of data inside is implementation defined and not guaranteed to be
+ * portable between different platforms or versions. It is however guaranteed to be 64 bytes in
+ * size, and can be safely copied/moved. If you need to convert to a format suitable for storage,
+ * transmission, or comparison, use the secp256k1_ecdsa_signature_serialize_* and
+ * secp256k1_ecdsa_signature_parse_* functions.
+ */
+ @FieldOrder({"data"})
+ public static class secp256k1_ecdsa_signature extends Structure {
+ public byte[] data = new byte[64];
+ }
+
+ /**
+ * Opaque data structured that holds a parsed ECDSA signature, supporting pubkey recovery.
+ *
+ * The exact representation of data inside is implementation defined and not guaranteed to be
+ * portable between different platforms or versions. It is however guaranteed to be 65 bytes in
+ * size, and can be safely copied/moved. If you need to convert to a format suitable for storage
+ * or transmission, use the secp256k1_ecdsa_signature_serialize_* and
+ * secp256k1_ecdsa_signature_parse_* functions.
+ *
+ * Furthermore, it is guaranteed that identical signatures (including their recoverability)
+ * will have identical representation, so they can be memcmp'ed.
+ */
+ @FieldOrder({"data"})
+ public static class secp256k1_ecdsa_recoverable_signature extends Structure {
+ public byte[] data = new byte[65];
+ }
+
+ /**
+ * Create a secp256k1 context object (in dynamically allocated memory).
+ *
+ * This function uses malloc to allocate memory. It is guaranteed that malloc is called at most
+ * once for every call of this function. If you need to avoid dynamic memory allocation entirely,
+ * see the functions in secp256k1_preallocated.h.
+ *
+ * See also secp256k1_context_randomize.
+ *
+ * @param flags which parts of the context to initialize.
+ * @return a newly created context object.
+ */
+ public static native PointerByReference secp256k1_context_create(final int flags);
+
+ /**
+ * Parse a variable-length public key into the pubkey object.
+ *
+ * This function supports parsing compressed (33 bytes, header byte 0x02 or 0x03), uncompressed
+ * (65 bytes, header byte 0x04), or hybrid (65 bytes, header byte 0x06 or 0x07) format public
+ * keys.
+ *
+ * @return 1 if the public key was fully valid. 0 if the public key could not be parsed or is
+ * invalid.
+ * @param ctx a secp256k1 context object.
+ * @param pubkey (output) pointer to a pubkey object. If 1 is returned, it is set to a parsed
+ * version of input. If not, its value is undefined.
+ * @param input pointer to a serialized public key
+ * @param inputlen length of the array pointed to by input
+ */
+ public static native int secp256k1_ec_pubkey_parse(
+ final PointerByReference ctx,
+ final secp256k1_pubkey pubkey,
+ final byte[] input,
+ final long inputlen);
+
+ /**
+ * Serialize a pubkey object into a serialized byte sequence.
+ *
+ * @return 1 always.
+ * @param ctx a secp256k1 context object.
+ * @param output (output) a pointer to a 65-byte (if compressed==0) or 33-byte (if compressed==1)
+ * byte array to place the serialized key in.
+ * @param outputlen (input/output) a pointer to an integer which is initially set to the size of
+ * output, and is overwritten with the written size.
+ * @param pubkey a pointer to a secp256k1_pubkey containing an initialized public key.
+ * @param flags SECP256K1_EC_COMPRESSED if serialization should be in compressed format, otherwise
+ * SECP256K1_EC_UNCOMPRESSED.
+ */
+ public static native int secp256k1_ec_pubkey_serialize(
+ final PointerByReference ctx,
+ final ByteBuffer output,
+ final LongByReference outputlen,
+ final secp256k1_pubkey pubkey,
+ final int flags);
+
+ /**
+ * Parse an ECDSA signature in compact (64 bytes) format.
+ *
+ * The signature must consist of a 32-byte big endian R value, followed by a 32-byte big endian
+ * S value. If R or S fall outside of [0..order-1], the encoding is invalid. R and S with value 0
+ * are allowed in the encoding.
+ *
+ * After the call, sig will always be initialized. If parsing failed or R or S are zero, the
+ * resulting sig value is guaranteed to fail validation for any message and public key.
+ *
+ * @return 1 when the signature could be parsed, 0 otherwise.
+ * @param ctx a secp256k1 context object.
+ * @param sig (output) a pointer to a signature object
+ * @param input64 a pointer to the 64-byte array to parse
+ */
+ public static native int secp256k1_ecdsa_signature_parse_compact(
+ final PointerByReference ctx, final secp256k1_ecdsa_signature sig, final byte[] input64);
+
+ /**
+ * Verify an ECDSA signature.
+ *
+ * To avoid accepting malleable signatures, only ECDSA signatures in lower-S form are accepted.
+ *
+ * If you need to accept ECDSA signatures from sources that do not obey this rule, apply
+ * secp256k1_ecdsa_signature_normalize to the signature prior to validation, but be aware that
+ * doing so results in malleable signatures.
+ *
+ * For details, see the comments for that function.
+ *
+ * @return 1 if it is a correct signature, 0 if it is an incorrect or unparseable signature.
+ * @param ctx a secp256k1 context object, initialized for verification.
+ * @param sig the signature being verified (cannot be NULL)
+ * @param msg32 the 32-byte message hash being verified (cannot be NULL)
+ * @param pubkey pointer to an initialized public key to verify with (cannot be NULL)
+ */
+ public static native int secp256k1_ecdsa_verify(
+ final PointerByReference ctx,
+ final secp256k1_ecdsa_signature sig,
+ final byte[] msg32,
+ final secp256k1_pubkey pubkey);
+
+ /**
+ * Compute the public key for a secret key.
+ *
+ * @return 1 if secret was valid, public key stores, 0 if secret was invalid, try again.
+ * @param ctx pointer to a context object, initialized for signing (cannot be NULL)
+ * @param pubkey (output) pointer to the created public key (cannot be NULL)
+ * @param seckey pointer to a 32-byte private key (cannot be NULL)
+ */
+ public static native int secp256k1_ec_pubkey_create(
+ final PointerByReference ctx, final secp256k1_pubkey pubkey, final byte[] seckey);
+
+ /**
+ * Updates the context randomization to protect against side-channel leakage. While secp256k1 code
+ * is written to be constant-time no matter what secret values are, it's possible that a future
+ * compiler may output code which isn't, and also that the CPU may not emit the same radio
+ * frequencies or draw the same amount power for all values.
+ *
+ * This function provides a seed which is combined into the blinding value: that blinding value
+ * is added before each multiplication (and removed afterwards) so that it does not affect
+ * function results, but shields against attacks which rely on any input-dependent behaviour.
+ *
+ * This function has currently an effect only on contexts initialized for signing because
+ * randomization is currently used only for signing. However, this is not guaranteed and may
+ * change in the future. It is safe to call this function on contexts not initialized for signing;
+ * then it will have no effect and return 1.
+ *
+ * You should call this after secp256k1_context_create or secp256k1_context_clone (and
+ * secp256k1_context_preallocated_create or secp256k1_context_clone, resp.), and you may call this
+ * repeatedly afterwards.
+ *
+ * @param ctx pointer to a context object (cannot be NULL)
+ * @param seed32 pointer to a 32-byte random seed (NULL resets to initial state)
+ * @return Returns 1 if randomization successfully updated or nothing to randomize or 0 if an
+ * error occured
+ */
+ public static native int secp256k1_context_randomize(
+ final PointerByReference ctx, final byte[] seed32);
+
+ /**
+ * Parse a compact ECDSA signature (64 bytes + recovery id).
+ *
+ * @return 1 when the signature could be parsed, 0 otherwise
+ * @param ctx a secp256k1 context object
+ * @param sig (output) a pointer to a signature object
+ * @param input64 a pointer to a 64-byte compact signature
+ * @param recid the recovery id (0, 1, 2 or 3)
+ */
+ public static native int secp256k1_ecdsa_recoverable_signature_parse_compact(
+ final PointerByReference ctx,
+ final secp256k1_ecdsa_recoverable_signature sig,
+ final byte[] input64,
+ final int recid);
+
+ /**
+ * Serialize an ECDSA signature in compact format (64 bytes + recovery id).
+ *
+ * @param ctx a secp256k1 context object
+ * @param output64 (output) a pointer to a 64-byte array of the compact signature (cannot be NULL)
+ * @param recid (output) a pointer to an integer to hold the recovery id (can be NULL).
+ * @param sig a pointer to an initialized signature object (cannot be NULL)
+ */
+ public static native void secp256k1_ecdsa_recoverable_signature_serialize_compact(
+ final PointerByReference ctx,
+ final ByteBuffer output64,
+ final IntByReference recid,
+ final secp256k1_ecdsa_recoverable_signature sig);
+
+ /**
+ * Create a recoverable ECDSA signature.
+ *
+ * @return 1 if signature created, 0 if the nonce generation function failed or the private key
+ * was invalid.
+ * @param ctx pointer to a context object, initialized for signing (cannot be NULL)
+ * @param sig (output) pointer to an array where the signature will be placed (cannot be NULL)
+ * @param msg32 the 32-byte message hash being signed (cannot be NULL)
+ * @param seckey pointer to a 32-byte secret key (cannot be NULL)
+ * @param noncefp pointer to a nonce generation function. If NULL,
+ * secp256k1_nonce_function_default is used
+ * @param ndata pointer to arbitrary data used by the nonce generation function (can be NULL)
+ */
+ public static native int secp256k1_ecdsa_sign_recoverable(
+ final PointerByReference ctx,
+ final secp256k1_ecdsa_recoverable_signature sig,
+ final byte[] msg32,
+ final byte[] seckey,
+ final secp256k1_nonce_function noncefp,
+ final Pointer ndata);
+
+ /**
+ * Recover an ECDSA public key from a signature.
+ *
+ * @return 1 if public key successfully recovered (which guarantees a correct signature), 0
+ * otherwise.
+ * @param ctx pointer to a context object, initialized for verification (cannot be NULL)
+ * @param pubkey (output) pointer to the recovered public key (cannot be NULL)
+ * @param sig pointer to initialized signature that supports pubkey recovery (cannot be NULL)
+ * @param msg32 the 32-byte message hash assumed to be signed (cannot be NULL)
+ */
+ public static native int secp256k1_ecdsa_recover(
+ final PointerByReference ctx,
+ final secp256k1_pubkey pubkey,
+ final secp256k1_ecdsa_recoverable_signature sig,
+ final byte[] msg32);
+}
diff --git a/src/main/java/irita/sdk/crypto/eth/libsecp256k1/SECP256K1.java b/src/main/java/irita/sdk/crypto/eth/libsecp256k1/SECP256K1.java
new file mode 100644
index 0000000..d7882e2
--- /dev/null
+++ b/src/main/java/irita/sdk/crypto/eth/libsecp256k1/SECP256K1.java
@@ -0,0 +1,103 @@
+package irita.sdk.crypto.eth.libsecp256k1;
+
+
+import com.sun.jna.ptr.LongByReference;
+import irita.sdk.crypto.eth.AbstractSECP256;
+import irita.sdk.crypto.eth.SECPPublicKey;
+import irita.sdk.crypto.eth.SECPSignature;
+import org.apache.tuweni.bytes.Bytes;
+import org.apache.tuweni.bytes.Bytes32;
+import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.util.Optional;
+
+/*
+ * Adapted from the BitcoinJ ECKey (Apache 2 License) implementation:
+ * https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/core/ECKey.java
+ *
+ *
+ * Adapted from the web3j (Apache 2 License) implementations:
+ * https://github.com/web3j/web3j/crypto/src/main/java/org/web3j/crypto/*.java
+ */
+public class SECP256K1 extends AbstractSECP256 {
+
+ //private static final Logger LOG = LogManager.getLogger();
+
+ private boolean useNative;
+
+ public static final String CURVE_NAME = "secp256k1";
+
+ public SECP256K1() {
+ super(CURVE_NAME, SecP256K1Curve.q);
+
+ // use the native library implementation, if it is available
+ useNative = LibSecp256k1.CONTEXT != null;
+ if (!useNative) {
+ //LOG.info("Native secp256k1 not available");
+ }
+ }
+
+ @Override
+ public Optional