Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Option<T> with Result<T> #1

Merged
merged 1 commit into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public byte[] getSerialized() {
return P256k1PubKey.integerTo32Bytes(x);
}

public static Optional<P256K1XOnlyPubKey> parse(byte[] serialized) {
public static Result<P256K1XOnlyPubKey> 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));
}
}
38 changes: 38 additions & 0 deletions secp256k1-api/src/main/java/org/bitcoinj/secp256k1/api/Result.java
Original file line number Diff line number Diff line change
@@ -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<T> {
record Ok<T>(T result) implements Result<T> {}
record Err<T>(int code) implements Result<T> {}

static <T> Result<T> ok(T result) {
return new Ok<>(result);
}

static <T> Result<T> err(int error_code) {
return new Err<>(error_code);
}

/**
* Return a value if error_code is equal to one (no error).
*/
static <T> Result<T> 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<T> ok -> ok.result();
case Err<T> err -> throw new IllegalStateException(message + ": " + err.code());
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ public interface Secp256k1 extends AutoCloseable {

CompressedPubKeyData ecPubKeySerialize(P256k1PubKey pubKey, int flags);

Optional<P256k1PubKey> ecPubKeyParse(CompressedPubKeyData inputData);
Result<P256k1PubKey> ecPubKeyParse(CompressedPubKeyData inputData);

Optional<SignatureData> ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey);
Result<SignatureData> ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey);

Optional<CompressedSignatureData> ecdsaSignatureSerializeCompact(SignatureData sig);
Result<CompressedSignatureData> ecdsaSignatureSerializeCompact(SignatureData sig);

Optional<SignatureData> ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature);
Result<SignatureData> ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature);

Optional<Boolean> ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey);
Result<Boolean> 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));
Expand All @@ -54,5 +54,5 @@ default byte[] taggedSha256(String tag, String message) {

Object schnorrSigSign32(byte[] msg_hash, P256K1KeyPair keyPair);

Optional<Boolean> schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey);
Result<Boolean> schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -109,28 +110,28 @@ public CompressedPubKeyData ecPubKeySerialize(P256k1PubKey pubKey, int flags) {
}

@Override
public Optional<P256k1PubKey> ecPubKeyParse(CompressedPubKeyData inputData) {
return Optional.empty();
public Result<P256k1PubKey> ecPubKeyParse(CompressedPubKeyData inputData) {
return Result.err(-1);
}

@Override
public Optional<SignatureData> ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey) {
return Optional.empty();
public Result<SignatureData> ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey) {
return Result.err(-1);
}

@Override
public Optional<CompressedSignatureData> ecdsaSignatureSerializeCompact(SignatureData sig) {
return Optional.empty();
public Result<CompressedSignatureData> ecdsaSignatureSerializeCompact(SignatureData sig) {
return Result.err(-1);
}

@Override
public Optional<SignatureData> ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature) {
return Optional.empty();
public Result<SignatureData> ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature) {
return Result.err(-1);
}

@Override
public Optional<Boolean> ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey) {
return Optional.empty();
public Result<Boolean> ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey) {
return Result.err(-1);
}
@Override
public byte[] taggedSha256(byte[] tag, byte[] message) {
Expand All @@ -143,8 +144,8 @@ public Object schnorrSigSign32(byte[] msg_hash, P256K1KeyPair keyPair) {
}

@Override
public Optional<Boolean> schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey) {
return Optional.empty();
public Result<Boolean> schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey) {
return Result.err(-1);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -238,16 +239,14 @@ public CompressedPubKeyData ecPubKeySerialize(P256k1PubKey pubKey, int flags) {
}

@Override
public Optional<P256k1PubKey> ecPubKeyParse(CompressedPubKeyData inputData) {
public Result<P256k1PubKey> 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) {
Expand All @@ -261,7 +260,7 @@ private MemorySegment pubKeyParse(P256k1PubKey pubKeyData) {
}

@Override
public Optional<SignatureData> ecdsaSign(byte[] msg_hash_data, P256k1PrivKey seckey) {
public Result<SignatureData> 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
Expand All @@ -273,28 +272,25 @@ public Optional<SignatureData> 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<CompressedSignatureData> ecdsaSignatureSerializeCompact(SignatureData sig) {
public Result<CompressedSignatureData> 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<SignatureData> ecdsaSignatureParseCompact(CompressedSignatureData serialized_signature) {
public Result<SignatureData> 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<Boolean> ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256k1PubKey pubKey) {
public Result<Boolean> 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
Expand All @@ -304,7 +300,7 @@ public Optional<Boolean> 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
Expand All @@ -330,15 +326,15 @@ public byte[] schnorrSigSign32(byte[] messageHash, P256K1KeyPair keyPair) {
}

@Override
public Optional<Boolean> schnorrSigVerify(byte[] signature, byte[] msg_hash, P256K1XOnlyPubKey pubKey) {
public Result<Boolean> 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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down