From b298f90f2315d4ed392f5adc711a3ab9d40ac07b Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Tue, 27 Feb 2024 22:47:41 -0800 Subject: [PATCH] Replace Option with Result Option was a temporary placeholder. Result can actually return an error code, Optional can't. --- .../secp256k1/api/P256K1XOnlyPubKey.java | 6 +-- .../org/bitcoinj/secp256k1/api/Result.java | 38 +++++++++++++++++++ .../org/bitcoinj/secp256k1/api/Secp256k1.java | 12 +++--- .../secp256k1/bouncy/Bouncy256k1.java | 25 ++++++------ .../bitcoinj/secp256k1/examples/Ecdsa.java | 10 ++--- .../bitcoinj/secp256k1/examples/Schnorr.java | 4 +- .../secp256k1/foreign/Secp256k1Foreign.java | 32 +++++++--------- .../secp256k1/bitcoinj/AddressTest.java | 2 +- 8 files changed, 82 insertions(+), 47 deletions(-) create mode 100644 secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/Result.java diff --git a/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/P256K1XOnlyPubKey.java b/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/P256K1XOnlyPubKey.java index 468b448..2151430 100644 --- a/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/P256K1XOnlyPubKey.java +++ b/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/P256K1XOnlyPubKey.java @@ -30,10 +30,10 @@ public byte[] getSerialized() { return P256k1PubKey.integerTo32Bytes(x); } - public static Optional parse(byte[] serialized) { + public static Result parse(byte[] serialized) { BigInteger x = new BigInteger(1, serialized); return (x.compareTo((Secp256k1.FIELD.getP())) > 0) - ? Optional.empty() - : Optional.of(new P256K1XOnlyPubKey(x)); + ? Result.err(-1) + : Result.ok(new P256K1XOnlyPubKey(x)); } } diff --git a/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/Result.java b/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/Result.java new file mode 100644 index 0000000..c1cca6b --- /dev/null +++ b/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/Result.java @@ -0,0 +1,38 @@ +package org.bitcoinj.secp256k1.api; + +/** + * Functional-style result for secp256k1. Returns either {@link Ok} or {@link Err}. + * If result is {@code Ok} the success result is in {@link Ok#result()}, if a failure + * occurred the error code is in {@link Err#code()}. + */ +public sealed interface Result { + record Ok(T result) implements Result {} + record Err(int code) implements Result {} + + static Result ok(T result) { + return new Ok<>(result); + } + + static Result err(int error_code) { + return new Err<>(error_code); + } + + /** + * Return a value if error_code is equal to one (no error). + */ + static Result checked(int error_code, T result) { + return (error_code == 1) ? Result.ok(result) : Result.err(error_code); + } + + // TODO: define well-known error codes and messages and map between them + // TODO: Consider creating an enum (or other type) for results rather than using int. + default T unwrap() { + return unwrap("Error"); + } + default T unwrap(String message) { + return switch(this) { + case Ok ok -> ok.result(); + case Err err -> throw new IllegalStateException(message + ": " + err.code()); + }; + } +} diff --git a/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/Secp256k1.java b/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/Secp256k1.java index 13efff6..f45fed7 100644 --- a/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/Secp256k1.java +++ b/secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/Secp256k1.java @@ -36,15 +36,15 @@ public interface Secp256k1 extends AutoCloseable { CompressedPubKeyData ecPubKeySerialize(P256k1PubKey pubKey, int flags); - Optional ecPubKeyParse(CompressedPubKeyData inputData); + Result ecPubKeyParse(CompressedPubKeyData inputData); - Optional ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey); + Result ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey); - Optional ecdsaSignatureSerializeCompact(SignatureData sig); + Result ecdsaSignatureSerializeCompact(SignatureData sig); - Optional ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature); + Result ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature); - Optional ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey); + Result ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey); default byte[] taggedSha256(String tag, String message) { return taggedSha256(tag.getBytes(StandardCharsets.UTF_8), message.getBytes(StandardCharsets.UTF_8)); @@ -54,5 +54,5 @@ default byte[] taggedSha256(String tag, String message) { Object schnorrSigSign32(byte[] msg_hash, P256K1KeyPair keyPair); - Optional schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey); + Result schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey); } diff --git a/secp256k1-bouncy/src/main/java/org/bitcoinj/secp256k1/bouncy/Bouncy256k1.java b/secp256k1-bouncy/src/main/java/org/bitcoinj/secp256k1/bouncy/Bouncy256k1.java index 8c486b7..6807c4a 100644 --- a/secp256k1-bouncy/src/main/java/org/bitcoinj/secp256k1/bouncy/Bouncy256k1.java +++ b/secp256k1-bouncy/src/main/java/org/bitcoinj/secp256k1/bouncy/Bouncy256k1.java @@ -1,5 +1,6 @@ package org.bitcoinj.secp256k1.bouncy; +import org.bitcoinj.secp256k1.api.Result; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.ec.CustomNamedCurves; @@ -109,28 +110,28 @@ public CompressedPubKeyData ecPubKeySerialize(P256k1PubKey pubKey, int flags) { } @Override - public Optional ecPubKeyParse(CompressedPubKeyData inputData) { - return Optional.empty(); + public Result ecPubKeyParse(CompressedPubKeyData inputData) { + return Result.err(-1); } @Override - public Optional ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey) { - return Optional.empty(); + public Result ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey) { + return Result.err(-1); } @Override - public Optional ecdsaSignatureSerializeCompact(SignatureData sig) { - return Optional.empty(); + public Result ecdsaSignatureSerializeCompact(SignatureData sig) { + return Result.err(-1); } @Override - public Optional ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature) { - return Optional.empty(); + public Result ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature) { + return Result.err(-1); } @Override - public Optional ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey) { - return Optional.empty(); + public Result ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey) { + return Result.err(-1); } @Override public byte[] taggedSha256(byte[] tag, byte[] message) { @@ -143,8 +144,8 @@ public Object schnorrSigSign32(byte[] msg_hash, P256K1KeyPair keyPair) { } @Override - public Optional schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey) { - return Optional.empty(); + public Result schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey) { + return Result.err(-1); } @Override diff --git a/secp256k1-examples-java/src/main/java/org/bitcoinj/secp256k1/examples/Ecdsa.java b/secp256k1-examples-java/src/main/java/org/bitcoinj/secp256k1/examples/Ecdsa.java index b7a8143..195963d 100644 --- a/secp256k1-examples-java/src/main/java/org/bitcoinj/secp256k1/examples/Ecdsa.java +++ b/secp256k1-examples-java/src/main/java/org/bitcoinj/secp256k1/examples/Ecdsa.java @@ -46,24 +46,24 @@ public static void main(String[] args) { /* Generate an ECDSA signature using the RFC-6979 safe default nonce. * Signing with a valid context, verified secret key and the default nonce function should never fail. */ - SignatureData sig = secp.ecdsaSign(msg_hash, privKey).orElseThrow(); + SignatureData sig = secp.ecdsaSign(msg_hash, privKey).unwrap(); /* Serialize the signature in a compact form. Should always succeed according to the documentation in secp256k1.h. */ - CompressedSignatureData serialized_signature = secp.ecdsaSignatureSerializeCompact(sig).orElseThrow(); + CompressedSignatureData serialized_signature = secp.ecdsaSignatureSerializeCompact(sig).unwrap(); /* === Verification === */ /* Deserialize the signature. This will return empty if the signature can't be parsed correctly. */ - SignatureData sig2 = secp.ecdsaSignatureParseCompact(serialized_signature).orElseThrow(); + SignatureData sig2 = secp.ecdsaSignatureParseCompact(serialized_signature).unwrap(); assert(Arrays.equals(sig.bytes(), sig2.bytes())); /* Deserialize the public key. This will return empty if the public key can't be parsed correctly. */ - P256k1PubKey pubkey2 = secp.ecPubKeyParse(compressed_pubkey).orElseThrow(); + P256k1PubKey pubkey2 = secp.ecPubKeyParse(compressed_pubkey).unwrap(); assert(pubkey.getW().equals(pubkey2.getW())); /* Verify a signature. This will return true if it's valid and false if it's not. */ - boolean is_signature_valid = secp.ecdsaVerify(sig2, msg_hash, pubkey2).orElseThrow(); + boolean is_signature_valid = secp.ecdsaVerify(sig2, msg_hash, pubkey2).unwrap(); System.out.printf("Is the signature valid? %s\n", is_signature_valid); System.out.printf("Secret Key: %s\n", privKey.getS().toString(16)); diff --git a/secp256k1-examples-java/src/main/java/org/bitcoinj/secp256k1/examples/Schnorr.java b/secp256k1-examples-java/src/main/java/org/bitcoinj/secp256k1/examples/Schnorr.java index 3c5c298..9d1917c 100644 --- a/secp256k1-examples-java/src/main/java/org/bitcoinj/secp256k1/examples/Schnorr.java +++ b/secp256k1-examples-java/src/main/java/org/bitcoinj/secp256k1/examples/Schnorr.java @@ -43,12 +43,12 @@ public static void main(String[] args) { /* === Verification === */ - P256K1XOnlyPubKey xOnly2 = P256K1XOnlyPubKey.parse(serializedXOnly).orElseThrow(); + P256K1XOnlyPubKey xOnly2 = P256K1XOnlyPubKey.parse(serializedXOnly).unwrap(); /* Compute the tagged hash on the received message using the same tag as the signer. */ byte[] msg_hash2 = secp.taggedSha256(tag, msg); - boolean is_signature_valid = secp.schnorrSigVerify(signature, msg_hash2, xOnly2).orElseThrow(); + boolean is_signature_valid = secp.schnorrSigVerify(signature, msg_hash2, xOnly2).unwrap(); System.out.printf("Is the signature valid? %s\n", is_signature_valid); System.out.printf("Secret Key: %s\n", keyPair.getS().toString(16)); diff --git a/secp256k1-foreign/src/main/java/org/bitcoinj/secp256k1/foreign/Secp256k1Foreign.java b/secp256k1-foreign/src/main/java/org/bitcoinj/secp256k1/foreign/Secp256k1Foreign.java index 9251a48..e472718 100644 --- a/secp256k1-foreign/src/main/java/org/bitcoinj/secp256k1/foreign/Secp256k1Foreign.java +++ b/secp256k1-foreign/src/main/java/org/bitcoinj/secp256k1/foreign/Secp256k1Foreign.java @@ -2,6 +2,7 @@ import org.bitcoinj.secp256k1.api.P256K1KeyPair; import org.bitcoinj.secp256k1.api.P256K1XOnlyPubKey; +import org.bitcoinj.secp256k1.api.Result; import org.bitcoinj.secp256k1.api.Secp256k1; import org.bitcoinj.secp256k1.api.CompressedPubKeyData; import org.bitcoinj.secp256k1.api.CompressedSignatureData; @@ -238,16 +239,14 @@ public CompressedPubKeyData ecPubKeySerialize(P256k1PubKey pubKey, int flags) { } @Override - public Optional ecPubKeyParse(CompressedPubKeyData inputData) { + public Result ecPubKeyParse(CompressedPubKeyData inputData) { MemorySegment input = arena.allocateArray(JAVA_BYTE, inputData.bytes()); MemorySegment pubkey = secp256k1_pubkey.allocate(arena); int return_val = secp256k1_h.secp256k1_ec_pubkey_parse(ctx, pubkey, input, input.byteSize()); if (return_val != 1) { System.out.println("Failed parsing the public key\n"); } - return (return_val == 1) - ? Optional.of(new PubKeyPojo(toPoint(pubkey))) - : Optional.empty(); + return Result.checked(return_val, new PubKeyPojo(toPoint(pubkey))); } private MemorySegment pubKeyParse(P256k1PubKey pubKeyData) { @@ -261,7 +260,7 @@ private MemorySegment pubKeyParse(P256k1PubKey pubKeyData) { } @Override - public Optional ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey) { + public Result ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey) { /* Generate an ECDSA signature `noncefp` and `ndata` allows you to pass a * custom nonce function, passing `NULL` will use the RFC-6979 safe default. * Signing with a valid context, verified secret key @@ -273,28 +272,25 @@ public Optional ecdsaSign(byte[] msg_hash_data, P256k1PrivKey sec MemorySegment privKeySeg = arena.allocateArray(JAVA_BYTE, seckey.getEncoded()); int return_val = secp256k1_h.secp256k1_ecdsa_sign(ctx, sig, msg_hash, privKeySeg, nullCallback, nullPointer); privKeySeg.fill((byte) 0x00); - assert(return_val == 1); - return Optional.of(new SignaturePojo(sig)); + return Result.checked(return_val, new SignaturePojo(sig)); } @Override - public Optional ecdsaSignatureSerializeCompact(SignatureData sig) { + public Result ecdsaSignatureSerializeCompact(SignatureData sig) { MemorySegment serialized_signature = secp256k1_ecdsa_signature.allocate(arena); int return_val = secp256k1_h.secp256k1_ecdsa_signature_serialize_compact(ctx, serialized_signature, arena.allocateArray(JAVA_BYTE, sig.bytes())); - assert(return_val == 1); - return Optional.of(new CompressedSignaturePojo(serialized_signature)); + return Result.checked(return_val, new CompressedSignaturePojo(serialized_signature)); } @Override - public Optional ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature) { + public Result ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature) { MemorySegment sig = secp256k1_ecdsa_signature.allocate(arena); int return_val = secp256k1_h.secp256k1_ecdsa_signature_parse_compact(ctx, sig, arena.allocateArray(JAVA_BYTE, serialized_signature.bytes())); - assert(return_val == 1); - return Optional.of(new SignaturePojo(sig)); + return Result.checked(return_val, new SignaturePojo(sig)); } @Override - public Optional ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey) { + public Result ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey) { /* Generate an ECDSA signature `noncefp` and `ndata` allows you to pass a * custom nonce function, passing `NULL` will use the RFC-6979 safe default. * Signing with a valid context, verified secret key @@ -304,7 +300,7 @@ public Optional ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P2 arena.allocateArray(JAVA_BYTE, sig.bytes()), msg_hash, pubKeyParse(pubKey)); - return Optional.of(return_val == 1); + return Result.ok(return_val == 1); } @Override @@ -330,15 +326,15 @@ public byte[] schnorrSigSign32(byte[] messageHash, P256K1KeyPair keyPair) { } @Override - public Optional schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey) { + public Result schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey) { MemorySegment sigSegment = arena.allocateArray(JAVA_BYTE, signature); MemorySegment msgSegment = arena.allocateArray(JAVA_BYTE, msg_hash); MemorySegment pubKeySegment = arena.allocateArray(JAVA_BYTE, pubKey.getSerialized()); // 32-byte MemorySegment pubKeySegmentOpaque = secp256k1_xonly_pubkey.allocate(arena); // 64-byte opaque int r = secp256k1_h.secp256k1_xonly_pubkey_parse(ctx, pubKeySegmentOpaque, pubKeySegment); - assert(r == 1); + if (r != 1) return Result.err(r); int return_val = secp256k1_h.secp256k1_schnorrsig_verify(ctx, sigSegment, msgSegment, msg_hash.length, pubKeySegmentOpaque); - return Optional.of(return_val == 1); + return Result.ok(return_val == 1); } /** diff --git a/src/test/java/org/consensusj/secp256k1/bitcoinj/AddressTest.java b/src/test/java/org/consensusj/secp256k1/bitcoinj/AddressTest.java index 9e10c42..7cd4bf2 100644 --- a/src/test/java/org/consensusj/secp256k1/bitcoinj/AddressTest.java +++ b/src/test/java/org/consensusj/secp256k1/bitcoinj/AddressTest.java @@ -77,7 +77,7 @@ void createAddressTestBouncyXO() throws Exception { Address tapRootAddress; try (Secp256k1 secp = new Bouncy256k1()) { byte[] serial = HexFormat.of().parseHex("d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d"); - P256K1XOnlyPubKey xOnlyKey = P256K1XOnlyPubKey.parse(serial).orElseThrow(); + P256K1XOnlyPubKey xOnlyKey = P256K1XOnlyPubKey.parse(serial).unwrap(); BigInteger tweakInt = calcTweak(xOnlyKey); P256k1PubKey G = new PubKeyPojo(Secp256k1.EC_PARAMS.getGenerator()); P256k1PubKey P2 = secp.ecPubKeyTweakMul(G, tweakInt);