├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ ├── layout
│ │ │ │ ├── content_main.xml
│ │ │ │ ├── fragment_main.xml
│ │ │ │ └── activity_main.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── org
│ │ │ │ └── nkn
│ │ │ │ └── app
│ │ │ │ ├── MainActivityFragment.kt
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── org
│ │ │ └── nkn
│ │ │ └── app
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── org
│ │ └── nkn
│ │ └── app
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── library
├── consumer-rules.pro
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ └── xml
│ │ │ │ └── network_security_config.xml
│ │ ├── java
│ │ │ └── org
│ │ │ │ └── nkn
│ │ │ │ └── sdk
│ │ │ │ ├── configure
│ │ │ │ ├── Rpc.kt
│ │ │ │ ├── Configure.kt
│ │ │ │ └── Seed.kt
│ │ │ │ ├── const
│ │ │ │ ├── StatusCode.kt
│ │ │ │ └── crypto.kt
│ │ │ │ ├── error
│ │ │ │ ├── RpcError.kt
│ │ │ │ └── WalletError.kt
│ │ │ │ ├── cache
│ │ │ │ └── memberCache.kt
│ │ │ │ ├── crypto
│ │ │ │ ├── Account.kt
│ │ │ │ ├── encryption.kt
│ │ │ │ ├── hash.kt
│ │ │ │ └── Key.kt
│ │ │ │ ├── ClientListener.kt
│ │ │ │ ├── transaction
│ │ │ │ ├── transaction.kt
│ │ │ │ └── payload.kt
│ │ │ │ ├── utils
│ │ │ │ ├── EncodeUtils.kt
│ │ │ │ ├── Base58.kt
│ │ │ │ └── Utils.kt
│ │ │ │ ├── network
│ │ │ │ ├── RpcApi.kt
│ │ │ │ └── WsApi.kt
│ │ │ │ ├── protocol
│ │ │ │ ├── encryption.kt
│ │ │ │ └── protocol.kt
│ │ │ │ ├── Client.kt
│ │ │ │ ├── Wallet.kt
│ │ │ │ └── pb
│ │ │ │ └── NodeProto.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── org
│ │ │ └── nkn
│ │ │ └── sdk
│ │ │ ├── ExampleUnitTest.kt
│ │ │ ├── RpcApiTest.kt
│ │ │ ├── WalletTest.kt
│ │ │ ├── EncryptionTest.kt
│ │ │ ├── HashTest.kt
│ │ │ └── UtilsTest.kt
│ └── androidTest
│ │ └── java
│ │ └── org
│ │ └── nkn
│ │ └── sdk
│ │ └── ExampleInstrumentedTest.kt
├── pb
│ ├── node.proto
│ ├── payloads.proto
│ ├── sigchain.proto
│ ├── block.proto
│ ├── clientmessage.proto
│ ├── transaction.proto
│ └── nodemessage.proto
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':library', ':app'
2 | rootProject.name='nkn-sdk-android'
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | library
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nknorg/nkn-sdk-android/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/configure/Rpc.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.configure
2 |
3 | const val RPC_ADDR = "https://mainnet-rpc-node-0001.nkn.org/mainnet/api/wallet"
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | app
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/const/StatusCode.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.const
2 |
3 | enum class StatusCode(val code: Int) {
4 | SUCCESS(0),
5 | WRONG_NODE(48001)
6 | }
--------------------------------------------------------------------------------
/library/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | .idea
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 03 14:38:24 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/configure/Configure.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.configure
2 |
3 | const val NKN_ACC_MUL = 100000000
4 | const val ENCRYPT = true
5 | const val MSG_HOLDING_SECONDS = 3600
6 | const val RECONNECT_INTERVAL_MIN = 1000L
7 | const val RECONNECT_INTERVAL_MAX = 64000L
8 | const val RESPONSE_TIMEOUT = 10
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/error/RpcError.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.error
2 |
3 |
4 | enum class RpcErrorCode(val code: Int) {
5 | UNKNOWN_ERROR(0)
6 |
7 | }
8 |
9 | class RpcError(val code: RpcErrorCode, override val message: String) : Throwable() {
10 | companion object {
11 | const val UNKNOWN_ERROR = "unknown error"
12 | }
13 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/cache/memberCache.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.cache
2 |
3 | import android.util.LruCache
4 |
5 | const val MULTI_CLIENT_CACHE_SIZE = 4 * 1024 * 1024
6 | const val SHARED_KEY_CACHE_SIZE = 16 * 1024 * 1024
7 |
8 | val multiClientCache = LruCache(MULTI_CLIENT_CACHE_SIZE)
9 | val sharedKeyCache = LruCache(SHARED_KEY_CACHE_SIZE)
10 |
--------------------------------------------------------------------------------
/library/src/test/java/org/nkn/sdk/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 | import org.junit.Test
4 |
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 |
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/const/crypto.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.const
2 |
3 | const val SEED_SIZE: Int = 32
4 | const val SECRET_KEY_SIZE: Int = 64
5 | const val PUBLICKEY_SIZE: Int = 32
6 | const val SHARED_SIZE: Int = 32
7 | const val KEY_SIZE: Int = 32
8 | const val NONCE_SIZE: Int = 24
9 | const val SECRETBOX_NONCE_SIZE: Int = 24
10 | const val SECRET_NONCE_SIZE: Int = 24
11 | const val SIGNATURE_SIZE: Int = 64
12 |
13 |
--------------------------------------------------------------------------------
/app/src/test/java/org/nkn/app/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.app
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/library/pb/node.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pb;
4 |
5 | option java_package = "org.nkn.sdk.pb";
6 | option java_outer_classname = "NodeProto";
7 |
8 | enum SyncState {
9 | WAIT_FOR_SYNCING = 0;
10 | SYNC_STARTED = 1;
11 | SYNC_FINISHED = 2;
12 | PERSIST_FINISHED = 3;
13 | }
14 |
15 | message NodeData {
16 | bytes public_key = 1;
17 | uint32 websocket_port = 2;
18 | uint32 json_rpc_port = 3;
19 | uint32 protocol_version = 4;
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/library/src/test/java/org/nkn/sdk/RpcApiTest.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 |
4 | import org.json.JSONObject
5 | import org.junit.Assert
6 | import org.junit.Test
7 | import org.nkn.sdk.network.RpcApi
8 | import kotlinx.coroutines.*
9 | import kotlin.system.measureTimeMillis
10 |
11 | class RpcApiTest {
12 | @Test
13 | fun rpcApi_test() = runBlocking {
14 | var api = RpcApi()
15 | var res = api.request("getbalancebyaddr", mapOf("address" to "NKNVCZYpUk94xe3p3miNGSoQnkidQUfPMQxx"))
16 | println(res)
17 |
18 | }
19 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/crypto/Account.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.crypto
2 |
3 | import org.nkn.sdk.utils.Utils
4 |
5 | class Account {
6 | val key: Key
7 | val address: String
8 | val contract: String
9 | val signatureRedeem: String
10 |
11 | constructor(seed: Any? = null) {
12 | key = Key(seed)
13 | address = Utils.programHashStringToAddress(key.programHash)
14 | contract = Utils.genAccountContractString(key.signatureRedeem, key.programHash)
15 | signatureRedeem = key.signatureRedeem
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/ClientListener.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 | abstract class ClientListener {
4 | abstract fun onConnect()
5 | abstract fun onMessage(src: String, data: String?, pid: ByteArray, type: Int, encrypted: Boolean): Any?
6 | abstract fun onBinaryMessage(
7 | src: String,
8 | data: ByteArray?,
9 | pid: ByteArray,
10 | type: Int,
11 | encrypted: Boolean
12 | ): Any?
13 | abstract fun onClosing()
14 | abstract fun onClosed()
15 | abstract fun onError(e: Throwable)
16 | abstract fun onBlock()
17 | }
--------------------------------------------------------------------------------
/library/pb/payloads.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pb.client;
4 |
5 | option java_package = "org.nkn.sdk.pb";
6 | option java_outer_classname = "PayloadsProto";
7 |
8 | enum PayloadType {
9 | BINARY = 0;
10 | TEXT = 1;
11 | ACK = 2;
12 | }
13 |
14 | message Message {
15 | bytes payload = 1;
16 | bool encrypted = 2;
17 | bytes nonce = 3;
18 | bytes encrypted_key = 4;
19 | }
20 |
21 | message Payload {
22 | PayloadType type = 1;
23 | bytes pid = 2;
24 | bytes data = 3;
25 | bytes reply_to_pid = 4;
26 | bool no_ack = 5;
27 | }
28 |
29 | message TextData {
30 | string text = 1;
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/library/src/test/java/org/nkn/sdk/WalletTest.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 |
4 | import android.util.LruCache
5 | import org.json.JSONObject
6 | import org.junit.Assert
7 | import org.junit.Test
8 | import org.nkn.sdk.network.RpcApi
9 | import kotlinx.coroutines.*
10 | import kotlin.system.measureTimeMillis
11 | import org.nkn.sdk.utils.*
12 | import org.nkn.sdk.Wallet
13 | import org.nkn.sdk.Client
14 | import org.nkn.sdk.ClientListener
15 | import org.nkn.sdk.utils.Utils
16 |
17 | class WalletTest {
18 | @Test
19 | fun transfer_test() = runBlocking {
20 | val wallet = Wallet.createRandom()
21 | val client = Client(Utils.hexEncode(wallet.seed), "identifier")
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/nkn/app/MainActivityFragment.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.app
2 |
3 | import androidx.fragment.app.Fragment
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import com.google.android.material.snackbar.Snackbar
9 | import kotlinx.android.synthetic.main.fragment_main.*
10 |
11 | /**
12 | * A placeholder fragment containing a simple view.
13 | */
14 | class MainActivityFragment : Fragment() {
15 |
16 | override fun onCreateView(
17 | inflater: LayoutInflater, container: ViewGroup?,
18 | savedInstanceState: Bundle?
19 | ): View? {
20 | return inflater.inflate(R.layout.fragment_main, container, false)
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/org/nkn/app/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.app
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("org.nkn.app", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/library/src/androidTest/java/org/nkn/sdk/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("org.nkn.sdk.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/library/pb/sigchain.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pb;
4 |
5 | option java_package = "org.nkn.sdk.pb";
6 | option java_outer_classname = "SigChainProto";
7 |
8 | enum SigAlgo {
9 | SIGNATURE = 0;
10 | VRF = 1;
11 | }
12 |
13 | message SigChainElem {
14 | bytes id = 1;
15 | bytes next_pubkey = 2;
16 | bool mining = 3;
17 | bytes signature = 4;
18 | SigAlgo sig_algo = 5;
19 | bytes vrf = 6;
20 | bytes proof = 7;
21 | }
22 |
23 | message SigChain {
24 | uint32 nonce = 1;
25 | uint32 data_size = 2;
26 | bytes block_hash = 3;
27 | bytes src_id = 4;
28 | bytes src_pubkey = 5;
29 | bytes dest_id = 6;
30 | bytes dest_pubkey = 7;
31 | repeated SigChainElem elems = 8;
32 | }
33 |
--------------------------------------------------------------------------------
/library/pb/block.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pb;
4 |
5 | option java_package = "org.nkn.sdk.pb";
6 | option java_outer_classname = "BlockProto";
7 |
8 | import "pb/transaction.proto";
9 |
10 | enum WinnerType {
11 | GENESIS_SIGNER = 0;
12 | TXN_SIGNER = 1;
13 | BLOCK_SIGNER = 2;
14 | }
15 |
16 | message UnsignedHeader {
17 | uint32 version = 1;
18 | bytes prev_block_hash = 2;
19 | bytes transactions_root = 3;
20 | bytes state_root = 4;
21 | int64 timestamp = 5;
22 | uint32 height = 6;
23 | bytes random_beacon = 7;
24 | bytes winner_hash = 8;
25 | WinnerType winner_type = 9;
26 | bytes signer_pk = 10;
27 | bytes signer_id = 11;
28 | }
29 |
30 | message Header {
31 | UnsignedHeader unsigned_header = 1;
32 | bytes signature = 2;
33 | }
34 |
35 | message Block {
36 | Header header = 1;
37 | repeated Transaction transactions = 2;
38 | }
39 |
--------------------------------------------------------------------------------
/library/pb/clientmessage.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pb;
4 |
5 | option java_package = "org.nkn.sdk.pb";
6 | option java_outer_classname = "ClientMessageProto";
7 |
8 | enum ClientMessageType {
9 | OUTBOUND_MESSAGE = 0;
10 | INBOUND_MESSAGE = 1;
11 | RECEIPT = 2;
12 | }
13 |
14 | enum CompressionType {
15 | COMPRESSION_NONE = 0;
16 | COMPRESSION_ZLIB = 1;
17 | }
18 |
19 | message ClientMessage {
20 | ClientMessageType message_type = 1;
21 | bytes message = 2;
22 | CompressionType compression_type = 3;
23 | }
24 |
25 | message OutboundMessage {
26 | string dest = 1;
27 | bytes payload = 2;
28 | repeated string dests = 3;
29 | uint32 max_holding_seconds = 4;
30 | uint32 nonce = 5;
31 | bytes block_hash = 6;
32 | repeated bytes signatures = 7;
33 | repeated bytes payloads = 8;
34 | }
35 |
36 | message InboundMessage {
37 | string src = 1;
38 | bytes payload = 2;
39 | bytes prev_signature = 3;
40 | }
41 |
42 | message Receipt {
43 | bytes prev_signature = 1;
44 | bytes signature = 2;
45 | }
46 |
--------------------------------------------------------------------------------
/library/src/test/java/org/nkn/sdk/EncryptionTest.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 | import org.junit.Assert
4 | import org.junit.Test
5 | import org.nkn.sdk.crypto.*
6 | import org.nkn.sdk.utils.Utils
7 |
8 |
9 | class EncryptionTest {
10 | @Test
11 | fun aesEncrypt_test() {
12 | var res = aesEncrypt(Utils.hexDecode("d6d4e00674b6ee0d19e41c42bf92f5e919b97f3e02f33e18acb699a101355174"),
13 | Utils.hexDecode("0523d457e3ed9d803691f10c37a01361a8fb1a8392596ca059734d6cabe7dadc"),
14 | Utils.hexDecode("05f474918f8ea2dce1c94bfaae44fbbd"))
15 | Assert.assertEquals(Utils.hexEncode(res), "527cf7cda271b39cb86eaf56aa3689aeee28ee8c3b5289d576a340419ad6fdf0")
16 | }
17 |
18 | @Test
19 | fun aesDecrypt_test() {
20 | var res = aesDecrypt(Utils.hexDecode("527cf7cda271b39cb86eaf56aa3689aeee28ee8c3b5289d576a340419ad6fdf0"),
21 | Utils.hexDecode("0523d457e3ed9d803691f10c37a01361a8fb1a8392596ca059734d6cabe7dadc"),
22 | Utils.hexDecode("05f474918f8ea2dce1c94bfaae44fbbd"))
23 | Assert.assertEquals(Utils.hexEncode(res), "d6d4e00674b6ee0d19e41c42bf92f5e919b97f3e02f33e18acb699a101355174")
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/error/WalletError.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.error
2 |
3 |
4 | enum class WalletErrorCode(val code: Int) {
5 | UNKNOWN_ERROR(0),
6 | NOT_ENOUGH_BALANCE(1),
7 | INVALID_ADDRESS(2),
8 | WRONG_PASSWORD(3),
9 | INVALID_WALLET_FORMAT(4),
10 | INVALID_WALLET_VERSION(5),
11 | INVALID_ARGUMENT(6),
12 | INVALID_RESPONSE(7),
13 | NO_RPC_SERVER(8),
14 | SERVER_ERROR(9),
15 |
16 | }
17 |
18 | class WalletError(val code: WalletErrorCode, override val message: String) : Throwable() {
19 | companion object {
20 | const val UNKNOWN_ERROR = "unknown error"
21 | const val NOT_ENOUGH_BALANCE = "not enough balance"
22 | const val INVALID_ADDRESS = "invalid wallet address"
23 | const val WRONG_PASSWORD = "invalid password"
24 | const val INVALID_WALLET_FORMAT = "invalid wallet format"
25 | const val INVALID_WALLET_VERSION = "invalid wallet verison"
26 | const val INVALID_ARGUMENT = "invalid argument"
27 | const val INVALID_RESPONSE = "invalid response from server"
28 | const val NO_RPC_SERVER = "RPC server address is not set"
29 | const val SERVER_ERROR = "error from server"
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
26 |
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/library/src/test/java/org/nkn/sdk/HashTest.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 | import org.junit.Assert
4 | import org.junit.Test
5 | import org.nkn.sdk.crypto.*
6 | import org.nkn.sdk.utils.Utils
7 |
8 |
9 | class HashTest {
10 | @Test
11 | fun sha256_test() {
12 | var res = Utils.hexEncode(sha256("123456"))
13 |
14 | Assert.assertEquals(res, "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92")
15 | }
16 | @Test
17 | fun doubleSha256_test() {
18 | var res = Utils.hexEncode(doubleSha256("123456"))
19 | Assert.assertEquals(res, "ff7f73b854845fc02aa13b777ac090fb1d9ebfe16c8950c7d26499371dd0b479")
20 | }
21 | @Test
22 | fun sha256Hex_test() {
23 | var res = Utils.hexEncode(sha256Hex("123456"))
24 | Assert.assertEquals(res, "bf7cbe09d71a1bcc373ab9a764917f730a6ed951ffa1a7399b7abd8f8fd73cb4")
25 | }
26 | @Test
27 | fun doubleSha256Hex_test() {
28 | var res = Utils.hexEncode(doubleSha256Hex("123456"))
29 | Assert.assertEquals(res, "00574e0a61d00de8bb60d6aad57d3c105268b70a81a68979afc63b5d4809c25e")
30 | }
31 |
32 | @Test
33 | fun ripemd160Hex_test(){
34 | var res = Utils.hexEncode(ripemd160Hex("123456"))
35 | Assert.assertEquals(res, "99b6f3a3b7d96110deda57fa6c35153729eea168")
36 | }
37 | }
--------------------------------------------------------------------------------
/library/src/test/java/org/nkn/sdk/UtilsTest.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 |
4 | import org.json.JSONObject
5 | import org.junit.Assert
6 | import org.junit.Test
7 | import org.nkn.sdk.network.RpcApi
8 | import kotlinx.coroutines.*
9 | import org.nkn.sdk.pb.TransactionProto
10 | import kotlin.system.measureTimeMillis
11 | import org.nkn.sdk.utils.*
12 |
13 | class UtilsTest {
14 | @Test
15 | fun encodeUint_test() {
16 | Assert.assertEquals(Utils.hexEncode(encodeUint8(0)), "00")
17 | Assert.assertEquals(Utils.hexEncode(encodeUint8(44)), "2c")
18 | Assert.assertEquals(Utils.hexEncode(encodeUint8(55)), "37")
19 | Assert.assertEquals(Utils.hexEncode(encodeUint8(1)), "01")
20 |
21 | Assert.assertEquals(Utils.hexEncode(encodeUint64(0)), "0000000000000000")
22 | Assert.assertEquals(Utils.hexEncode(encodeUint64(44)), "2c00000000000000")
23 | Assert.assertEquals(Utils.hexEncode(encodeUint64(55)), "3700000000000000")
24 | Assert.assertEquals(Utils.hexEncode(encodeUint64(1)), "0100000000000000")
25 | }
26 |
27 | @Test
28 | fun getPublicKeyByClientAddr_test() {
29 | var res = Utils.getPublicKeyByClientAddr("heron.25ac590eaca614a0ba4c4387d8514a0b54e948d120c6ff49564e7830c9dec929")
30 | Assert.assertEquals(Utils.hexEncode(res), "25ac590eaca614a0ba4c4387d8514a0b54e948d120c6ff49564e7830c9dec929")
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 29
7 | buildToolsVersion "29.0.2"
8 |
9 |
10 | defaultConfig {
11 | applicationId "org.nkn.app"
12 | minSdkVersion 21
13 | targetSdkVersion 29
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 |
27 | }
28 |
29 | dependencies {
30 | implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
31 | implementation project(':library')
32 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
33 | implementation 'androidx.appcompat:appcompat:1.1.0'
34 | implementation 'androidx.core:core-ktx:1.1.0'
35 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
36 | implementation 'com.google.android.material:material:1.0.0'
37 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
38 | implementation 'com.github.joshjdevl.libsodiumjni:libsodium-jni-aar:2.0.2'
39 | testImplementation 'junit:junit:4.12'
40 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
41 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
42 | }
43 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/crypto/encryption.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.crypto
2 |
3 | import org.libsodium.jni.crypto.SecretBox
4 | import org.nkn.sdk.utils.Utils
5 | import javax.crypto.Cipher
6 | import javax.crypto.spec.IvParameterSpec
7 | import javax.crypto.spec.SecretKeySpec
8 |
9 | fun genAESIV(): ByteArray {
10 | return Utils.randomBytes(16)
11 | }
12 |
13 | fun genAESPassword(): ByteArray {
14 | return Utils.randomBytes(32)
15 | }
16 |
17 | fun aesEncrypt(
18 | data: ByteArray,
19 | key: ByteArray,
20 | iv: ByteArray,
21 | isSimplePassword: Boolean = false
22 | ): ByteArray {
23 | var password = if (isSimplePassword) doubleSha256(key) else key
24 | var c = Cipher.getInstance("AES/CBC/NoPadding")
25 | c.init(Cipher.ENCRYPT_MODE, SecretKeySpec(password, "AES"), IvParameterSpec(iv))
26 | return c.doFinal(data)
27 | }
28 |
29 | fun aesDecrypt(
30 | data: ByteArray,
31 | key: ByteArray,
32 | iv: ByteArray,
33 | isSimplePassword: Boolean = false
34 | ): ByteArray {
35 | var password = if (isSimplePassword) doubleSha256(key) else key
36 | var c = Cipher.getInstance("AES/CBC/NoPadding")
37 | c.init(Cipher.DECRYPT_MODE, SecretKeySpec(password, "AES"), IvParameterSpec(iv))
38 | return c.doFinal(data)
39 | }
40 |
41 | fun decrypt(encrypted: ByteArray, nonce: ByteArray, key: ByteArray): ByteArray {
42 | return SecretBox(key).decrypt(nonce, encrypted)
43 | }
44 |
45 | fun encrypt(message: ByteArray, nonce: ByteArray, key: ByteArray): ByteArray {
46 | return SecretBox(key).encrypt(nonce, message)
47 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/crypto/hash.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.crypto
2 |
3 | import org.bouncycastle.crypto.digests.RIPEMD160Digest
4 | import org.libsodium.jni.encoders.Hex.HEX
5 | import java.security.MessageDigest
6 |
7 | fun sha256(raw: Any): ByteArray {
8 | val md = MessageDigest.getInstance("SHA-256")
9 | var bytes: ByteArray = when (raw) {
10 | is ByteArray -> raw
11 | is String -> raw.toByteArray()
12 | else -> throw IllegalArgumentException("raw must be ByteArray or String")
13 | }
14 | return md.digest(bytes)
15 | }
16 |
17 | fun doubleSha256(raw: Any): ByteArray {
18 | return sha256(sha256(raw))
19 | }
20 |
21 | fun sha256Hex(hex: Any): ByteArray {
22 | val md = MessageDigest.getInstance("SHA-256")
23 | var bytes: ByteArray = when (hex) {
24 | is ByteArray -> hex
25 | is String -> HEX.decode(hex)
26 | else -> throw IllegalArgumentException("raw must be ByteArray or String")
27 | }
28 | return md.digest(bytes)
29 | }
30 |
31 | fun doubleSha256Hex(raw: Any): ByteArray {
32 | return sha256Hex(sha256Hex(raw))
33 | }
34 |
35 | fun ripemd160Hex(raw: Any): ByteArray {
36 | val r160Digest = RIPEMD160Digest()
37 | var bytes: ByteArray = when (raw) {
38 | is ByteArray -> raw
39 | is String -> HEX.decode(raw)
40 | else -> throw IllegalArgumentException("raw must be ByteArray or String")
41 | }
42 | r160Digest.update(bytes, 0, bytes.size)
43 | var r160: ByteArray = ByteArray(r160Digest.digestSize)
44 | r160Digest.doFinal(r160, 0)
45 | return r160
46 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/transaction/transaction.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.transaction
2 |
3 | import com.google.protobuf.ByteString
4 | import org.nkn.sdk.configure.NKN_ACC_MUL
5 | import org.nkn.sdk.crypto.Account
6 | import org.nkn.sdk.crypto.sha256Hex
7 | import org.nkn.sdk.pb.TransactionProto
8 | import org.nkn.sdk.utils.*
9 |
10 | fun serializeUnsignedTx(unsignedTx: TransactionProto.UnsignedTx): ByteArray {
11 | return serializePayload(unsignedTx.payload) + encodeUint64(unsignedTx.nonce) +encodeUint64(unsignedTx.fee) + encodeBytes(unsignedTx.attributes.toByteArray())
12 | }
13 |
14 | fun signTx(account: Account, txn: TransactionProto.Transaction.Builder) {
15 | val unsignedTx = txn.unsignedTx
16 | val hex = serializeUnsignedTx(unsignedTx)
17 | val digest = sha256Hex(hex)
18 | val signature = account.key.sign(digest)
19 | val prgm = TransactionProto.Program.newBuilder()
20 | prgm.code = ByteString.copyFrom(Utils.hexDecode(account.signatureRedeem))
21 | prgm.parameter = ByteString.copyFrom(signatureToParameter(signature))
22 | txn.addPrograms(prgm.build())
23 | }
24 |
25 | fun newTransaction(
26 | account: Account,
27 | pld: TransactionProto.Payload,
28 | nonce: Long,
29 | fee: Double = 0.0,
30 | attrs: String = ""
31 | ): TransactionProto.Transaction {
32 |
33 | val feeValue = (fee * NKN_ACC_MUL).toLong()
34 |
35 | val unsignedTx = TransactionProto.UnsignedTx.newBuilder()
36 | unsignedTx.payload = pld
37 | unsignedTx.nonce = nonce
38 | unsignedTx.fee = feeValue
39 |
40 | if (attrs.isNotEmpty()) unsignedTx.attributes = ByteString.copyFrom(Utils.hexDecode(attrs))
41 |
42 | val txn = TransactionProto.Transaction.newBuilder()
43 | txn.unsignedTx = unsignedTx.build()
44 | signTx(account, txn)
45 | return txn.build()
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | android {
5 | compileSdkVersion 29
6 | buildToolsVersion "29.0.2"
7 |
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 29
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles 'consumer-rules.pro'
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | repositories {
27 | maven {
28 | url "https://dl.bintray.com/terl/lazysodium-maven"
29 | }
30 | jcenter()
31 | }
32 |
33 | testOptions {
34 | unitTests {
35 | returnDefaultValues = true
36 | includeAndroidResources = true
37 | }
38 |
39 | }
40 | }
41 |
42 | dependencies {
43 | implementation fileTree(dir: 'libs', include: ['*.jar'])
44 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
45 | implementation 'androidx.appcompat:appcompat:1.1.0'
46 | implementation 'androidx.core:core-ktx:1.1.0'
47 | implementation 'com.github.joshjdevl.libsodiumjni:libsodium-jni-aar:2.0.2'
48 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
49 | implementation 'com.squareup.okhttp3:okhttp:3.12.1'
50 | implementation 'com.google.protobuf:protobuf-java:3.11.0'
51 | implementation 'com.google.code.gson:gson:2.8.6'
52 | implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
53 | testImplementation 'junit:junit:4.12'
54 | testImplementation 'androidx.test:core:1.2.0'
55 | testImplementation 'org.mockito:mockito-core:1.10.19'
56 | testImplementation 'org.json:json:20190722'
57 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
58 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/org/nkn/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.app
2 |
3 | import android.os.Bundle
4 | import com.google.android.material.snackbar.Snackbar
5 | import androidx.appcompat.app.AppCompatActivity
6 | import android.view.Menu
7 | import android.view.MenuItem
8 |
9 | import kotlinx.android.synthetic.main.activity_main.*
10 | import kotlinx.coroutines.*
11 | import org.nkn.sdk.Client
12 | import org.nkn.sdk.Wallet
13 | import org.nkn.sdk.cache.sharedKeyCache
14 | import org.nkn.sdk.crypto.Key
15 | import org.nkn.sdk.network.RpcApi
16 | import org.nkn.sdk.network.WsApi
17 | import org.nkn.sdk.utils.Utils
18 |
19 | class MainActivity : AppCompatActivity() {
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_main)
24 | setSupportActionBar(toolbar)
25 | var client = Client(
26 | "32e677190540797d3252e09bea9a03d2e286a74ad54bffcba6378481c6e3e9cd",
27 | "nkn"
28 | )
29 | client.connect()
30 |
31 | fab.setOnClickListener { view ->
32 | GlobalScope.launch {
33 | // client.send("nkn.8488c5ee3010ec45989ffcbf5c74283e23d0f18c4f8e9ea2f6adb1a942dc8d74", """{"contentType":"text","isPrivate":true,"content":"hello"}""")
34 | Snackbar.make(view, "res: ", Snackbar.LENGTH_LONG).show()
35 | }
36 | }
37 |
38 |
39 | }
40 |
41 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
42 | // Inflate the menu; this adds items to the action bar if it is present.
43 | menuInflater.inflate(R.menu.menu_main, menu)
44 | return true
45 | }
46 |
47 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
48 | // Handle action bar item clicks here. The action bar will
49 | // automatically handle clicks on the Home/Up button, so long
50 | // as you specify a parent activity in AndroidManifest.xml.
51 | return when (item.itemId) {
52 | R.id.action_settings -> true
53 | else -> super.onOptionsItemSelected(item)
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/utils/EncodeUtils.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.utils
2 |
3 | fun encodeUint64(l: Long): ByteArray {
4 | return byteArrayOf(
5 | (l and 0xFF).toByte(),
6 | (l and 0xFF00 shr 8).toByte(),
7 | (l and 0xFF0000 shr 16).toByte(),
8 | (l and -0x1000000 shr 24).toByte(),
9 | (l and 0xFF00000000L shr 32).toByte(),
10 | (l and 0xFF0000000000L shr 40).toByte(),
11 | (l and 0xFF000000000000L shr 48).toByte(),
12 | (l and -0x100000000000000L shr 56).toByte()
13 | )
14 |
15 | }
16 |
17 | fun encodeUint32(i: Int): ByteArray {
18 | return byteArrayOf(
19 | (i and 0xFF).toByte(),
20 | (i and 0xFF00 shr 8).toByte(),
21 | (i and 0xFF0000 shr 16).toByte(),
22 | (i and -0x1000000 shr 24).toByte()
23 | )
24 |
25 | }
26 |
27 | fun encodeUint16(i: Int): ByteArray {
28 | return byteArrayOf(
29 | (i and 0xFF).toByte(),
30 | (i and 0xFF00 shr 8).toByte()
31 | )
32 |
33 | }
34 |
35 | fun encodeUint8(i: Int): ByteArray {
36 | return byteArrayOf(
37 | (i and 0xFF).toByte()
38 | )
39 |
40 | }
41 |
42 | fun encodeUint(n: Long): ByteArray {
43 | return if (n >= 0) {
44 | if (n < 0xfd) {
45 | encodeUint8(n.toInt())
46 | } else if (n <= 0xffff) {
47 | byteArrayOf(0xfd.toByte()) + encodeUint16(n.toInt())
48 | } else if (n <= 0xffffffffL) {
49 | byteArrayOf(0xfe.toByte()) + encodeUint32(n.toInt())
50 | } else {
51 | byteArrayOf(0xff.toByte()) + encodeUint64(n)
52 | }
53 | } else {
54 | byteArrayOf(0xff.toByte()) + encodeUint64(n)
55 | }
56 | }
57 |
58 | fun encodeBytes(value: ByteArray): ByteArray {
59 | return encodeUint(value.size.toLong()) + value
60 | }
61 |
62 | fun encodeString(value: ByteArray): ByteArray {
63 | return encodeUint(value.size.toLong()) + value
64 | }
65 |
66 | fun encodeBool(b: Boolean): ByteArray {
67 | return encodeUint8(if (b) 1 else 0)
68 | }
69 |
70 | fun signatureToParameter(signed: ByteArray): ByteArray {
71 | return byteArrayOf(signed.size.toByte()) + signed
72 | }
73 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/configure/Seed.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.configure
2 |
3 | val seed = listOf(
4 | "http://mainnet-seed-0001.nkn.org:30003",
5 | "http://mainnet-seed-0002.nkn.org:30003",
6 | "http://mainnet-seed-0003.nkn.org:30003",
7 | "http://mainnet-seed-0004.nkn.org:30003",
8 | "http://mainnet-seed-0005.nkn.org:30003",
9 | "http://mainnet-seed-0006.nkn.org:30003",
10 | "http://mainnet-seed-0007.nkn.org:30003",
11 | "http://mainnet-seed-0008.nkn.org:30003",
12 | "http://mainnet-seed-0009.nkn.org:30003",
13 | "http://mainnet-seed-0010.nkn.org:30003",
14 | "http://mainnet-seed-0011.nkn.org:30003",
15 | "http://mainnet-seed-0012.nkn.org:30003",
16 | "http://mainnet-seed-0013.nkn.org:30003",
17 | "http://mainnet-seed-0014.nkn.org:30003",
18 | "http://mainnet-seed-0015.nkn.org:30003",
19 | "http://mainnet-seed-0016.nkn.org:30003",
20 | "http://mainnet-seed-0017.nkn.org:30003",
21 | "http://mainnet-seed-0018.nkn.org:30003",
22 | "http://mainnet-seed-0019.nkn.org:30003",
23 | "http://mainnet-seed-0020.nkn.org:30003",
24 | "http://mainnet-seed-0021.nkn.org:30003",
25 | "http://mainnet-seed-0022.nkn.org:30003",
26 | "http://mainnet-seed-0023.nkn.org:30003",
27 | "http://mainnet-seed-0024.nkn.org:30003",
28 | "http://mainnet-seed-0025.nkn.org:30003",
29 | "http://mainnet-seed-0026.nkn.org:30003",
30 | "http://mainnet-seed-0027.nkn.org:30003",
31 | "http://mainnet-seed-0028.nkn.org:30003",
32 | "http://mainnet-seed-0029.nkn.org:30003",
33 | "http://mainnet-seed-0030.nkn.org:30003",
34 | "http://mainnet-seed-0031.nkn.org:30003",
35 | "http://mainnet-seed-0032.nkn.org:30003",
36 | "http://mainnet-seed-0033.nkn.org:30003",
37 | "http://mainnet-seed-0034.nkn.org:30003",
38 | "http://mainnet-seed-0035.nkn.org:30003",
39 | "http://mainnet-seed-0036.nkn.org:30003",
40 | "http://mainnet-seed-0037.nkn.org:30003",
41 | "http://mainnet-seed-0038.nkn.org:30003",
42 | "http://mainnet-seed-0039.nkn.org:30003",
43 | "http://mainnet-seed-0040.nkn.org:30003",
44 | "http://mainnet-seed-0041.nkn.org:30003",
45 | "http://mainnet-seed-0042.nkn.org:30003",
46 | "http://mainnet-seed-0043.nkn.org:30003",
47 | "http://mainnet-seed-0044.nkn.org:30003"
48 | )
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/transaction/payload.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.transaction
2 |
3 | import com.google.protobuf.ByteString
4 | import org.nkn.sdk.configure.NKN_ACC_MUL
5 | import org.nkn.sdk.pb.TransactionProto
6 | import org.nkn.sdk.pb.TransactionProto.TransferAsset
7 | import org.nkn.sdk.utils.Utils
8 | import org.nkn.sdk.utils.*
9 |
10 | fun newTransfer(sender: String, recipient: String, amount: Double): TransactionProto.Payload {
11 | val coin = (amount * NKN_ACC_MUL).toLong()
12 | val transfer = TransferAsset.newBuilder()
13 | transfer.sender = ByteString.copyFrom(Utils.hexDecode(sender))
14 | transfer.recipient = ByteString.copyFrom(Utils.hexDecode(recipient))
15 | transfer.amount = coin
16 | val pld = TransactionProto.Payload.newBuilder()
17 | pld.type = TransactionProto.PayloadType.TRANSFER_ASSET_TYPE
18 | pld.data = transfer.build().toByteString()
19 | return pld.build()
20 | }
21 |
22 | fun newSubscribe(
23 | subscriber: String, identifier: String, topic: String,
24 | duration: Int, meta: String
25 | ): TransactionProto.Payload {
26 | val subscribe = TransactionProto.Subscribe.newBuilder()
27 | subscribe.subscriber = ByteString.copyFrom(Utils.hexDecode(subscriber))
28 | subscribe.identifier = identifier
29 | subscribe.topic = topic
30 | subscribe.duration = duration
31 | subscribe.meta = meta
32 |
33 | val pld = TransactionProto.Payload.newBuilder()
34 | pld.type = TransactionProto.PayloadType.SUBSCRIBE_TYPE
35 | pld.data = subscribe.build().toByteString()
36 | return pld.build()
37 | }
38 |
39 | fun newUnsubscribe(
40 | subscriber: String,
41 | identifier: String,
42 | topic: String
43 | ): TransactionProto.Payload {
44 | val unsubscribe = TransactionProto.Unsubscribe.newBuilder()
45 | unsubscribe.subscriber = ByteString.copyFrom(Utils.hexDecode(subscriber))
46 | unsubscribe.identifier = identifier
47 | unsubscribe.topic = topic
48 |
49 | val pld = TransactionProto.Payload.newBuilder()
50 | pld.type = TransactionProto.PayloadType.UNSUBSCRIBE_TYPE
51 | pld.data = unsubscribe.build().toByteString()
52 | return pld.build()
53 | }
54 |
55 | fun serializePayload(payload: TransactionProto.Payload): ByteArray {
56 | return encodeUint32(payload.typeValue) + encodeBytes(payload.data.toByteArray())
57 | }
--------------------------------------------------------------------------------
/library/pb/transaction.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pb;
4 |
5 | option java_package = "org.nkn.sdk.pb";
6 | option java_outer_classname = "TransactionProto";
7 |
8 | message UnsignedTx {
9 | Payload payload = 1;
10 | uint64 nonce = 2;
11 | int64 fee = 3;
12 | bytes attributes = 4;
13 | }
14 |
15 | message Transaction {
16 | UnsignedTx unsigned_tx = 1;
17 | repeated Program programs = 2;
18 | }
19 |
20 | message Program {
21 | bytes code = 1;
22 | bytes parameter = 2;
23 | }
24 |
25 | enum PayloadType {
26 | COINBASE_TYPE = 0;
27 | TRANSFER_ASSET_TYPE = 1;
28 | SIG_CHAIN_TXN_TYPE = 2;
29 | REGISTER_NAME_TYPE = 3;
30 | TRANSFER_NAME_TYPE = 4;
31 | DELETE_NAME_TYPE = 5;
32 | SUBSCRIBE_TYPE = 6;
33 | UNSUBSCRIBE_TYPE = 7;
34 | GENERATE_ID_TYPE = 8;
35 | NANO_PAY_TYPE = 9;
36 | ISSUE_ASSET_TYPE = 10;
37 | }
38 |
39 | message Payload {
40 | PayloadType type = 1;
41 | bytes data = 2;
42 | }
43 |
44 | message Coinbase {
45 | bytes sender = 1;
46 | bytes recipient = 2;
47 | int64 amount = 3;
48 | }
49 |
50 | message SigChainTxn {
51 | bytes sigChain = 1;
52 | bytes submitter = 2;
53 | }
54 |
55 | message RegisterName {
56 | bytes registrant = 1;
57 | string name = 2;
58 | }
59 |
60 | message DeleteName {
61 | bytes registrant = 1;
62 | string name = 2;
63 | }
64 |
65 | message Subscribe {
66 | bytes subscriber = 1;
67 | string identifier = 2;
68 | string topic = 3;
69 | uint32 bucket = 4 [deprecated = true];
70 | uint32 duration = 5;
71 | string meta = 6;
72 | }
73 |
74 | message Unsubscribe {
75 | bytes subscriber = 1;
76 | string identifier = 2;
77 | string topic = 3;
78 | }
79 |
80 | message TransferAsset {
81 | bytes sender = 1;
82 | bytes recipient = 2;
83 | int64 amount = 3;
84 | }
85 |
86 | message GenerateID {
87 | bytes public_key = 1;
88 | int64 registration_fee = 2;
89 | }
90 |
91 | message NanoPay {
92 | bytes sender = 1;
93 | bytes recipient = 2;
94 | uint64 id = 3;
95 | int64 amount = 4;
96 | uint32 txn_expiration = 5;
97 | uint32 nano_pay_expiration = 6;
98 | }
99 |
100 | message IssueAsset {
101 | bytes sender = 1;
102 | string name = 2;
103 | string symbol = 3;
104 | int64 total_supply = 4;
105 | uint32 precision = 5;
106 | }
107 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/crypto/Key.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.crypto
2 |
3 | import org.libsodium.jni.NaCl
4 | import org.libsodium.jni.Sodium
5 | import org.libsodium.jni.crypto.Random
6 | import org.libsodium.jni.encoders.Encoder.HEX
7 | import org.libsodium.jni.keys.SigningKey
8 | import org.nkn.sdk.cache.sharedKeyCache
9 | import org.nkn.sdk.const.PUBLICKEY_SIZE
10 | import org.nkn.sdk.const.SECRET_KEY_SIZE
11 | import org.nkn.sdk.const.SEED_SIZE
12 | import org.nkn.sdk.utils.Utils
13 |
14 |
15 | class Key(seed: Any?) {
16 | var seed: ByteArray
17 | val privateKey: ByteArray
18 | val publicKey: ByteArray
19 |
20 | val privateKeyHash: String
21 | val publicKeyHash: String
22 | val signatureRedeem: String
23 | val programHash: String
24 | val curveSecretKey: ByteArray
25 | private var signingKey: SigningKey
26 |
27 | init {
28 | NaCl.sodium()
29 | if (seed is ByteArray) {
30 | Utils.checkLength(seed, SEED_SIZE)
31 | this.seed = seed
32 | } else if (seed is String) {
33 | this.seed = HEX.decode(seed)
34 | Utils.checkLength(this.seed, SEED_SIZE)
35 | } else {
36 | this.seed = Random().randomBytes()
37 | }
38 | this.privateKey = ByteArray(SECRET_KEY_SIZE)
39 | this.publicKey = ByteArray(PUBLICKEY_SIZE)
40 | Sodium.crypto_sign_ed25519_seed_keypair(this.publicKey, this.privateKey, this.seed)
41 | this.privateKeyHash = HEX.encode(privateKey)
42 | this.publicKeyHash = HEX.encode(publicKey)
43 | this.signatureRedeem = Utils.publicKeyToSignatureRedeem(publicKeyHash)
44 | this.programHash = Utils.hexStringToProgramHash(signatureRedeem)
45 | this.signingKey = SigningKey(this.seed)
46 | curveSecretKey = Utils.convertSecretKey(this.privateKey)
47 | }
48 |
49 | fun getCacheSharedKeyByTargetPubKey(pk: ByteArray): ByteArray {
50 | var sharedKey = sharedKeyCache.get(pk)
51 | if (sharedKey == null) {
52 | sharedKey = Utils.computeSharedKey(this.curveSecretKey, Utils.convertPublicKey(pk))
53 | sharedKeyCache.put(pk, sharedKey)
54 | }
55 | return sharedKey
56 | }
57 |
58 | fun sign(message: ByteArray): ByteArray {
59 | return this.signingKey.sign(message)
60 | }
61 |
62 | fun encrypt(message: ByteArray, nonce: ByteArray, otherPubKey: ByteArray): ByteArray? {
63 | return Utils.encrypt(message, nonce, getCacheSharedKeyByTargetPubKey(otherPubKey))
64 | }
65 |
66 | fun decrypt(encryptedMessage: ByteArray, nonce: ByteArray, otherPubKey: ByteArray): ByteArray? {
67 | return Utils.decrypt(encryptedMessage, nonce, getCacheSharedKeyByTargetPubKey(otherPubKey))
68 | }
69 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nkn-sdk-android
2 |
3 | ## Install
4 | ### 1.download nkn-sdk aar
5 | > download aar [https://github.com/nknorg/nkn-sdk-android/releases](https://github.com/nknorg/nkn-sdk-android/releases)
6 |
7 | or
8 |
9 | > Add library to dependencies:
10 | ```gradle
11 | // build.gradle
12 | allprojects {
13 | repositories {
14 | maven { url 'https://jitpack.io' }
15 | }
16 | }
17 | ...
18 | dependencies {
19 | ...
20 | implementation 'com.github.nknorg:nkn-sdk-android:VERSION'
21 | }
22 | ```
23 |
24 | ### 2. add libsodium-jni-aar to dependencies:
25 | ```gradle
26 | // build.gradle
27 | dependencies {
28 | ...
29 | implementation 'com.github.joshjdevl.libsodiumjni:libsodium-jni-aar:2.0.1'
30 | }
31 | ```
32 |
33 | ### 3. To fix the warning [allowBackup](src/main/AndroidManifest.xml), add `xmlns:tools="http://schemas.android.com/tools"` and `tools:replace="android:allowBackup"` to your Manifest:
34 | ```xml
35 |
36 |
37 |
40 |
43 |
44 | ...
45 |
46 |
47 |
48 | ```
49 |
50 | ### Usage
51 | #### Kotlin
52 | ```kotlin
53 | import org.nkn.sdk.Wallet
54 | import org.nkn.sdk.Client
55 |
56 | // wallet
57 | // creat new NKN Wallet
58 | val wallet = Wallet.createRandom()
59 | // get NKN Wallet balance
60 | val balance = wallet.getBalance()
61 | // get nonce
62 | val nonce = wallet.getNonce()
63 | // encrypt a keystore with password
64 | val keystore = wallet.encrypt("PASSWORD")
65 | // load wallet from keystore
66 | val wallet = Wallet.fromKeystore("{KEYSTORE}", "PASSWORD")
67 | // load wallet from seed
68 | val wallet = Wallet.fromSeed("SEED", ["PASSWORD"])
69 |
70 | // client
71 | val seed = "WALLET SEED"
72 | val id = "CLIENT ID"
73 | inner class MessageListener : ClientListener() {
74 | override fun onBinaryMessage(src: String, data: ByteArray?, pid: ByteArray, type: Int, encrypted: Boolean): Any? {
75 | return null
76 | }
77 |
78 | override fun onBlock() {
79 |
80 | }
81 |
82 | override fun onClosed() {
83 |
84 | }
85 |
86 | override fun onClosing() {
87 |
88 | }
89 |
90 | override fun onConnect() {
91 | // connect already, now can send message
92 | }
93 |
94 | override fun onError(e: Throwable) {
95 |
96 | }
97 |
98 | override fun onMessage(src: String, data: String?, pid: ByteArray, type: Int, encrypted: Boolean): Any? {
99 | // onMessage do something
100 | return false
101 | }
102 |
103 | }
104 |
105 | var client = Client(seed, id, listener = MessageListener())
106 | client.connect()
107 |
108 | // get client address
109 | val address = client.address
110 |
111 | // send message
112 | client.send(to, message)
113 | ```
114 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/network/RpcApi.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.network
2 |
3 | import okhttp3.*
4 | import okhttp3.RequestBody
5 | import org.json.JSONObject
6 | import org.nkn.sdk.configure.RPC_ADDR
7 | import org.nkn.sdk.error.RpcError
8 | import org.nkn.sdk.error.RpcErrorCode
9 |
10 |
11 | class RpcApi(val rpcAddr: String? = null) {
12 | constructor() : this(RPC_ADDR)
13 |
14 | private val client = OkHttpClient()
15 | private val params = mapOf("jsonrpc" to "2.0", "id" to "nkn-sdk-android")
16 |
17 | fun request(method: String, params: Map): JSONObject? {
18 | val url = this.rpcAddr
19 | val body = mutableMapOf()
20 | body.putAll(this.params)
21 | body["method"] = method
22 | body["params"] = params
23 |
24 | val request = Request.Builder()
25 | .url(url!!)
26 | .post(RequestBody.create(null, JSONObject(body.toMap()).toString()))
27 | .build()
28 |
29 | val res = client.newCall(request).execute()
30 |
31 | if (res.code() != 200) {
32 | throw RpcError(RpcErrorCode.UNKNOWN_ERROR, RpcError.UNKNOWN_ERROR)
33 | }
34 | val json = JSONObject(res.body()!!.string())
35 |
36 | if (json.has("error"))
37 | return json.getJSONObject("error")
38 | return json
39 | }
40 |
41 | fun getBalanceByAddr(address: String): JSONObject? {
42 | return this.request("getbalancebyaddr", mapOf("address" to address))
43 | ?.getJSONObject("result")
44 | }
45 |
46 | fun getNonceByAddr(address: String): JSONObject? {
47 | return this.request("getnoncebyaddr", mapOf("address" to address))?.getJSONObject("result")
48 | }
49 |
50 | fun getBlockCount(): Long? {
51 | return this.request("getblockcount", mapOf())?.getLong("result")
52 | }
53 |
54 | fun getLatestBlockHeight(): Long?{
55 | return this.request("getlatestblockheight", mapOf())?.getLong("result")
56 | }
57 |
58 | fun getAddressByName(name: String): JSONObject? {
59 | return this.request("getaddressbyname", mapOf("name" to name))?.getJSONObject("result")
60 | }
61 |
62 | fun sendRawTransaction(tx: String): String? {
63 | return this.request("sendrawtransaction", mapOf("tx" to tx))?.getString("result")
64 | }
65 |
66 | fun getWsAddr(address: String): JSONObject? {
67 | val res = this.request("getwsaddr", mapOf("address" to address))
68 | return if (res != null && res.has("result")) res.getJSONObject("result") else null
69 | }
70 |
71 | fun getSubscribers(
72 | topic: String,
73 | offset: Int = 0,
74 | limit: Int = 0,
75 | meta: Boolean = false,
76 | txPool: Boolean = false
77 | ): JSONObject? {
78 | return this.request(
79 | "getsubscribers",
80 | mapOf(
81 | "topic" to topic,
82 | "offset" to offset,
83 | "limit" to limit,
84 | "meta" to meta,
85 | "txPool" to txPool
86 | )
87 | )?.getJSONObject("result")
88 | }
89 |
90 | fun getSubscribersCount(topic: String): JSONObject? {
91 | return this.request("getsubscriberscount", mapOf("topic" to topic))?.getJSONObject("result")
92 | }
93 |
94 | fun getSubscription(topic: String, subscriber: String): JSONObject? {
95 | return this.request("getsubscription", mapOf("topic" to topic, "subscriber" to subscriber))
96 | ?.getJSONObject("result")
97 | }
98 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/protocol/encryption.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.protocol
2 |
3 | import org.nkn.sdk.const.KEY_SIZE
4 | import org.nkn.sdk.const.NONCE_SIZE
5 | import org.nkn.sdk.const.SECRETBOX_NONCE_SIZE
6 | import org.nkn.sdk.crypto.Key
7 | import org.nkn.sdk.pb.PayloadsProto
8 | import org.nkn.sdk.utils.Utils
9 | import java.io.ByteArrayOutputStream
10 | import java.io.IOException
11 | import java.util.zip.DataFormatException
12 | import java.util.zip.Deflater
13 | import java.util.zip.Inflater
14 |
15 |
16 | fun encryptPayloads(payload: ByteArray, dests: Array, key: Key): Array {
17 | val nonce = Utils.randomBytes(NONCE_SIZE)
18 | val k = Utils.randomBytes(KEY_SIZE)
19 | val encryptedPayload = Utils.encrypt(payload, nonce, k) ?: throw Throwable("Encrypted payload failed.")
20 | val msgs = ArrayList()
21 | for (i in dests.indices) {
22 | val msgNonce = Utils.randomBytes(NONCE_SIZE)
23 | val sharedKey = key.getCacheSharedKeyByTargetPubKey(Utils.getPublicKeyByClientAddr(dests[i]))
24 | val encryptedMessage = Utils.encrypt(k, msgNonce, sharedKey)
25 | val mergedNonce = msgNonce + nonce
26 | val msg = newMessage(encryptedPayload, true, mergedNonce, encryptedMessage)
27 | msgs.add(msg)
28 | }
29 | return msgs.toTypedArray()
30 | }
31 |
32 | fun encryptPayload(payload: ByteArray, dest: String, key: Key): PayloadsProto.Message {
33 | val sharedKey = key.getCacheSharedKeyByTargetPubKey(Utils.getPublicKeyByClientAddr(dest))
34 | val nonce = Utils.randomBytes(NONCE_SIZE)
35 | val encrypted = Utils.encrypt(payload, nonce, sharedKey) ?: throw Throwable("Encrypted payload failed.")
36 | return newMessage(encrypted, true, nonce)
37 | }
38 |
39 | fun decryptPayload(msg: PayloadsProto.Message, srcPubKey: ByteArray, key: Key): ByteArray {
40 | val rawPayload = msg.payload.toByteArray()
41 | val nonce = msg.nonce.toByteArray()
42 | val encryptedKey = msg.encryptedKey.toByteArray()
43 | val decryptedPayload: ByteArray = if (encryptedKey != null && encryptedKey.isNotEmpty()) {
44 | if (nonce.size != NONCE_SIZE + SECRETBOX_NONCE_SIZE) {
45 | throw Throwable("Invalid nonce length.")
46 | }
47 | val sharedKey = key.decrypt(encryptedKey, nonce.sliceArray(0 until NONCE_SIZE), srcPubKey)
48 | ?: throw Throwable("Decrypt shared key failed.")
49 | Utils.decrypt(rawPayload, nonce.sliceArray( NONCE_SIZE until nonce.size), sharedKey)
50 | ?: throw Throwable("Decrypt message failed.")
51 | } else {
52 | if (nonce.size != NONCE_SIZE) {
53 | throw Throwable("Invalid nonce length.")
54 | }
55 | key.decrypt(rawPayload, nonce, srcPubKey)
56 | ?: throw Throwable("Decrypt message failed.")
57 | }
58 | return decryptedPayload
59 | }
60 |
61 | @Throws(IOException::class)
62 | fun compress(data: ByteArray): ByteArray? {
63 | val deflater = Deflater()
64 | deflater.setInput(data)
65 | val outputStream = ByteArrayOutputStream(data.size)
66 | deflater.finish()
67 | val buffer = ByteArray(4096)
68 | while (!deflater.finished()) {
69 | val count: Int = deflater.deflate(buffer) // returns the generated code... index
70 | outputStream.write(buffer, 0, count)
71 | }
72 | outputStream.close()
73 | val output: ByteArray = outputStream.toByteArray()
74 | return output
75 | }
76 |
77 | @Throws(IOException::class, DataFormatException::class)
78 | fun decompress(data: ByteArray): ByteArray? {
79 | val inflater = Inflater()
80 | inflater.setInput(data)
81 | val outputStream = ByteArrayOutputStream(data.size)
82 | val buffer = ByteArray(4096)
83 | while (!inflater.finished()) {
84 | val count: Int = inflater.inflate(buffer)
85 | outputStream.write(buffer, 0, count)
86 | }
87 | outputStream.close()
88 | val output = outputStream.toByteArray()
89 | return output
90 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/utils/Base58.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.utils
2 |
3 | /*
4 | * Created by vrotaru: https://gist.github.com/vrotaru
5 | * https://gist.github.com/vrotaru/1753908
6 | */
7 | object Base58 {
8 | private val ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
9 | .toCharArray()
10 | private val BASE_58 = ALPHABET.size
11 | private const val BASE_256 = 256
12 | private val INDEXES = IntArray(128)
13 | fun encode(input: ByteArray): String {
14 | var input = input
15 | if (input.size == 0) { // paying with the same coin
16 | return ""
17 | }
18 | //
19 | // Make a copy of the input since we are going to modify it.
20 | //
21 | input = copyOfRange(input, 0, input.size)
22 | //
23 | // Count leading zeroes
24 | //
25 | var zeroCount = 0
26 | while (zeroCount < input.size && input[zeroCount] == 0.toByte()) {
27 | ++zeroCount
28 | }
29 | //
30 | // The actual encoding
31 | //
32 | val temp = ByteArray(input.size * 2)
33 | var j = temp.size
34 | var startAt = zeroCount
35 | while (startAt < input.size) {
36 | val mod = divmod58(input, startAt)
37 | if (input[startAt] == 0.toByte()) {
38 | ++startAt
39 | }
40 | temp[--j] = ALPHABET[mod.toInt()].toByte()
41 | }
42 | //
43 | // Strip extra '1' if any
44 | //
45 | while (j < temp.size && temp[j] == ALPHABET[0].toByte()) {
46 | ++j
47 | }
48 | //
49 | // Add as many leading '1' as there were leading zeros.
50 | //
51 | while (--zeroCount >= 0) {
52 | temp[--j] = ALPHABET[0].toByte()
53 | }
54 | val output = copyOfRange(temp, j, temp.size)
55 | return String(output)
56 | }
57 |
58 | fun decode(input: String): ByteArray {
59 | if (input.length == 0) { // paying with the same coin
60 | return ByteArray(0)
61 | }
62 | val input58 = ByteArray(input.length)
63 | //
64 | // Transform the String to a base58 byte sequence
65 | //
66 | for (i in 0 until input.length) {
67 | val c = input[i]
68 | var digit58 = -1
69 | if (c.toInt() >= 0 && c.toInt() < 128) {
70 | digit58 = INDEXES[c.toInt()]
71 | }
72 | require(digit58 >= 0) { "Not a Base58 input: $input" }
73 | input58[i] = digit58.toByte()
74 | }
75 | //
76 | // Count leading zeroes
77 | //
78 | var zeroCount = 0
79 | while (zeroCount < input58.size && input58[zeroCount] == 0.toByte()) {
80 | ++zeroCount
81 | }
82 | //
83 | // The encoding
84 | //
85 | val temp = ByteArray(input.length)
86 | var j = temp.size
87 | var startAt = zeroCount
88 | while (startAt < input58.size) {
89 | val mod = divmod256(input58, startAt)
90 | if (input58[startAt] == 0.toByte()) {
91 | ++startAt
92 | }
93 | temp[--j] = mod
94 | }
95 | //
96 | // Do no add extra leading zeroes, move j to first non null byte.
97 | //
98 | while (j < temp.size && temp[j] == 0.toByte()) {
99 | ++j
100 | }
101 | return copyOfRange(temp, j - zeroCount, temp.size)
102 | }
103 |
104 | private fun divmod58(number: ByteArray, startAt: Int): Byte {
105 | var remainder = 0
106 | for (i in startAt until number.size) {
107 | val digit256 = number[i].toInt() and 0xFF
108 | val temp = remainder * BASE_256 + digit256
109 | number[i] = (temp / BASE_58).toByte()
110 | remainder = temp % BASE_58
111 | }
112 | return remainder.toByte()
113 | }
114 |
115 | private fun divmod256(number58: ByteArray, startAt: Int): Byte {
116 | var remainder = 0
117 | for (i in startAt until number58.size) {
118 | val digit58 = number58[i].toInt() and 0xFF
119 | val temp = remainder * BASE_58 + digit58
120 | number58[i] = (temp / BASE_256).toByte()
121 | remainder = temp % BASE_256
122 | }
123 | return remainder.toByte()
124 | }
125 |
126 | private fun copyOfRange(source: ByteArray, from: Int, to: Int): ByteArray {
127 | val range = ByteArray(to - from)
128 | System.arraycopy(source, from, range, 0, range.size)
129 | return range
130 | }
131 |
132 | init {
133 | for (i in INDEXES.indices) {
134 | INDEXES[i] = -1
135 | }
136 | for (i in ALPHABET.indices) {
137 | INDEXES[ALPHABET[i].toInt()] = i
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/utils/Utils.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.utils
2 |
3 | import org.libsodium.jni.NaCl
4 | import org.libsodium.jni.Sodium
5 | import org.libsodium.jni.crypto.Random
6 | import org.libsodium.jni.crypto.SecretBox
7 | import org.libsodium.jni.encoders.Hex.HEX
8 | import org.nkn.sdk.const.PUBLICKEY_SIZE
9 | import org.nkn.sdk.const.SECRET_KEY_SIZE
10 | import org.nkn.sdk.const.SEED_SIZE
11 | import org.nkn.sdk.const.SHARED_SIZE
12 | import org.nkn.sdk.crypto.*
13 |
14 |
15 | const val ADDRESS_GEN_PREFIX = "02b825"
16 | const val ADDRESS_GEN_PREFIX_LEN: Int = ADDRESS_GEN_PREFIX.length / 2
17 | const val UINT160_LEN = 20
18 | const val CHECKSUM_LEN = 4
19 | const val ADDRESS_LEN = ADDRESS_GEN_PREFIX_LEN + UINT160_LEN + CHECKSUM_LEN
20 |
21 |
22 | class Utils {
23 | companion object {
24 | @JvmOverloads
25 | @JvmStatic
26 | fun randomBytes(len: Int = SEED_SIZE): ByteArray {
27 | return Random().randomBytes(len)
28 | }
29 |
30 | @JvmStatic
31 | fun randomInt32(): Int {
32 | val b: ByteArray = randomBytes(4)
33 | b[0] = (b[0].toInt() and 127).toByte()
34 | return (b[0].toInt() shl 24) + (b[1].toInt() shl 16) + (b[2].toInt() shl 8) + b[3]
35 | }
36 |
37 | @JvmStatic
38 | fun checkLength(data: ByteArray?, size: Int) {
39 | if (data == null || data.size != size) throw RuntimeException("Invalid size: " + data?.size)
40 | }
41 |
42 | @JvmStatic
43 | fun hexEncode(raw: ByteArray): String {
44 | return HEX.encode(raw)
45 | }
46 |
47 | @JvmStatic
48 | fun hexDecode(data: String): ByteArray {
49 | return HEX.decode(data)
50 | }
51 |
52 | @JvmStatic
53 | fun publicKeyToSignatureRedeem(publicKey: String): String {
54 | return UINT160_LEN.toString() + publicKey + "ac"
55 | }
56 |
57 | @JvmStatic
58 | fun hexStringToProgramHash(hex: String): String {
59 | return hexEncode(ripemd160Hex(sha256Hex(hex)))
60 | }
61 |
62 | @JvmStatic
63 | fun genAddressVerifyBytesFromProgramHash(programHash: String): ByteArray {
64 | val verifyBytes = doubleSha256Hex(ADDRESS_GEN_PREFIX + programHash)
65 | return verifyBytes.slice(0 until CHECKSUM_LEN).toByteArray()
66 | }
67 |
68 | @JvmStatic
69 | fun programHashStringToAddress(programHash: String): String {
70 | val addressVerifyBytes = genAddressVerifyBytesFromProgramHash(programHash)
71 | val addressBaseData = hexDecode(ADDRESS_GEN_PREFIX + programHash)
72 | return Base58.encode(addressBaseData + addressVerifyBytes)
73 | }
74 |
75 | @JvmStatic
76 | fun prefixByteCountToHexString(hexStr: String): String {
77 | var len = hexStr.length
78 | if (0 == len) {
79 | return "00"
80 | }
81 | var res = hexStr
82 | if (1 == len % 2) {
83 | res = "0$res"
84 | len += 1
85 | }
86 |
87 | var byteCount = (len / 2).toString(16)
88 |
89 | if (1 == byteCount.length % 2) {
90 | byteCount = "0$byteCount"
91 | }
92 |
93 | return byteCount + res
94 | }
95 |
96 | @JvmStatic
97 | fun genAccountContractString(signatureRedeem: String, programHash: String): String {
98 | var contract = ""
99 | contract += prefixByteCountToHexString(signatureRedeem)
100 | contract += prefixByteCountToHexString("00")
101 | contract += programHash
102 | return contract
103 | }
104 |
105 | @JvmStatic
106 | fun addressStringToProgramHash(address: String): String {
107 | val addressBytes = Base58.decode(address)
108 | val programHashBytes =
109 | addressBytes.slice(ADDRESS_GEN_PREFIX_LEN until addressBytes.size - CHECKSUM_LEN)
110 | return hexEncode(programHashBytes.toByteArray())
111 | }
112 |
113 | @JvmStatic
114 | fun getAddressStringVerifyCode(address: String): String {
115 | val addressBytes = Base58.decode(address)
116 | val verifyBytes =
117 | addressBytes.slice(addressBytes.size - CHECKSUM_LEN until addressBytes.size)
118 |
119 | return hexEncode(verifyBytes.toByteArray())
120 | }
121 |
122 | @JvmStatic
123 | fun genAddressVerifyCodeFromProgramHash(programHash: String): String {
124 | val verifyBytes = genAddressVerifyBytesFromProgramHash(programHash)
125 | return hexEncode(verifyBytes)
126 | }
127 |
128 | @JvmStatic
129 | fun verifyAddress(address: String): Boolean {
130 | val addressBytes = Base58.decode(address)
131 | if (addressBytes.size != ADDRESS_LEN) {
132 | return false
133 | }
134 | val addressPrefixBytes = addressBytes.slice(0 until ADDRESS_GEN_PREFIX_LEN)
135 | val addressPrefix = hexEncode(addressPrefixBytes.toByteArray())
136 | if (addressPrefix != ADDRESS_GEN_PREFIX) {
137 | return false
138 | }
139 |
140 | val programHash = addressStringToProgramHash(address)
141 | val addressVerifyCode = getAddressStringVerifyCode(address);
142 | val programHashVerifyCode = genAddressVerifyCodeFromProgramHash(programHash)
143 | return addressVerifyCode == programHashVerifyCode
144 | }
145 |
146 | @JvmStatic
147 | fun getPublicKeyByClientAddr(addr: String): ByteArray {
148 | val n = addr.lastIndexOf(".")
149 | return if (n < 0) hexDecode(addr) else hexDecode(addr.substring(n + 1))
150 | }
151 |
152 | @JvmStatic
153 | fun convertPublicKey(ed25519pk: ByteArray): ByteArray {
154 | NaCl.sodium()
155 | var curvePubKey = ByteArray(PUBLICKEY_SIZE)
156 | Sodium.crypto_sign_ed25519_pk_to_curve25519(curvePubKey, ed25519pk)
157 | return curvePubKey
158 | }
159 |
160 | @JvmStatic
161 | fun convertSecretKey(ed25519sk: ByteArray): ByteArray {
162 | NaCl.sodium()
163 | var curveSecKey = ByteArray(SECRET_KEY_SIZE)
164 | Sodium.crypto_sign_ed25519_sk_to_curve25519(curveSecKey, ed25519sk)
165 | return curveSecKey
166 | }
167 |
168 | @JvmStatic
169 | fun computeSharedKey(myCurveSecretKey: ByteArray, otherCurvePubkey: ByteArray): ByteArray {
170 | NaCl.sodium()
171 | var sharedKey = ByteArray(SHARED_SIZE)
172 | Sodium.crypto_box_beforenm(sharedKey, otherCurvePubkey, myCurveSecretKey)
173 | return sharedKey
174 | }
175 |
176 | @JvmStatic
177 | fun decrypt(encrypted: ByteArray, nonce: ByteArray, key: ByteArray): ByteArray? {
178 | return SecretBox(key).decrypt(nonce, encrypted)
179 | }
180 |
181 | @JvmStatic
182 | fun encrypt(message: ByteArray, nonce: ByteArray, key: ByteArray): ByteArray? {
183 | return SecretBox(key).encrypt(nonce, message)
184 | }
185 | }
186 | }
187 |
188 |
189 |
190 |
191 |
192 |
--------------------------------------------------------------------------------
/library/pb/nodemessage.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package pb;
4 |
5 | import "pb/node.proto";
6 | import "pb/sigchain.proto";
7 | import "pb/block.proto";
8 | import "pb/transaction.proto";
9 |
10 | option java_package = "org.nkn.sdk.pb";
11 | option java_outer_classname = "NodeMessageProto";
12 |
13 | enum MessageType {
14 | MESSAGE_TYPE_PLACEHOLDER_DO_NOT_USE = 0; // Placeholder, do not use or change
15 | VOTE = 1;
16 | I_HAVE_BLOCK_PROPOSAL = 2;
17 | REQUEST_BLOCK_PROPOSAL = 3;
18 | REQUEST_BLOCK_PROPOSAL_REPLY = 4;
19 | GET_CONSENSUS_STATE = 5;
20 | GET_CONSENSUS_STATE_REPLY = 6;
21 | GET_BLOCK_HEADERS = 7;
22 | GET_BLOCK_HEADERS_REPLY = 8;
23 | GET_BLOCKS = 9;
24 | GET_BLOCKS_REPLY = 10;
25 | RELAY = 11;
26 | TRANSACTIONS = 12;
27 | BACKTRACK_SIGNATURE_CHAIN = 13;
28 | REQUEST_PROPOSAL_TRANSACTIONS = 14;
29 | REQUEST_PROPOSAL_TRANSACTIONS_REPLY = 15;
30 | I_HAVE_SIGNATURE_CHAIN_TRANSACTION = 16;
31 | REQUEST_SIGNATURE_CHAIN_TRANSACTION = 17;
32 | REQUEST_SIGNATURE_CHAIN_TRANSACTION_REPLY = 18;
33 | }
34 |
35 | // Message type that can be signed message
36 | // Name doesn't matter, but value nees to match the value in MessageType
37 | enum AllowedSignedMessageType {
38 | ALLOW_SIGNED_PLACEHOLDER_DO_NOT_USE = 0; // Placeholder, do not use or change
39 | }
40 |
41 | // Message type that can be unsigned message
42 | // Name doesn't matter, but value nees to match the value in MessageType
43 | enum AllowedUnsignedMessageType {
44 | ALLOW_UNSIGNED_PLACEHOLDER_DO_NOT_USE = 0; // Placeholder, do not use or change
45 | ALLOW_UNSIGNED_VOTE = 1;
46 | ALLOW_UNSIGNED_I_HAVE_BLOCK_PROPOSAL = 2;
47 | ALLOW_UNSIGNED_REQUEST_BLOCK_PROPOSAL = 3;
48 | ALLOW_UNSIGNED_REQUEST_BLOCK_REPLY = 4;
49 | ALLOW_UNSIGNED_GET_CONSENSUS_STATE = 5;
50 | ALLOW_UNSIGNED_GET_CONSENSUS_STATE_REPLY = 6;
51 | ALLOW_UNSIGNED_GET_BLOCK_HEADERS = 7;
52 | ALLOW_UNSIGNED_GET_BLOCK_HEADERS_REPLY = 8;
53 | ALLOW_UNSIGNED_GET_BLOCKS = 9;
54 | ALLOW_UNSIGNED_GET_BLOCKS_REPLY = 10;
55 | ALLOW_UNSIGNED_RELAY = 11;
56 | ALLOW_UNSIGNED_TRANSACTIONS = 12;
57 | ALLOW_UNSIGNED_BACKTRACK_SIGNATURE_CHAIN = 13;
58 | ALLOW_UNSIGNED_REQUEST_PROPOSAL_TRANSACTIONS = 14;
59 | ALLOW_UNSIGNED_REQUEST_PROPOSAL_TRANSACTIONS_REPLY = 15;
60 | ALLOW_UNSIGNED_I_HAVE_SIGNATURE_CHAIN_TRANSACTION = 16;
61 | ALLOW_UNSIGNED_REQUEST_SIGNATURE_CHAIN_TRANSACTION = 17;
62 | ALLOW_UNSIGNED_REQUEST_SIGNATURE_CHAIN_TRANSACTION_REPLY = 18;
63 | }
64 |
65 | // Message type that can be sent as direct message
66 | // Name doesn't matter, but value nees to match the value in MessageType
67 | enum AllowedDirectMessageType {
68 | ALLOW_DIRECT_PLACEHOLDER_DO_NOT_USE = 0; // Placeholder, do not use or change
69 | ALLOW_DIRECT_VOTE = 1;
70 | ALLOW_DIRECT_I_HAVE_BLOCK_PROPOSAL = 2;
71 | ALLOW_DIRECT_REQUEST_BLOCK_PROPOSAL = 3;
72 | ALLOW_DIRECT_REQUEST_BLOCK_REPLY = 4;
73 | ALLOW_DIRECT_GET_CONSENSUS_STATE = 5;
74 | ALLOW_DIRECT_GET_CONSENSUS_STATE_REPLY = 6;
75 | ALLOW_DIRECT_GET_BLOCK_HEADERS = 7;
76 | ALLOW_DIRECT_GET_BLOCK_HEADERS_REPLY = 8;
77 | ALLOW_DIRECT_GET_BLOCKS = 9;
78 | ALLOW_DIRECT_GET_BLOCKS_REPLY = 10;
79 | ALLOW_DIRECT_BACKTRACK_SIGNATURE_CHAIN = 13;
80 | ALLOW_DIRECT_REQUEST_PROPOSAL_TRANSACTIONS = 14;
81 | ALLOW_DIRECT_REQUEST_PROPOSAL_TRANSACTIONS_REPLY = 15;
82 | ALLOW_DIRECT_I_HAVE_SIGNATURE_CHAIN_TRANSACTION = 16;
83 | ALLOW_DIRECT_REQUEST_SIGNATURE_CHAIN_TRANSACTION = 17;
84 | ALLOW_DIRECT_REQUEST_SIGNATURE_CHAIN_TRANSACTION_REPLY = 18;
85 | }
86 |
87 | // Message type that can be sent as relay message
88 | // Name doesn't matter, but value nees to match the value in MessageType
89 | enum AllowedRelayMessageType {
90 | ALLOW_RELAY_PLACEHOLDER_DO_NOT_USE = 0; // Placeholder, do not use or change
91 | ALLOW_RELAY_RELAY = 11;
92 | }
93 |
94 | // Message type that can be sent as broadcast_push message
95 | // Name doesn't matter, but value nees to match the value in MessageType
96 | enum AllowedBroadcastPushMessageType {
97 | ALLOW_BROADCAST_PUSH_PLACEHOLDER_DO_NOT_USE = 0; // Placeholder, do not use or change
98 | ALLOW_BROADCAST_PUSH_TRANSACTIONS = 12;
99 | }
100 |
101 | // Message type that can be sent as broadcast_pull message
102 | // Name doesn't matter, but value nees to match the value in MessageType
103 | enum AllowedBroadcastPullMessageType {
104 | ALLOW_BROADCAST_PULL_PLACEHOLDER_DO_NOT_USE = 0; // Placeholder, do not use or change
105 | }
106 |
107 | // Message type that can be sent as broadcast_tree message
108 | // Name doesn't matter, but value nees to match the value in MessageType
109 | enum AllowedBroadcastTreeMessageType {
110 | ALLOW_BROADCAST_TREE_PLACEHOLDER_DO_NOT_USE = 0; // Placeholder, do not use or change
111 | ALLOW_BROADCAST_TREE_TRANSACTIONS = 12;
112 | }
113 |
114 | message UnsignedMessage {
115 | MessageType message_type = 1;
116 | bytes message = 2;
117 | }
118 |
119 | message SignedMessage {
120 | bytes message = 1;
121 | bytes signature = 2;
122 | }
123 |
124 | message Vote {
125 | uint32 height = 1;
126 | bytes block_hash = 2;
127 | }
128 |
129 | message IHaveBlockProposal {
130 | uint32 height = 1;
131 | bytes block_hash = 2;
132 | }
133 |
134 | enum RequestTransactionType {
135 | REQUEST_FULL_TRANSACTION = 0;
136 | REQUEST_TRANSACTION_HASH = 1;
137 | REQUEST_TRANSACTION_SHORT_HASH = 2;
138 | }
139 |
140 | message RequestBlockProposal {
141 | bytes block_hash = 1;
142 | RequestTransactionType type = 2;
143 | bytes short_hash_salt = 3;
144 | uint32 short_hash_size = 4;
145 | }
146 |
147 | message RequestBlockProposalReply {
148 | Block block = 1;
149 | repeated bytes transactions_hash = 2;
150 | }
151 |
152 | message RequestProposalTransactions {
153 | bytes block_hash = 1;
154 | RequestTransactionType type = 2;
155 | bytes short_hash_salt = 3;
156 | uint32 short_hash_size = 4;
157 | repeated bytes transactions_hash = 5;
158 | }
159 |
160 | message RequestProposalTransactionsReply {
161 | repeated Transaction transactions = 1;
162 | }
163 |
164 | message GetConsensusState {
165 | }
166 |
167 | message GetConsensusStateReply {
168 | bytes ledger_block_hash = 2;
169 | uint32 ledger_height = 1;
170 | uint32 consensus_height = 3;
171 | uint32 min_verifiable_height = 5;
172 | SyncState sync_state = 4;
173 | }
174 |
175 | message GetBlockHeaders {
176 | uint32 start_height = 1;
177 | uint32 end_height = 2;
178 | }
179 |
180 | message GetBlockHeadersReply {
181 | repeated Header block_headers = 1;
182 | }
183 |
184 | message GetBlocks {
185 | uint32 start_height = 1;
186 | uint32 end_height = 2;
187 | }
188 |
189 | message GetBlocksReply {
190 | repeated Block blocks = 1;
191 | }
192 |
193 | message Relay {
194 | string src_identifier = 1;
195 | bytes src_pubkey = 6;
196 | bytes dest_id = 2;
197 | bytes payload = 3;
198 | uint32 max_holding_seconds = 5;
199 | // It is important to use block hash instead of block height here to allow
200 | // node in syncing state to be able to sign the sigchain elem.
201 | bytes block_hash = 7;
202 | bytes last_signature = 8;
203 | uint32 sig_chain_len = 9;
204 | }
205 |
206 | message Transactions {
207 | repeated Transaction transactions = 1;
208 | }
209 |
210 | message BacktrackSignatureChain {
211 | repeated SigChainElem sig_chain_elems = 1;
212 | bytes prev_signature = 2;
213 | }
214 |
215 | message IHaveSignatureChainTransaction {
216 | uint32 height = 1;
217 | bytes signature_hash = 2;
218 | }
219 |
220 | message RequestSignatureChainTransaction {
221 | bytes signature_hash = 1;
222 | }
223 |
224 | message RequestSignatureChainTransactionReply {
225 | Transaction transaction = 1;
226 | }
227 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/Client.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 | import android.util.Log
4 | import org.nkn.sdk.cache.multiClientCache
5 | import kotlinx.coroutines.*
6 | import org.json.JSONObject
7 | import org.nkn.sdk.configure.*
8 | import org.nkn.sdk.network.RpcApi
9 | import org.nkn.sdk.network.WsApi
10 | import org.nkn.sdk.protocol.PID_SIZE
11 | import org.nkn.sdk.utils.Utils
12 |
13 |
14 | const val TAG = "Client"
15 | const val identifierRegex = """^__\d+__"""
16 | fun genIdentifier(base: String?, id: Int?): String {
17 | if (id == null) {
18 | return base!!
19 | }
20 | return "__${id}__" + (if (base.isNullOrEmpty()) "" else ".$base")
21 | }
22 |
23 | fun removeIdentifierPrefix(addr: String): String {
24 | return addr.replace(Regex("$identifierRegex."), "")
25 | }
26 |
27 | fun getIdentifierPrefix(addr: String): String {
28 | return Regex("$identifierRegex.").find(addr)?.value ?: ""
29 | }
30 |
31 |
32 | class Client @JvmOverloads constructor(
33 | seed: String,
34 | identifier: String?,
35 | val seedRpcServer: List? = org.nkn.sdk.configure.seed,
36 | encrypt: Boolean? = ENCRYPT,
37 | val msgHoldingSeconds: Int? = MSG_HOLDING_SECONDS,
38 | val reconnectIntervalMin: Long? = RECONNECT_INTERVAL_MIN,
39 | val reconnectIntervalMax: Long? = RECONNECT_INTERVAL_MAX,
40 | responseTimeout: Int? = RESPONSE_TIMEOUT,
41 | numSubClients: Int? = 3,
42 | var listener: ClientListener? = null,
43 | msgCacheExpire: Long? = 30000
44 | ) {
45 | val hashCode = this.hashCode()
46 | val clients: MutableList
47 | val defaultClient = WsApi(
48 | seed,
49 | identifier,
50 | seedRpcServer,
51 | encrypt,
52 | msgHoldingSeconds,
53 | reconnectIntervalMin,
54 | reconnectIntervalMax,
55 | responseTimeout,
56 | MultiClientListener()
57 | )
58 | val identifier: String = identifier ?: ""
59 | val address =
60 | if (this.identifier.isNullOrEmpty()) this.defaultClient.key.publicKeyHash else "${this.identifier}.${this.defaultClient.key.publicKeyHash}"
61 | var isReadly = false
62 | private var connectJobs: ArrayList = ArrayList()
63 | private val rpcApi = RpcApi()
64 |
65 | inner class MultiClientListener : ClientListener() {
66 | override fun onConnect() {
67 | isReadly = true
68 | if (multiClientCache.get(hashCode.toString()) != null) {
69 | return
70 | }
71 | Log.d(TAG, """Client listener "onConnect"""")
72 | multiClientCache.put(hashCode.toString(), true)
73 | listener?.onConnect()
74 | }
75 |
76 | override fun onMessage(
77 | src: String,
78 | data: String?,
79 | pid: ByteArray,
80 | type: Int,
81 | encrypted: Boolean
82 |
83 | ): Any? {
84 | val pidHash = Utils.hexEncode(pid)
85 | if (multiClientCache.get(pidHash) != null) {
86 | return false
87 | }
88 | multiClientCache.put(pidHash, true)
89 | var srcId = removeIdentifierPrefix(src)
90 |
91 | return listener?.onMessage(srcId, data, pid, type, encrypted)
92 | }
93 |
94 | override fun onBinaryMessage(
95 | src: String,
96 | data: ByteArray?,
97 | pid: ByteArray,
98 | type: Int,
99 | encrypted: Boolean
100 | ): Any? {
101 | val pidHash = Utils.hexEncode(pid)
102 | if (multiClientCache.get(pidHash) != null) {
103 | return false
104 | }
105 | multiClientCache.put(pidHash, true)
106 | var srcId = removeIdentifierPrefix(src)
107 | return listener?.onBinaryMessage(srcId, data, pid, type, encrypted)
108 | }
109 |
110 | override fun onClosing() {
111 | listener?.onClosing()
112 | }
113 |
114 | override fun onClosed() {
115 | multiClientCache.remove(hashCode.toString())
116 | listener?.onClosed()
117 | }
118 |
119 | override fun onError(e: Throwable) {
120 | multiClientCache.remove(hashCode.toString())
121 | listener?.onError(e)
122 | }
123 |
124 | override fun onBlock() {
125 | listener?.onBlock()
126 | }
127 | }
128 |
129 | init {
130 | clients = mutableListOf(defaultClient)
131 | for (i in 0 until numSubClients!!) {
132 | clients.add(
133 | WsApi(
134 | seed,
135 | genIdentifier(identifier, i),
136 | seedRpcServer,
137 | encrypt,
138 | msgHoldingSeconds,
139 | reconnectIntervalMin,
140 | reconnectIntervalMax,
141 | responseTimeout,
142 | MultiClientListener()
143 | )
144 | )
145 | }
146 | }
147 |
148 | fun connect() {
149 | GlobalScope.launch {
150 | for (i in clients) {
151 | connectJobs.add(launch { i.connect() })
152 | }
153 | }
154 |
155 | }
156 |
157 | fun close() {
158 | GlobalScope.launch {
159 | for (i in clients) {
160 | clients.map { item -> launch { item.close() } }
161 | }
162 | }
163 | }
164 |
165 | fun send(
166 | dest: String,
167 | data: String,
168 | pid: ByteArray? = null,
169 | replyToPid: ByteArray? = null,
170 | noReply: Boolean? = false,
171 | encrypt: Boolean? = true,
172 | msgHoldingSeconds: Int? = this.msgHoldingSeconds
173 | ) {
174 | var msgPid = pid
175 | if (pid == null) {
176 | msgPid = Utils.randomBytes(PID_SIZE)
177 | }
178 | if (isReadly) {
179 | clients.map { item -> item.send(getIdentifierPrefix(item.address) + dest, data, msgPid, replyToPid, noReply, encrypt, msgHoldingSeconds) }
180 | } else {
181 | //todo await connect
182 | throw Throwable("not connected yet")
183 | }
184 | }
185 |
186 | fun send(
187 | dests: Array,
188 | data: String,
189 | pid: ByteArray? = null,
190 | replyToPid: ByteArray? = null,
191 | noReply: Boolean? = false,
192 | encrypt: Boolean? = true,
193 | msgHoldingSeconds: Int? = this.msgHoldingSeconds
194 | ) {
195 | var msgPid = pid
196 | if (pid == null) {
197 | msgPid = Utils.randomBytes(PID_SIZE)
198 | }
199 | if (isReadly) {
200 | for (i in clients.indices) {
201 | clients[i].send(
202 | dests.map { dest -> getIdentifierPrefix(clients[i].address) + dest }.toTypedArray(),
203 | data,
204 | msgPid,
205 | replyToPid,
206 | noReply,
207 | encrypt,
208 | msgHoldingSeconds
209 | )
210 | }
211 |
212 | } else {
213 | //todo await connect
214 | throw Throwable("not connected yet")
215 | }
216 | }
217 |
218 | fun getSubscribers(topic: String, offset: Int = 0, limit: Int = 1000, meta: Boolean = false, txPool: Boolean = false): JSONObject? {
219 | return rpcApi.getSubscribers(topic, offset, limit, meta, txPool)
220 | }
221 |
222 | fun getSubscribersCount(topic: String): JSONObject? {
223 | return rpcApi.getSubscribersCount(topic)
224 | }
225 |
226 | fun getSubscription(topic: String, subscriber: String): JSONObject? {
227 | return rpcApi.getSubscription(topic, subscriber)
228 | }
229 |
230 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/protocol/protocol.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.protocol
2 |
3 | import com.google.protobuf.ByteString
4 | import org.nkn.sdk.crypto.Key
5 | import org.nkn.sdk.crypto.sha256
6 | import org.nkn.sdk.crypto.sha256Hex
7 | import org.nkn.sdk.pb.ClientMessageProto
8 | import org.nkn.sdk.pb.PayloadsProto
9 | import org.nkn.sdk.pb.SigChainProto
10 | import org.nkn.sdk.utils.Utils
11 | import org.nkn.sdk.utils.encodeBool
12 | import org.nkn.sdk.utils.encodeBytes
13 | import org.nkn.sdk.utils.encodeUint32
14 |
15 |
16 | const val PID_SIZE = 8
17 |
18 | fun serializeSigChainMetadata(sigChain: SigChainProto.SigChain): ByteArray {
19 | return encodeUint32(sigChain.nonce) +
20 | encodeUint32(sigChain.dataSize) +
21 | encodeBytes(sigChain.blockHash.toByteArray()) +
22 | encodeBytes(sigChain.srcId.toByteArray()) +
23 | encodeBytes(sigChain.srcPubkey.toByteArray()) +
24 | encodeBytes(sigChain.destId.toByteArray()) +
25 | encodeBytes(sigChain.destPubkey.toByteArray())
26 | }
27 |
28 | fun serializeSigChainElem(sigChainElem: SigChainProto.SigChainElem): ByteArray {
29 | return encodeBytes(sigChainElem.id.toByteArray()) + encodeBytes(sigChainElem.nextPubkey.toByteArray()) + encodeBool(sigChainElem.mining)
30 | }
31 |
32 | fun newPayload(type: PayloadsProto.PayloadType, replyToPid: ByteArray?, data: ByteArray?, msgPid: ByteArray?): PayloadsProto.Payload {
33 | val payload = PayloadsProto.Payload.newBuilder()
34 | payload.type = type
35 | if (replyToPid != null) {
36 | payload.replyToPid = ByteString.copyFrom(replyToPid)
37 | } else if (msgPid != null) {
38 | payload.pid = ByteString.copyFrom(msgPid)
39 | } else {
40 | payload.pid = ByteString.copyFrom(Utils.randomBytes(PID_SIZE))
41 | }
42 |
43 | if (data != null) payload.data = ByteString.copyFrom(data)
44 | return payload.build()
45 | }
46 |
47 | fun newClientMessage(messageType: ClientMessageProto.ClientMessageType, message: ByteArray, compressionType: ClientMessageProto.CompressionType): ClientMessageProto.ClientMessage {
48 | val msg = ClientMessageProto.ClientMessage.newBuilder()
49 | msg.messageType = messageType
50 | msg.compressionType = compressionType
51 | msg.message = when (compressionType) {
52 | ClientMessageProto.CompressionType.COMPRESSION_ZLIB -> {
53 | println("============== COMPRESSION_ZLIB =============")
54 | ByteString.copyFrom(compress(message))
55 | }
56 | ClientMessageProto.CompressionType.COMPRESSION_NONE -> ByteString.copyFrom(message)
57 | else -> throw Throwable("unknown compression type $compressionType")
58 | }
59 |
60 | return msg.build()
61 | }
62 |
63 | fun newReceipt(prevSignature: ByteArray, key: Key): ClientMessageProto.ClientMessage {
64 | val sigChainElem = SigChainProto.SigChainElem.newBuilder()
65 | val sigChainElemSerialized = serializeSigChainElem(sigChainElem.build())
66 | val digest = sha256Hex(sha256Hex(prevSignature) + sigChainElemSerialized)
67 | val signature = key.sign(digest)
68 | val msg = ClientMessageProto.Receipt.newBuilder()
69 | msg.prevSignature = ByteString.copyFrom(prevSignature)
70 | msg.signature = ByteString.copyFrom(signature)
71 | return newClientMessage(ClientMessageProto.ClientMessageType.RECEIPT, msg.build().toByteArray(), ClientMessageProto.CompressionType.COMPRESSION_NONE)
72 | }
73 |
74 | fun newMessage(payload: ByteArray, encrypted: Boolean, nonce: ByteArray? = null, encryptedKey: ByteArray? = null): PayloadsProto.Message {
75 | val msg = PayloadsProto.Message.newBuilder()
76 | msg.payload = ByteString.copyFrom(payload)
77 | msg.encrypted = encrypted
78 | if (nonce != null) {
79 | msg.nonce = ByteString.copyFrom(nonce)
80 | }
81 | if (encryptedKey != null) {
82 | msg.encryptedKey = ByteString.copyFrom(encryptedKey)
83 | }
84 | return msg.build()
85 | }
86 |
87 | fun newAckPayload(replyToPid: ByteArray, msgPid: ByteArray?): PayloadsProto.Payload {
88 | return newPayload(PayloadsProto.PayloadType.ACK, replyToPid, null, msgPid)
89 | }
90 |
91 | fun newBinaryPayload(data: ByteArray, replyToPid: ByteArray?, msgPid: ByteArray?): PayloadsProto.Payload {
92 | return newPayload(PayloadsProto.PayloadType.BINARY, replyToPid, data, msgPid)
93 | }
94 |
95 | fun newTextPayload(text: String, replyToPid: ByteArray?, msgPid: ByteArray?): PayloadsProto.Payload {
96 | val data = PayloadsProto.TextData.newBuilder()
97 | data.text = text
98 | return newPayload(PayloadsProto.PayloadType.TEXT, replyToPid, data.build().toByteArray(), msgPid)
99 | }
100 |
101 | fun newOutboundMessage(
102 | dest: Any,
103 | payload: Any,
104 | maxHoldingSeconds: Int,
105 | srcAddr: String,
106 | key: Key,
107 | pubkey: ByteArray,
108 | sigChainBlockHash: String? = null
109 | ): ClientMessageProto.ClientMessage {
110 | var dests: Array
111 | if (dest is String) {
112 | dests = arrayOf(dest)
113 | } else if (dest is Array<*>) {
114 | dests = dest as Array
115 | } else {
116 | throw Throwable("dest type must be String or Array")
117 | }
118 |
119 | if (dests.isNullOrEmpty()) {
120 | throw Throwable("no destination")
121 | }
122 |
123 | var payloads: Array
124 | if (payload is ByteArray) {
125 | payloads = arrayOf(payload)
126 | } else if (payload is Array<*>) {
127 | payloads = payload as Array
128 | } else {
129 | throw Throwable("payload type must be String or Array")
130 | }
131 | if (payloads.isNullOrEmpty()) {
132 | throw Throwable("no payloads")
133 | }
134 | if (payloads.size > 1 && payloads.size != dests.size) {
135 | throw Throwable("invalid payload count")
136 | }
137 |
138 | val sigChainElem = SigChainProto.SigChainElem.newBuilder()
139 | sigChainElem.nextPubkey = ByteString.copyFrom(pubkey)
140 | var sigChainElemSerialized = serializeSigChainElem(sigChainElem.build())
141 |
142 | val sigChain = SigChainProto.SigChain.newBuilder()
143 | sigChain.nonce = Utils.randomInt32()
144 | sigChain.dataSize = payloads.size
145 | if (sigChainBlockHash != null) {
146 | sigChain.blockHash = ByteString.copyFrom(Utils.hexDecode(sigChainBlockHash))
147 | }
148 | sigChain.srcId = ByteString.copyFrom(addr2Id(srcAddr))
149 | sigChain.srcPubkey = ByteString.copyFrom(key.publicKey)
150 |
151 | var signatures: ArrayList = ArrayList()
152 | for (i in dests.indices) {
153 | sigChain.destId = ByteString.copyFrom(addr2Id(dests[i]))
154 | sigChain.destPubkey = ByteString.copyFrom(Utils.getPublicKeyByClientAddr(dests[i]))
155 | if (!payloads.isNullOrEmpty()) {
156 | sigChain.dataSize = payloads[i].size
157 | } else {
158 | sigChain.dataSize = payloads[0].size
159 | }
160 | val hex = serializeSigChainMetadata(sigChain.build())
161 | val digest = sha256Hex(sha256Hex(hex) + sigChainElemSerialized)
162 | val signature = key.sign(digest)
163 | signatures.add(signature)
164 | }
165 |
166 | val obMsg = ClientMessageProto.OutboundMessage.newBuilder()
167 | obMsg.addAllDests(dests.toList())
168 | obMsg.addAllPayloads(payloads.map { item -> ByteString.copyFrom(item) }.toList())
169 | obMsg.maxHoldingSeconds = maxHoldingSeconds
170 | obMsg.nonce = sigChain.nonce
171 | obMsg.blockHash = sigChain.blockHash
172 | obMsg.addAllSignatures(signatures.map { item -> ByteString.copyFrom(item) }.toList())
173 |
174 | val compressionType = if (payloads.size > 1) ClientMessageProto.CompressionType.COMPRESSION_ZLIB else ClientMessageProto.CompressionType.COMPRESSION_NONE
175 |
176 | return newClientMessage(ClientMessageProto.ClientMessageType.OUTBOUND_MESSAGE, obMsg.build().toByteArray(), compressionType)
177 | }
178 |
179 | fun addr2Id(addr: String): ByteArray {
180 | return sha256(addr)
181 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/Wallet.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.JsonSyntaxException
5 | import org.nkn.sdk.crypto.*
6 | import org.nkn.sdk.utils.Utils
7 | import org.nkn.sdk.error.WalletError
8 | import org.nkn.sdk.error.WalletErrorCode
9 | import org.nkn.sdk.network.RpcApi
10 | import org.nkn.sdk.transaction.newSubscribe
11 | import org.nkn.sdk.transaction.newTransaction
12 | import org.nkn.sdk.transaction.newTransfer
13 | import org.nkn.sdk.transaction.newUnsubscribe
14 |
15 | val rpcApi = RpcApi()
16 |
17 | fun createPasswordHash(pass: String): ByteArray {
18 | return doubleSha256(pass)
19 | }
20 |
21 | class Wallet(
22 | val account: Account,
23 | password: String = "",
24 | prevMasterKey: String? = null,
25 | prevIV: String? = null
26 | ) {
27 | companion object {
28 | @JvmField
29 | val WALLET_VERSION = 1
30 | @JvmField
31 | val MIN_COMPATIBLE_WALLET_VERSION = 1
32 | @JvmField
33 | val MAX_COMPATIBLE_WALLET_VERSION = 1
34 |
35 | data class KeyStore(
36 | val Version: Int,
37 | val PasswordHash: String,
38 | val MasterKey: String,
39 | val IV: String,
40 | val SeedEncrypted: String,
41 | val Address: String,
42 | val ProgramHash: String,
43 | val ContractData: String
44 | ) {
45 | fun toJson(): String {
46 | return Gson().toJson(this)
47 | }
48 | }
49 |
50 | @JvmStatic
51 | fun createRandom(): Wallet {
52 | return Wallet()
53 | }
54 |
55 | @JvmOverloads
56 | @JvmStatic
57 | fun fromSeed(seed: String, password: String? = null): Wallet {
58 | var account = Account(seed)
59 | return Wallet(account, password ?: "")
60 | }
61 |
62 | @JvmStatic
63 | fun fromKeystore(keystore: String, pass: String): Wallet {
64 | val walletJson: KeyStore
65 | try {
66 | walletJson = Gson().fromJson(keystore, KeyStore::class.java)
67 | } catch (e: JsonSyntaxException) {
68 | throw WalletError(
69 | WalletErrorCode.INVALID_WALLET_FORMAT,
70 | WalletError.INVALID_WALLET_FORMAT
71 | )
72 | }
73 |
74 | val version = walletJson.Version
75 | if (version < MIN_COMPATIBLE_WALLET_VERSION || version > MAX_COMPATIBLE_WALLET_VERSION) {
76 | throw WalletError(
77 | WalletErrorCode.INVALID_WALLET_VERSION,
78 | WalletError.INVALID_WALLET_VERSION
79 | )
80 | }
81 | val pwdHash = createPasswordHash(pass)
82 | if (walletJson.PasswordHash != Utils.hexEncode(sha256Hex(pwdHash))) {
83 | throw WalletError(WalletErrorCode.WRONG_PASSWORD, WalletError.WRONG_PASSWORD)
84 | }
85 | val decryptMasterKey = aesDecrypt(
86 | Utils.hexDecode(walletJson.MasterKey),
87 | pwdHash,
88 | Utils.hexDecode(walletJson.IV)
89 | )
90 | val seed = aesDecrypt(
91 | Utils.hexDecode(walletJson.SeedEncrypted),
92 | decryptMasterKey,
93 | Utils.hexDecode(walletJson.IV)
94 | )
95 | return Wallet(Account(seed), pass, Utils.hexEncode(decryptMasterKey), walletJson.IV)
96 | }
97 |
98 | @JvmStatic
99 | fun getBalanceByAddress(address: String): Double {
100 | val json = rpcApi.getBalanceByAddr(address)
101 | return json?.getDouble("amount") ?: 0.0
102 | }
103 |
104 | @JvmStatic
105 | fun getNonceByAddress(address: String): Long? {
106 | val json = rpcApi.getNonceByAddr(address)
107 | return json?.getLong("nonce")
108 | }
109 |
110 |
111 | }
112 |
113 | val seed: ByteArray
114 | val passwordHash: ByteArray
115 | val iv: ByteArray
116 | val masterKey: ByteArray
117 | val seedEncrypted: ByteArray
118 | val version: Int = WALLET_VERSION
119 |
120 | val keyStore: KeyStore
121 |
122 | init {
123 | this.seed = this.account.key.seed
124 | val masterKey =
125 | if (prevMasterKey != null) Utils.hexDecode(prevMasterKey) else genAESPassword()
126 | val pwdHash = createPasswordHash(password)
127 | this.passwordHash = sha256Hex(pwdHash)
128 | this.iv = if (prevIV != null) Utils.hexDecode(prevIV) else genAESIV()
129 | this.masterKey = aesEncrypt(masterKey, pwdHash, this.iv)
130 | this.seedEncrypted = aesEncrypt(this.seed, masterKey, this.iv)
131 | this.keyStore = KeyStore(
132 | this.version,
133 | Utils.hexEncode(this.passwordHash),
134 | Utils.hexEncode(this.masterKey),
135 | Utils.hexEncode(this.iv),
136 | Utils.hexEncode(this.seedEncrypted),
137 | this.account.address,
138 | this.account.key.programHash,
139 | this.account.contract
140 | )
141 | }
142 |
143 | val address: String = this.account.address
144 | val privateKey: ByteArray = this.account.key.privateKey
145 | val privateKeyHash: String = this.account.key.privateKeyHash
146 | val publicKey: ByteArray = this.account.key.publicKey
147 | val publicKeyHash: String = this.account.key.publicKeyHash
148 | val programHash: String = this.account.key.programHash
149 | val signatureRedeem: String = this.account.key.signatureRedeem
150 | val contractData: String = this.account.contract
151 |
152 |
153 | constructor() : this(Account())
154 |
155 | fun toJson(): String {
156 | return Gson().toJson(
157 | this.keyStore
158 | )
159 | }
160 |
161 | fun verifyPassword(pass: String): Boolean {
162 | val passwordHash = createPasswordHash(pass)
163 | return this.passwordHash.contentEquals(sha256Hex(passwordHash))
164 | }
165 |
166 | @JvmOverloads
167 | fun encrypt(pass: String? = null): String {
168 | if (pass == null) {
169 | return this.toJson()
170 | } else {
171 | val wallet = fromSeed(Utils.hexEncode(this.seed), pass)
172 | return wallet.toJson()
173 | }
174 | }
175 |
176 | fun getBalance(): Double {
177 | return getBalanceByAddress(this.address)
178 | }
179 |
180 | fun getNonce(): Long? {
181 | return getNonceByAddress(this.address)
182 | }
183 |
184 | @JvmOverloads
185 | fun transferTo(
186 | toAddress: String,
187 | amount: Double,
188 | fee: Double = 0.0,
189 | nonce: Long? = null,
190 | attrs: String = ""
191 | ): String? {
192 | if (!Utils.verifyAddress(toAddress)) {
193 | throw WalletError(WalletErrorCode.INVALID_ADDRESS, WalletError.INVALID_ADDRESS)
194 | }
195 | val balance = this.getBalance()
196 | if (balance < amount) {
197 | throw WalletError(WalletErrorCode.NOT_ENOUGH_BALANCE, WalletError.NOT_ENOUGH_BALANCE)
198 | }
199 |
200 | val nextNonce = nonce ?: this.getNonce() ?: 0
201 | val pld = newTransfer(
202 | this.programHash,
203 | Utils.addressStringToProgramHash(toAddress),
204 | amount
205 | )
206 |
207 | val txn = newTransaction(this.account, pld, nextNonce, fee, attrs)
208 | return rpcApi.sendRawTransaction(Utils.hexEncode(txn.toByteArray()))
209 | }
210 |
211 | @JvmOverloads
212 | fun subscribe(
213 | topic: String,
214 | duration: Int,
215 | identifier: String? = "",
216 | meta: String? = "",
217 | nonce: Long? = null,
218 | fee: Double? = 0.0,
219 | attrs: String? = ""
220 | ): String? {
221 | val nextNonce = nonce ?: this.getNonce() ?: 0
222 | val pld = newSubscribe(this.publicKeyHash, identifier ?: "", topic, duration, meta ?: "")
223 | val txn = newTransaction(this.account, pld, nextNonce, fee ?: 0.0, attrs ?: "")
224 | return rpcApi.sendRawTransaction(Utils.hexEncode(txn.toByteArray()))
225 | }
226 |
227 | @JvmOverloads
228 | fun unsubscribe(
229 | topic: String,
230 | identifier: String? = "",
231 | nonce: Long? = null,
232 | fee: Double? = 0.0,
233 | attrs: String? = ""
234 | ): String? {
235 | val nextNonce = nonce ?: this.getNonce() ?: 0
236 | val pld = newUnsubscribe(this.publicKeyHash, identifier ?: "", topic)
237 | val txn = newTransaction(this.account, pld, nextNonce, fee ?: 0.0, attrs ?: "")
238 | return rpcApi.sendRawTransaction(Utils.hexEncode(txn.toByteArray()))
239 | }
240 |
241 | }
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/network/WsApi.kt:
--------------------------------------------------------------------------------
1 | package org.nkn.sdk.network
2 |
3 | import android.util.Log
4 | import okhttp3.*
5 | import okio.ByteString
6 | import org.json.JSONObject
7 | import org.nkn.sdk.ClientListener
8 | import org.nkn.sdk.configure.*
9 | import org.nkn.sdk.const.SIGNATURE_SIZE
10 | import org.nkn.sdk.const.StatusCode
11 | import org.nkn.sdk.crypto.Key
12 | import org.nkn.sdk.pb.ClientMessageProto
13 | import org.nkn.sdk.pb.PayloadsProto
14 | import org.nkn.sdk.protocol.*
15 | import org.nkn.sdk.utils.Utils
16 | import java.nio.ByteBuffer
17 | import java.util.*
18 | import java.util.concurrent.TimeUnit
19 | import kotlin.collections.ArrayList
20 | import kotlin.concurrent.schedule
21 | import kotlin.random.Random
22 |
23 | const val TAG = "WsApi"
24 |
25 | const val MAX_CLIENT_MESSAGE_SIZE = 4000000
26 |
27 | class WsApi @JvmOverloads constructor(
28 | seed: String,
29 | identifier: String?,
30 | val seedRpcServer: List? = org.nkn.sdk.configure.seed,
31 | encrypt: Boolean? = ENCRYPT,
32 | val msgHoldingSeconds: Int? = MSG_HOLDING_SECONDS,
33 | val reconnectIntervalMin: Long? = RECONNECT_INTERVAL_MIN,
34 | val reconnectIntervalMax: Long? = RECONNECT_INTERVAL_MAX,
35 | val responseTimeout: Int? = RESPONSE_TIMEOUT,
36 | var listener: ClientListener? = null
37 | ) {
38 | var ws: WebSocket? = null
39 | var reconnectInterval: Long = reconnectIntervalMin!!
40 | val key: Key = Key(seed)
41 | val curveSecretKey = Utils.convertSecretKey(key.privateKey)
42 | val identifier: String = identifier ?: ""
43 | val address =
44 | if (this.identifier.isNullOrEmpty()) this.key.publicKeyHash else "${this.identifier}.${this.key.publicKeyHash}"
45 | var sigChainBlockHash: String? = null
46 | var node: JSONObject? = null
47 | var isReadly = false
48 | private val client = OkHttpClient()
49 | private var shouldReconnect = false
50 |
51 | inner class WsListener : WebSocketListener() {
52 | override fun onOpen(webSocket: WebSocket, response: Response) {
53 | Log.d(TAG, """"addr: $address, WebSocket listener "onOpen"""")
54 | }
55 |
56 | override fun onMessage(webSocket: WebSocket, text: String) {
57 | Log.d(TAG, """"addr: $address, WebSocket listener "onMessage", text: $text""")
58 | val message = JSONObject(text)
59 | if (message.has("Error") && message.getInt("Error") != StatusCode.SUCCESS.code) {
60 | if (message.getInt("Error") == StatusCode.WRONG_NODE.code) {
61 | if (message.getJSONObject("Result").getString("addr") == node!!.getString("addr"))
62 | return
63 | close()
64 | ws = null
65 | createWebSocketConnection(message.getJSONObject("Result"))
66 | } else if (message.getString("Action") == "setClient") {
67 | ws?.close(1000, null)
68 | }
69 | return
70 | }
71 | when (message.getString("Action")) {
72 | "setClient" -> {
73 | sigChainBlockHash = message.getJSONObject("Result").getString("sigChainBlockHash")
74 | isReadly = true
75 | listener?.onConnect()
76 | }
77 | "updateSigChainBlockHash" -> sigChainBlockHash = message.getString("Result")
78 | "sendRawBlock" -> listener?.onBlock()
79 | else -> Log.e(TAG, "Unknown msg type: $message")
80 | }
81 |
82 | }
83 |
84 | override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
85 | Log.d(TAG, """"addr: $address, WebSocket listener "onBinaryMessage", bytes: ${bytes.size()}""")
86 | val handled = handleBinaryMessage(bytes.toByteArray())
87 | if (!handled) Log.d(TAG, "Unhandled msg")
88 | }
89 |
90 | override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
91 | Log.d(TAG, """"addr: $address, WebSocket listener "onClosing", code: $code, reason: $reason""")
92 | listener?.onClosing()
93 | }
94 |
95 | override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
96 | Log.d(TAG, """"addr: $address, WebSocket listener "onClosed", code: $code, reason: $reason""")
97 | listener?.onClosed()
98 | if (shouldReconnect) reconnect()
99 | }
100 |
101 | override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
102 | Log.d(TAG, """"addr: $address, WebSocket listener "onFailure", error: $t, reason: $response""")
103 | listener?.onError(t)
104 | if (shouldReconnect) reconnect()
105 | }
106 | }
107 |
108 |
109 | fun handleInboundMsg(raw: ByteArray): Boolean {
110 | val msg = ClientMessageProto.InboundMessage.parseFrom(raw)
111 | if (msg.prevSignature.size() > 0) {
112 | var receipt = newReceipt(msg.prevSignature.toByteArray(), key)
113 | this.ws?.send(ByteString.of(ByteBuffer.wrap(receipt.toByteArray())))
114 | }
115 | val pldMsg = PayloadsProto.Message.parseFrom(msg.payload)
116 | val pldBytes = if (pldMsg.encrypted) {
117 | decryptPayload(pldMsg, Utils.getPublicKeyByClientAddr(msg.src), key)
118 | } else {
119 | pldMsg.payload.toByteArray()
120 | }
121 |
122 | val payload = PayloadsProto.Payload.parseFrom(pldBytes)
123 | var data: String? = null
124 | when (payload.type) {
125 | PayloadsProto.PayloadType.TEXT -> {
126 | var textData = PayloadsProto.TextData.parseFrom(payload.data)
127 | data = textData.text
128 | }
129 | PayloadsProto.PayloadType.ACK -> {
130 | data = null
131 | }
132 | }
133 |
134 | when (payload.type) {
135 | PayloadsProto.PayloadType.TEXT, PayloadsProto.PayloadType.BINARY -> {
136 | Log.i(TAG, """addr: $address, receive message: $data""")
137 | val response = listener?.onMessage(msg.src, data, payload.pid.toByteArray(), payload.typeValue, pldMsg.encrypted)
138 | Log.d(TAG, """addr: $address, response: $response""")
139 | if (response is Boolean && !response) {
140 | return false
141 | } else if (response != null && response is String) {
142 | this.send(msg.src, response, replyToPid = payload.pid.toByteArray(), encrypt = pldMsg.encrypted, msgHoldingSeconds = 0, noReply = true)
143 | } else {
144 | this.sendACK(msg.src, payload.pid.toByteArray(), pldMsg.encrypted)
145 | }
146 |
147 | return true
148 | }
149 | }
150 |
151 | return false
152 | }
153 |
154 | fun handleBinaryMessage(raw: ByteArray): Boolean {
155 | val clientMessage = ClientMessageProto.ClientMessage.parseFrom(raw)
156 | when (clientMessage.messageType) {
157 | ClientMessageProto.ClientMessageType.INBOUND_MESSAGE -> return handleInboundMsg(clientMessage.message.toByteArray())
158 | else -> return false
159 | }
160 | }
161 |
162 | fun handleConnect(url: String) {
163 | Log.d(TAG, "addr: $address, connect to ws://$url")
164 | val request = Request.Builder()
165 | .url("ws://$url")
166 | .build()
167 |
168 | this.ws = client.newBuilder()
169 | .pingInterval(10, TimeUnit.SECONDS)
170 | .callTimeout(responseTimeout!!.toLong(), TimeUnit.SECONDS)
171 | .build().newWebSocket(request, WsListener())
172 | this.shouldReconnect = true
173 | this.reconnectInterval = this.reconnectIntervalMin!!
174 | }
175 |
176 | fun createWebSocketConnection(nodeInfo: JSONObject) {
177 | if (!nodeInfo.has("addr")) {
178 | Log.e(TAG, "No address in node info $nodeInfo")
179 | this.reconnect()
180 | return
181 | }
182 | this.node = nodeInfo
183 | this.handleConnect(nodeInfo.getString("addr"))
184 | Log.d(TAG, "send setClient :${JSONObject(mapOf("Action" to "setClient", "Addr" to this.address))}")
185 | this.ws!!.send(JSONObject(mapOf("Action" to "setClient", "Addr" to this.address)).toString())
186 | }
187 |
188 | fun connect() {
189 | val rpcAddr = seedRpcServer?.get(Random.nextInt(0, seedRpcServer.size))
190 | val rpcApi = RpcApi(rpcAddr)
191 |
192 | try {
193 | val nodeInfo = rpcApi.getWsAddr(address)
194 | if (nodeInfo == null) {
195 | Log.e(TAG, "get ws addr is null")
196 | this.reconnect()
197 | return
198 | }
199 | this.createWebSocketConnection(nodeInfo)
200 | } catch (e: Throwable) {
201 | Log.e(TAG, "RPC call failed, $e")
202 | this.reconnect()
203 | }
204 | }
205 |
206 | fun reconnect() {
207 | if (shouldReconnect) {
208 | Log.i(TAG, "Reconnecting in ${reconnectInterval / 1000} s...")
209 | Timer().schedule(reconnectInterval) {
210 | connect()
211 | }
212 | reconnectInterval *= 2
213 | if (reconnectInterval > this.reconnectIntervalMax!!) {
214 | reconnectInterval = this.reconnectIntervalMax
215 | }
216 | } else {
217 | this.listener?.onClosed()
218 | }
219 | }
220 |
221 | fun close() {
222 | this.shouldReconnect = false
223 | this.ws?.close(1000, null)
224 | }
225 |
226 | fun messageFromPayload(payload: PayloadsProto.Payload, encrypt: Boolean, dest: String): PayloadsProto.Message {
227 | if (encrypt) {
228 | return encryptPayload(payload.toByteArray(), dest, key)
229 | }
230 | return newMessage(payload.toByteArray(), false)
231 | }
232 |
233 | fun messageFromPayloads(payload: PayloadsProto.Payload, encrypt: Boolean, dests: Array): Array {
234 | return encryptPayloads(payload.toByteArray(), dests, key)
235 | }
236 |
237 | fun sendACK(dests: Array, pid: ByteArray, encrypt: Boolean) {
238 | // todo multi
239 | // if (dest is Array<*>) {
240 | // if (dest.size == 0) return
241 | // if (dest.size == 1) {
242 | // sendACK(dest[0], pid, encrypt)
243 | // return
244 | // }
245 | // if (dest.size > 1 && encrypt) {
246 | // Log.i(TAG, "Encrypted ACK with multicast is not supported, fallback to unicast.")
247 | // for (i in 0 until dest.size) {
248 | // sendACK(dest[i], pid, encrypt)
249 | // }
250 | // return
251 | // }
252 | // }
253 | }
254 |
255 | @JvmOverloads
256 | fun sendACK(dest: String, pid: ByteArray, encrypt: Boolean) {
257 | val payload = newAckPayload(pid, null)
258 | val pldMessage = messageFromPayload(payload, encrypt, dest)
259 | val obMsg = newOutboundMessage(dest, pldMessage.toByteArray(), 0, this.address, this.key, Utils.hexDecode(this.node!!.getString("pubkey")), this.sigChainBlockHash)
260 | this.ws?.send(ByteString.of(ByteBuffer.wrap(obMsg.toByteArray())))
261 | }
262 |
263 | fun sendMsg(dests: Any, data: Any, encrypt: Boolean, maxHoldingSeconds: Int, replyToPid: ByteArray?, msgPid: ByteArray?): ByteArray? {
264 | if (!isReadly) {
265 | return null
266 | }
267 | val payload = if (data is String) newTextPayload(data, replyToPid, msgPid) else newBinaryPayload(data as ByteArray, replyToPid, msgPid)
268 | when (dests) {
269 | is String -> {
270 | val pldMsg = this.messageFromPayload(payload, encrypt, dests)
271 | val obMsg = newOutboundMessage(
272 | dests,
273 | pldMsg.toByteArray(),
274 | maxHoldingSeconds,
275 | this.address,
276 | this.key,
277 | Utils.hexDecode(this.node!!.getString("pubkey")),
278 | this.sigChainBlockHash
279 | )
280 | this.ws?.send(ByteString.of(ByteBuffer.wrap(obMsg.toByteArray())))
281 | return payload.pid.toByteArray()
282 | }
283 | is Array<*> -> {
284 | val pldMsg = this.messageFromPayloads(payload, encrypt, dests as Array)
285 | val msgs: ArrayList = ArrayList()
286 | var destList: ArrayList = ArrayList()
287 | var pldList: ArrayList = ArrayList()
288 | var totalSize = 0
289 | for (i in pldMsg.indices) {
290 | val size = pldMsg[i].toByteArray().size + dests[i].length + SIGNATURE_SIZE
291 | if (size > MAX_CLIENT_MESSAGE_SIZE) {
292 | throw Throwable("message size is greater than $MAX_CLIENT_MESSAGE_SIZE bytes")
293 | }
294 | if (totalSize + size > MAX_CLIENT_MESSAGE_SIZE) {
295 | msgs.add(
296 | newOutboundMessage(
297 | destList.toTypedArray(),
298 | pldList.toTypedArray(),
299 | maxHoldingSeconds,
300 | this.address,
301 | this.key,
302 | Utils.hexDecode(this.node!!.getString("pubkey")),
303 | this.sigChainBlockHash
304 | )
305 | )
306 | destList = ArrayList()
307 | pldList = ArrayList()
308 | totalSize = 0
309 | }
310 | destList.add(dests[i])
311 | pldList.add(pldMsg[i].toByteArray())
312 | totalSize += size
313 | }
314 |
315 | msgs.add(
316 | newOutboundMessage(
317 | destList.toTypedArray(),
318 | pldList.toTypedArray(),
319 | maxHoldingSeconds,
320 | this.address,
321 | this.key,
322 | Utils.hexDecode(this.node!!.getString("pubkey")),
323 | this.sigChainBlockHash
324 | )
325 | )
326 |
327 | if (msgs.size > 1) {
328 | Log.i(TAG, "Client message size is greater than ${MAX_CLIENT_MESSAGE_SIZE} bytes, split into ${msgs.size} batches.")
329 | }
330 | msgs.forEach { msg -> this.ws?.send(ByteString.of(ByteBuffer.wrap(msg.toByteArray()))) }
331 | return payload.pid.toByteArray()
332 | }
333 | else -> {
334 | throw Throwable("dest type must be String or Array")
335 | }
336 | }
337 |
338 | return null
339 | }
340 |
341 | @JvmOverloads
342 | fun send(
343 | dest: String,
344 | data: String,
345 | pid: ByteArray? = null,
346 | replyToPid: ByteArray? = null,
347 | noReply: Boolean? = false,
348 | encrypt: Boolean? = true,
349 | msgHoldingSeconds: Int? = this.msgHoldingSeconds
350 | ) {
351 | this.sendMsg(dest, data, encrypt!!, msgHoldingSeconds!!, replyToPid, pid)
352 | }
353 |
354 | @JvmOverloads
355 | fun send(
356 | dests: Array,
357 | data: String,
358 | pid: ByteArray? = null,
359 | replyToPid: ByteArray? = null,
360 | noReply: Boolean? = false,
361 | encrypt: Boolean? = true,
362 | msgHoldingSeconds: Int? = this.msgHoldingSeconds
363 | ) {
364 | this.sendMsg(dests, data, encrypt!!, msgHoldingSeconds!!, replyToPid, pid)
365 | }
366 |
367 | // @JvmOverloads
368 | // fun send(
369 | // dests: Array,
370 | // data: Array,
371 | // pid: ByteArray? = null,
372 | // replyToPid: ByteArray? = null,
373 | // noReply: Boolean? = false,
374 | // encrypt: Boolean = true,
375 | // msgHoldingSeconds: Int? = this.msgHoldingSeconds
376 | // ) {
377 | //
378 | // }
379 | }
380 |
381 |
--------------------------------------------------------------------------------
/library/src/main/java/org/nkn/sdk/pb/NodeProto.java:
--------------------------------------------------------------------------------
1 | // Generated by the protocol buffer compiler. DO NOT EDIT!
2 | // source: pb/node.proto
3 |
4 | package org.nkn.sdk.pb;
5 |
6 | public final class NodeProto {
7 | private NodeProto() {}
8 | public static void registerAllExtensions(
9 | com.google.protobuf.ExtensionRegistryLite registry) {
10 | }
11 |
12 | public static void registerAllExtensions(
13 | com.google.protobuf.ExtensionRegistry registry) {
14 | registerAllExtensions(
15 | (com.google.protobuf.ExtensionRegistryLite) registry);
16 | }
17 | /**
18 | * Protobuf enum {@code pb.SyncState}
19 | */
20 | public enum SyncState
21 | implements com.google.protobuf.ProtocolMessageEnum {
22 | /**
23 | * WAIT_FOR_SYNCING = 0;
24 | */
25 | WAIT_FOR_SYNCING(0),
26 | /**
27 | * SYNC_STARTED = 1;
28 | */
29 | SYNC_STARTED(1),
30 | /**
31 | * SYNC_FINISHED = 2;
32 | */
33 | SYNC_FINISHED(2),
34 | /**
35 | * PERSIST_FINISHED = 3;
36 | */
37 | PERSIST_FINISHED(3),
38 | UNRECOGNIZED(-1),
39 | ;
40 |
41 | /**
42 | * WAIT_FOR_SYNCING = 0;
43 | */
44 | public static final int WAIT_FOR_SYNCING_VALUE = 0;
45 | /**
46 | * SYNC_STARTED = 1;
47 | */
48 | public static final int SYNC_STARTED_VALUE = 1;
49 | /**
50 | * SYNC_FINISHED = 2;
51 | */
52 | public static final int SYNC_FINISHED_VALUE = 2;
53 | /**
54 | * PERSIST_FINISHED = 3;
55 | */
56 | public static final int PERSIST_FINISHED_VALUE = 3;
57 |
58 |
59 | public final int getNumber() {
60 | if (this == UNRECOGNIZED) {
61 | throw new java.lang.IllegalArgumentException(
62 | "Can't get the number of an unknown enum value.");
63 | }
64 | return value;
65 | }
66 |
67 | /**
68 | * @param value The numeric wire value of the corresponding enum entry.
69 | * @return The enum associated with the given numeric wire value.
70 | * @deprecated Use {@link #forNumber(int)} instead.
71 | */
72 | @java.lang.Deprecated
73 | public static SyncState valueOf(int value) {
74 | return forNumber(value);
75 | }
76 |
77 | /**
78 | * @param value The numeric wire value of the corresponding enum entry.
79 | * @return The enum associated with the given numeric wire value.
80 | */
81 | public static SyncState forNumber(int value) {
82 | switch (value) {
83 | case 0: return WAIT_FOR_SYNCING;
84 | case 1: return SYNC_STARTED;
85 | case 2: return SYNC_FINISHED;
86 | case 3: return PERSIST_FINISHED;
87 | default: return null;
88 | }
89 | }
90 |
91 | public static com.google.protobuf.Internal.EnumLiteMap
92 | internalGetValueMap() {
93 | return internalValueMap;
94 | }
95 | private static final com.google.protobuf.Internal.EnumLiteMap<
96 | SyncState> internalValueMap =
97 | new com.google.protobuf.Internal.EnumLiteMap() {
98 | public SyncState findValueByNumber(int number) {
99 | return SyncState.forNumber(number);
100 | }
101 | };
102 |
103 | public final com.google.protobuf.Descriptors.EnumValueDescriptor
104 | getValueDescriptor() {
105 | return getDescriptor().getValues().get(ordinal());
106 | }
107 | public final com.google.protobuf.Descriptors.EnumDescriptor
108 | getDescriptorForType() {
109 | return getDescriptor();
110 | }
111 | public static final com.google.protobuf.Descriptors.EnumDescriptor
112 | getDescriptor() {
113 | return org.nkn.sdk.pb.NodeProto.getDescriptor().getEnumTypes().get(0);
114 | }
115 |
116 | private static final SyncState[] VALUES = values();
117 |
118 | public static SyncState valueOf(
119 | com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
120 | if (desc.getType() != getDescriptor()) {
121 | throw new java.lang.IllegalArgumentException(
122 | "EnumValueDescriptor is not for this type.");
123 | }
124 | if (desc.getIndex() == -1) {
125 | return UNRECOGNIZED;
126 | }
127 | return VALUES[desc.getIndex()];
128 | }
129 |
130 | private final int value;
131 |
132 | private SyncState(int value) {
133 | this.value = value;
134 | }
135 |
136 | // @@protoc_insertion_point(enum_scope:pb.SyncState)
137 | }
138 |
139 | public interface NodeDataOrBuilder extends
140 | // @@protoc_insertion_point(interface_extends:pb.NodeData)
141 | com.google.protobuf.MessageOrBuilder {
142 |
143 | /**
144 | * bytes public_key = 1;
145 | * @return The publicKey.
146 | */
147 | com.google.protobuf.ByteString getPublicKey();
148 |
149 | /**
150 | * uint32 websocket_port = 2;
151 | * @return The websocketPort.
152 | */
153 | int getWebsocketPort();
154 |
155 | /**
156 | * uint32 json_rpc_port = 3;
157 | * @return The jsonRpcPort.
158 | */
159 | int getJsonRpcPort();
160 |
161 | /**
162 | * uint32 protocol_version = 4;
163 | * @return The protocolVersion.
164 | */
165 | int getProtocolVersion();
166 | }
167 | /**
168 | * Protobuf type {@code pb.NodeData}
169 | */
170 | public static final class NodeData extends
171 | com.google.protobuf.GeneratedMessageV3 implements
172 | // @@protoc_insertion_point(message_implements:pb.NodeData)
173 | NodeDataOrBuilder {
174 | private static final long serialVersionUID = 0L;
175 | // Use NodeData.newBuilder() to construct.
176 | private NodeData(com.google.protobuf.GeneratedMessageV3.Builder> builder) {
177 | super(builder);
178 | }
179 | private NodeData() {
180 | publicKey_ = com.google.protobuf.ByteString.EMPTY;
181 | }
182 |
183 | @java.lang.Override
184 | @SuppressWarnings({"unused"})
185 | protected java.lang.Object newInstance(
186 | UnusedPrivateParameter unused) {
187 | return new NodeData();
188 | }
189 |
190 | @java.lang.Override
191 | public final com.google.protobuf.UnknownFieldSet
192 | getUnknownFields() {
193 | return this.unknownFields;
194 | }
195 | private NodeData(
196 | com.google.protobuf.CodedInputStream input,
197 | com.google.protobuf.ExtensionRegistryLite extensionRegistry)
198 | throws com.google.protobuf.InvalidProtocolBufferException {
199 | this();
200 | if (extensionRegistry == null) {
201 | throw new java.lang.NullPointerException();
202 | }
203 | com.google.protobuf.UnknownFieldSet.Builder unknownFields =
204 | com.google.protobuf.UnknownFieldSet.newBuilder();
205 | try {
206 | boolean done = false;
207 | while (!done) {
208 | int tag = input.readTag();
209 | switch (tag) {
210 | case 0:
211 | done = true;
212 | break;
213 | case 10: {
214 |
215 | publicKey_ = input.readBytes();
216 | break;
217 | }
218 | case 16: {
219 |
220 | websocketPort_ = input.readUInt32();
221 | break;
222 | }
223 | case 24: {
224 |
225 | jsonRpcPort_ = input.readUInt32();
226 | break;
227 | }
228 | case 32: {
229 |
230 | protocolVersion_ = input.readUInt32();
231 | break;
232 | }
233 | default: {
234 | if (!parseUnknownField(
235 | input, unknownFields, extensionRegistry, tag)) {
236 | done = true;
237 | }
238 | break;
239 | }
240 | }
241 | }
242 | } catch (com.google.protobuf.InvalidProtocolBufferException e) {
243 | throw e.setUnfinishedMessage(this);
244 | } catch (java.io.IOException e) {
245 | throw new com.google.protobuf.InvalidProtocolBufferException(
246 | e).setUnfinishedMessage(this);
247 | } finally {
248 | this.unknownFields = unknownFields.build();
249 | makeExtensionsImmutable();
250 | }
251 | }
252 | public static final com.google.protobuf.Descriptors.Descriptor
253 | getDescriptor() {
254 | return org.nkn.sdk.pb.NodeProto.internal_static_pb_NodeData_descriptor;
255 | }
256 |
257 | @java.lang.Override
258 | protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
259 | internalGetFieldAccessorTable() {
260 | return org.nkn.sdk.pb.NodeProto.internal_static_pb_NodeData_fieldAccessorTable
261 | .ensureFieldAccessorsInitialized(
262 | org.nkn.sdk.pb.NodeProto.NodeData.class, org.nkn.sdk.pb.NodeProto.NodeData.Builder.class);
263 | }
264 |
265 | public static final int PUBLIC_KEY_FIELD_NUMBER = 1;
266 | private com.google.protobuf.ByteString publicKey_;
267 | /**
268 | * bytes public_key = 1;
269 | * @return The publicKey.
270 | */
271 | public com.google.protobuf.ByteString getPublicKey() {
272 | return publicKey_;
273 | }
274 |
275 | public static final int WEBSOCKET_PORT_FIELD_NUMBER = 2;
276 | private int websocketPort_;
277 | /**
278 | * uint32 websocket_port = 2;
279 | * @return The websocketPort.
280 | */
281 | public int getWebsocketPort() {
282 | return websocketPort_;
283 | }
284 |
285 | public static final int JSON_RPC_PORT_FIELD_NUMBER = 3;
286 | private int jsonRpcPort_;
287 | /**
288 | * uint32 json_rpc_port = 3;
289 | * @return The jsonRpcPort.
290 | */
291 | public int getJsonRpcPort() {
292 | return jsonRpcPort_;
293 | }
294 |
295 | public static final int PROTOCOL_VERSION_FIELD_NUMBER = 4;
296 | private int protocolVersion_;
297 | /**
298 | * uint32 protocol_version = 4;
299 | * @return The protocolVersion.
300 | */
301 | public int getProtocolVersion() {
302 | return protocolVersion_;
303 | }
304 |
305 | private byte memoizedIsInitialized = -1;
306 | @java.lang.Override
307 | public final boolean isInitialized() {
308 | byte isInitialized = memoizedIsInitialized;
309 | if (isInitialized == 1) return true;
310 | if (isInitialized == 0) return false;
311 |
312 | memoizedIsInitialized = 1;
313 | return true;
314 | }
315 |
316 | @java.lang.Override
317 | public void writeTo(com.google.protobuf.CodedOutputStream output)
318 | throws java.io.IOException {
319 | if (!publicKey_.isEmpty()) {
320 | output.writeBytes(1, publicKey_);
321 | }
322 | if (websocketPort_ != 0) {
323 | output.writeUInt32(2, websocketPort_);
324 | }
325 | if (jsonRpcPort_ != 0) {
326 | output.writeUInt32(3, jsonRpcPort_);
327 | }
328 | if (protocolVersion_ != 0) {
329 | output.writeUInt32(4, protocolVersion_);
330 | }
331 | unknownFields.writeTo(output);
332 | }
333 |
334 | @java.lang.Override
335 | public int getSerializedSize() {
336 | int size = memoizedSize;
337 | if (size != -1) return size;
338 |
339 | size = 0;
340 | if (!publicKey_.isEmpty()) {
341 | size += com.google.protobuf.CodedOutputStream
342 | .computeBytesSize(1, publicKey_);
343 | }
344 | if (websocketPort_ != 0) {
345 | size += com.google.protobuf.CodedOutputStream
346 | .computeUInt32Size(2, websocketPort_);
347 | }
348 | if (jsonRpcPort_ != 0) {
349 | size += com.google.protobuf.CodedOutputStream
350 | .computeUInt32Size(3, jsonRpcPort_);
351 | }
352 | if (protocolVersion_ != 0) {
353 | size += com.google.protobuf.CodedOutputStream
354 | .computeUInt32Size(4, protocolVersion_);
355 | }
356 | size += unknownFields.getSerializedSize();
357 | memoizedSize = size;
358 | return size;
359 | }
360 |
361 | @java.lang.Override
362 | public boolean equals(final java.lang.Object obj) {
363 | if (obj == this) {
364 | return true;
365 | }
366 | if (!(obj instanceof org.nkn.sdk.pb.NodeProto.NodeData)) {
367 | return super.equals(obj);
368 | }
369 | org.nkn.sdk.pb.NodeProto.NodeData other = (org.nkn.sdk.pb.NodeProto.NodeData) obj;
370 |
371 | if (!getPublicKey()
372 | .equals(other.getPublicKey())) return false;
373 | if (getWebsocketPort()
374 | != other.getWebsocketPort()) return false;
375 | if (getJsonRpcPort()
376 | != other.getJsonRpcPort()) return false;
377 | if (getProtocolVersion()
378 | != other.getProtocolVersion()) return false;
379 | if (!unknownFields.equals(other.unknownFields)) return false;
380 | return true;
381 | }
382 |
383 | @java.lang.Override
384 | public int hashCode() {
385 | if (memoizedHashCode != 0) {
386 | return memoizedHashCode;
387 | }
388 | int hash = 41;
389 | hash = (19 * hash) + getDescriptor().hashCode();
390 | hash = (37 * hash) + PUBLIC_KEY_FIELD_NUMBER;
391 | hash = (53 * hash) + getPublicKey().hashCode();
392 | hash = (37 * hash) + WEBSOCKET_PORT_FIELD_NUMBER;
393 | hash = (53 * hash) + getWebsocketPort();
394 | hash = (37 * hash) + JSON_RPC_PORT_FIELD_NUMBER;
395 | hash = (53 * hash) + getJsonRpcPort();
396 | hash = (37 * hash) + PROTOCOL_VERSION_FIELD_NUMBER;
397 | hash = (53 * hash) + getProtocolVersion();
398 | hash = (29 * hash) + unknownFields.hashCode();
399 | memoizedHashCode = hash;
400 | return hash;
401 | }
402 |
403 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(
404 | java.nio.ByteBuffer data)
405 | throws com.google.protobuf.InvalidProtocolBufferException {
406 | return PARSER.parseFrom(data);
407 | }
408 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(
409 | java.nio.ByteBuffer data,
410 | com.google.protobuf.ExtensionRegistryLite extensionRegistry)
411 | throws com.google.protobuf.InvalidProtocolBufferException {
412 | return PARSER.parseFrom(data, extensionRegistry);
413 | }
414 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(
415 | com.google.protobuf.ByteString data)
416 | throws com.google.protobuf.InvalidProtocolBufferException {
417 | return PARSER.parseFrom(data);
418 | }
419 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(
420 | com.google.protobuf.ByteString data,
421 | com.google.protobuf.ExtensionRegistryLite extensionRegistry)
422 | throws com.google.protobuf.InvalidProtocolBufferException {
423 | return PARSER.parseFrom(data, extensionRegistry);
424 | }
425 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(byte[] data)
426 | throws com.google.protobuf.InvalidProtocolBufferException {
427 | return PARSER.parseFrom(data);
428 | }
429 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(
430 | byte[] data,
431 | com.google.protobuf.ExtensionRegistryLite extensionRegistry)
432 | throws com.google.protobuf.InvalidProtocolBufferException {
433 | return PARSER.parseFrom(data, extensionRegistry);
434 | }
435 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(java.io.InputStream input)
436 | throws java.io.IOException {
437 | return com.google.protobuf.GeneratedMessageV3
438 | .parseWithIOException(PARSER, input);
439 | }
440 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(
441 | java.io.InputStream input,
442 | com.google.protobuf.ExtensionRegistryLite extensionRegistry)
443 | throws java.io.IOException {
444 | return com.google.protobuf.GeneratedMessageV3
445 | .parseWithIOException(PARSER, input, extensionRegistry);
446 | }
447 | public static org.nkn.sdk.pb.NodeProto.NodeData parseDelimitedFrom(java.io.InputStream input)
448 | throws java.io.IOException {
449 | return com.google.protobuf.GeneratedMessageV3
450 | .parseDelimitedWithIOException(PARSER, input);
451 | }
452 | public static org.nkn.sdk.pb.NodeProto.NodeData parseDelimitedFrom(
453 | java.io.InputStream input,
454 | com.google.protobuf.ExtensionRegistryLite extensionRegistry)
455 | throws java.io.IOException {
456 | return com.google.protobuf.GeneratedMessageV3
457 | .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
458 | }
459 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(
460 | com.google.protobuf.CodedInputStream input)
461 | throws java.io.IOException {
462 | return com.google.protobuf.GeneratedMessageV3
463 | .parseWithIOException(PARSER, input);
464 | }
465 | public static org.nkn.sdk.pb.NodeProto.NodeData parseFrom(
466 | com.google.protobuf.CodedInputStream input,
467 | com.google.protobuf.ExtensionRegistryLite extensionRegistry)
468 | throws java.io.IOException {
469 | return com.google.protobuf.GeneratedMessageV3
470 | .parseWithIOException(PARSER, input, extensionRegistry);
471 | }
472 |
473 | @java.lang.Override
474 | public Builder newBuilderForType() { return newBuilder(); }
475 | public static Builder newBuilder() {
476 | return DEFAULT_INSTANCE.toBuilder();
477 | }
478 | public static Builder newBuilder(org.nkn.sdk.pb.NodeProto.NodeData prototype) {
479 | return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
480 | }
481 | @java.lang.Override
482 | public Builder toBuilder() {
483 | return this == DEFAULT_INSTANCE
484 | ? new Builder() : new Builder().mergeFrom(this);
485 | }
486 |
487 | @java.lang.Override
488 | protected Builder newBuilderForType(
489 | com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
490 | Builder builder = new Builder(parent);
491 | return builder;
492 | }
493 | /**
494 | * Protobuf type {@code pb.NodeData}
495 | */
496 | public static final class Builder extends
497 | com.google.protobuf.GeneratedMessageV3.Builder implements
498 | // @@protoc_insertion_point(builder_implements:pb.NodeData)
499 | org.nkn.sdk.pb.NodeProto.NodeDataOrBuilder {
500 | public static final com.google.protobuf.Descriptors.Descriptor
501 | getDescriptor() {
502 | return org.nkn.sdk.pb.NodeProto.internal_static_pb_NodeData_descriptor;
503 | }
504 |
505 | @java.lang.Override
506 | protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
507 | internalGetFieldAccessorTable() {
508 | return org.nkn.sdk.pb.NodeProto.internal_static_pb_NodeData_fieldAccessorTable
509 | .ensureFieldAccessorsInitialized(
510 | org.nkn.sdk.pb.NodeProto.NodeData.class, org.nkn.sdk.pb.NodeProto.NodeData.Builder.class);
511 | }
512 |
513 | // Construct using org.nkn.sdk.pb.NodeProto.NodeData.newBuilder()
514 | private Builder() {
515 | maybeForceBuilderInitialization();
516 | }
517 |
518 | private Builder(
519 | com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
520 | super(parent);
521 | maybeForceBuilderInitialization();
522 | }
523 | private void maybeForceBuilderInitialization() {
524 | if (com.google.protobuf.GeneratedMessageV3
525 | .alwaysUseFieldBuilders) {
526 | }
527 | }
528 | @java.lang.Override
529 | public Builder clear() {
530 | super.clear();
531 | publicKey_ = com.google.protobuf.ByteString.EMPTY;
532 |
533 | websocketPort_ = 0;
534 |
535 | jsonRpcPort_ = 0;
536 |
537 | protocolVersion_ = 0;
538 |
539 | return this;
540 | }
541 |
542 | @java.lang.Override
543 | public com.google.protobuf.Descriptors.Descriptor
544 | getDescriptorForType() {
545 | return org.nkn.sdk.pb.NodeProto.internal_static_pb_NodeData_descriptor;
546 | }
547 |
548 | @java.lang.Override
549 | public org.nkn.sdk.pb.NodeProto.NodeData getDefaultInstanceForType() {
550 | return org.nkn.sdk.pb.NodeProto.NodeData.getDefaultInstance();
551 | }
552 |
553 | @java.lang.Override
554 | public org.nkn.sdk.pb.NodeProto.NodeData build() {
555 | org.nkn.sdk.pb.NodeProto.NodeData result = buildPartial();
556 | if (!result.isInitialized()) {
557 | throw newUninitializedMessageException(result);
558 | }
559 | return result;
560 | }
561 |
562 | @java.lang.Override
563 | public org.nkn.sdk.pb.NodeProto.NodeData buildPartial() {
564 | org.nkn.sdk.pb.NodeProto.NodeData result = new org.nkn.sdk.pb.NodeProto.NodeData(this);
565 | result.publicKey_ = publicKey_;
566 | result.websocketPort_ = websocketPort_;
567 | result.jsonRpcPort_ = jsonRpcPort_;
568 | result.protocolVersion_ = protocolVersion_;
569 | onBuilt();
570 | return result;
571 | }
572 |
573 | @java.lang.Override
574 | public Builder clone() {
575 | return super.clone();
576 | }
577 | @java.lang.Override
578 | public Builder setField(
579 | com.google.protobuf.Descriptors.FieldDescriptor field,
580 | java.lang.Object value) {
581 | return super.setField(field, value);
582 | }
583 | @java.lang.Override
584 | public Builder clearField(
585 | com.google.protobuf.Descriptors.FieldDescriptor field) {
586 | return super.clearField(field);
587 | }
588 | @java.lang.Override
589 | public Builder clearOneof(
590 | com.google.protobuf.Descriptors.OneofDescriptor oneof) {
591 | return super.clearOneof(oneof);
592 | }
593 | @java.lang.Override
594 | public Builder setRepeatedField(
595 | com.google.protobuf.Descriptors.FieldDescriptor field,
596 | int index, java.lang.Object value) {
597 | return super.setRepeatedField(field, index, value);
598 | }
599 | @java.lang.Override
600 | public Builder addRepeatedField(
601 | com.google.protobuf.Descriptors.FieldDescriptor field,
602 | java.lang.Object value) {
603 | return super.addRepeatedField(field, value);
604 | }
605 | @java.lang.Override
606 | public Builder mergeFrom(com.google.protobuf.Message other) {
607 | if (other instanceof org.nkn.sdk.pb.NodeProto.NodeData) {
608 | return mergeFrom((org.nkn.sdk.pb.NodeProto.NodeData)other);
609 | } else {
610 | super.mergeFrom(other);
611 | return this;
612 | }
613 | }
614 |
615 | public Builder mergeFrom(org.nkn.sdk.pb.NodeProto.NodeData other) {
616 | if (other == org.nkn.sdk.pb.NodeProto.NodeData.getDefaultInstance()) return this;
617 | if (other.getPublicKey() != com.google.protobuf.ByteString.EMPTY) {
618 | setPublicKey(other.getPublicKey());
619 | }
620 | if (other.getWebsocketPort() != 0) {
621 | setWebsocketPort(other.getWebsocketPort());
622 | }
623 | if (other.getJsonRpcPort() != 0) {
624 | setJsonRpcPort(other.getJsonRpcPort());
625 | }
626 | if (other.getProtocolVersion() != 0) {
627 | setProtocolVersion(other.getProtocolVersion());
628 | }
629 | this.mergeUnknownFields(other.unknownFields);
630 | onChanged();
631 | return this;
632 | }
633 |
634 | @java.lang.Override
635 | public final boolean isInitialized() {
636 | return true;
637 | }
638 |
639 | @java.lang.Override
640 | public Builder mergeFrom(
641 | com.google.protobuf.CodedInputStream input,
642 | com.google.protobuf.ExtensionRegistryLite extensionRegistry)
643 | throws java.io.IOException {
644 | org.nkn.sdk.pb.NodeProto.NodeData parsedMessage = null;
645 | try {
646 | parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
647 | } catch (com.google.protobuf.InvalidProtocolBufferException e) {
648 | parsedMessage = (org.nkn.sdk.pb.NodeProto.NodeData) e.getUnfinishedMessage();
649 | throw e.unwrapIOException();
650 | } finally {
651 | if (parsedMessage != null) {
652 | mergeFrom(parsedMessage);
653 | }
654 | }
655 | return this;
656 | }
657 |
658 | private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY;
659 | /**
660 | * bytes public_key = 1;
661 | * @return The publicKey.
662 | */
663 | public com.google.protobuf.ByteString getPublicKey() {
664 | return publicKey_;
665 | }
666 | /**
667 | * bytes public_key = 1;
668 | * @param value The publicKey to set.
669 | * @return This builder for chaining.
670 | */
671 | public Builder setPublicKey(com.google.protobuf.ByteString value) {
672 | if (value == null) {
673 | throw new NullPointerException();
674 | }
675 |
676 | publicKey_ = value;
677 | onChanged();
678 | return this;
679 | }
680 | /**
681 | * bytes public_key = 1;
682 | * @return This builder for chaining.
683 | */
684 | public Builder clearPublicKey() {
685 |
686 | publicKey_ = getDefaultInstance().getPublicKey();
687 | onChanged();
688 | return this;
689 | }
690 |
691 | private int websocketPort_ ;
692 | /**
693 | * uint32 websocket_port = 2;
694 | * @return The websocketPort.
695 | */
696 | public int getWebsocketPort() {
697 | return websocketPort_;
698 | }
699 | /**
700 | * uint32 websocket_port = 2;
701 | * @param value The websocketPort to set.
702 | * @return This builder for chaining.
703 | */
704 | public Builder setWebsocketPort(int value) {
705 |
706 | websocketPort_ = value;
707 | onChanged();
708 | return this;
709 | }
710 | /**
711 | * uint32 websocket_port = 2;
712 | * @return This builder for chaining.
713 | */
714 | public Builder clearWebsocketPort() {
715 |
716 | websocketPort_ = 0;
717 | onChanged();
718 | return this;
719 | }
720 |
721 | private int jsonRpcPort_ ;
722 | /**
723 | * uint32 json_rpc_port = 3;
724 | * @return The jsonRpcPort.
725 | */
726 | public int getJsonRpcPort() {
727 | return jsonRpcPort_;
728 | }
729 | /**
730 | * uint32 json_rpc_port = 3;
731 | * @param value The jsonRpcPort to set.
732 | * @return This builder for chaining.
733 | */
734 | public Builder setJsonRpcPort(int value) {
735 |
736 | jsonRpcPort_ = value;
737 | onChanged();
738 | return this;
739 | }
740 | /**
741 | * uint32 json_rpc_port = 3;
742 | * @return This builder for chaining.
743 | */
744 | public Builder clearJsonRpcPort() {
745 |
746 | jsonRpcPort_ = 0;
747 | onChanged();
748 | return this;
749 | }
750 |
751 | private int protocolVersion_ ;
752 | /**
753 | * uint32 protocol_version = 4;
754 | * @return The protocolVersion.
755 | */
756 | public int getProtocolVersion() {
757 | return protocolVersion_;
758 | }
759 | /**
760 | * uint32 protocol_version = 4;
761 | * @param value The protocolVersion to set.
762 | * @return This builder for chaining.
763 | */
764 | public Builder setProtocolVersion(int value) {
765 |
766 | protocolVersion_ = value;
767 | onChanged();
768 | return this;
769 | }
770 | /**
771 | * uint32 protocol_version = 4;
772 | * @return This builder for chaining.
773 | */
774 | public Builder clearProtocolVersion() {
775 |
776 | protocolVersion_ = 0;
777 | onChanged();
778 | return this;
779 | }
780 | @java.lang.Override
781 | public final Builder setUnknownFields(
782 | final com.google.protobuf.UnknownFieldSet unknownFields) {
783 | return super.setUnknownFields(unknownFields);
784 | }
785 |
786 | @java.lang.Override
787 | public final Builder mergeUnknownFields(
788 | final com.google.protobuf.UnknownFieldSet unknownFields) {
789 | return super.mergeUnknownFields(unknownFields);
790 | }
791 |
792 |
793 | // @@protoc_insertion_point(builder_scope:pb.NodeData)
794 | }
795 |
796 | // @@protoc_insertion_point(class_scope:pb.NodeData)
797 | private static final org.nkn.sdk.pb.NodeProto.NodeData DEFAULT_INSTANCE;
798 | static {
799 | DEFAULT_INSTANCE = new org.nkn.sdk.pb.NodeProto.NodeData();
800 | }
801 |
802 | public static org.nkn.sdk.pb.NodeProto.NodeData getDefaultInstance() {
803 | return DEFAULT_INSTANCE;
804 | }
805 |
806 | private static final com.google.protobuf.Parser
807 | PARSER = new com.google.protobuf.AbstractParser() {
808 | @java.lang.Override
809 | public NodeData parsePartialFrom(
810 | com.google.protobuf.CodedInputStream input,
811 | com.google.protobuf.ExtensionRegistryLite extensionRegistry)
812 | throws com.google.protobuf.InvalidProtocolBufferException {
813 | return new NodeData(input, extensionRegistry);
814 | }
815 | };
816 |
817 | public static com.google.protobuf.Parser parser() {
818 | return PARSER;
819 | }
820 |
821 | @java.lang.Override
822 | public com.google.protobuf.Parser getParserForType() {
823 | return PARSER;
824 | }
825 |
826 | @java.lang.Override
827 | public org.nkn.sdk.pb.NodeProto.NodeData getDefaultInstanceForType() {
828 | return DEFAULT_INSTANCE;
829 | }
830 |
831 | }
832 |
833 | private static final com.google.protobuf.Descriptors.Descriptor
834 | internal_static_pb_NodeData_descriptor;
835 | private static final
836 | com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
837 | internal_static_pb_NodeData_fieldAccessorTable;
838 |
839 | public static com.google.protobuf.Descriptors.FileDescriptor
840 | getDescriptor() {
841 | return descriptor;
842 | }
843 | private static com.google.protobuf.Descriptors.FileDescriptor
844 | descriptor;
845 | static {
846 | java.lang.String[] descriptorData = {
847 | "\n\rpb/node.proto\022\002pb\"g\n\010NodeData\022\022\n\npubli" +
848 | "c_key\030\001 \001(\014\022\026\n\016websocket_port\030\002 \001(\r\022\025\n\rj" +
849 | "son_rpc_port\030\003 \001(\r\022\030\n\020protocol_version\030\004" +
850 | " \001(\r*\\\n\tSyncState\022\024\n\020WAIT_FOR_SYNCING\020\000\022" +
851 | "\020\n\014SYNC_STARTED\020\001\022\021\n\rSYNC_FINISHED\020\002\022\024\n\020" +
852 | "PERSIST_FINISHED\020\003B\033\n\016org.nkn.sdk.pbB\tNo" +
853 | "deProtob\006proto3"
854 | };
855 | descriptor = com.google.protobuf.Descriptors.FileDescriptor
856 | .internalBuildGeneratedFileFrom(descriptorData,
857 | new com.google.protobuf.Descriptors.FileDescriptor[] {
858 | });
859 | internal_static_pb_NodeData_descriptor =
860 | getDescriptor().getMessageTypes().get(0);
861 | internal_static_pb_NodeData_fieldAccessorTable = new
862 | com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
863 | internal_static_pb_NodeData_descriptor,
864 | new java.lang.String[] { "PublicKey", "WebsocketPort", "JsonRpcPort", "ProtocolVersion", });
865 | }
866 |
867 | // @@protoc_insertion_point(outer_class_scope)
868 | }
869 |
--------------------------------------------------------------------------------