From 8b03882d6b7111fd970ae6413929c10e2066805f Mon Sep 17 00:00:00 2001 From: jpe7s Date: Sun, 11 Aug 2024 16:28:17 -0500 Subject: [PATCH] move json parser related code to rpc module. --- README.md | 12 +++++++---- core/build.gradle | 1 - core/src/main/java/module-info.java | 2 +- .../sava/core/accounts/PublicKey.java | 10 +-------- .../software/sava/core/accounts/Signer.java | 2 -- .../java/software/sava/core/SignerTest.java | 21 ++++++------------- rpc/src/main/java/module-info.java | 1 + .../sava/rpc/json}/PrivateKeyEncoding.java | 9 ++++---- .../sava/rpc/json/PublicKeyEncoding.java | 17 +++++++++++++++ .../json/http/client/SolanaJsonRpcClient.java | 8 ++++--- .../rpc/json/http/response/AccountInfo.java | 5 +++-- .../json/http/response/ParsedAccountData.java | 13 ++++++------ .../rpc/json/http/response/TxSimulation.java | 3 ++- .../test/soljava/rpc/json/SignerTest.java | 18 ++++++++++++++++ 14 files changed, 73 insertions(+), 49 deletions(-) rename {core/src/main/java/software/sava/core/accounts => rpc/src/main/java/software/sava/rpc/json}/PrivateKeyEncoding.java (91%) create mode 100644 rpc/src/main/java/software/sava/rpc/json/PublicKeyEncoding.java create mode 100644 rpc/src/test/java/test/soljava/rpc/json/SignerTest.java diff --git a/README.md b/README.md index 14b257a..4b96839 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Sava [![Build](https://github.com/sava-software/sava/actions/workflows/gradle.yml/badge.svg)](https://github.com/sava-software/sava/actions/workflows/gradle.yml) [![Release](https://github.com/sava-software/sava/actions/workflows/release.yml/badge.svg)](https://github.com/sava-software/sava/actions/workflows/release.yml) -## Features +### Features - HTTP and WebSocket JSON RPC Clients. - Transaction (de)serialization. @@ -11,18 +11,22 @@ - Crypto utilities for elliptic curve Ed25519 and Solana accounts. - Borsh (de)serialization. -## Requirements +### Requirements - The latest generally available JDK. This project will continue to move to the latest and will not maintain versions released against previous JDK's. -## Dependencies +### Dependencies +#### Core - java.base - java.net.http - [Bouncy Castle](https://www.bouncycastle.org/download/bouncy-castle-java/#latest) + +#### RPC +- software.sava.core - [JSON Iterator](https://github.com/comodal/json-iterator?tab=readme-ov-file#json-iterator) -## Contribution +### Contribution Unit tests are needed and welcomed. Otherwise, please open an issue or send an email before working on a pull request. diff --git a/core/build.gradle b/core/build.gradle index 7406c16..0c26cb1 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,4 +1,3 @@ dependencies { - implementation "systems.comodal:json-iterator:$jsoniter" implementation "org.bouncycastle:bcprov-$bouncycastleMinor:$bouncycastlePatch" } \ No newline at end of file diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 22e364e..72abdbd 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -1,6 +1,6 @@ module software.sava.core { requires java.net.http; - requires systems.comodal.json_iterator; + requires org.bouncycastle.provider; exports software.sava.core.accounts; diff --git a/core/src/main/java/software/sava/core/accounts/PublicKey.java b/core/src/main/java/software/sava/core/accounts/PublicKey.java index 4576e9b..3566263 100644 --- a/core/src/main/java/software/sava/core/accounts/PublicKey.java +++ b/core/src/main/java/software/sava/core/accounts/PublicKey.java @@ -3,8 +3,6 @@ import software.sava.core.crypto.Hash; import software.sava.core.crypto.ed25519.Ed25519Util; import software.sava.core.encoding.Base58; -import systems.comodal.jsoniter.CharBufferFunction; -import systems.comodal.jsoniter.JsonIterator; import java.util.Arrays; import java.util.Base64; @@ -17,13 +15,7 @@ public interface PublicKey extends Comparable { int PUBLIC_KEY_LENGTH = 32; - CharBufferFunction PARSE_BASE58_PUBLIC_KEY = PublicKey::fromBase58Encoded; - // 11111111111111111111111111111111 - PublicKey NONE = new PublicKeyBytes(new byte[PUBLIC_KEY_LENGTH]); - - static PublicKey parseBase58Encoded(final JsonIterator ji) { - return ji.applyChars(PublicKey.PARSE_BASE58_PUBLIC_KEY); - } + PublicKey NONE = new PublicKeyBytes(new byte[PUBLIC_KEY_LENGTH]); // 11111111111111111111111111111111 static PublicKey readPubKey(final byte[] bytes, final int offset) { return new PublicKeyBytes(Arrays.copyOfRange(bytes, offset, offset + PublicKey.PUBLIC_KEY_LENGTH)); diff --git a/core/src/main/java/software/sava/core/accounts/Signer.java b/core/src/main/java/software/sava/core/accounts/Signer.java index 48bacc0..27d596b 100644 --- a/core/src/main/java/software/sava/core/accounts/Signer.java +++ b/core/src/main/java/software/sava/core/accounts/Signer.java @@ -65,7 +65,6 @@ static byte[] createKeyPairBytesFromPrivateKey(final byte[] privateKey) { static Signer createFromPrivateKey(final byte[] privateKey) { final byte[] copiedPrivateKey = Arrays.copyOfRange(privateKey, 0, Signer.KEY_LENGTH); - Arrays.fill(privateKey, (byte) 0); final byte[] publicKey = new byte[Signer.KEY_LENGTH]; Ed25519Util.generatePublicKey(copiedPrivateKey, 0, publicKey, 0); validateKeyPair(copiedPrivateKey, publicKey); @@ -82,7 +81,6 @@ static Signer createFromKeyPair(final byte[] keyPair) { static Signer createFromKeyPair(final byte[] publicKey, final byte[] privateKey) { validateKeyPair(privateKey, publicKey); final var copiedPrivateKey = Arrays.copyOfRange(privateKey, 0, Signer.KEY_LENGTH); - Arrays.fill(privateKey, (byte) 0); return new KeyPairSigner( Arrays.copyOfRange(publicKey, 0, Signer.KEY_LENGTH), copiedPrivateKey diff --git a/core/src/test/java/software/sava/core/SignerTest.java b/core/src/test/java/software/sava/core/SignerTest.java index f653a3f..df3be7e 100644 --- a/core/src/test/java/software/sava/core/SignerTest.java +++ b/core/src/test/java/software/sava/core/SignerTest.java @@ -2,11 +2,9 @@ import org.bouncycastle.math.ec.rfc8032.Ed25519; import org.junit.jupiter.api.Test; -import software.sava.core.accounts.PrivateKeyEncoding; import software.sava.core.accounts.Signer; import software.sava.core.encoding.Base58; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -16,7 +14,7 @@ final class SignerTest { @Test - public void generateKeyPair() { + void generateKeyPair() { final byte[] keyPair = Signer.generatePrivateKeyPairBytes(); final var expectedPublicKey = Base58.encode(Arrays.copyOfRange(keyPair, 32, 64)); @@ -31,38 +29,31 @@ public void generateKeyPair() { } @Test - public void accountFromSecretKey() { + void accountFromSecretKey() { final byte[] secretKey = Base58.decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs"); assertEquals("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo", Signer.createFromKeyPair(secretKey).publicKey().toString()); } @Test - public void fromBip44Mnemonic() { + void fromBip44Mnemonic() { final var acc = Signer.fromBip44Mnemonic(Arrays.asList("hint", "begin", "crowd", "dolphin", "drive", "render", "finger", "above", "sponsor", "prize", "runway", "invest", "dizzy", "pony", "bitter", "trial", "ignore", "crop", "please", "industry", "hockey", "wire", "use", "side"), ""); assertEquals("G75kGJiizyFNdnvvHxkrBrcwLomGJT2CigdXnsYzrFHv", acc.publicKey().toString()); } @Test - public void fromBip44MnemonicChange() { + void fromBip44MnemonicChange() { final var acc = Signer.fromBip44MnemonicWithChange(Arrays.asList("hint", "begin", "crowd", "dolphin", "drive", "render", "finger", "above", "sponsor", "prize", "runway", "invest", "dizzy", "pony", "bitter", "trial", "ignore", "crop", "please", "industry", "hockey", "wire", "use", "side"), ""); assertEquals("AaXs7cLGcSVAsEt8QxstVrqhLhYN2iGhFNRemwYnHitV", acc.publicKey().toString()); } @Test - public void fromJson() { - final var json = "[94,151,102,217,69,77,121,169,76,7,9,241,196,119,233,67,25,222,209,40,113,70,33,81,154,33,136,30,208,45,227,28,23,245,32,61,13,33,156,192,84,169,95,202,37,105,150,21,157,105,107,130,13,134,235,7,16,130,50,239,93,206,244,0]"; - final var acc = PrivateKeyEncoding.fromJsonArray(json.getBytes(StandardCharsets.US_ASCII)); - assertEquals("2cXAj2TagK3t6rb2CGRwyhF6sTFJgLyzyDGSWBcGd8Go", acc.publicKey().toString()); - } - - @Test - public void fromBip39MnemonicTest() { + void fromBip39MnemonicTest() { final var account = Signer.fromBip39Mnemonic(Arrays.asList("iron", "make", "indoor", "where", "explain", "model", "maximum", "wonder", "toward", "salad", "fan", "try"), ""); assertEquals("BeepMww3KwiDeEhEeZmqk4TegvJYNuDERPWm142X6Mx3", account.publicKey().toBase58()); } @Test - public void recoverPublicKey() { + void recoverPublicKey() { final byte[] keyPairBytes = Signer.generatePrivateKeyPairBytes(); final byte[] privatePublicCopy = Arrays.copyOf(keyPairBytes, keyPairBytes.length); final var signer = Signer.createFromKeyPair(keyPairBytes); diff --git a/rpc/src/main/java/module-info.java b/rpc/src/main/java/module-info.java index 9eae706..7b7e83b 100644 --- a/rpc/src/main/java/module-info.java +++ b/rpc/src/main/java/module-info.java @@ -4,6 +4,7 @@ requires systems.comodal.json_iterator; requires java.net.http; + exports software.sava.rpc.json; exports software.sava.rpc.json.http; exports software.sava.rpc.json.http.client; exports software.sava.rpc.json.http.request; diff --git a/core/src/main/java/software/sava/core/accounts/PrivateKeyEncoding.java b/rpc/src/main/java/software/sava/rpc/json/PrivateKeyEncoding.java similarity index 91% rename from core/src/main/java/software/sava/core/accounts/PrivateKeyEncoding.java rename to rpc/src/main/java/software/sava/rpc/json/PrivateKeyEncoding.java index 1f3390a..3efe349 100644 --- a/core/src/main/java/software/sava/core/accounts/PrivateKeyEncoding.java +++ b/rpc/src/main/java/software/sava/rpc/json/PrivateKeyEncoding.java @@ -1,5 +1,6 @@ -package software.sava.core.accounts; +package software.sava.rpc.json; +import software.sava.core.accounts.Signer; import software.sava.core.encoding.Base58; import systems.comodal.jsoniter.CharBufferFunction; import systems.comodal.jsoniter.JsonIterator; @@ -40,8 +41,7 @@ public static Signer fromJsonArray(final JsonIterator ji) { for (int i = 0; ji.readArray(); ) { publicKey[i++] = (byte) ji.readInt(); } - Signer.validateKeyPair(privateKey, publicKey); - return new KeyPairSigner(publicKey, privateKey); + return Signer.createFromKeyPair(publicKey, privateKey); } public static Signer fromJsonArray(final byte[] jsonArrayKeyPair) { @@ -49,8 +49,7 @@ public static Signer fromJsonArray(final byte[] jsonArrayKeyPair) { return fromJsonArray(ji); } - public static Signer fromJsonPrivateKey(final JsonIterator ji, - final PrivateKeyEncoding encoding) { + public static Signer fromJsonPrivateKey(final JsonIterator ji, final PrivateKeyEncoding encoding) { return switch (encoding) { case jsonKeyPairArray -> fromJsonArray(ji); case base64PrivateKey -> Signer.createFromPrivateKey(ji.decodeBase64String()); diff --git a/rpc/src/main/java/software/sava/rpc/json/PublicKeyEncoding.java b/rpc/src/main/java/software/sava/rpc/json/PublicKeyEncoding.java new file mode 100644 index 0000000..f62a9ec --- /dev/null +++ b/rpc/src/main/java/software/sava/rpc/json/PublicKeyEncoding.java @@ -0,0 +1,17 @@ +package software.sava.rpc.json; + +import software.sava.core.accounts.PublicKey; +import systems.comodal.jsoniter.CharBufferFunction; +import systems.comodal.jsoniter.JsonIterator; + +public final class PublicKeyEncoding { + + public static CharBufferFunction PARSE_BASE58_PUBLIC_KEY = PublicKey::fromBase58Encoded; + + public static PublicKey parseBase58Encoded(final JsonIterator ji) { + return ji.applyChars(PARSE_BASE58_PUBLIC_KEY); + } + + private PublicKeyEncoding() { + } +} diff --git a/rpc/src/main/java/software/sava/rpc/json/http/client/SolanaJsonRpcClient.java b/rpc/src/main/java/software/sava/rpc/json/http/client/SolanaJsonRpcClient.java index cd9ad9d..e6ee9dc 100644 --- a/rpc/src/main/java/software/sava/rpc/json/http/client/SolanaJsonRpcClient.java +++ b/rpc/src/main/java/software/sava/rpc/json/http/client/SolanaJsonRpcClient.java @@ -6,6 +6,7 @@ import software.sava.core.accounts.token.TokenAccount; import software.sava.core.rpc.Filter; import software.sava.core.tx.Transaction; +import software.sava.rpc.json.PublicKeyEncoding; import software.sava.rpc.json.http.request.Commitment; import software.sava.rpc.json.http.request.ContextBoolVal; import software.sava.rpc.json.http.response.*; @@ -24,6 +25,7 @@ import java.util.stream.Collectors; import static java.lang.String.format; +import static software.sava.rpc.json.PublicKeyEncoding.parseBase58Encoded; final class SolanaJsonRpcClient extends JsonRpcHttpClient implements SolanaRpcClient { @@ -54,7 +56,7 @@ final class SolanaJsonRpcClient extends JsonRpcHttpClient implements SolanaRpcCl }; private static final Function, Map> LEADER_SCHEDULE = applyResponseResult(ji -> { final var schedule = new HashMap(); - for (PublicKey validator; (validator = ji.applyObjField(PublicKey.PARSE_BASE58_PUBLIC_KEY)) != null; ) { + for (PublicKey validator; (validator = PublicKeyEncoding.parseBase58Encoded(ji)) != null; ) { schedule.put(validator, PARSE_LONG_ARRAY.apply(ji)); } return schedule; @@ -68,11 +70,11 @@ final class SolanaJsonRpcClient extends JsonRpcHttpClient implements SolanaRpcCl private static final Function, Instant> INSTANT = applyResponseResult(ji -> Instant.ofEpochSecond(ji.readLong())); private static final Function, ContextBoolVal> CONTEXT_BOOL_VAL = applyResponseValue(ContextBoolVal::parse); private static final Function, String> STRING = applyResponseResult(JsonIterator::readString); - private static final Function, PublicKey> PUBLIC_KEY = applyResponseResult(PublicKey::parseBase58Encoded); + private static final Function, PublicKey> PUBLIC_KEY = applyResponseResult(PublicKeyEncoding::parseBase58Encoded); private static final Function, List> PUBLIC_KEY_LIST = applyResponseResult(ji -> { final var strings = new ArrayList(); while (ji.readArray()) { - strings.add(PublicKey.parseBase58Encoded(ji)); + strings.add(parseBase58Encoded(ji)); } return strings; }); diff --git a/rpc/src/main/java/software/sava/rpc/json/http/response/AccountInfo.java b/rpc/src/main/java/software/sava/rpc/json/http/response/AccountInfo.java index 27b8f16..1ffb7c4 100644 --- a/rpc/src/main/java/software/sava/rpc/json/http/response/AccountInfo.java +++ b/rpc/src/main/java/software/sava/rpc/json/http/response/AccountInfo.java @@ -4,6 +4,7 @@ import software.sava.core.accounts.lookup.AddressLookupTable; import software.sava.core.util.DecimalIntegerAmount; import software.sava.core.util.LamportDecimal; +import software.sava.rpc.json.PublicKeyEncoding; import systems.comodal.jsoniter.ContextFieldBufferPredicate; import systems.comodal.jsoniter.JsonIterator; import systems.comodal.jsoniter.ValueType; @@ -102,7 +103,7 @@ public static List> parseAccounts(final JsonIterator ji, } else if (fieldEquals("lamports", buf, offset, len)) { builder.lamports = ji.readLong(); } else if (fieldEquals("owner", buf, offset, len)) { - builder.owner = PublicKey.parseBase58Encoded(ji); + builder.owner = PublicKeyEncoding.parseBase58Encoded(ji); } else if (fieldEquals("rentEpoch", buf, offset, len)) { builder.rentEpoch = ji.readBigInteger(); } else if (fieldEquals("space", buf, offset, len)) { @@ -117,7 +118,7 @@ public static List> parseAccounts(final JsonIterator ji, if (fieldEquals("account", buf, offset, len)) { ji.testObject(builder, ACCOUNT_PARSER); } else if (fieldEquals("pubkey", buf, offset, len)) { - builder.pubKey = PublicKey.parseBase58Encoded(ji); + builder.pubKey = PublicKeyEncoding.parseBase58Encoded(ji); } else { ji.skip(); } diff --git a/rpc/src/main/java/software/sava/rpc/json/http/response/ParsedAccountData.java b/rpc/src/main/java/software/sava/rpc/json/http/response/ParsedAccountData.java index e513693..421a7c9 100644 --- a/rpc/src/main/java/software/sava/rpc/json/http/response/ParsedAccountData.java +++ b/rpc/src/main/java/software/sava/rpc/json/http/response/ParsedAccountData.java @@ -1,6 +1,7 @@ package software.sava.rpc.json.http.response; import software.sava.core.accounts.PublicKey; +import software.sava.rpc.json.PublicKeyEncoding; import systems.comodal.jsoniter.ContextFieldBufferPredicate; import systems.comodal.jsoniter.JsonIterator; @@ -31,13 +32,13 @@ public static ParsedAccountData parse(final JsonIterator ji, final Context conte if (fieldEquals("isNative", buf, offset, len)) { builder.isNative = ji.readBoolean(); } else if (fieldEquals("mint", buf, offset, len)) { - builder.mint = PublicKey.parseBase58Encoded(ji); + builder.mint = PublicKeyEncoding.parseBase58Encoded(ji); } else if (fieldEquals("owner", buf, offset, len)) { - builder.owner = PublicKey.parseBase58Encoded(ji); + builder.owner = PublicKeyEncoding.parseBase58Encoded(ji); } else if (fieldEquals("freezeAuthority", buf, offset, len)) { - builder.freezeAuthority = PublicKey.parseBase58Encoded(ji); + builder.freezeAuthority = PublicKeyEncoding.parseBase58Encoded(ji); } else if (fieldEquals("mintAuthority", buf, offset, len)) { - builder.mintAuthority = PublicKey.parseBase58Encoded(ji); + builder.mintAuthority = PublicKeyEncoding.parseBase58Encoded(ji); } else if (fieldEquals("isInitialized", buf, offset, len)) { builder.isInitialized = ji.readBoolean(); } else if (fieldEquals("state", buf, offset, len)) { @@ -51,7 +52,7 @@ public static ParsedAccountData parse(final JsonIterator ji, final Context conte } else if (fieldEquals("addresses", buf, offset, len)) { final var addresses = new ArrayList(); while (ji.readArray()) { - addresses.add(PublicKey.parseBase58Encoded(ji)); + addresses.add(PublicKeyEncoding.parseBase58Encoded(ji)); } builder.addresses = addresses; } else { @@ -73,7 +74,7 @@ public static ParsedAccountData parse(final JsonIterator ji, final Context conte private static final ContextFieldBufferPredicate PARSER = (builder, buf, offset, len, ji) -> { if (fieldEquals("program", buf, offset, len)) { - builder.program = PublicKey.parseBase58Encoded(ji); + builder.program = PublicKeyEncoding.parseBase58Encoded(ji); } else if (fieldEquals("space", buf, offset, len)) { builder.space = ji.readInt(); } else if (fieldEquals("parsed", buf, offset, len)) { diff --git a/rpc/src/main/java/software/sava/rpc/json/http/response/TxSimulation.java b/rpc/src/main/java/software/sava/rpc/json/http/response/TxSimulation.java index 29b81af..4769bdb 100644 --- a/rpc/src/main/java/software/sava/rpc/json/http/response/TxSimulation.java +++ b/rpc/src/main/java/software/sava/rpc/json/http/response/TxSimulation.java @@ -1,6 +1,7 @@ package software.sava.rpc.json.http.response; import software.sava.core.accounts.PublicKey; +import software.sava.rpc.json.PublicKeyEncoding; import systems.comodal.jsoniter.ContextFieldBufferPredicate; import systems.comodal.jsoniter.JsonIterator; @@ -27,7 +28,7 @@ public static TxSimulation parse(final List accounts, final JsonItera private static final ContextFieldBufferPredicate RETURN_DATA_PARSER = (builder, buf, offset, len, ji) -> { if (fieldEquals("programId", buf, offset, len)) { - builder.programId(PublicKey.parseBase58Encoded(ji)); + builder.programId(PublicKeyEncoding.parseBase58Encoded(ji)); } else if (fieldEquals("data", buf, offset, len)) { builder.data(parseEncodedData(ji)); } else { diff --git a/rpc/src/test/java/test/soljava/rpc/json/SignerTest.java b/rpc/src/test/java/test/soljava/rpc/json/SignerTest.java new file mode 100644 index 0000000..7070f86 --- /dev/null +++ b/rpc/src/test/java/test/soljava/rpc/json/SignerTest.java @@ -0,0 +1,18 @@ +package test.soljava.rpc.json; + +import org.junit.jupiter.api.Test; +import software.sava.rpc.json.PrivateKeyEncoding; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +final class SignerTest { + + @Test + void fromJson() { + final var json = "[94,151,102,217,69,77,121,169,76,7,9,241,196,119,233,67,25,222,209,40,113,70,33,81,154,33,136,30,208,45,227,28,23,245,32,61,13,33,156,192,84,169,95,202,37,105,150,21,157,105,107,130,13,134,235,7,16,130,50,239,93,206,244,0]"; + final var acc = PrivateKeyEncoding.fromJsonArray(json.getBytes(StandardCharsets.US_ASCII)); + assertEquals("2cXAj2TagK3t6rb2CGRwyhF6sTFJgLyzyDGSWBcGd8Go", acc.publicKey().toString()); + } +}