Skip to content

Commit

Permalink
move json parser related code to rpc module.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpe7s committed Aug 11, 2024
1 parent 8fd7ced commit 8b03882
Show file tree
Hide file tree
Showing 14 changed files with 73 additions and 49 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
1 change: 0 additions & 1 deletion core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
dependencies {
implementation "systems.comodal:json-iterator:$jsoniter"
implementation "org.bouncycastle:bcprov-$bouncycastleMinor:$bouncycastlePatch"
}
2 changes: 1 addition & 1 deletion core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
10 changes: 1 addition & 9 deletions core/src/main/java/software/sava/core/accounts/PublicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,13 +15,7 @@
public interface PublicKey extends Comparable<PublicKey> {

int PUBLIC_KEY_LENGTH = 32;
CharBufferFunction<PublicKey> 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));
Expand Down
2 changes: 0 additions & 2 deletions core/src/main/java/software/sava/core/accounts/Signer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down
21 changes: 6 additions & 15 deletions core/src/test/java/software/sava/core/SignerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));

Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions rpc/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -40,17 +41,15 @@ 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) {
final var ji = JsonIterator.parse(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());
Expand Down
17 changes: 17 additions & 0 deletions rpc/src/main/java/software/sava/rpc/json/PublicKeyEncoding.java
Original file line number Diff line number Diff line change
@@ -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<PublicKey> PARSE_BASE58_PUBLIC_KEY = PublicKey::fromBase58Encoded;

public static PublicKey parseBase58Encoded(final JsonIterator ji) {
return ji.applyChars(PARSE_BASE58_PUBLIC_KEY);
}

private PublicKeyEncoding() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand All @@ -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 {

Expand Down Expand Up @@ -54,7 +56,7 @@ final class SolanaJsonRpcClient extends JsonRpcHttpClient implements SolanaRpcCl
};
private static final Function<HttpResponse<byte[]>, Map<PublicKey, long[]>> LEADER_SCHEDULE = applyResponseResult(ji -> {
final var schedule = new HashMap<PublicKey, long[]>();
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;
Expand All @@ -68,11 +70,11 @@ final class SolanaJsonRpcClient extends JsonRpcHttpClient implements SolanaRpcCl
private static final Function<HttpResponse<byte[]>, Instant> INSTANT = applyResponseResult(ji -> Instant.ofEpochSecond(ji.readLong()));
private static final Function<HttpResponse<byte[]>, ContextBoolVal> CONTEXT_BOOL_VAL = applyResponseValue(ContextBoolVal::parse);
private static final Function<HttpResponse<byte[]>, String> STRING = applyResponseResult(JsonIterator::readString);
private static final Function<HttpResponse<byte[]>, PublicKey> PUBLIC_KEY = applyResponseResult(PublicKey::parseBase58Encoded);
private static final Function<HttpResponse<byte[]>, PublicKey> PUBLIC_KEY = applyResponseResult(PublicKeyEncoding::parseBase58Encoded);
private static final Function<HttpResponse<byte[]>, List<PublicKey>> PUBLIC_KEY_LIST = applyResponseResult(ji -> {
final var strings = new ArrayList<PublicKey>();
while (ji.readArray()) {
strings.add(PublicKey.parseBase58Encoded(ji));
strings.add(parseBase58Encoded(ji));
}
return strings;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -102,7 +103,7 @@ public static <T> List<AccountInfo<T>> 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)) {
Expand All @@ -117,7 +118,7 @@ public static <T> List<AccountInfo<T>> 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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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)) {
Expand All @@ -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<PublicKey>();
while (ji.readArray()) {
addresses.add(PublicKey.parseBase58Encoded(ji));
addresses.add(PublicKeyEncoding.parseBase58Encoded(ji));
}
builder.addresses = addresses;
} else {
Expand All @@ -73,7 +74,7 @@ public static ParsedAccountData parse(final JsonIterator ji, final Context conte

private static final ContextFieldBufferPredicate<Builder> 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)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -27,7 +28,7 @@ public static TxSimulation parse(final List<PublicKey> accounts, final JsonItera

private static final ContextFieldBufferPredicate<Builder> 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 {
Expand Down
18 changes: 18 additions & 0 deletions rpc/src/test/java/test/soljava/rpc/json/SignerTest.java
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit 8b03882

Please sign in to comment.