Skip to content

Commit

Permalink
WIP: Upgrade to JDK 22
Browse files Browse the repository at this point in the history
* Use Java Toolchains to use JDK 22 for compile/test
* Remove `--enable-preview`
* Use JDK 21 for -api and -bouncy modules (we're using a JDK 21 feature in API
  currently, we need to backport this to en earlier JDK version)
* Use JDK 21 temporarily for Kotlin examples (until Kotlin supports JDK 22)
* Use BouncySecp256k1 in the Kotlin examples (which means they won't run until Bouncy is finished) but
  they will at least compile.
* Update the extract-headers.sh script to work with JDK 22 and the latest jextract
* Update OpaqueKeyPair and Secp256k1Foreign to use the JDK 22 FFM API
* Replace the `o.b.s.f.jextract` Java files with the files generated by the latest jextract.
  • Loading branch information
msgilligan committed Mar 7, 2024
1 parent 0e766f1 commit f46cc07
Show file tree
Hide file tree
Showing 45 changed files with 3,292 additions and 1,756 deletions.
17 changes: 11 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,29 @@ subprojects {
}

java {
sourceCompatibility = JavaVersion.toVersion("21")
targetCompatibility = JavaVersion.toVersion("21")
sourceCompatibility = JavaVersion.toVersion("22")
targetCompatibility = JavaVersion.toVersion("22")
toolchain {
// `languageVersion` is used to configure the "Java Toolchain" used for the build. This includes `javac`,
// `jlink`, and the `jpackage` tool.
// See `gradle.properties` for the setting of `javaToolchainVersion` and other setting that are used
// to find and/or download JDK versions.
languageVersion = JavaLanguageVersion.of(javaToolchainVersion)
// vendor = JvmVendorSpec.matching(javaToolchainVendor)
}
}

test {
useJUnitPlatform()
systemProperty "java.library.path", findProperty("javaPath") ?: "/nix/store/j9mf1fh4wbb8c3x1zwqfs218bhml1rbw-secp256k1-0.4.0/lib/"
jvmArgs += '--enable-preview'
jvmArgs += '--enable-native-access=ALL-UNNAMED'
}

tasks.withType(JavaCompile).configureEach {
options.release = 21
options.compilerArgs += ['--enable-preview']
options.release = 22
}

tasks.withType(JavaExec) {
jvmArgs += '--enable-preview'
jvmArgs += '--enable-native-access=ALL-UNNAMED'
}
}
4 changes: 2 additions & 2 deletions extract-headers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ LIB_PKG="secp256k1-0.4.0"
HASH="j9mf1fh4wbb8c3x1zwqfs218bhml1rbw"
SECP_PATH=$NIX_STORE/$HASH-$LIB_PKG
mkdir -p build
jextract --target-package org.bitcoinj.secp256k1.foreign.jextract \
./bin/jextract-22/bin/jextract --target-package org.bitcoinj.secp256k1.foreign.jextract \
--output build \
--source \
--use-system-load-library \
-lsecp256k1 \
--header-class-name secp256k1_h \
$SECP_PATH/include/secp256k1_schnorrsig.h
21 changes: 20 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
secp256k1Version = 0.0.1
secp256k1Version = 0.0.1

# Major (whole number) version of JDK to use for javac, jlink, jpackage, etc.
javaToolchainVersion = 22
# Vendor for javaToolChain. (Should be indicator string from Gradle's KnownJvmVendor enum or empty string)
# Official builds use 'Eclipse Adoptium'
#javaToolchainVendor = Eclipse Adoptium
javaToolchainVendor =

# Where to look for JDKs (via environment variables)
org.gradle.java.installations.fromEnv = JAVA_HOME, JDK22

# Auto-detection can be disabled if you have multiple JDKs of the
# same version installed and Gradle won't reliably select the version you actually want
org.gradle.java.installations.auto-detect = true

# auto-download should generally be disabled and is definitely annoying if you are using
# JDK early access versions and Gradle just reports errors when trying to download them.
org.gradle.java.installations.auto-download = false

2 changes: 1 addition & 1 deletion secp256k1-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ plugins {
}

tasks.withType(JavaCompile).configureEach {
//options.release = 9
options.release = 21
}

ext.moduleName = 'org.bitcoinj.secp256k1.api'
Expand Down
2 changes: 1 addition & 1 deletion secp256k1-bitcoinj/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ plugins {
}

tasks.withType(JavaCompile).configureEach {
//options.release = 9
//options.release = 22
}

ext.moduleName = 'org.bitcoinj.secp256k1.bitcoinj'
Expand Down
2 changes: 1 addition & 1 deletion secp256k1-bouncy/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ plugins {
}

tasks.withType(JavaCompile).configureEach {
//options.release = 9
options.release = 21
}

ext.moduleName = 'org.bitcoinj.secp256k1.bouncy'
Expand Down
11 changes: 10 additions & 1 deletion secp256k1-examples-kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ plugins {
dependencies {
implementation project(':secp256k1-api')
implementation project(':secp256k1-bouncy')
implementation project(':secp256k1-foreign')
//implementation project(':secp256k1-foreign')
implementation 'org.bouncycastle:bcprov-jdk18on:1.77'
}

tasks.withType(JavaCompile).configureEach {
options.release = 21 // Temporary, until Kotlin supports JDK 22
}

kotlin {
jvmToolchain(21)
}

application {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.bitcoinj.secp256k1.kotlin.examples

import org.bitcoinj.secp256k1.foreign.Secp256k1Foreign
import org.bitcoinj.secp256k1.bouncy.Bouncy256k1
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
Expand All @@ -20,7 +20,7 @@ object Ecdsa {
@JvmStatic
fun main(args: Array<String>) {
println("Running secp256k1-jdk Ecdsa example...")
Secp256k1Foreign().use { secp ->
Bouncy256k1().use { secp ->
/* === Key Generation === */
/* Return a non-zero, in-range private key */
val privKey = secp.ecPrivKeyCreate()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.bitcoinj.secp256k1.kotlin.examples

import org.bitcoinj.secp256k1.api.P256K1XOnlyPubKey
import org.bitcoinj.secp256k1.foreign.Secp256k1Foreign
import org.bitcoinj.secp256k1.bouncy.Bouncy256k1
import java.util.*

/**
Expand All @@ -16,7 +16,7 @@ object Schnorr {
@JvmStatic
fun main(args: Array<String>) {
println("Running secp256k1-jdk Schnorr example...")
Secp256k1Foreign().use { secp ->
Bouncy256k1().use { secp ->
/* === Key Generation === */
/* Return a non-zero, in-range private key */
val keyPair = secp.ecKeyPairCreate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public OpaqueKeyPair(byte[] opaque) {

@Override
public P256k1PubKey getPublic() {
MemorySegment keyPairSegment = Secp256k1Foreign.globalArena.allocateArray(JAVA_BYTE, opaque);
MemorySegment keyPairSegment = Secp256k1Foreign.globalArena.allocateFrom(JAVA_BYTE, opaque);
MemorySegment pubKeySegment = secp256k1_pubkey.allocate(Secp256k1Foreign.globalArena);
int return_val = secp256k1_h.secp256k1_keypair_pub(secp256k1_h.secp256k1_context_static$get(), pubKeySegment, keyPairSegment);
int return_val = secp256k1_h.secp256k1_keypair_pub(secp256k1_h.secp256k1_context_static(), pubKeySegment, keyPairSegment);
assert(return_val == 1);
ECPoint pubKeyPoint = Secp256k1Foreign.toPoint(pubKeySegment);
return new PubKeyPojo(pubKeyPoint);
Expand All @@ -41,9 +41,9 @@ public P256k1PrivKey getPrivate() {

@Override
public byte[] getEncoded() {
MemorySegment keyPairSegment = Secp256k1Foreign.globalArena.allocateArray(JAVA_BYTE, opaque);
MemorySegment keyPairSegment = Secp256k1Foreign.globalArena.allocateFrom(JAVA_BYTE, opaque);
MemorySegment privKeySegment = Secp256k1Foreign.globalArena.allocate(32);
int return_val = secp256k1_h.secp256k1_keypair_sec(secp256k1_h.secp256k1_context_static$get(), privKeySegment, keyPairSegment);
int return_val = secp256k1_h.secp256k1_keypair_sec(secp256k1_h.secp256k1_context_static(), privKeySegment, keyPairSegment);
assert(return_val == 1);
return privKeySegment.toArray(JAVA_BYTE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class Secp256k1Foreign implements AutoCloseable, Secp256k1 {
public final Arena arena;
public final MemorySegment ctx;
/* package */ static final Arena globalArena = Arena.ofAuto();
/* package */ static final MemorySegment secp256k1StaticContext = secp256k1_h.secp256k1_context_static$get();
/* package */ static final MemorySegment secp256k1StaticContext = secp256k1_h.secp256k1_context_static();
/**
* TBD: Static verify method that doesn't require a class instance.
*/
Expand All @@ -46,7 +46,7 @@ public static boolean ecdsaVerify(MemorySegment sig, MemorySegment msg_hash, Mem
secp256k1_context_create(). We can simply use the static (i.e., global)
context secp256k1_context_static. See its description in
include/secp256k1.h for details. */
int is_sig_valid = secp256k1_h.secp256k1_ecdsa_verify(secp256k1_h.secp256k1_context_static$get(), sig, msg_hash, pubkey);
int is_sig_valid = secp256k1_h.secp256k1_ecdsa_verify(secp256k1_h.secp256k1_context_static(), sig, msg_hash, pubkey);
return is_sig_valid == 1;
}

Expand Down Expand Up @@ -102,7 +102,7 @@ public P256k1PrivKey ecPrivKeyCreate() {
@Override
public P256k1PubKey ecPubKeyCreate(P256k1PrivKey privkey) {
// Should we verify the key here for safety? (Probably)
MemorySegment privkeySegment = arena.allocateArray(JAVA_BYTE, privkey.getEncoded());
MemorySegment privkeySegment = arena.allocateFrom(JAVA_BYTE, privkey.getEncoded());
MemorySegment pubKey = ecPubKeyCreate(privkeySegment);
privkeySegment.fill((byte) 0x00);
// Return serialized pubkey
Expand Down Expand Up @@ -156,7 +156,7 @@ public P256K1KeyPair ecKeyPairCreate() {
@Override
public P256K1KeyPair ecKeyPairCreate(P256k1PrivKey privKey) {
MemorySegment keyPairSeg = secp256k1_keypair.allocate(arena);
MemorySegment seckey = arena.allocateArray(JAVA_BYTE, privKey.getEncoded());
MemorySegment seckey = arena.allocateFrom(JAVA_BYTE, privKey.getEncoded());
int return_val = secp256k1_h.secp256k1_keypair_create(ctx, keyPairSeg, seckey);
assert(return_val == 1);
P256K1KeyPair keyPair = new OpaqueKeyPair(keyPairSeg.toArray(JAVA_BYTE));
Expand All @@ -168,7 +168,7 @@ public P256K1KeyPair ecKeyPairCreate(P256k1PrivKey privKey) {
public P256k1PubKey ecPubKeyTweakMul(P256k1PubKey pubKey, BigInteger scalarMultiplier) {
MemorySegment pubKeySeg = pubKeyParse(pubKey);
byte[] tweakBytes = P256k1PubKey.integerTo32Bytes(scalarMultiplier);
MemorySegment tweakSeg = arena.allocateArray(JAVA_BYTE, tweakBytes);
MemorySegment tweakSeg = arena.allocateFrom(JAVA_BYTE, tweakBytes);
int return_val = secp256k1_h.secp256k1_ec_pubkey_tweak_mul(ctx, pubKeySeg, tweakSeg);
if (return_val != 1) {
throw new IllegalStateException("Tweak_mul failed");
Expand All @@ -179,7 +179,7 @@ public P256k1PubKey ecPubKeyTweakMul(P256k1PubKey pubKey, BigInteger scalarMulti
@Override
public P256k1PubKey ecPubKeyCombine(P256k1PubKey key1, P256k1PubKey key2) {
MemorySegment resultKeySeg = secp256k1_pubkey.allocate(arena);
MemorySegment ins = arena.allocateArray(C_POINTER, 2);
MemorySegment ins = arena.allocate(C_POINTER, 2);
ins.setAtIndex(C_POINTER, 0, pubKeyParse(key1));
ins.setAtIndex(C_POINTER, 1, pubKeyParse(key2));
int return_val = secp256k1_h.secp256k1_ec_pubkey_combine(ctx, resultKeySeg, ins, 2);
Expand All @@ -191,7 +191,7 @@ public P256k1PubKey ecPubKeyCombine(P256k1PubKey key1, P256k1PubKey key2) {

public P256k1PubKey ecPubKeyCombine(P256k1PubKey key1) {
MemorySegment resultKeySeg = secp256k1_pubkey.allocate(arena);
MemorySegment ins = arena.allocateArray(C_POINTER, 1);
MemorySegment ins = arena.allocate(C_POINTER, 1);
ins.setAtIndex(C_POINTER, 0, pubKeyParse(key1));
int return_val = secp256k1_h.secp256k1_ec_pubkey_combine(ctx, resultKeySeg, ins, 1);
if (return_val != 1) {
Expand Down Expand Up @@ -240,7 +240,7 @@ public CompressedPubKeyData ecPubKeySerialize(P256k1PubKey pubKey, int flags) {

@Override
public Result<P256k1PubKey> ecPubKeyParse(CompressedPubKeyData inputData) {
MemorySegment input = arena.allocateArray(JAVA_BYTE, inputData.bytes());
MemorySegment input = arena.allocateFrom(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) {
Expand All @@ -250,7 +250,7 @@ public Result<P256k1PubKey> ecPubKeyParse(CompressedPubKeyData inputData) {
}

private MemorySegment pubKeyParse(P256k1PubKey pubKeyData) {
MemorySegment input = arena.allocateArray(JAVA_BYTE, pubKeyData.getEncoded()); // 65 byte, uncompressed format
MemorySegment input = arena.allocateFrom(JAVA_BYTE, pubKeyData.getEncoded()); // 65 byte, uncompressed format
MemorySegment pubkey = secp256k1_pubkey.allocate(arena);
int return_val = secp256k1_h.secp256k1_ec_pubkey_parse(ctx, pubkey, input, input.byteSize());
if (return_val != 1) {
Expand All @@ -265,11 +265,11 @@ public Result<SignatureData> ecdsaSign(byte[] msg_hash_data, P256k1PrivKey secke
* custom nonce function, passing `NULL` will use the RFC-6979 safe default.
* Signing with a valid context, verified secret key
* and the default nonce function should never fail. */
MemorySegment msg_hash = arena.allocateArray(JAVA_BYTE, msg_hash_data);
MemorySegment msg_hash = arena.allocateFrom(JAVA_BYTE, msg_hash_data);
MemorySegment sig = secp256k1_ecdsa_signature.allocate(arena);
MemorySegment nullCallback = secp256k1_h.NULL(); // Double-check this (normally you shouldn't use a NULL pointer for a null callback)
MemorySegment nullPointer = secp256k1_h.NULL();
MemorySegment privKeySeg = arena.allocateArray(JAVA_BYTE, seckey.getEncoded());
MemorySegment privKeySeg = arena.allocateFrom(JAVA_BYTE, seckey.getEncoded());
int return_val = secp256k1_h.secp256k1_ecdsa_sign(ctx, sig, msg_hash, privKeySeg, nullCallback, nullPointer);
privKeySeg.fill((byte) 0x00);
return Result.checked(return_val, new SignaturePojo(sig));
Expand All @@ -278,14 +278,14 @@ public Result<SignatureData> ecdsaSign(byte[] msg_hash_data, P256k1PrivKey secke
@Override
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()));
int return_val = secp256k1_h.secp256k1_ecdsa_signature_serialize_compact(ctx, serialized_signature, arena.allocateFrom(JAVA_BYTE, sig.bytes()));
return Result.checked(return_val, new CompressedSignaturePojo(serialized_signature));
}

@Override
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()));
int return_val = secp256k1_h.secp256k1_ecdsa_signature_parse_compact(ctx, sig, arena.allocateFrom(JAVA_BYTE, serialized_signature.bytes()));
return Result.checked(return_val, new SignaturePojo(sig));
}

Expand All @@ -295,9 +295,9 @@ public Result<Boolean> ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256
* custom nonce function, passing `NULL` will use the RFC-6979 safe default.
* Signing with a valid context, verified secret key
* and the default nonce function should never fail. */
MemorySegment msg_hash = arena.allocateArray(JAVA_BYTE, msg_hash_data);
MemorySegment msg_hash = arena.allocateFrom(JAVA_BYTE, msg_hash_data);
int return_val = secp256k1_h.secp256k1_ecdsa_verify(ctx,
arena.allocateArray(JAVA_BYTE, sig.bytes()),
arena.allocateFrom(JAVA_BYTE, sig.bytes()),
msg_hash,
pubKeyParse(pubKey));
return Result.ok(return_val == 1);
Expand All @@ -306,8 +306,8 @@ public Result<Boolean> ecdsaVerify(SignatureData sig, byte[] msg_hash_data, P256
@Override
public byte[] taggedSha256(byte[] tag, byte[] message) {
MemorySegment hash32 = arena.allocate(32);
MemorySegment tagSeg = arena.allocateArray(JAVA_BYTE, tag);
MemorySegment msgSeg = arena.allocateArray(JAVA_BYTE, message);
MemorySegment tagSeg = arena.allocateFrom(JAVA_BYTE, tag);
MemorySegment msgSeg = arena.allocateFrom(JAVA_BYTE, message);
int return_val = secp256k1_h.secp256k1_tagged_sha256(ctx, hash32, tagSeg, tag.length, msgSeg, message.length);
assert(return_val == 1);
return hash32.toArray(JAVA_BYTE);
Expand All @@ -316,9 +316,9 @@ public byte[] taggedSha256(byte[] tag, byte[] message) {
@Override
public byte[] schnorrSigSign32(byte[] messageHash, P256K1KeyPair keyPair) {
MemorySegment sig = arena.allocate(64);
MemorySegment msg_hash = arena.allocateArray(JAVA_BYTE, messageHash);
MemorySegment msg_hash = arena.allocateFrom(JAVA_BYTE, messageHash);
MemorySegment auxiliary_rand = fill_random(arena, 32);
MemorySegment keypair = arena.allocateArray(JAVA_BYTE, ((OpaqueKeyPair) keyPair).getOpaque());
MemorySegment keypair = arena.allocateFrom(JAVA_BYTE, ((OpaqueKeyPair) keyPair).getOpaque());

int return_val = secp256k1_schnorrsig_sign32(ctx, sig, msg_hash, keypair, auxiliary_rand);
assert(return_val == 1);
Expand All @@ -327,9 +327,9 @@ public byte[] schnorrSigSign32(byte[] messageHash, P256K1KeyPair keyPair) {

@Override
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 sigSegment = arena.allocateFrom(JAVA_BYTE, signature);
MemorySegment msgSegment = arena.allocateFrom(JAVA_BYTE, msg_hash);
MemorySegment pubKeySegment = arena.allocateFrom(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);
if (r != 1) return Result.err(r);
Expand All @@ -348,6 +348,6 @@ public static MemorySegment fill_random(SegmentAllocator allocator, int size) {
var rnd = new SecureRandom();
byte[] data = new byte[size];
rnd.nextBytes(data);
return allocator.allocateArray(JAVA_BYTE, data);
return allocator.allocateFrom(JAVA_BYTE, data);
}
}
Loading

0 comments on commit f46cc07

Please sign in to comment.