-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from mcxross/crypto
feat: add account management
- Loading branch information
Showing
34 changed files
with
988 additions
and
635 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
lib/src/androidJvmMain/kotlin/xyz/mcxross/ksui/core/crypto/ED25519KeyDerive.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ChildNumber> = 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) | ||
} | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
lib/src/androidJvmMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.androidjvm.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String> = ArrayList() | ||
|
||
try { | ||
mnemonic = MnemonicCode.INSTANCE.toMnemonic(entropy) | ||
} catch (e: java.lang.Exception) { | ||
// MnemonicLengthException won't happen | ||
} | ||
return mnemonic.joinToString(" ") | ||
} | ||
|
||
actual fun generateSeed(mnemonic: List<String>): 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<String>): KeyPair { | ||
val seed = MnemonicCode.toSeed(mnemonic, "") | ||
return generateKeyPair(seed, SignatureScheme.ED25519) | ||
} |
53 changes: 53 additions & 0 deletions
53
lib/src/appleMain/kotlin/xyz/mcxross/ksui/core/crypto/Platform.apple.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String>): 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<String>): KeyPair { | ||
TODO("Not yet implemented") | ||
} |
125 changes: 125 additions & 0 deletions
125
lib/src/commonMain/kotlin/xyz/mcxross/ksui/account/Account.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String>, 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<String>, scheme: SignatureScheme = SignatureScheme.ED25519): Account { | ||
return when (scheme) { | ||
SignatureScheme.ED25519 -> { | ||
import(phrases.joinToString(" ")) | ||
} | ||
else -> throw SignatureSchemeNotSupportedException() | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.