diff --git a/.github/badges/branches.svg b/.github/badges/branches.svg index 422739d..5dc08e3 100644 --- a/.github/badges/branches.svg +++ b/.github/badges/branches.svg @@ -1 +1 @@ -branches36.7% \ No newline at end of file +branches39.5% \ No newline at end of file diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg index 50d0d19..4b6025e 100644 --- a/.github/badges/jacoco.svg +++ b/.github/badges/jacoco.svg @@ -1 +1 @@ -coverage76.7% \ No newline at end of file +coverage78.2% \ No newline at end of file diff --git a/README.md b/README.md index bac75e8..3d313cd 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,39 @@ cd sui/target/release TODO +## Supported APIs +- [ ] sui_batchTransaction +- [x] sui_dryRunTransaction +- [ ] sui_executeTransaction +- [ ] sui_getCoinMetadata +- [x] sui_getCommitteeInfo +- [x] sui_getEvents +- [x] sui_getMoveFunctionArgTypes +- [x] sui_getNormalizedMoveFunction +- [x] sui_getNormalizedMoveModule +- [x] sui_getNormalizedMoveModulesByPackage +- [x] sui_getNormalizedMoveStruct +- [x] sui_getObject +- [x] sui_getObjectsOwnedByAddress +- [x] sui_getObjectsOwnedByObject +- [x] sui_getRawObject +- [x] sui_getTotalTransactionNumber +- [x] sui_getTransaction +- [ ] sui_getTransactions +- [x] sui_getTransactionsInRange +- [x] sui_mergeCoins +- [x] sui_moveCall +- [x] sui_pay +- [x] sui_payAllSui +- [x] sui_paySui +- [x] sui_publish +- [x] sui_splitCoin +- [x] sui_splitCoinEqual +- [ ] sui_subscribeEvent +- [x] sui_transferObject +- [x] sui_transferSui +- [ ] sui_tryGetPastObject + ## Examples ### create client diff --git a/build.gradle b/build.gradle index 0eb932c..be92a5a 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,14 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:4.10.0' // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 implementation 'org.apache.commons:commons-lang3:3.12.0' + // https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on + implementation 'org.bouncycastle:bcprov-jdk18on:1.72' + // https://mvnrepository.com/artifact/org.web3j/crypto + implementation('org.web3j:crypto:4.9.5') { + exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on' + exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind' + exclude group: 'org.slf4j', module: 'slf4j-api' + } // https://mvnrepository.com/artifact/com.squareup.okhttp3/mockwebserver testImplementation 'com.squareup.okhttp3:mockwebserver:4.10.0' diff --git a/src/integrationTest/java/io/sui/ExecutionClientImplIntTests.java b/src/integrationTest/java/io/sui/ExecutionClientImplIntTests.java index c922822..e718283 100644 --- a/src/integrationTest/java/io/sui/ExecutionClientImplIntTests.java +++ b/src/integrationTest/java/io/sui/ExecutionClientImplIntTests.java @@ -17,6 +17,8 @@ package io.sui; +import io.sui.clients.ExecutionClient; +import io.sui.clients.ExecutionClientImpl; import io.sui.jsonrpc.GsonJsonHandler; import io.sui.jsonrpc.JsonHandler; import io.sui.jsonrpc.JsonRpcClientProvider; diff --git a/src/integrationTest/java/io/sui/JsonRpcTransactionBuilderIntTests.java b/src/integrationTest/java/io/sui/JsonRpcTransactionBuilderIntTests.java index 4494361..56d83f6 100644 --- a/src/integrationTest/java/io/sui/JsonRpcTransactionBuilderIntTests.java +++ b/src/integrationTest/java/io/sui/JsonRpcTransactionBuilderIntTests.java @@ -18,6 +18,8 @@ import com.google.common.collect.Lists; +import io.sui.clients.JsonRpcTransactionBuilder; +import io.sui.clients.TransactionBuilder; import io.sui.jsonrpc.GsonJsonHandler; import io.sui.jsonrpc.JsonHandler; import io.sui.jsonrpc.JsonRpcClientProvider; diff --git a/src/integrationTest/java/io/sui/QueryClientImplIntTests.java b/src/integrationTest/java/io/sui/QueryClientImplIntTests.java index 0c9d32d..d297127 100644 --- a/src/integrationTest/java/io/sui/QueryClientImplIntTests.java +++ b/src/integrationTest/java/io/sui/QueryClientImplIntTests.java @@ -18,6 +18,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import io.sui.clients.QueryClient; +import io.sui.clients.QueryClientImpl; import io.sui.jsonrpc.GsonJsonHandler; import io.sui.jsonrpc.JsonHandler; import io.sui.jsonrpc.JsonRpc20Response.Error.ErrorCode; @@ -317,7 +319,7 @@ void getTransactionsInRange() throws ExecutionException, InterruptedException { @DisplayName("Test getEvents.") void getEvents() throws ExecutionException, InterruptedException { TransactionEventQuery query = new TransactionEventQuery(); - query.setTransaction("ov1tDrhdOqRW2uFweTbSSTaQbBbnjHWmrsh675lwb0Q="); + query.setTransaction("9HF7ZAfdStA8d9eUuxfKBn4V2vWcvzT8tccs4CAVrFtj"); CompletableFuture res = client.getEvents(query, null, 1, false); System.out.println(res.get()); } diff --git a/src/main/java/io/sui/ExecutionClient.java b/src/main/java/io/sui/clients/ExecutionClient.java similarity index 97% rename from src/main/java/io/sui/ExecutionClient.java rename to src/main/java/io/sui/clients/ExecutionClient.java index f43c4e3..41d47e2 100644 --- a/src/main/java/io/sui/ExecutionClient.java +++ b/src/main/java/io/sui/clients/ExecutionClient.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.sui; +package io.sui.clients; import io.sui.models.transactions.TransactionEffects; diff --git a/src/main/java/io/sui/ExecutionClientImpl.java b/src/main/java/io/sui/clients/ExecutionClientImpl.java similarity index 98% rename from src/main/java/io/sui/ExecutionClientImpl.java rename to src/main/java/io/sui/clients/ExecutionClientImpl.java index 55ed8a1..9876b13 100644 --- a/src/main/java/io/sui/ExecutionClientImpl.java +++ b/src/main/java/io/sui/clients/ExecutionClientImpl.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.sui; +package io.sui.clients; import com.google.common.collect.Lists; diff --git a/src/main/java/io/sui/JsonRpcTransactionBuilder.java b/src/main/java/io/sui/clients/JsonRpcTransactionBuilder.java similarity index 99% rename from src/main/java/io/sui/JsonRpcTransactionBuilder.java rename to src/main/java/io/sui/clients/JsonRpcTransactionBuilder.java index 583cd2c..53ad64d 100644 --- a/src/main/java/io/sui/JsonRpcTransactionBuilder.java +++ b/src/main/java/io/sui/clients/JsonRpcTransactionBuilder.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.sui; +package io.sui.clients; import com.google.common.collect.Lists; diff --git a/src/main/java/io/sui/QueryClient.java b/src/main/java/io/sui/clients/QueryClient.java similarity index 99% rename from src/main/java/io/sui/QueryClient.java rename to src/main/java/io/sui/clients/QueryClient.java index 0cff940..d7d11e7 100644 --- a/src/main/java/io/sui/QueryClient.java +++ b/src/main/java/io/sui/clients/QueryClient.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.sui; +package io.sui.clients; import io.sui.models.CommitteeInfoResponse; diff --git a/src/main/java/io/sui/QueryClientImpl.java b/src/main/java/io/sui/clients/QueryClientImpl.java similarity index 99% rename from src/main/java/io/sui/QueryClientImpl.java rename to src/main/java/io/sui/clients/QueryClientImpl.java index 87014fc..192556d 100644 --- a/src/main/java/io/sui/QueryClientImpl.java +++ b/src/main/java/io/sui/clients/QueryClientImpl.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.sui; +package io.sui.clients; import com.google.common.collect.Lists; diff --git a/src/main/java/io/sui/TransactionBuilder.java b/src/main/java/io/sui/clients/TransactionBuilder.java similarity index 99% rename from src/main/java/io/sui/TransactionBuilder.java rename to src/main/java/io/sui/clients/TransactionBuilder.java index 6bc1caf..0873ec5 100644 --- a/src/main/java/io/sui/TransactionBuilder.java +++ b/src/main/java/io/sui/clients/TransactionBuilder.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.sui; +package io.sui.clients; import io.sui.models.transactions.TransactionBytes; diff --git a/src/main/java/io/sui/crypto/AbstractKeyStore.java b/src/main/java/io/sui/crypto/AbstractKeyStore.java new file mode 100644 index 0000000..6007bd1 --- /dev/null +++ b/src/main/java/io/sui/crypto/AbstractKeyStore.java @@ -0,0 +1,32 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + + +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * The type Abstract key store. + * + * @author grapebaba + * @since 2022.11 + */ +public abstract class AbstractKeyStore implements KeyStore { + + /** The Keys. */ + protected final ConcurrentSkipListMap> keys = new ConcurrentSkipListMap<>(); +} diff --git a/src/main/java/io/sui/crypto/ED25519KeyPair.java b/src/main/java/io/sui/crypto/ED25519KeyPair.java new file mode 100644 index 0000000..28eff1e --- /dev/null +++ b/src/main/java/io/sui/crypto/ED25519KeyPair.java @@ -0,0 +1,72 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + + +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; +import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters; +import org.bouncycastle.jcajce.provider.digest.SHA3.Digest256; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +/** + * The type Secp256k1 key pair. + * + * @author grapebaba + * @since 2022.11 + */ +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +public class ED25519KeyPair extends SuiKeyPair { + + /** + * Instantiates a new Ed 25519 key pair. + * + * @param privateKeyParameters the private key parameters + * @param publicKeyParameters the public key parameters + */ + public ED25519KeyPair( + Ed25519PrivateKeyParameters privateKeyParameters, + Ed25519PublicKeyParameters publicKeyParameters) { + this.keyPair = new AsymmetricCipherKeyPair(publicKeyParameters, privateKeyParameters); + } + + @Override + public String address() { + final Digest256 digest256 = new Digest256(); + final byte[] hash = + digest256.digest( + Arrays.prepend( + ((Ed25519PublicKeyParameters) keyPair.getPublic()).getEncoded(), + SignatureScheme.ED25519.getScheme())); + return "0x" + StringUtils.substring(Hex.toHexString(hash), 0, 40); + } + + /** + * Decode base 64 sui key pair. + * + * @param encoded the encoded + * @return the sui key pair + */ + public static ED25519KeyPair decodeBase64(byte[] encoded) { + Ed25519PrivateKeyParameters privateKeyParameters = + new Ed25519PrivateKeyParameters(encoded, 1 + Ed25519PublicKeyParameters.KEY_SIZE); + Ed25519PublicKeyParameters publicKeyParameters = privateKeyParameters.generatePublicKey(); + return new ED25519KeyPair(privateKeyParameters, publicKeyParameters); + } +} diff --git a/src/main/java/io/sui/crypto/FileBasedKeyStore.java b/src/main/java/io/sui/crypto/FileBasedKeyStore.java new file mode 100644 index 0000000..f47174d --- /dev/null +++ b/src/main/java/io/sui/crypto/FileBasedKeyStore.java @@ -0,0 +1,72 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * The type File based key store. + * + * @author grapebaba + * @since 2022.11 + */ +public class FileBasedKeyStore extends AbstractKeyStore { + + private final String path; + + /** + * Instantiates a new File based key store. + * + * @param path the path + */ + public FileBasedKeyStore(String path) { + this.path = path; + if (Files.exists(Paths.get(this.path))) { + try { + JsonArray json = + new Gson().fromJson(Files.newBufferedReader(Paths.get(this.path)), JsonArray.class); + json.asList() + .forEach( + jsonElement -> { + try { + final SuiKeyPair keyPair = + SuiKeyPair.decodeBase64(jsonElement.getAsString()); + FileBasedKeyStore.super.keys.putIfAbsent(keyPair.address(), keyPair); + } catch (SignatureSchemeNotSupportedException e) { + throw new FileBasedKeyStoreInitException(e); + } + }); + } catch (IOException e) { + throw new FileBasedKeyStoreInitException(e); + } + } + } + + /** + * Gets path. + * + * @return the path + */ + public String getPath() { + return path; + } +} diff --git a/src/main/java/io/sui/crypto/FileBasedKeyStoreInitException.java b/src/main/java/io/sui/crypto/FileBasedKeyStoreInitException.java new file mode 100644 index 0000000..8b6a441 --- /dev/null +++ b/src/main/java/io/sui/crypto/FileBasedKeyStoreInitException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + +/** + * The type FileBasedKeyStoreInitException. + * + * @author grapebaba + * @since 2022.11 + */ +public class FileBasedKeyStoreInitException extends RuntimeException { + + /** + * Instantiates a new File based key store init exception. + * + * @param cause the cause + */ + public FileBasedKeyStoreInitException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/io/sui/crypto/KeyStore.java b/src/main/java/io/sui/crypto/KeyStore.java new file mode 100644 index 0000000..d60546d --- /dev/null +++ b/src/main/java/io/sui/crypto/KeyStore.java @@ -0,0 +1,25 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + +/** + * The interface Key store. + * + * @author grapebaba + * @since 2022.11 + */ +public interface KeyStore {} diff --git a/src/main/java/io/sui/crypto/SECP256K1KeyPair.java b/src/main/java/io/sui/crypto/SECP256K1KeyPair.java new file mode 100644 index 0000000..08326bd --- /dev/null +++ b/src/main/java/io/sui/crypto/SECP256K1KeyPair.java @@ -0,0 +1,68 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + +import static org.bouncycastle.util.Arrays.prepend; + +import java.util.Arrays; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.jcajce.provider.digest.SHA3; +import org.bouncycastle.util.encoders.Hex; +import org.web3j.crypto.ECKeyPair; +import org.web3j.crypto.Sign; + +/** + * The type Secp256k1 key pair. + * + * @author grapebaba + * @since 2022.11 + */ +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +public class SECP256K1KeyPair extends SuiKeyPair { + + /** + * Instantiates a new Secp 256 k 1 key pair. + * + * @param privateKey the secret key + */ + public SECP256K1KeyPair(byte[] privateKey) { + this.keyPair = ECKeyPair.create(privateKey); + } + + /** + * Decode base 64 sui key pair. + * + * @param encoded the encoded + * @return the sui key pair + */ + public static SECP256K1KeyPair decodeBase64(byte[] encoded) { + final int compressedPublicKeySize = 33; + return new SECP256K1KeyPair( + Arrays.copyOfRange(encoded, 1 + compressedPublicKeySize, encoded.length)); + } + + @Override + public String address() { + SHA3.Digest256 digest256 = new SHA3.Digest256(); + byte[] hash = + digest256.digest( + prepend( + Sign.publicPointFromPrivate(keyPair.getPrivateKey()).getEncoded(true), + SignatureScheme.Secp256k1.getScheme())); + return "0x" + StringUtils.substring(Hex.toHexString(hash), 0, 40); + } +} diff --git a/src/main/java/io/sui/crypto/SignatureScheme.java b/src/main/java/io/sui/crypto/SignatureScheme.java new file mode 100644 index 0000000..2abdd58 --- /dev/null +++ b/src/main/java/io/sui/crypto/SignatureScheme.java @@ -0,0 +1,69 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + + +import java.util.HashMap; +import java.util.Map; + +/** + * The enum Signature scheme. + * + * @author grapebaba + * @since 2022.11 + */ +public enum SignatureScheme { + /** Ed 25519 signature scheme. */ + ED25519((byte) 0x00), + /** Secp 256 k 1 signature scheme. */ + Secp256k1((byte) 0x01), + /** Bls 12381 signature scheme. */ + BLS12381((byte) 0xff); + + private static final Map BY_SCHEME = new HashMap<>(); + + static { + for (SignatureScheme e : values()) { + BY_SCHEME.put(e.scheme, e); + } + } + + private final byte scheme; + + SignatureScheme(byte scheme) { + this.scheme = scheme; + } + + /** + * Gets scheme. + * + * @return the scheme + */ + public byte getScheme() { + return scheme; + } + + /** + * Value of signature scheme. + * + * @param scheme the scheme + * @return the signature scheme + */ + public static SignatureScheme valueOf(byte scheme) { + return BY_SCHEME.get(scheme); + } +} diff --git a/src/main/java/io/sui/crypto/SignatureSchemeNotSupportedException.java b/src/main/java/io/sui/crypto/SignatureSchemeNotSupportedException.java new file mode 100644 index 0000000..4d57d72 --- /dev/null +++ b/src/main/java/io/sui/crypto/SignatureSchemeNotSupportedException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + +/** + * The type Signature scheme not supported exception. + * + * @author grapebaba + * @since 2022.11 + */ +public class SignatureSchemeNotSupportedException extends Exception { + + /** Instantiates a new Signature scheme not supported exception. */ + public SignatureSchemeNotSupportedException() { + super("only ed25519 and secp256k1 signature scheme supported."); + } +} diff --git a/src/main/java/io/sui/crypto/SuiKeyPair.java b/src/main/java/io/sui/crypto/SuiKeyPair.java new file mode 100644 index 0000000..7cd1ffa --- /dev/null +++ b/src/main/java/io/sui/crypto/SuiKeyPair.java @@ -0,0 +1,79 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + + +import org.bouncycastle.util.encoders.Base64; + +/** + * The type Sui key pair. + * + * @param the type parameter + * @author grapebaba + * @since 2022.11 + */ +public abstract class SuiKeyPair { + + /** The Key pair. */ + protected T keyPair; + + /** + * Gets key pair. + * + * @return the key pair + */ + public T getKeyPair() { + return keyPair; + } + + @Override + public String toString() { + return "SuiKeyPair{" + "keyPair=" + keyPair + '}'; + } + + /** + * Address string. + * + * @return the string + */ + public abstract String address(); + + /** + * Decode base64 sui key pair. + * + * @param encoded the encoded + * @return the sui key pair + * @throws SignatureSchemeNotSupportedException the signature scheme not supported exception + */ + public static SuiKeyPair decodeBase64(String encoded) + throws SignatureSchemeNotSupportedException { + final byte[] keyPairBytes = Base64.decode(encoded); + + final SignatureScheme scheme = SignatureScheme.valueOf(keyPairBytes[0]); + if (scheme == null) { + throw new SignatureSchemeNotSupportedException(); + } + switch (scheme) { + case ED25519: + return ED25519KeyPair.decodeBase64(keyPairBytes); + case Secp256k1: + return SECP256K1KeyPair.decodeBase64(keyPairBytes); + default: + throw new SignatureSchemeNotSupportedException(); + } + } +} diff --git a/src/main/java/io/sui/jsonrpc/JsonRpcClientProvider.java b/src/main/java/io/sui/jsonrpc/JsonRpcClientProvider.java index 4a33190..1743e6c 100644 --- a/src/main/java/io/sui/jsonrpc/JsonRpcClientProvider.java +++ b/src/main/java/io/sui/jsonrpc/JsonRpcClientProvider.java @@ -72,9 +72,12 @@ public CompletableFuture callAndUnwrapResponse( .thenAccept( jsonRpc20Response -> { if (jsonRpc20Response.getError() != null) { - SuiApiException e = new SuiApiException(jsonRpc20Response.getError()); + final SuiApiException e; if (jsonRpc20Response.getThrowable() != null) { - e.setCause(jsonRpc20Response.getThrowable()); + e = new SuiApiException(jsonRpc20Response.getThrowable()); + e.setError(jsonRpc20Response.getError()); + } else { + e = new SuiApiException(jsonRpc20Response.getError()); } future.completeExceptionally(e); } else { diff --git a/src/main/java/io/sui/models/SuiApiException.java b/src/main/java/io/sui/models/SuiApiException.java index df144e3..99f603e 100644 --- a/src/main/java/io/sui/models/SuiApiException.java +++ b/src/main/java/io/sui/models/SuiApiException.java @@ -25,12 +25,10 @@ * @author grapebaba * @since 2022.11 */ -public class SuiApiException extends RuntimeException { +public class SuiApiException extends Exception { private JsonRpc20Response.Error error; - private Throwable cause; - /** * Instantiates a new Sui api exception. * @@ -57,13 +55,4 @@ public JsonRpc20Response.Error getError() { public void setError(JsonRpc20Response.Error error) { this.error = error; } - - public void setCause(Throwable cause) { - this.cause = cause; - } - - @Override - public Throwable getCause() { - return cause; - } } diff --git a/src/test/java/io/sui/ExecutionClientImplTest.java b/src/test/java/io/sui/clients/ExecutionClientImplTest.java similarity index 99% rename from src/test/java/io/sui/ExecutionClientImplTest.java rename to src/test/java/io/sui/clients/ExecutionClientImplTest.java index fd0a744..f317095 100644 --- a/src/test/java/io/sui/ExecutionClientImplTest.java +++ b/src/test/java/io/sui/clients/ExecutionClientImplTest.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.sui; +package io.sui.clients; import static io.sui.models.transactions.ExecutionStatus.ExecutionStatusType.success; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/io/sui/JsonRpcTransactionBuilderTests.java b/src/test/java/io/sui/clients/JsonRpcTransactionBuilderTests.java similarity index 99% rename from src/test/java/io/sui/JsonRpcTransactionBuilderTests.java rename to src/test/java/io/sui/clients/JsonRpcTransactionBuilderTests.java index 1e8bebd..bd6c122 100644 --- a/src/test/java/io/sui/JsonRpcTransactionBuilderTests.java +++ b/src/test/java/io/sui/clients/JsonRpcTransactionBuilderTests.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.sui; +package io.sui.clients; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/io/sui/QueryClientImplTests.java b/src/test/java/io/sui/clients/QueryClientImplTests.java similarity index 99% rename from src/test/java/io/sui/QueryClientImplTests.java rename to src/test/java/io/sui/clients/QueryClientImplTests.java index 73edf16..ae4e611 100644 --- a/src/test/java/io/sui/QueryClientImplTests.java +++ b/src/test/java/io/sui/clients/QueryClientImplTests.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.sui; +package io.sui.clients; import static io.sui.models.objects.MoveFunctionArgType.ObjectValueKindMoveFunctionArgType.ObjectValueKind.ByMutableReference; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/io/sui/crypto/ED25519KeyPairTest.java b/src/test/java/io/sui/crypto/ED25519KeyPairTest.java new file mode 100644 index 0000000..904ae9c --- /dev/null +++ b/src/test/java/io/sui/crypto/ED25519KeyPairTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.StandardCharsets; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.signers.Ed25519Signer; +import org.bouncycastle.jcajce.provider.digest.SHA3; +import org.bouncycastle.jcajce.provider.digest.SHA3.Digest256; +import org.bouncycastle.util.encoders.Base64; +import org.junit.jupiter.api.Test; + +/** + * The type Ed25519 key pair test. + * + * @author grapebaba + * @since 2022.11 + */ +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +class ED25519KeyPairTest { + + /** Address. */ + @Test + void address() { + final String base64 = + "AOSqUUDaiPGYESoI/G13YwT2qyWW/RRvsW2G7IoCG" + + "URYYbRLmbwt4NZ9m9x7s8taYhCJg9OQdkrmSTVioUVpCSM="; + final ED25519KeyPair keyPair = ED25519KeyPair.decodeBase64(Base64.decode(base64)); + + assertEquals("0x1e7752f22228753e5745f5ac8ad4ef1bbc502845", keyPair.address()); + } + + /** Decode base 64. */ + @Test + void decodeBase64() throws CryptoException { + final String base64 = + "AOSqUUDaiPGYESoI/G13YwT2qyWW/RRvsW2G7IoCG" + + "URYYbRLmbwt4NZ9m9x7s8taYhCJg9OQdkrmSTVioUVpCSM="; + final ED25519KeyPair ed25519KeyPair = ED25519KeyPair.decodeBase64(Base64.decode(base64)); + + Signer signer = new Ed25519Signer(); + signer.init(true, ed25519KeyPair.getKeyPair().getPrivate()); + final String msg = "test"; + final SHA3.Digest256 digest = new Digest256(); + final byte[] encodedHash = digest.digest(msg.getBytes(StandardCharsets.UTF_8)); + signer.update(encodedHash, 0, encodedHash.length); + byte[] signature = signer.generateSignature(); + + Signer verifier = new Ed25519Signer(); + verifier.init(false, ed25519KeyPair.getKeyPair().getPublic()); + verifier.update(encodedHash, 0, encodedHash.length); + assertTrue(verifier.verifySignature(signature)); + } +} diff --git a/src/test/java/io/sui/crypto/FileBasedKeyStoreTest.java b/src/test/java/io/sui/crypto/FileBasedKeyStoreTest.java new file mode 100644 index 0000000..aa0c1ca --- /dev/null +++ b/src/test/java/io/sui/crypto/FileBasedKeyStoreTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Paths; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; + +/** + * The type File based key store test. + * + * @author grapebaba + * @since 2022.11 + */ +class FileBasedKeyStoreTest { + + /** Gets path. */ + @Test + void getPath() { + FileBasedKeyStore fileBasedKeyStore = + new FileBasedKeyStore( + Paths.get("src/test/resources/config/sui.keystore").toAbsolutePath().toString()); + System.out.println(fileBasedKeyStore.getPath()); + assertTrue(StringUtils.endsWith(fileBasedKeyStore.getPath(), "config/sui.keystore")); + } + + /** Init key pairs. */ + @Test + void initKeyPairs() { + FileBasedKeyStore fileBasedKeyStore = + new FileBasedKeyStore( + Paths.get("src/test/resources/config/sui.keystore").toAbsolutePath().toString()); + + assertEquals(11, fileBasedKeyStore.keys.size()); + String expected = + "0x1e7752f22228753e5745f5ac8ad4ef1bbc502845\n" + + "0x207f2c9f08472b1ff68644fdfc7a70df10cb3d4e\n" + + "0x49ef9b602b76a37e0f9177783755c1a190866e72\n" + + "0x51972acc644b8c6dd81d6088780b40e842a0a10c\n" + + "0x51de405091c9f971fc6085d384f9ba764f268fca\n" + + "0x63485e00efc944d62349b79f88a11b7cacc2a764\n" + + "0x78cec6011e9d0690d5fbbfa4d25987a087a88ee7\n" + + "0x887ddfbf2bc37d757eabb08d62bf725a04922bde\n" + + "0xca21af1b5b347d315d7355ff9e6e73cc79d0a4d0\n" + + "0xe8da3f038048e2cd6339e916a926874d0d0604b7\n" + + "0xea79464d86786b7a7a63e3f13f798f29f5e65947\n"; + StringBuilder actual = new StringBuilder(); + for (String key : fileBasedKeyStore.keys.navigableKeySet()) { + actual.append(key).append("\n"); + } + assertEquals(expected, actual.toString()); + } +} diff --git a/src/test/java/io/sui/crypto/SECP256K1KeyPairTest.java b/src/test/java/io/sui/crypto/SECP256K1KeyPairTest.java new file mode 100644 index 0000000..febd146 --- /dev/null +++ b/src/test/java/io/sui/crypto/SECP256K1KeyPairTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import org.bouncycastle.jcajce.provider.digest.SHA3; +import org.bouncycastle.jcajce.provider.digest.SHA3.Digest256; +import org.bouncycastle.util.encoders.Base64; +import org.junit.jupiter.api.Test; +import org.web3j.crypto.ECKeyPair; +import org.web3j.crypto.Sign; +import org.web3j.crypto.Sign.SignatureData; + +/** + * The type Secp256k1 key pair test. + * + * @author grapebaba + * @since 2022.11 + */ +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +class SECP256K1KeyPairTest { + + /** + * Address. + * + * @throws NoSuchAlgorithmException the no such algorithm exception + */ + @Test + void address() throws NoSuchAlgorithmException { + final String base64 = + "AQLE7fDdDt4nrbGgCX8umsFscJRFY4t3Bkrk3MaB" + + "b1nnA6dD5QHIFrPAdPQtdDyfoJNjiN/ghxuVLxfHxehcwec0"; + final SuiKeyPair secp256K1KeyPair = + SECP256K1KeyPair.decodeBase64(Base64.decode(base64)); + + assertEquals("0xe8da3f038048e2cd6339e916a926874d0d0604b7", secp256K1KeyPair.address()); + } + + /** + * Decode base 64. + * + * @throws SignatureException the signature exception + */ + @Test + void decodeBase64() throws SignatureException { + final String base64 = + "AQLE7fDdDt4nrbGgCX8umsFscJRFY4t3Bkrk3MaBb1nnA6dD5QH" + + "IFrPAdPQtdDyfoJNjiN/ghxuVLxfHxehcwec0"; + final SuiKeyPair secp256K1KeyPair = + SECP256K1KeyPair.decodeBase64(Base64.decode(base64)); + final String msg = "test"; + final SHA3.Digest256 digest = new Digest256(); + final byte[] encodedHash = digest.digest(msg.getBytes(StandardCharsets.UTF_8)); + final SignatureData signatureData = + Sign.signMessage(encodedHash, secp256K1KeyPair.keyPair, false); + BigInteger pubKeyRecovered = Sign.signedMessageHashToKey(encodedHash, signatureData); + assertEquals(secp256K1KeyPair.getKeyPair().getPublicKey(), pubKeyRecovered); + } +} diff --git a/src/test/java/io/sui/crypto/SuiKeyPairTest.java b/src/test/java/io/sui/crypto/SuiKeyPairTest.java new file mode 100644 index 0000000..df77a23 --- /dev/null +++ b/src/test/java/io/sui/crypto/SuiKeyPairTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 281165273grape@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.sui.crypto; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.charset.StandardCharsets; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Base64; +import org.junit.jupiter.api.Test; + +/** + * The type Sui key pair test. + * + * @author grapebaba + * @since 2022.11 + */ +class SuiKeyPairTest { + + /** Decode base 64. */ + @Test + void decodeBase64() { + final String base64 = + "AQLE7fDdDt4nrbGgCX8umsFscJRFY4t3Bkrk3MaB" + + "b1nnA6dD5QHIFrPAdPQtdDyfoJNjiN/ghxuVLxfHxehcwec0"; + try { + SuiKeyPair.decodeBase64(base64); + } catch (SignatureSchemeNotSupportedException e) { + e.printStackTrace(); + } + + final String wrongBase64 = Base64.toBase64String("test".getBytes(StandardCharsets.UTF_8)); + assertThrows( + SignatureSchemeNotSupportedException.class, () -> SuiKeyPair.decodeBase64(wrongBase64)); + + final String blsBase64 = + Base64.toBase64String(Arrays.prepend("test".getBytes(StandardCharsets.UTF_8), (byte) 0xff)); + assertThrows( + SignatureSchemeNotSupportedException.class, () -> SuiKeyPair.decodeBase64(blsBase64)); + } +} diff --git a/src/test/resources/config/sui.keystore b/src/test/resources/config/sui.keystore new file mode 100644 index 0000000..e745c28 --- /dev/null +++ b/src/test/resources/config/sui.keystore @@ -0,0 +1,13 @@ +[ + "AOSqUUDaiPGYESoI/G13YwT2qyWW/RRvsW2G7IoCGURYYbRLmbwt4NZ9m9x7s8taYhCJg9OQdkrmSTVioUVpCSM=", + "AQOn0iLM/e8juxoMI4V1Usun+6ohvRqH5JBZ6gOV4bFW89iK30AcgeoKuTw6ftEdYeHtuIo+cJ9+nh9iV5QsjwHz", + "APuC5Q1OqLitEra+foDkhcuNCw5QXl3+SWyJfgN6W/UvWnP2cVdopkxK/yhW2s+R3YGMiukPURMUX4q5mKfGTyk=", + "ACQKog3HkkIWw7/hGXekeyc/oLUua2H7JZwhjXgycpxggZoUkSgyKt2MTevSG9PB5aZLN84cvgwMqzxpDtkFDJY=", + "AJty6phE36GqKobHqHGOtplnLXQ6jq1AVaiiX7XkDuA7ZRbMX0ps9KUnAm3mKO4vXR+El4vdf7qk1FYMK3PAIaY=", + "AGkJtIyKHM0hf528r/PxwwK7oKEDgXXtv+AGO1qaVfNxVN9ABp/RbUimEybWK1o0pY3JpEUnIve/6v1sHOLzfik=", + "AQLeapMz9pSCMPHXOf+fl7snw6O5wgz/WQgLM+Rmzh7BFWlfRE6/2ROHOa6K88gdMRdsAAI7nP/1TRJqhuasG4nW", + "AFjviUpW2gMO7bs9D7lRKO1zlmjNTpvbZJXdNUZhizPFkL+LtkqeZuDLV+GmDb9IP2L3pS6J19uz24r8aoFj//Q=", + "AF7bpdmbv/s7zvt/vjps6lLj/4bkz9FzGeXzmjYHAfz8hdarcmYgoQvtlCS4EtEAR2AUX9g7HDuwlxs1HbXTlEU=", + "AQLE7fDdDt4nrbGgCX8umsFscJRFY4t3Bkrk3MaBb1nnA6dD5QHIFrPAdPQtdDyfoJNjiN/ghxuVLxfHxehcwec0", + "AJ/On29cDTJ1yHMIJV2Wj87LMPHNxD+IIOsa498SQcmIGTayUd4nokl1C9qFhtCXatrzKUSEIHX7l0dZeAO2TVs=" +] \ No newline at end of file