From 742a1d6ac550e46534b9c43500f098a28c9bcf0a Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 2 Oct 2026 14:30:04 -0400 Subject: [PATCH] fix(keys): build PublicKey.ZERO without reading Key32.zero Key32's companion constructs PublicKeys. When Key32 initialized first, PublicKey's companion ran while Key32.zero was still null, and PublicKey.ZERO threw. Both classes then failed to initialize for the rest of the JVM, which broke every later test that used a key. --- .../com/getcode/solana/keys/PublicKey.kt | 4 ++- .../getcode/solana/keys/KeyInitOrderTest.kt | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 libs/encryption/keys/src/test/kotlin/com/getcode/solana/keys/KeyInitOrderTest.kt diff --git a/libs/encryption/keys/src/main/kotlin/com/getcode/solana/keys/PublicKey.kt b/libs/encryption/keys/src/main/kotlin/com/getcode/solana/keys/PublicKey.kt index 108222754f..8668741349 100644 --- a/libs/encryption/keys/src/main/kotlin/com/getcode/solana/keys/PublicKey.kt +++ b/libs/encryption/keys/src/main/kotlin/com/getcode/solana/keys/PublicKey.kt @@ -28,7 +28,9 @@ open class PublicKey(bytes: List) : Key32(bytes), Parcelable { return PublicKey(byteString.toByteArray().toList()) } - val ZERO: PublicKey = PublicKey(zero.bytes) + // Not Key32.zero: Key32's companion constructs PublicKeys, so when Key32 initializes + // first, this runs before Key32.zero is assigned. + val ZERO: PublicKey = PublicKey(ByteArray(LENGTH_32).toList()) @JvmField val CREATOR: Parcelable.Creator = diff --git a/libs/encryption/keys/src/test/kotlin/com/getcode/solana/keys/KeyInitOrderTest.kt b/libs/encryption/keys/src/test/kotlin/com/getcode/solana/keys/KeyInitOrderTest.kt new file mode 100644 index 0000000000..2c37167894 --- /dev/null +++ b/libs/encryption/keys/src/test/kotlin/com/getcode/solana/keys/KeyInitOrderTest.kt @@ -0,0 +1,36 @@ +package com.getcode.solana.keys + +import java.io.File +import java.net.URLClassLoader +import kotlin.test.Test +import kotlin.test.assertEquals + +class KeyInitOrderTest { + + // Key32's companion builds PublicKeys, so which of the two classes the JVM + // initializes first matters. A fresh class loader controls that order + // regardless of what other tests in this JVM have already touched. + @Test + fun `Key32 initializes when it is loaded before PublicKey`() { + freshClassLoader().use { loader -> + val key32 = Class.forName("com.getcode.solana.keys.Key32", true, loader) + val companion = key32.getField("Companion").get(null) + val zero = companion.javaClass.getMethod("getZero").invoke(companion) + + val publicKey = Class.forName("com.getcode.solana.keys.PublicKey", false, loader) + val publicKeyCompanion = publicKey.getField("Companion").get(null) + val publicZero = publicKeyCompanion.javaClass.getMethod("getZERO").invoke(publicKeyCompanion) + + val bytes = key32.getMethod("getBytes") + assertEquals(bytes.invoke(zero), bytes.invoke(publicZero)) + } + } + + private fun freshClassLoader(): URLClassLoader { + val urls = System.getProperty("java.class.path") + .split(File.pathSeparator) + .map { File(it).toURI().toURL() } + .toTypedArray() + return URLClassLoader(urls, null) + } +}