diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 87e8655f..3c9ae908 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ graphql-multiplatform = "0.1.0-beta07" [libraries] bcs = { module = "xyz.mcxross.bcs:bcs", version = "0.1.2" } +bitcoinj-core = { module = "org.bitcoinj:bitcoinj-core", version = "0.16.1" } graphql-multiplatform-client = { module = "xyz.mcxross.graphql.client:graphql-multiplatform-client", version.ref = "graphql-multiplatform" } junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index ed79bc02..8e843e55 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -62,8 +62,19 @@ kotlin { applyDefaultHierarchyTemplate() sourceSets { + val androidJvmMain by creating { + dependsOn(commonMain.get()) + dependencies { + implementation(libs.bitcoinj.core) + } + } appleMain.dependencies { implementation(libs.ktor.client.darwin) } - androidMain.dependencies { implementation(libs.ktor.client.okhttp) } + val androidMain by getting { + dependsOn(androidJvmMain) + dependencies { + implementation(libs.ktor.client.okhttp) + } + } commonMain.dependencies { implementation(libs.ktor.client.core) implementation(libs.ktor.client.content.negotiation) @@ -79,7 +90,12 @@ kotlin { implementation(libs.kotlin.test) } jsMain.dependencies { implementation(libs.ktor.client.js) } - jvmMain.dependencies { implementation(libs.ktor.client.cio) } + val jvmMain by getting { + dependsOn(androidJvmMain) + dependencies { + implementation(libs.ktor.client.cio) + } + } linuxMain.dependencies { implementation(libs.ktor.client.curl) } mingwMain.dependencies { implementation(libs.ktor.client.winhttp) } } @@ -136,7 +152,7 @@ mavenPublishing { javadocJar = JavadocJar.Dokka("dokkaHtml"), sourcesJar = true, androidVariantsToPublish = listOf("debug", "release"), - ) + ), ) pom { diff --git a/lib/src/androidJvmMain/kotlin/xyz/mcxross/ksui/core/crypto/ED25519KeyDerive.kt b/lib/src/androidJvmMain/kotlin/xyz/mcxross/ksui/core/crypto/ED25519KeyDerive.kt new file mode 100644 index 00000000..6a865ce1 --- /dev/null +++ b/lib/src/androidJvmMain/kotlin/xyz/mcxross/ksui/core/crypto/ED25519KeyDerive.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +import com.google.common.primitives.Bytes +import java.nio.ByteBuffer +import java.nio.charset.Charset +import java.util.* +import org.bitcoinj.crypto.ChildNumber +import org.bitcoinj.crypto.HDPath +import org.bitcoinj.crypto.HDUtils + +class ED25519KeyDerive(val key: ByteArray, val chaincode: ByteArray) { + + fun derive(index: Int): ED25519KeyDerive { + if (!hasHardenedBit(index)) { + // todo: create an exception + throw RuntimeException() + } + + val indexBytes = ByteArray(4) + ByteBuffer.wrap(indexBytes).putInt(index) + + val data = Bytes.concat(byteArrayOf(0x00), this.key, indexBytes) + + val i = HDUtils.hmacSha512(this.chaincode, data) + val il = Arrays.copyOfRange(i, 0, 32) + val ir = Arrays.copyOfRange(i, 32, 64) + + return ED25519KeyDerive(il, ir) + } + + fun deriveFromPath(path: String = DEFAULT_DERIVE_PATH): ED25519KeyDerive { + require(path.isNotBlank()) { "Path cannot be blank" } + val hdPath = HDPath.parsePath(path) + val it: Iterator = hdPath.iterator() + var current = this + while (it.hasNext()) { + current = current.derive(it.next().i) + } + return current + } + + private fun hasHardenedBit(a: Int): Boolean { + return (a and ChildNumber.HARDENED_BIT) != 0 + } + + companion object { + private const val DEFAULT_DERIVE_PATH = "m/44H/784H/0H/0H/0H" + + fun createKeyByDefaultPath(seed: ByteArray): ED25519KeyDerive { + return createMasterKey(seed).deriveFromPath() + } + + fun createMasterKey(seed: ByteArray): ED25519KeyDerive { + val i = HDUtils.hmacSha512("ed25519 seed".toByteArray(Charset.defaultCharset()), seed) + val il = Arrays.copyOfRange(i, 0, 32) + val ir = Arrays.copyOfRange(i, 32, 64) + return ED25519KeyDerive(il, ir) + } + } +} diff --git a/lib/src/androidJvmMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.androidjvm.kt b/lib/src/androidJvmMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.androidjvm.kt new file mode 100644 index 00000000..774e8f81 --- /dev/null +++ b/lib/src/androidJvmMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.androidjvm.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +import java.security.SecureRandom +import java.util.ArrayList +import org.bitcoinj.crypto.MnemonicCode +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters +import org.bouncycastle.jcajce.provider.digest.Blake2b +import xyz.mcxross.ksui.exception.SignatureSchemeNotSupportedException + +actual fun hash(hash: Hash, data: ByteArray): ByteArray { + return when (hash) { + Hash.BLAKE2B256 -> Blake2b.Blake2b256().digest(data) + } +} + +actual fun generateMnemonic(): String { + val secureRandom = SecureRandom() + val entropy = ByteArray(16) + secureRandom.nextBytes(entropy) + var mnemonic: List = ArrayList() + + try { + mnemonic = MnemonicCode.INSTANCE.toMnemonic(entropy) + } catch (e: java.lang.Exception) { + // MnemonicLengthException won't happen + } + return mnemonic.joinToString(" ") +} + +actual fun generateSeed(mnemonic: List): ByteArray { + return MnemonicCode.toSeed(mnemonic, "") +} + +@Throws(SignatureSchemeNotSupportedException::class) +actual fun generateKeyPair(seed: ByteArray, scheme: SignatureScheme): KeyPair { + return when (scheme) { + SignatureScheme.ED25519 -> { + val key: ED25519KeyDerive = ED25519KeyDerive.createKeyByDefaultPath(seed) + val parameters = Ed25519PrivateKeyParameters(key.key) + val publicKeyParameters = parameters.generatePublicKey() + KeyPair(parameters.encoded, publicKeyParameters.encoded) + } + else -> throw SignatureSchemeNotSupportedException() + } +} + +actual fun derivePublicKey(privateKey: PrivateKey, schema: SignatureScheme): PublicKey { + return when (schema) { + SignatureScheme.ED25519 -> { + val privateKeyParameters = Ed25519PrivateKeyParameters(privateKey.data) + val publicKeyParameters = privateKeyParameters.generatePublicKey() + Ed25519PublicKey(publicKeyParameters.encoded) + } + else -> throw SignatureSchemeNotSupportedException() + } +} + +@Throws(SignatureSchemeNotSupportedException::class) +actual fun importFromMnemonic(mnemonic: String): KeyPair { + return importFromMnemonic(mnemonic.split(" ")) +} + +actual fun importFromMnemonic(mnemonic: List): KeyPair { + val seed = MnemonicCode.toSeed(mnemonic, "") + return generateKeyPair(seed, SignatureScheme.ED25519) +} diff --git a/lib/src/appleMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.apple.kt b/lib/src/appleMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.apple.kt new file mode 100644 index 00000000..268f319e --- /dev/null +++ b/lib/src/appleMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.apple.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +import xyz.mcxross.ksui.exception.SignatureSchemeNotSupportedException + +actual fun hash(hash: Hash, data: ByteArray): ByteArray { + TODO("Not yet implemented") +} + +actual fun generateMnemonic(): String { + TODO("Not yet implemented") +} + +actual fun generateSeed(mnemonic: List): ByteArray { + TODO("Not yet implemented") +} + +@Throws(SignatureSchemeNotSupportedException::class) +actual fun generateKeyPair( + seed: ByteArray, + scheme: SignatureScheme +): KeyPair { + TODO("Not yet implemented") +} + +actual fun derivePublicKey( + privateKey: PrivateKey, + schema: SignatureScheme +): PublicKey { + TODO("Not yet implemented") +} + +actual fun importFromMnemonic(mnemonic: String): KeyPair { + TODO("Not yet implemented") +} + +actual fun importFromMnemonic(mnemonic: List): KeyPair { + TODO("Not yet implemented") +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/account/Account.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/account/Account.kt new file mode 100644 index 00000000..5835d267 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/account/Account.kt @@ -0,0 +1,125 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.account + +import xyz.mcxross.ksui.core.crypto.Ed25519PrivateKey +import xyz.mcxross.ksui.core.crypto.PublicKey +import xyz.mcxross.ksui.core.crypto.SignatureScheme +import xyz.mcxross.ksui.core.crypto.importFromMnemonic +import xyz.mcxross.ksui.exception.SignatureSchemeNotSupportedException +import xyz.mcxross.ksui.model.AccountAddress + +/** + * This file defines the `Account` abstract class and its companion object, which provides methods + * for creating and importing accounts using different signature schemes. + * + * The `[Account]` class has the following abstract properties: + * - `[publicKey]`: The public key of the account. + * - `[address]`: The account address. + * - `[scheme]`: The signature scheme used by the account. + * + * The companion object provides the following methods: + * - `create(scheme: SignatureScheme = SignatureScheme.ED25519)`: Creates a new account using the + * specified signature scheme. Defaults to ED25519. + * - `import(privateKey: ByteArray, scheme: SignatureScheme = SignatureScheme.ED25519)`: Imports an + * account using the provided private key and signature scheme. Defaults to ED25519. + * - `import(phrase: String, scheme: SignatureScheme = SignatureScheme.ED25519)`: Imports an account + * using the provided mnemonic phrase and signature scheme. Defaults to ED25519. + * - `import(phrases: List, scheme: SignatureScheme = SignatureScheme.ED25519)`: Imports an + * account using the provided list of mnemonic phrases and signature scheme. Defaults to ED25519. + * + * The `[create]` and `[import]` methods throw a `[SignatureSchemeNotSupportedException]` if the + * specified signature scheme is not supported. + */ +abstract class Account { + + abstract val publicKey: PublicKey + + abstract val address: AccountAddress + + abstract val scheme: SignatureScheme + + companion object { + + /** + * Creates a new account using the specified signature scheme. + * + * @param scheme The signature scheme to use. Defaults to ED25519. + * @return The new account. + * @throws SignatureSchemeNotSupportedException If the specified signature scheme is not + * supported. + */ + fun create(scheme: SignatureScheme = SignatureScheme.ED25519): Account { + return when (scheme) { + SignatureScheme.ED25519 -> Ed25519Account.generate() + else -> throw SignatureSchemeNotSupportedException() + } + } + + /** + * Imports an account using the provided private key and signature scheme. + * + * @param privateKey The private key of the account. + * @param scheme The signature scheme to use. Defaults to ED25519. + * @return The imported account. + * @throws SignatureSchemeNotSupportedException If the specified signature scheme is not + * supported. + */ + fun import(privateKey: ByteArray, scheme: SignatureScheme = SignatureScheme.ED25519): Account { + return when (scheme) { + SignatureScheme.ED25519 -> Ed25519Account(Ed25519PrivateKey(privateKey)) + else -> throw SignatureSchemeNotSupportedException() + } + } + + /** + * Imports an account using the provided mnemonic phrase and signature scheme. + * + * @param phrase The mnemonic phrase of the account. + * @param scheme The signature scheme to use. Defaults to ED25519. + * @return The imported account. + * @throws SignatureSchemeNotSupportedException If the specified signature scheme is not + * supported. + */ + fun import(phrase: String, scheme: SignatureScheme = SignatureScheme.ED25519): Account { + return when (scheme) { + SignatureScheme.ED25519 -> { + val keyPair = importFromMnemonic(phrase) + Ed25519Account(Ed25519PrivateKey(keyPair.privateKey), phrase) + } + else -> throw SignatureSchemeNotSupportedException() + } + } + + /** + * Imports an account using the provided list of mnemonic phrases and signature scheme. + * + * @param phrases The list of mnemonic phrases of the account. + * @param scheme The signature scheme to use. Defaults to ED25519. + * @return The imported account. + * @throws SignatureSchemeNotSupportedException If the specified signature scheme is not + * supported. + */ + fun import(phrases: List, scheme: SignatureScheme = SignatureScheme.ED25519): Account { + return when (scheme) { + SignatureScheme.ED25519 -> { + import(phrases.joinToString(" ")) + } + else -> throw SignatureSchemeNotSupportedException() + } + } + } +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/account/Ed25519Account.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/account/Ed25519Account.kt new file mode 100644 index 00000000..bcd3df46 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/account/Ed25519Account.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.account + +import xyz.mcxross.ksui.core.crypto.Ed25519PrivateKey +import xyz.mcxross.ksui.core.crypto.Ed25519PublicKey +import xyz.mcxross.ksui.core.crypto.SignatureScheme +import xyz.mcxross.ksui.core.crypto.generateKeyPair +import xyz.mcxross.ksui.core.crypto.generateMnemonic +import xyz.mcxross.ksui.core.crypto.generateSeed +import xyz.mcxross.ksui.model.AccountAddress + +/** + * This file defines the `Ed25519Account` class, which extends the `Account` abstract class and + * provides specific implementations for the Ed25519 signature scheme. + * + * The `Ed25519Account` class has the following properties: + * - `privateKey`: The private key of the account. + * - `mnemonic`: A string representing the mnemonic phrase associated with the account. + * - `publicKey`: The public key of the account, derived from the private key. + * - `address`: The account address, derived from the public key and the signature scheme. + * - `scheme`: The signature scheme used by the account, which is Ed25519. + * + * The class provides the following methods: + * - `toString()`: Returns a string representation of the account, including the mnemonic and + * address. + * + * The companion object provides the following methods: + * - `generate()`: Generates a new `Ed25519Account` using a randomly generated mnemonic phrase and + * seed. + */ +class Ed25519Account(private val privateKey: Ed25519PrivateKey) : Account() { + + var mnemonic: String = "" + + override val publicKey: Ed25519PublicKey + get() = privateKey.publicKey + + override val address: AccountAddress + get() = AccountAddress.from(byteArrayOf(scheme.scheme) + publicKey.data) + + override val scheme: SignatureScheme + get() = SignatureScheme.ED25519 + + constructor(privateKey: Ed25519PrivateKey, mnemonic: String) : this(privateKey) { + this.mnemonic = mnemonic + } + + override fun toString(): String { + return "Ed25519Account{mnemonic=$mnemonic, address=$address}" + } + + companion object { + + /** + * Generates a new `Ed25519Account` using a randomly generated mnemonic phrase and seed. + * + * @return The new `Ed25519Account`. + */ + fun generate(): Ed25519Account { + val seedPhrase = generateMnemonic().split(" ") + val seed = generateSeed(seedPhrase) + val keyPair = generateKeyPair(seed, SignatureScheme.ED25519) + return Ed25519Account(Ed25519PrivateKey(keyPair.privateKey), seedPhrase.joinToString(" ")) + } + } +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/Hex.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/Hex.kt new file mode 100644 index 00000000..58029874 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/Hex.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core + +import kotlinx.serialization.Serializable +import xyz.mcxross.ksui.exception.ParsingException + +/** This enum is used to explain why parsing might have failed. */ +enum class HexInvalidReason(val reason: String) { + TOO_SHORT("Too Short"), + INVALID_LENGTH("Invalid Length"), + INVALID_HEX_CHARS("Invalid Hex Chars"), +} + +/** + * This class is used to represent a hex string. + * + * It is used for working with hex strings. Hex strings, when represented as a string, generally + * look like these examples: + * - 0x1 + * - 0xaa86fe99004361f747f91342ca13c426ca0cccb0c1217677180c9493bad6ef0c + * + * @constructor Creates a hex string from a string. + * @property data The data of the hex string. + */ +@Serializable +class Hex() { + private var data: ByteArray = byteArrayOf() + + /** + * This constructor is used to create a new Hex from a byte array. + * + * @param data The byte array to create the hex from. + */ + constructor(data: ByteArray) : this() { + this.data = data + } + + fun toByteArray(): ByteArray { + return data + } + + fun toStringWithoutPrefix(): String { + return data.joinToString("") { it.toUByte().toString(16).padStart(2, '0') } + } + + override fun toString(): String { + return "0x${toStringWithoutPrefix()}" + } + + companion object { + + fun empty(): Hex { + return Hex() + } + + fun fromString(hex: String): Hex { + val hexString = hex.removePrefix("0x") + if (hexString.length % 2 != 0) { + throw ParsingException(HexInvalidReason.INVALID_LENGTH.reason) + } + return Hex(hexString.chunked(2).map { it.toInt(16).toByte() }.toByteArray()) + } + + fun fromHexInput(byteArray: ByteArray): Hex { + return Hex(byteArray) + } + } +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/Ed25519.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/Ed25519.kt new file mode 100644 index 00000000..237e5f35 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/Ed25519.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +import xyz.mcxross.ksui.core.Hex + + +class Ed25519PrivateKey(private val privateKey: ByteArray) : PrivateKey { + + override val data: ByteArray + get() = privateKey + + override val publicKey: Ed25519PublicKey + get() = derivePublicKey(this, SignatureScheme.ED25519) as Ed25519PublicKey +} + +class Ed25519PublicKey(val data: ByteArray) : PublicKey { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as Ed25519PublicKey + + return data.contentEquals(other.data) + } + + override fun hashCode(): Int { + return data.contentHashCode() + } + + override fun toString(): String { + return Hex(hash(Hash.BLAKE2B256, data)).toString() + } +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/Hash.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/Hash.kt new file mode 100644 index 00000000..c2fa8d8c --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/Hash.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +enum class Hash { + BLAKE2B256, +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/KeyPair.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/KeyPair.kt new file mode 100644 index 00000000..e2a5f29d --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/KeyPair.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +/** + * Represents a pair of signing keys: a public key and a private key. + * + * @property privateKey The public key in hex format. + * @property publicKey The private key in hex format. + */ +data class KeyPair(val privateKey: ByteArray, val publicKey: ByteArray) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as KeyPair + + if (!privateKey.contentEquals(other.privateKey)) return false + if (!publicKey.contentEquals(other.publicKey)) return false + + return true + } + + override fun hashCode(): Int { + var result = privateKey.contentHashCode() + result = 31 * result + publicKey.contentHashCode() + return result + } +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.kt new file mode 100644 index 00000000..49c24da9 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +expect fun hash(hash: Hash, data: ByteArray): ByteArray + +expect fun generateMnemonic(): String + +expect fun generateSeed(mnemonic: List): ByteArray + +expect fun generateKeyPair(seed: ByteArray, scheme: SignatureScheme): KeyPair + +expect fun derivePublicKey(privateKey: PrivateKey, schema: SignatureScheme): PublicKey + +expect fun importFromMnemonic(mnemonic: String): KeyPair + +expect fun importFromMnemonic(mnemonic: List): KeyPair diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/PrivateKey.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/PrivateKey.kt new file mode 100644 index 00000000..4eafa7a4 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/PrivateKey.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +interface PrivateKey { + val data: ByteArray + val publicKey: PublicKey +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/PublicKey.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/PublicKey.kt new file mode 100644 index 00000000..64ab90e5 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/PublicKey.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +interface PublicKey diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/SignatureScheme.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/SignatureScheme.kt new file mode 100644 index 00000000..76e6a0d8 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/core/crypto/SignatureScheme.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +enum class SignatureScheme(val scheme: Byte) { + ED25519(0x00.toByte()), + Secp256k1(0x01.toByte()), + Secp256r1(0x02.toByte()), + BLS12381(0xff.toByte()); + + companion object { + private val BY_SCHEME: MutableMap = HashMap() + + init { + for (e in entries) { + BY_SCHEME[e.scheme] = e + } + } + + fun valueOf(scheme: Byte): SignatureScheme? { + return BY_SCHEME[scheme] + } + } +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/exception/ParsingException.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/exception/ParsingException.kt new file mode 100644 index 00000000..ef904dae --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/exception/ParsingException.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.exception + +/** This error is used to explain why parsing failed. */ +open class ParsingException(message: String) : Exception(message) diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/exception/SignatureSchemeNotSupportedException.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/exception/SignatureSchemeNotSupportedException.kt new file mode 100644 index 00000000..19fbba90 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/exception/SignatureSchemeNotSupportedException.kt @@ -0,0 +1,5 @@ +package xyz.mcxross.ksui.exception + +class SignatureSchemeNotSupportedException +/** Instantiates a new Signature scheme not supported exception. */ +: RuntimeException("only ed25519 and secp256k1 signature scheme supported.") diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/exception/SigningException.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/exception/SigningException.kt new file mode 100644 index 00000000..96bd9996 --- /dev/null +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/exception/SigningException.kt @@ -0,0 +1,7 @@ +package xyz.mcxross.ksui.exception + +class SigningException : RuntimeException { + constructor(cause: Throwable?) : super(cause) + + constructor(message: String?) : super(message) +} diff --git a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/model/AccountAddress.kt b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/model/AccountAddress.kt index cb2faf81..ac832760 100644 --- a/lib/src/commonMain/kotlin/xyz/mcxross/ksui/model/AccountAddress.kt +++ b/lib/src/commonMain/kotlin/xyz/mcxross/ksui/model/AccountAddress.kt @@ -16,6 +16,9 @@ package xyz.mcxross.ksui.model import kotlinx.serialization.Serializable +import xyz.mcxross.ksui.core.Hex +import xyz.mcxross.ksui.core.crypto.Hash +import xyz.mcxross.ksui.core.crypto.hash import xyz.mcxross.ksui.serializer.SuiAddressSerializer /** @@ -40,7 +43,7 @@ data class AccountAddress(@Serializable(with = SuiAddressSerializer::class) val } override fun toString(): String { - return data.toString() + return Hex(hash(Hash.BLAKE2B256, data)).toString() } companion object { @@ -49,6 +52,11 @@ data class AccountAddress(@Serializable(with = SuiAddressSerializer::class) val val EMPTY = AccountAddress(ByteArray(LENGTH)) + fun from(data: ByteArray): AccountAddress { + require(data.size == LENGTH + 1) { "Address must be $LENGTH bytes long, but was ${data.size}" } + return AccountAddress(data) + } + fun fromString(s: String): AccountAddress { return try { fromHexLiteral(s) diff --git a/lib/src/commonTest/kotlin/xyz.mcxross.ksui/unit/AccountTest.kt b/lib/src/commonTest/kotlin/xyz.mcxross.ksui/unit/AccountTest.kt new file mode 100644 index 00000000..c7ec8b13 --- /dev/null +++ b/lib/src/commonTest/kotlin/xyz.mcxross.ksui/unit/AccountTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.unit + +import kotlin.test.Test +import kotlin.test.assertTrue +import xyz.mcxross.ksui.account.Account +import xyz.mcxross.ksui.account.Ed25519Account +import xyz.mcxross.ksui.core.crypto.Ed25519PublicKey +import xyz.mcxross.ksui.core.crypto.SignatureScheme + +class AccountTest { + + // Test account generation + @Test + fun testAccountGeneration() { + val account = Account.create() + assertTrue( + "Account generation failed. Default Account signature scheme must be SignatureScheme.ED25519" + ) { + account.scheme == SignatureScheme.ED25519 + } + assertTrue("Account generation failed. Default public key must be of type Ed25519PublicKey") { + account.publicKey is Ed25519PublicKey + } + assertTrue("Account generation failed. Mnemonic can't be empty.") { + (account as Ed25519Account).mnemonic.isNotEmpty() + } + assertTrue("Account generation failed. Default word length expected to be 12 words.") { + (account as Ed25519Account).mnemonic.split(" ").size == 12 + } + assertTrue("Account generation failed. Address can't be empty.") { + (account as Ed25519Account).address.toString().isNotEmpty() + } + assertTrue("Account generation failed. Invalid address length.") { + account.address.toString().length == 66 + } + } + + @Test + fun testAccountImport() { + val account = + Account.import("dry clock defense build educate lonely cycle hand phrase kitchen enemy seed") + assertTrue( + "Account import failed. Default Account signature scheme must be SignatureScheme.ED25519" + ) { + account.scheme == SignatureScheme.ED25519 + } + assertTrue("Account import failed. Default public key must be of type Ed25519PublicKey") { + account.publicKey is Ed25519PublicKey + } + assertTrue("Account import failed. Mnemonic can't be empty.") { + (account as Ed25519Account).mnemonic.isNotEmpty() + } + assertTrue("Account import failed. Default word length expected to be 12 words.") { + (account as Ed25519Account).mnemonic.split(" ").size == 12 + } + assertTrue("Account import failed. Address can't be empty.") { + account.address.toString().isNotEmpty() + } + assertTrue("Account import failed. Invalid address length.") { + account.address.toString().length == 66 + } + } +} diff --git a/lib/src/jsMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.js.kt b/lib/src/jsMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.js.kt new file mode 100644 index 00000000..e37af2d6 --- /dev/null +++ b/lib/src/jsMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.js.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +actual fun hash(hash: Hash, data: ByteArray): ByteArray { + TODO("Not yet implemented") +} + +actual fun generateMnemonic(): String { + TODO("Not yet implemented") +} + +actual fun generateSeed(mnemonic: List): ByteArray { + TODO("Not yet implemented") +} + +actual fun generateKeyPair(seed: ByteArray, scheme: SignatureScheme): KeyPair { + TODO("Not yet implemented") +} + +actual fun derivePublicKey(privateKey: PrivateKey, schema: SignatureScheme): PublicKey { + TODO("Not yet implemented") +} + +actual fun importFromMnemonic(mnemonic: String): KeyPair { + TODO("Not yet implemented") +} + +actual fun importFromMnemonic(mnemonic: List): KeyPair { + TODO("Not yet implemented") +} diff --git a/lib/src/linuxMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.linux.kt b/lib/src/linuxMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.linux.kt new file mode 100644 index 00000000..af8f207c --- /dev/null +++ b/lib/src/linuxMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.linux.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +import xyz.mcxross.ksui.exception.SignatureSchemeNotSupportedException + +actual fun hash(hash: Hash, data: ByteArray): ByteArray { + TODO("Not yet implemented") +} + +actual fun generateMnemonic(): String { + TODO("Not yet implemented") +} + +actual fun generateSeed(mnemonic: List): ByteArray { + TODO("Not yet implemented") +} + +@Throws(SignatureSchemeNotSupportedException::class) +actual fun generateKeyPair(seed: ByteArray, scheme: SignatureScheme): KeyPair { + TODO("Not yet implemented") +} + +actual fun derivePublicKey(privateKey: PrivateKey, schema: SignatureScheme): PublicKey { + TODO("Not yet implemented") +} + +actual fun importFromMnemonic(mnemonic: String): KeyPair { + TODO("Not yet implemented") +} + +actual fun importFromMnemonic(mnemonic: List): KeyPair { + TODO("Not yet implemented") +} diff --git a/lib/src/mingwMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.mingw.kt b/lib/src/mingwMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.mingw.kt new file mode 100644 index 00000000..af8f207c --- /dev/null +++ b/lib/src/mingwMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.mingw.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2024 McXross + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.mcxross.ksui.core.crypto + +import xyz.mcxross.ksui.exception.SignatureSchemeNotSupportedException + +actual fun hash(hash: Hash, data: ByteArray): ByteArray { + TODO("Not yet implemented") +} + +actual fun generateMnemonic(): String { + TODO("Not yet implemented") +} + +actual fun generateSeed(mnemonic: List): ByteArray { + TODO("Not yet implemented") +} + +@Throws(SignatureSchemeNotSupportedException::class) +actual fun generateKeyPair(seed: ByteArray, scheme: SignatureScheme): KeyPair { + TODO("Not yet implemented") +} + +actual fun derivePublicKey(privateKey: PrivateKey, schema: SignatureScheme): PublicKey { + TODO("Not yet implemented") +} + +actual fun importFromMnemonic(mnemonic: String): KeyPair { + TODO("Not yet implemented") +} + +actual fun importFromMnemonic(mnemonic: List): KeyPair { + TODO("Not yet implemented") +} diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/Crypto.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/Crypto.kt deleted file mode 100644 index fbdd77c5..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/Crypto.kt +++ /dev/null @@ -1,62 +0,0 @@ -package xyz.mcxross.ksui.sample - -import java.security.SecureRandom -import java.util.* -import org.apache.commons.lang3.StringUtils -import org.bitcoinj.crypto.MnemonicCode -import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters - -/** - * Generate new key response. - * - * @param schema the schema - * @return the key response - * @throws SignatureSchemeNotSupportedException the signature scheme not supported exception - */ -@Throws(SignatureSchemeNotSupportedException::class) -fun generateNewKey(schema: SignatureScheme = SignatureScheme.ED25519): KeyResponse { - val secureRandom = SecureRandom() - val entropy = ByteArray(16) - secureRandom.nextBytes(entropy) - var mnemonic: List? = ArrayList() - - try { - mnemonic = MnemonicCode.INSTANCE.toMnemonic(entropy) - } catch (e: java.lang.Exception) { - // MnemonicLengthException won't happen - } - - val seed = MnemonicCode.toSeed(mnemonic, "") - val keyPair: SuiKeyPair<*> = genSuiKeyPair(seed, schema) - - return KeyResponse(StringUtils.join(mnemonic, " "), keyPair.address()) -} - -@Throws(SignatureSchemeNotSupportedException::class) -private fun genSuiKeyPair(seed: ByteArray, schema: SignatureScheme): SuiKeyPair<*> { - return when (schema) { - SignatureScheme.ED25519 -> genED25519KeyPair(seed) - else -> throw SignatureSchemeNotSupportedException() - } -} - -private fun genED25519KeyPair(seed: ByteArray): ED25519KeyPair { - val key: ED25519KeyDerive = ED25519KeyDerive.createKeyByDefaultPath(seed) - val parameters: Ed25519PrivateKeyParameters = Ed25519PrivateKeyParameters(key.key) - val publicKeyParameters = parameters.generatePublicKey() - - return ED25519KeyPair(parameters, publicKeyParameters) -} - -@Throws(SignatureSchemeNotSupportedException::class) -fun importFromMnemonic(mnemonic: String, schema: SignatureScheme = SignatureScheme.ED25519): SuiKeyPair<*> { - // todo check mnemonic - - val seed = - MnemonicCode.toSeed( - listOf(*mnemonic.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()), - "", - ) - - return genSuiKeyPair(seed, schema) -} diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/ED25519KeyDerive.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/ED25519KeyDerive.kt deleted file mode 100644 index b3690a91..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/ED25519KeyDerive.kt +++ /dev/null @@ -1,102 +0,0 @@ -package xyz.mcxross.ksui.sample - -import com.google.common.primitives.Bytes -import org.apache.commons.lang3.StringUtils -import org.bitcoinj.crypto.ChildNumber -import org.bitcoinj.crypto.HDPath -import org.bitcoinj.crypto.HDUtils -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.util.* - - -class ED25519KeyDerive /** - * Instantiates a new Ed 25519 key derive. - * - * @param key the key - * @param chaincode the chaincode - */( - /** - * Get key byte [ ]. - * - * @return the byte [ ] - */ - val key: ByteArray, /** - * Get chaincode byte [ ]. - * - * @return the byte [ ] - */ - val chaincode: ByteArray) { - /** - * Derive ed 25519 key derive. - * - * @param index the index - * @return the ed 25519 key derive - */ - fun derive(index: Int): ED25519KeyDerive { - if (!hasHardenedBit(index)) { - // todo: create an exception - throw RuntimeException() - } - - val indexBytes = ByteArray(4) - ByteBuffer.wrap(indexBytes).putInt(index) - - val data = Bytes.concat(byteArrayOf(0x00), this.key, indexBytes) - - val i = HDUtils.hmacSha512(this.chaincode, data) - val il = Arrays.copyOfRange(i, 0, 32) - val ir = Arrays.copyOfRange(i, 32, 64) - - return ED25519KeyDerive(il, ir) - } - - /** - * Derive from path ed 25519 key derive. - * - * @param path the path - * @return the ed 25519 key derive - */ - fun deriveFromPath(path: String?): ED25519KeyDerive { - var path = path - if (StringUtils.isAnyBlank(path)) { - path = DEFAULT_DERIVE_PATH - } - val hdPath = HDPath.parsePath(path!!) - val it: Iterator = hdPath.iterator() - var current = this - while (it.hasNext()) { - current = current.derive(it.next().i) - } - return current - } - - private fun hasHardenedBit(a: Int): Boolean { - return (a and ChildNumber.HARDENED_BIT) != 0 - } - - companion object { - private const val DEFAULT_DERIVE_PATH = "m/44H/784H/0H/0H/0H" - /** - * Create key by default path ed 25519 key derive. - * - * @param seed the seed - * @return the ed 25519 key derive - */ - fun createKeyByDefaultPath(seed: ByteArray?): ED25519KeyDerive { - return createMasterKey(seed).deriveFromPath("") - } - - /** - * Create master key ed 25519 key derive. - * - * @param seed the seed - * @return the ed 25519 key derive - */ - fun createMasterKey(seed: ByteArray?): ED25519KeyDerive { - val i = HDUtils.hmacSha512("ed25519 seed".toByteArray(Charset.defaultCharset()), seed) - val il = Arrays.copyOfRange(i, 0, 32) - val ir = Arrays.copyOfRange(i, 32, 64) - return ED25519KeyDerive(il, ir) - } - }} diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/ED25519KeyPair.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/ED25519KeyPair.kt deleted file mode 100644 index ce4c61ba..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/ED25519KeyPair.kt +++ /dev/null @@ -1,86 +0,0 @@ -package xyz.mcxross.ksui.sample - -import com.google.common.primitives.Bytes -import org.apache.commons.lang3.StringUtils -import org.bouncycastle.crypto.AsymmetricCipherKeyPair -import org.bouncycastle.crypto.CryptoException -import org.bouncycastle.crypto.Signer -import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters -import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters -import org.bouncycastle.crypto.signers.Ed25519Signer -import org.bouncycastle.jcajce.provider.digest.Blake2b -import org.bouncycastle.util.Arrays -import org.bouncycastle.util.encoders.Base64 -import org.bouncycastle.util.encoders.Hex - -class ED25519KeyPair( - privateKeyParameters: Ed25519PrivateKeyParameters?, - publicKeyParameters: Ed25519PublicKeyParameters?, -) : SuiKeyPair() { - /** - * Instantiates a new Ed 25519 key pair. - * - * @param privateKeyParameters the private key parameters - * @param publicKeyParameters the public key parameters - */ - init { - this.keyPair = AsymmetricCipherKeyPair(publicKeyParameters, privateKeyParameters) - } - - override fun address(): String { - val blake2b256 = Blake2b.Blake2b256() - val hash = - blake2b256.digest( - Arrays.prepend( - (keyPair!!.public as Ed25519PublicKeyParameters).encoded, - SignatureScheme.ED25519.scheme, - ) - ) - return "0x" + StringUtils.substring(Hex.toHexString(hash), 0, 64) - } - - override fun publicKeyBytes(): ByteArray? { - return (keyPair!!.public as Ed25519PublicKeyParameters).encoded - } - - override fun signatureScheme(): SignatureScheme? { - return SignatureScheme.ED25519 - } - - @Throws(SigningException::class) - override fun sign(msg: ByteArray?): ByteArray { - val signer: Signer = Ed25519Signer() - signer.init(true, keyPair!!.private) - signer.update(msg, 0, msg!!.size) - try { - return signer.generateSignature() - } catch (e: CryptoException) { - throw SigningException(e) - } - } - - /** - * Encode base 64 sui key. - * - * @return the sui key - */ - override fun encodePrivateKey(): String? { - val pair = keyPair!!.private as Ed25519PrivateKeyParameters - val data = Bytes.concat(byteArrayOf(SignatureScheme.ED25519.scheme), pair.encoded) - return Base64.toBase64String(data) - } - - companion object { - /** - * Decode base 64 sui key pair. - * - * @param encoded the encoded - * @return the sui key pair - */ - fun decodeBase64(encoded: ByteArray?): ED25519KeyPair { - val privateKeyParameters = Ed25519PrivateKeyParameters(encoded, 1) - val publicKeyParameters = privateKeyParameters.generatePublicKey() - return ED25519KeyPair(privateKeyParameters, publicKeyParameters) - } - } -} diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/Encode.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/Encode.kt deleted file mode 100644 index 8131d578..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/Encode.kt +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.mcxross.ksui.sample - -import org.bouncycastle.util.encoders.Hex - -fun bytesToHex(bytes: ByteArray): String = Hex.toHexString(bytes) - diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/FileBasedKeystore.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/FileBasedKeystore.kt deleted file mode 100644 index cb9d7772..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/FileBasedKeystore.kt +++ /dev/null @@ -1,46 +0,0 @@ -package xyz.mcxross.ksui.sample - -import java.io.File -import java.security.KeyFactory -import java.security.KeyPair -import java.security.spec.PKCS8EncodedKeySpec -import java.security.spec.X509EncodedKeySpec -import kotlin.io.encoding.Base64 -import kotlin.io.encoding.ExperimentalEncodingApi - -@OptIn(ExperimentalEncodingApi::class) -fun saveKeysToFile(file: File, keyPair: KeyPair): Boolean { - val privateKey = keyPair.private.encoded - val publicKey = keyPair.public.encoded - - file.writeText("PRIVATE KEY=${Base64.encode(privateKey)}\n") - file.appendText("PUBLIC KEY=${Base64.encode(publicKey)}\n") - - return true -} - -@OptIn(ExperimentalEncodingApi::class) -fun loadKeysFromFile(file: File): KeyPair { - val lines = file.readLines() - val privateKeyLine = - lines.find { it.startsWith("PRIVATE KEY=") } - ?: throw IllegalArgumentException("Private key not found") - val publicKeyLine = - lines.find { it.startsWith("PUBLIC KEY=") } - ?: throw IllegalArgumentException("Public key not found") - - println("Private Key: ${privateKeyLine.substringAfter("PRIVATE KEY)")}") - println("Public Key: ${publicKeyLine.substringAfter("PUBLIC KEY)")}") - - val privateKeyData = Base64.decode(privateKeyLine.substringAfter("PRIVATE KEY=")) - val publicKeyData = Base64.decode(publicKeyLine.substringAfter("PUBLIC KEY=")) - - val keyFactory = KeyFactory.getInstance("Ed25519", "BC") - val privateKeySpec = PKCS8EncodedKeySpec(privateKeyData) - val publicKeySpec = X509EncodedKeySpec(publicKeyData) - - val privateKey = keyFactory.generatePrivate(privateKeySpec) - val publicKey = keyFactory.generatePublic(publicKeySpec) - - return KeyPair(publicKey, privateKey) -} diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/KeyResponse.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/KeyResponse.kt deleted file mode 100644 index 7e7af66b..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/KeyResponse.kt +++ /dev/null @@ -1,6 +0,0 @@ -package xyz.mcxross.ksui.sample - -data class KeyResponse( - val mnemonic: String, - val address: String, -) diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SignatureScheme.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SignatureScheme.kt deleted file mode 100644 index cb1cab1d..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SignatureScheme.kt +++ /dev/null @@ -1,39 +0,0 @@ -package xyz.mcxross.ksui.sample - -enum class SignatureScheme( - /** - * Gets scheme. - * - * @return the scheme - */ - val scheme: Byte -) { - /** Ed25519 signature scheme. */ - ED25519(0x00.toByte()), - /** Secp256k1 signature scheme. */ - Secp256k1(0x01.toByte()), - /** Secp256r1 signature scheme. */ - Secp256r1(0x02.toByte()), - /** BLS12381 signature scheme. */ - BLS12381(0xff.toByte()); - - companion object { - private val BY_SCHEME: MutableMap = HashMap() - - init { - for (e in entries) { - BY_SCHEME[e.scheme] = e - } - } - - /** - * Value of signature scheme. - * - * @param scheme the scheme - * @return the signature scheme - */ - fun valueOf(scheme: Byte): SignatureScheme? { - return BY_SCHEME[scheme] - } - } -} diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SignatureSchemeNotSupportedException.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SignatureSchemeNotSupportedException.kt deleted file mode 100644 index 2a5bc6ac..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SignatureSchemeNotSupportedException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package xyz.mcxross.ksui.sample - -class SignatureSchemeNotSupportedException /** Instantiates a new Signature scheme not supported exception. */ -: RuntimeException("only ed25519 and secp256k1 signature scheme supported.") diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SigningException.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SigningException.kt deleted file mode 100644 index 4504c8f4..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SigningException.kt +++ /dev/null @@ -1,13 +0,0 @@ -package xyz.mcxross.ksui.sample - - -class SigningException : RuntimeException { - /** - * Instantiates a new Signing exception. - * - * @param cause the cause - */ - constructor(cause: Throwable?) : super(cause) - - constructor(message: String?) : super(message) -} diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SuiKeyPair.kt b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SuiKeyPair.kt deleted file mode 100644 index bba95b5c..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/SuiKeyPair.kt +++ /dev/null @@ -1,99 +0,0 @@ -package xyz.mcxross.ksui.sample - -import java.security.Security -import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.bouncycastle.util.encoders.Base64 - -abstract class SuiKeyPair { - /** - * Gets key pair. - * - * @return the key pair - */ - /** The Key pair. */ - var keyPair: T? = null - protected set - - override fun toString(): String { - return "SuiKeyPair{keyPair=$keyPair}" - } - - /** - * Address string. - * - * @return the string - */ - abstract fun address(): String - - /** - * Public key string. - * - * @return the string - */ - fun publicKey(): String { - return Base64.toBase64String(this.publicKeyBytes()) - } - - /** - * Public key byte [ ]. - * - * @return the byte [ ] - */ - abstract fun publicKeyBytes(): ByteArray? - - /** - * Signature scheme signature scheme. - * - * @return the signature scheme - */ - abstract fun signatureScheme(): SignatureScheme? - - @Throws(SigningException::class) - fun sign(msg: String?): String { - val msgBytes = Base64.decode(msg) - return Base64.toBase64String(this.sign(msgBytes)) - } - - /** - * Sign string. - * - * @param msg the msg - * @return the string - * @throws SigningException the signing exception - */ - @Throws(SigningException::class) abstract fun sign(msg: ByteArray?): ByteArray - - /** - * encode base64 sui key. - * - * @return the sui key - */ - abstract fun encodePrivateKey(): String? - - companion object { - init { - if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { - Security.addProvider(BouncyCastleProvider()) - } - } - - /** - * Decode base64 sui key pair. - * - * @param encoded the encoded - * @return the sui key pair - * @throws SignatureSchemeNotSupportedException the signature scheme not supported exception - */ - @Throws(SignatureSchemeNotSupportedException::class) - fun decodeBase64(encoded: String?): SuiKeyPair<*> { - val keyPairBytes = Base64.decode(encoded) - - val scheme = - SignatureScheme.valueOf(keyPairBytes[0]) ?: throw SignatureSchemeNotSupportedException() - return when (scheme) { - SignatureScheme.ED25519 -> ED25519KeyPair.decodeBase64(keyPairBytes) - else -> throw SignatureSchemeNotSupportedException() - } - } - } -} diff --git a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/demo.json b/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/demo.json deleted file mode 100644 index 9dc01ce5..00000000 --- a/sample/jvm/src/main/kotlin/xyz/mcxross/ksui/sample/demo.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "jsonrpc": "2.0", - "result": { - "digest": "FDMxrrBSW2bCUuSFbno4DUKU9gDAfPHY21tJqG9cXRTf", - "transaction": { - "data": { - "messageVersion": "v1", - "transaction": { - "kind": "ProgrammableTransaction", - "inputs": [ - { - "type": "pure", - "valueType": "u64", - "value": "1000000000" - }, - { - "type": "pure", - "valueType": "address", - "value": "0xa087ff13a8e27a85a54db187c01de5b6f6a10f9ae74c86ce25620374a05f2f1f" - } - ], - "transactions": [ - { - "SplitCoins": [ - "GasCoin", - [ - { - "Input": 0 - } - ] - ] - }, - { - "TransferObjects": [ - [ - { - "Result": 0 - } - ], - { - "Input": 1 - } - ] - } - ] - }, - "sender": "0x1cad61a6ebad93bacdf16e183eb9a1792e2f9576499b9217a44c8d1c14491299", - "gasData": { - "payment": [ - { - "objectId": "0x7d8ae7b9ffe707b6b7ad37fae9e8ead69c2eaa0e0e028c9c10c1ddd45f34d996", - "version": 64, - "digest": "AuGAQy8J4uvycARF49wEK5C43HY467JMhXyERS7fEsU7" - } - ], - "owner": "0x1cad61a6ebad93bacdf16e183eb9a1792e2f9576499b9217a44c8d1c14491299", - "price": "1000", - "budget": "5000000" - } - }, - "txSignatures": [ - "AJBA6hBLbS3RhQ3d9EOYhFmAf8RSgDh7d1EZmpL8U9gB6ivRNUIctZ/taMiTQFiKP6PmlwIcwEGQDuR62//PNQqtZbRpA5gWoPG4hnao2g/qVkHq5rFvoq5DfhE7DND8Rg==" - ] - }, - "rawTransaction": "AQAAAAAAAgAIAMqaOwAAAAAAIKCH/xOo4nqFpU2xh8Ad5bb2oQ+a50yGziViA3SgXy8fAgIAAQEAAAEBAgAAAQEAHK1hpuutk7rN8W4YPrmheS4vlXZJm5IXpEyNHBRJEpkBfYrnuf/nB7a3rTf66ejq1pwuqg4OAoycEMHd1F802ZZAAAAAAAAAACCTHIdKBAx82NAJM64PrfcT77UT1fryLxqe1M1l2cQqlBytYabrrZO6zfFuGD65oXkuL5V2SZuSF6RMjRwUSRKZ6AMAAAAAAABAS0wAAAAAAAABYQCQQOoQS20t0YUN3fRDmIRZgH/EUoA4e3dRGZqS/FPYAeor0TVCHLWf7WjIk0BYij+j5pcCHMBBkA7ketv/zzUKrWW0aQOYFqDxuIZ2qNoP6lZB6uaxb6KuQ34ROwzQ/EY=", - "effects": { - "messageVersion": "v1", - "status": { - "status": "success" - }, - "executedEpoch": "146", - "gasUsed": { - "computationCost": "1000000", - "storageCost": "1976000", - "storageRebate": "978120", - "nonRefundableStorageFee": "9880" - }, - "modifiedAtVersions": [ - { - "objectId": "0x7d8ae7b9ffe707b6b7ad37fae9e8ead69c2eaa0e0e028c9c10c1ddd45f34d996", - "sequenceNumber": "64" - } - ], - "transactionDigest": "FDMxrrBSW2bCUuSFbno4DUKU9gDAfPHY21tJqG9cXRTf", - "created": [ - { - "owner": { - "AddressOwner": "0xa087ff13a8e27a85a54db187c01de5b6f6a10f9ae74c86ce25620374a05f2f1f" - }, - "reference": { - "objectId": "0x330f60087f28278d5982742dbb4b4966fd527a82abc94d02489ad0675c51ebcb", - "version": 65, - "digest": "GXDZq7LVHqfaFEsoxPTdRV3A6QDcB8hPd6MSu1QQcqto" - } - } - ], - "mutated": [ - { - "owner": { - "AddressOwner": "0x1cad61a6ebad93bacdf16e183eb9a1792e2f9576499b9217a44c8d1c14491299" - }, - "reference": { - "objectId": "0x7d8ae7b9ffe707b6b7ad37fae9e8ead69c2eaa0e0e028c9c10c1ddd45f34d996", - "version": 65, - "digest": "8HTJqRJYsYNuqFpyV2zRhWiDHf7KUw3QAQW7x2xLLEN" - } - } - ], - "gasObject": { - "owner": { - "AddressOwner": "0x1cad61a6ebad93bacdf16e183eb9a1792e2f9576499b9217a44c8d1c14491299" - }, - "reference": { - "objectId": "0x7d8ae7b9ffe707b6b7ad37fae9e8ead69c2eaa0e0e028c9c10c1ddd45f34d996", - "version": 65, - "digest": "8HTJqRJYsYNuqFpyV2zRhWiDHf7KUw3QAQW7x2xLLEN" - } - }, - "dependencies": [ - "F1rU8tUUF5nUgd7DbqsFA15JwxkU1eNJJUbbTnr8C6tw" - ] - }, - "events": [], - "objectChanges": [ - { - "type": "mutated", - "sender": "0x1cad61a6ebad93bacdf16e183eb9a1792e2f9576499b9217a44c8d1c14491299", - "owner": { - "AddressOwner": "0x1cad61a6ebad93bacdf16e183eb9a1792e2f9576499b9217a44c8d1c14491299" - }, - "objectType": "0x2::coin::Coin<0x2::sui::SUI>", - "objectId": "0x7d8ae7b9ffe707b6b7ad37fae9e8ead69c2eaa0e0e028c9c10c1ddd45f34d996", - "version": "65", - "previousVersion": "64", - "digest": "8HTJqRJYsYNuqFpyV2zRhWiDHf7KUw3QAQW7x2xLLEN" - }, - { - "type": "created", - "sender": "0x1cad61a6ebad93bacdf16e183eb9a1792e2f9576499b9217a44c8d1c14491299", - "owner": { - "AddressOwner": "0xa087ff13a8e27a85a54db187c01de5b6f6a10f9ae74c86ce25620374a05f2f1f" - }, - "objectType": "0x2::coin::Coin<0x2::sui::SUI>", - "objectId": "0x330f60087f28278d5982742dbb4b4966fd527a82abc94d02489ad0675c51ebcb", - "version": "65", - "digest": "GXDZq7LVHqfaFEsoxPTdRV3A6QDcB8hPd6MSu1QQcqto" - } - ], - "balanceChanges": [ - { - "owner": { - "AddressOwner": "0x1cad61a6ebad93bacdf16e183eb9a1792e2f9576499b9217a44c8d1c14491299" - }, - "coinType": "0x2::sui::SUI", - "amount": "-1001997880" - }, - { - "owner": { - "AddressOwner": "0xa087ff13a8e27a85a54db187c01de5b6f6a10f9ae74c86ce25620374a05f2f1f" - }, - "coinType": "0x2::sui::SUI", - "amount": "1000000000" - } - ], - "confirmedLocalExecution": true - }, - "id": 1 -}