diff --git a/.github/workflows/shared-core-tests.yml b/.github/workflows/shared-core-tests.yml index c331a438ae..777eba4fbd 100644 --- a/.github/workflows/shared-core-tests.yml +++ b/.github/workflows/shared-core-tests.yml @@ -67,6 +67,7 @@ jobs: :libs:encryption:base58:iosSimulatorArm64Test \ :libs:encryption:ed25519:iosSimulatorArm64Test \ :libs:encryption:hmac:iosSimulatorArm64Test \ + :libs:encryption:mnemonic:iosSimulatorArm64Test \ :libs:encryption:sha256:iosSimulatorArm64Test \ :libs:encryption:sha512:iosSimulatorArm64Test diff --git a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/SeedInputViewModel.kt b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/SeedInputViewModel.kt index 4747f7f660..9a905b8690 100644 --- a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/SeedInputViewModel.kt +++ b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/SeedInputViewModel.kt @@ -62,7 +62,7 @@ internal class SeedInputViewModel @Inject constructor( if (isLoading || isSuccess) return val userWordList = wordsString.lowercase(Locale.CANADA).split(" ") - val wordCount = getValidCount(userWordList, mnemonicCode.wordList) + val wordCount = getValidCount(userWordList, mnemonicCode.getWordList()) uiFlow.update { it.copy( wordsString = wordsString, diff --git a/kmp/shared-core/build.gradle.kts b/kmp/shared-core/build.gradle.kts index 641f31bca3..b260f6abd4 100644 --- a/kmp/shared-core/build.gradle.kts +++ b/kmp/shared-core/build.gradle.kts @@ -25,7 +25,7 @@ kotlin { minSdk = 29 } - val appleTargets = listOf(iosArm64(), iosSimulatorArm64(), iosX64()) + val appleTargets = listOf(iosArm64(), iosSimulatorArm64(), iosX64(), macosArm64(), macosX64()) appleTargets.forEach { it.binaries.framework { baseName = "SharedCore" @@ -36,6 +36,7 @@ kotlin { export(project(":libs:encryption:sha512")) export(project(":libs:encryption:hmac")) export(project(":libs:encryption:ed25519")) + export(project(":libs:encryption:mnemonic")) } } @@ -48,6 +49,7 @@ kotlin { api(project(":libs:encryption:sha512")) api(project(":libs:encryption:hmac")) api(project(":libs:encryption:ed25519")) + api(project(":libs:encryption:mnemonic")) } } } @@ -65,5 +67,6 @@ kmmbridge { // with a custom file it's `spm/Package.swift` that decides, so keep the two in step. spm(spmDirectory = spmPackageDir, useCustomPackageFile = true, swiftToolVersion = "5.9") { iOS { v("15") } + macOS { v("14") } } } diff --git a/kmp/shared-core/spm/Package.swift b/kmp/shared-core/spm/Package.swift index 59ea27ccca..f6214e374a 100644 --- a/kmp/shared-core/spm/Package.swift +++ b/kmp/shared-core/spm/Package.swift @@ -54,7 +54,8 @@ let binaryTarget: Target = sharedCoreLocalRoot.map { let package = Package( name: packageName, platforms: [ - .iOS(.v15) + .iOS(.v15), + .macOS(.v14), ], products: [ // The only product on purpose. Callers get Swift types; the Kotlin framework's diff --git a/kmp/shared-core/spm/Sources/SharedCoreKit/Derivation.swift b/kmp/shared-core/spm/Sources/SharedCoreKit/Derivation.swift new file mode 100644 index 0000000000..d94fc038cd --- /dev/null +++ b/kmp/shared-core/spm/Sources/SharedCoreKit/Derivation.swift @@ -0,0 +1,18 @@ +import Foundation +import SharedCore + +/// BIP-39 seed derivation + SLIP-0010 ed25519 key derivation, backed by the shared Kotlin +/// implementation in `:libs:encryption:mnemonic`. +public enum SharedDerivation { + + /// Derives a 64-byte PBKDF2-SHA512 seed from a BIP-39 mnemonic phrase. + public static func seed(mnemonic: [String], passphrase: String = "") -> Data { + Data(MnemonicCode.shared.toSeed(words: mnemonic, passphrase: passphrase)) + } + + /// Derives the raw 32-byte private key by walking `hardenedIndexes` (each already offset by + /// 0x80000000) from `seed` per SLIP-0010. + public static func derivedKey(seed: Data, hardenedIndexes: [Int64]) -> Data { + Data(Derive.shared.derivedKey(seed: seed.kotlinByteArray, hardenedIndexes: hardenedIndexes.kotlinLongArray)) + } +} diff --git a/kmp/shared-core/spm/Sources/SharedCoreKit/KotlinLongArray+Bridge.swift b/kmp/shared-core/spm/Sources/SharedCoreKit/KotlinLongArray+Bridge.swift new file mode 100644 index 0000000000..920b7e7c0e --- /dev/null +++ b/kmp/shared-core/spm/Sources/SharedCoreKit/KotlinLongArray+Bridge.swift @@ -0,0 +1,12 @@ +import SharedCore + +extension Array where Element == Int64 { + /// Converts this array to a `KotlinLongArray` for calling into shared Kotlin. + var kotlinLongArray: KotlinLongArray { + let array = KotlinLongArray(size: Int32(count)) + for (index, value) in enumerated() { + array.set(index: Int32(index), value: value) + } + return array + } +} diff --git a/kmp/shared-core/spm/Tests/SharedCoreKitTests/DerivationTests.swift b/kmp/shared-core/spm/Tests/SharedCoreKitTests/DerivationTests.swift new file mode 100644 index 0000000000..1808bf442d --- /dev/null +++ b/kmp/shared-core/spm/Tests/SharedCoreKitTests/DerivationTests.swift @@ -0,0 +1,39 @@ +import Testing +import Foundation +@testable import SharedCoreKit + +@Suite("SharedDerivation") +struct DerivationTests { + + struct Vector: Decodable { + let name, mnemonic, passphrase, path, derivedKey, publicKey: String + } + struct Fixture: Decodable { let vectors: [Vector] } + + @Test("derivation matches the canonical SLIP-10 vectors") + func derivationMatchesCanonicalVectors() throws { + let fixture = try Fixtures.load("slip10", as: Fixture.self) + #expect(!fixture.vectors.isEmpty) + + for v in fixture.vectors { + let words = v.mnemonic.split(separator: " ").map(String.init) + let seed = SharedDerivation.seed(mnemonic: words, passphrase: v.passphrase) + + let hardenedIndexes = try v.path + .split(separator: "/") + .dropFirst() // leading "m" + .map { component -> Int64 in + let hardened = component.hasSuffix("'") + let digits = hardened ? String(component.dropLast()) : String(component) + let value = try #require(Int64(digits)) + return 0x8000_0000 + value + } + + let derivedKey = SharedDerivation.derivedKey(seed: seed, hardenedIndexes: hardenedIndexes) + #expect(derivedKey.hexString == v.derivedKey, "derivedKey mismatch for \(v.name)") + + let keyPair = SharedEd25519.keyPair(seed: derivedKey) + #expect(keyPair.publicKey.hexString == v.publicKey, "publicKey mismatch for \(v.name)") + } + } +} diff --git a/libs/encryption/mnemonic/src/androidTest/assets/slip10.json b/kmp/shared-core/spm/Tests/SharedCoreKitTests/Fixtures/slip10.json similarity index 100% rename from libs/encryption/mnemonic/src/androidTest/assets/slip10.json rename to kmp/shared-core/spm/Tests/SharedCoreKitTests/Fixtures/slip10.json diff --git a/libs/codes/kikcode/build.gradle.kts b/libs/codes/kikcode/build.gradle.kts index 4dc7a31f19..c108aeb864 100644 --- a/libs/codes/kikcode/build.gradle.kts +++ b/libs/codes/kikcode/build.gradle.kts @@ -21,6 +21,8 @@ kotlin { iosArm64() iosSimulatorArm64() iosX64() + macosArm64() + macosX64() sourceSets { commonMain { diff --git a/libs/encryption/base58/build.gradle.kts b/libs/encryption/base58/build.gradle.kts index fd202f1619..01b0d38805 100644 --- a/libs/encryption/base58/build.gradle.kts +++ b/libs/encryption/base58/build.gradle.kts @@ -21,6 +21,8 @@ kotlin { iosArm64() iosSimulatorArm64() iosX64() + macosArm64() + macosX64() sourceSets { commonMain { diff --git a/libs/encryption/ed25519/build.gradle.kts b/libs/encryption/ed25519/build.gradle.kts index 94c3e5b8ea..fd2ef0faea 100644 --- a/libs/encryption/ed25519/build.gradle.kts +++ b/libs/encryption/ed25519/build.gradle.kts @@ -36,6 +36,8 @@ val appleTargetDefs = listOf( AppleTarget("iosArm64", "iphoneos", "arm64"), AppleTarget("iosSimulatorArm64", "iphonesimulator", "arm64"), AppleTarget("iosX64", "iphonesimulator", "x86_64"), + AppleTarget("macosArm64", "macosx", "arm64"), + AppleTarget("macosX64", "macosx", "x86_64"), ) appleTargetDefs.forEach { target -> @@ -81,6 +83,8 @@ kotlin { iosArm64() iosSimulatorArm64() iosX64() + macosArm64() + macosX64() // ── Cinterop + linker wiring for each Apple target ──────────────────────── targets.withType().configureEach { diff --git a/libs/encryption/ed25519/src/iosMain/kotlin/com/getcode/ed25519kmp/Ed25519Kmp.ios.kt b/libs/encryption/ed25519/src/appleMain/kotlin/com/getcode/ed25519kmp/Ed25519Kmp.apple.kt similarity index 96% rename from libs/encryption/ed25519/src/iosMain/kotlin/com/getcode/ed25519kmp/Ed25519Kmp.ios.kt rename to libs/encryption/ed25519/src/appleMain/kotlin/com/getcode/ed25519kmp/Ed25519Kmp.apple.kt index c304e61b1f..46ebd8a71a 100644 --- a/libs/encryption/ed25519/src/iosMain/kotlin/com/getcode/ed25519kmp/Ed25519Kmp.ios.kt +++ b/libs/encryption/ed25519/src/appleMain/kotlin/com/getcode/ed25519kmp/Ed25519Kmp.apple.kt @@ -13,7 +13,7 @@ import kotlinx.cinterop.toCValues import kotlinx.cinterop.UByteVar /** - * iOS actual: calls the vendored orlp/ed25519 C library directly via Kotlin/Native cinterop. + * Apple actual (iOS + macOS): calls the vendored orlp/ed25519 C library directly via Kotlin/Native cinterop. * * Memory safety: * - All byte arrays are pinned or copied into C-managed memory inside [memScoped]. diff --git a/libs/encryption/hmac/build.gradle.kts b/libs/encryption/hmac/build.gradle.kts index e54a5aa429..9bf5f8b55c 100644 --- a/libs/encryption/hmac/build.gradle.kts +++ b/libs/encryption/hmac/build.gradle.kts @@ -14,6 +14,8 @@ kotlin { iosArm64() iosSimulatorArm64() iosX64() + macosArm64() + macosX64() sourceSets { commonMain { diff --git a/libs/encryption/mnemonic/build.gradle.kts b/libs/encryption/mnemonic/build.gradle.kts index 57db319e15..fe1049a4f0 100644 --- a/libs/encryption/mnemonic/build.gradle.kts +++ b/libs/encryption/mnemonic/build.gradle.kts @@ -1,26 +1,60 @@ plugins { - alias(libs.plugins.flipcash.android.library) - alias(libs.plugins.flipcash.android.ed25519.shadow) + kotlin("multiplatform") + id("com.android.kotlin.multiplatform.library") + alias(libs.plugins.flipcash.kmp.test.fixtures) } -android { - namespace = "${Gradle.codeNamespace}.encryption.mnemonic" +// Compiles `src/commonTest/resources` into a generated `TestFixtures.kt` on `commonTest` -- +// see the `flipcash.kmp.test.fixtures` convention plugin. +testFixtures { + packageName = "com.getcode.crypt" } -dependencies { - implementation(project(":libs:encryption:base58")) - implementation(project(":libs:encryption:ed25519")) - implementation(project(":libs:encryption:hmac")) - implementation(project(":libs:encryption:sha256")) - implementation(project(":libs:encryption:sha512")) - implementation(project(":libs:encryption:utils")) - implementation(libs.grpc.okhttp) - implementation(libs.grpc.kotlin) - implementation(libs.androidx.core) +kotlin { + android { + namespace = "com.getcode.encryption.mnemonic" + compileSdk = 37 + minSdk = 29 + withHostTest {} + withDeviceTest {} + } - testImplementation(kotlin("test")) + iosArm64() + iosSimulatorArm64() + iosX64() + macosArm64() + macosX64() - // Cross-platform derivation test-vector gate (instrumented: wordlist res/raw + JNI ed25519). - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.test.runner) + sourceSets { + commonMain { + dependencies { + implementation(project(":libs:encryption:ed25519")) + implementation(project(":libs:encryption:hmac")) + implementation(project(":libs:encryption:sha256")) + implementation(project(":libs:encryption:sha512")) + implementation(project(":libs:encryption:utils")) + } + } + commonTest { + dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.serialization.json) + } + } + androidMain { + dependencies { + // Legacy vendored Base58 codec (fromEntropyB58/getBase58EncodedEntropy). + implementation(project(":libs:encryption:base58")) + // Domain.kt uses android.net.Uri. + implementation(libs.androidx.core) + } + } + getByName("androidDeviceTest") { + dependencies { + // Cross-platform derivation test-vector gate (instrumented: JNI ed25519 pipeline). + implementation(libs.androidx.junit) + implementation(libs.androidx.test.runner) + } + } + } } diff --git a/libs/encryption/mnemonic/src/androidDeviceTest/assets/slip10.json b/libs/encryption/mnemonic/src/androidDeviceTest/assets/slip10.json new file mode 100644 index 0000000000..fbb0e3331e --- /dev/null +++ b/libs/encryption/mnemonic/src/androidDeviceTest/assets/slip10.json @@ -0,0 +1,66 @@ +{ + "algorithm": "bip39+slip10-ed25519", + "note": "BIP39 seed -> SLIP-0010 ed25519 (all indices force-hardened) -> ed25519 keypair. Apps must reproduce publicKey/address for each mnemonic+path.", + "vectors": [ + { + "name": "abandon-x11-about m/44'/501'/0'/0'", + "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "passphrase": "", + "path": "m/44'/501'/0'/0'", + "seedBip39": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", + "derivedKey": "37df573b3ac4ad5b522e064e25b63ea16bcbe79d449e81a0268d1047948bb445", + "publicKey": "f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7", + "address": "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk" + }, + { + "name": "abandon-x11-about m/44'/501'/0'/0'/7665'/0", + "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "passphrase": "", + "path": "m/44'/501'/0'/0'/7665'/0", + "seedBip39": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", + "derivedKey": "1f317a98c39b1459a28fbc5d7e4ed9c9799dc69d194157ec7b9d645c4ed3a474", + "publicKey": "051d88bbb9c70cc1045090c3c01675620b060123f2d1f1700d8ef1f5dadcc5d8", + "address": "LyACZbC6QzPP3ixxyBjuCC1sKWuAi1bZU1xgTuFVpRD" + }, + { + "name": "abandon-x11-about m/44'/501'/0'/0'/2335'/5", + "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "passphrase": "", + "path": "m/44'/501'/0'/0'/2335'/5", + "seedBip39": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", + "derivedKey": "4b83957e1b5ca6b16e9f39fdd38c88a6a96c38e61f9b4cecb7fda43e5e249764", + "publicKey": "7bc1de0ce3459814c74dae0529df060261d896dea23264e298eb90d1d236c123", + "address": "9L6c3GSoNLLXbXAoxCgHR1fkhbX96jNaLqmQnsRYUCo4" + }, + { + "name": "legal-winner m/44'/501'/0'/0'", + "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank yellow", + "passphrase": "", + "path": "m/44'/501'/0'/0'", + "seedBip39": "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096", + "derivedKey": "6987bdb06aa8a243a3019f41489ffa8e609c953a885a748d1849a8df760aa479", + "publicKey": "999d46fb3d1256f7049c8ed09314d7268612e8a91b800e91934463848305c98c", + "address": "BLeUXTx9thHGT7VJUtF9vHEmfMDgW1nnKZ9UVer2CoLX" + }, + { + "name": "legal-winner m/44'/501'/0'/0'/7665'/0", + "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank yellow", + "passphrase": "", + "path": "m/44'/501'/0'/0'/7665'/0", + "seedBip39": "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096", + "derivedKey": "82c2531aae3c058eb076618c696c3d496c3249b066dd528cb83adc4f4bbe8294", + "publicKey": "9ed602ebfe2399e961e3a08535a50fd4391fdefc0341b1c790e0c4947ccd16a0", + "address": "Bh2gxt6UiDWjWkmsfK9qp46kRrjTPgvXn7DhvaPTxUr3" + }, + { + "name": "legal-winner m/44'/501'/0'/0'/2335'/5", + "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank yellow", + "passphrase": "", + "path": "m/44'/501'/0'/0'/2335'/5", + "seedBip39": "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096", + "derivedKey": "4eb3bb8632aa83691f51a4cff44021ff785df069432007bb023e858b6416f6ec", + "publicKey": "b5310f3190a4a39d5146f17072a767fb5c566593e4f2417d293d11fcd3b246f7", + "address": "DCJBY1iQroEzsNi2BMwu3zNmnB27rCV6iJHHJDAqTTmg" + } + ] +} diff --git a/libs/encryption/mnemonic/src/androidTest/java/com/getcode/crypt/Slip10DerivationVectorTest.kt b/libs/encryption/mnemonic/src/androidDeviceTest/kotlin/com/getcode/crypt/Slip10DerivationVectorTest.kt similarity index 100% rename from libs/encryption/mnemonic/src/androidTest/java/com/getcode/crypt/Slip10DerivationVectorTest.kt rename to libs/encryption/mnemonic/src/androidDeviceTest/kotlin/com/getcode/crypt/Slip10DerivationVectorTest.kt diff --git a/libs/encryption/mnemonic/src/androidHostTest/jniLibs/libed25519.dylib b/libs/encryption/mnemonic/src/androidHostTest/jniLibs/libed25519.dylib new file mode 100755 index 0000000000..09cc85ffa9 Binary files /dev/null and b/libs/encryption/mnemonic/src/androidHostTest/jniLibs/libed25519.dylib differ diff --git a/libs/encryption/mnemonic/src/androidHostTest/jniLibs/libnative-lib.dylib b/libs/encryption/mnemonic/src/androidHostTest/jniLibs/libnative-lib.dylib new file mode 100755 index 0000000000..b12ffa424b Binary files /dev/null and b/libs/encryption/mnemonic/src/androidHostTest/jniLibs/libnative-lib.dylib differ diff --git a/libs/encryption/mnemonic/src/test/kotlin/com/getcode/crypt/MnemonicPhraseTest.kt b/libs/encryption/mnemonic/src/androidHostTest/kotlin/com/getcode/crypt/MnemonicPhraseTest.kt similarity index 100% rename from libs/encryption/mnemonic/src/test/kotlin/com/getcode/crypt/MnemonicPhraseTest.kt rename to libs/encryption/mnemonic/src/androidHostTest/kotlin/com/getcode/crypt/MnemonicPhraseTest.kt diff --git a/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/DerivePath.android.kt b/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/DerivePath.android.kt new file mode 100644 index 0000000000..102f3202c5 --- /dev/null +++ b/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/DerivePath.android.kt @@ -0,0 +1,7 @@ +package com.getcode.crypt + +import com.getcode.model.Domain + +fun DerivePath.Companion.relationship(domain: Domain): DerivePath { + return DerivePath.newInstance("m/44'/501'/0'/0'/0'/0", password = domain.relationshipHost)!! +} diff --git a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/DerivedKey.kt b/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/DerivedKey.kt similarity index 99% rename from libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/DerivedKey.kt rename to libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/DerivedKey.kt index 412d3b34e2..a4536b5429 100644 --- a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/DerivedKey.kt +++ b/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/DerivedKey.kt @@ -31,4 +31,4 @@ data class DerivedKey(val path: DerivePath, val keyPair: Ed25519.KeyPair) { return result } -} \ No newline at end of file +} diff --git a/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/MnemonicCache.kt b/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/MnemonicCache.kt new file mode 100644 index 0000000000..44b068ffd9 --- /dev/null +++ b/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/MnemonicCache.kt @@ -0,0 +1,15 @@ +package com.getcode.crypt + +import android.content.Context + +object MnemonicCache { + val cachedCode: MnemonicCode = MnemonicCode + + fun init(context: Context) { + // MnemonicCode no longer needs Android resources -- the wordlist is a compile-time + // constant (see Bip39EnglishWordList). Kept so MnemonicCacheInitializer's startup + // call still compiles. + } + + val cache = mutableMapOf, String>, ByteArray>() +} diff --git a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicPhrase.kt b/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/MnemonicPhrase.kt similarity index 95% rename from libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicPhrase.kt rename to libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/MnemonicPhrase.kt index a0f7533898..c8cd5bd23d 100644 --- a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicPhrase.kt +++ b/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/crypt/MnemonicPhrase.kt @@ -25,7 +25,7 @@ class MnemonicPhrase(val kind: Kind, val words: List) { mnemonicCode.check(words) - return Derive.path(mnemonicSeed, path) + return Ed25519.createKeyPair(Derive.derivedKey(mnemonicSeed, path).encodeBase64()) } val wordString: String @@ -74,4 +74,4 @@ class MnemonicPhrase(val kind: Kind, val words: List) { return newInstance(words)!! } } -} \ No newline at end of file +} diff --git a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/model/Domain.kt b/libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/model/Domain.kt similarity index 100% rename from libs/encryption/mnemonic/src/main/kotlin/com/getcode/model/Domain.kt rename to libs/encryption/mnemonic/src/androidMain/kotlin/com/getcode/model/Domain.kt diff --git a/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/Bip39EnglishWordList.kt b/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/Bip39EnglishWordList.kt new file mode 100644 index 0000000000..de16062b6b --- /dev/null +++ b/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/Bip39EnglishWordList.kt @@ -0,0 +1,2057 @@ +package com.getcode.crypt + +// Generated once from src/main/res/raw/english.txt (BIP-39 English wordlist, +// 2048 words, alphabetically sorted). Do not hand-edit; regenerate from the raw +// resource file if the wordlist ever needs to change. +internal object Bip39EnglishWordList { + val words: List = listOf( + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo", + ) +} diff --git a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/Derive.kt b/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/Derive.kt similarity index 55% rename from libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/Derive.kt rename to libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/Derive.kt index f96269a72c..7debb46780 100644 --- a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/Derive.kt +++ b/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/Derive.kt @@ -1,37 +1,38 @@ package com.getcode.crypt -import com.getcode.ed25519.Ed25519 -import com.getcode.utils.encodeBase64 import com.getcode.utils.subByteArray -import java.nio.ByteBuffer - +/// Deterministic wallet generation for ED25519 curve using SLIP-0010 spec +/// Reference: https://github.com/satoshilabs/slips/blob/master/slip-0010.md object Derive { private const val curve = "ed25519 seed" private const val algorithm = "HmacSHA512" - private const val hardenedOffset = 0x80000000 + private const val hardenedOffset = 0x80000000L + + /** Derives the raw 32-byte private key for [path] (defaults to [DerivePath.primary]) from [seed]. */ + fun derivedKey(seed: ByteArray, path: DerivePath? = null): ByteArray { + val indexes = (path?.indexes ?: DerivePath.primary.indexes).map { hardenedOffset + it.value } + return derivedKey(seed, indexes.toLongArray()) + } - fun path(seed: ByteArray, path: DerivePath? = null): Ed25519.KeyPair { + /** Derives the raw 32-byte private key by walking [hardenedIndexes] (each already offset by 0x80000000) from [seed]. */ + fun derivedKey(seed: ByteArray, hardenedIndexes: LongArray): ByteArray { var descriptor = masterKey(seed) - (path?.indexes?.map { it } ?: DerivePath.primary.indexes).forEach { index -> - descriptor = CKDPriv( - keyDescriptor = descriptor, - index = hardenedOffset + index.value - ) + hardenedIndexes.forEach { index -> + descriptor = CKDPriv(keyDescriptor = descriptor, index = index) } - - return Ed25519.createKeyPair(descriptor.key.encodeBase64()) + return descriptor.key } private fun CKDPriv(keyDescriptor: KeyDescriptor, index: Long): KeyDescriptor { + val i = index.toInt() val entropy = mutableListOf() entropy.add(0) entropy.addAll(keyDescriptor.key.toList()) - - ByteBuffer.allocate(Int.SIZE_BYTES).apply { - putInt(index.toInt()) - entropy.addAll(array().toList()) - } + entropy.add((i ushr 24).toByte()) + entropy.add((i ushr 16).toByte()) + entropy.add((i ushr 8).toByte()) + entropy.add(i.toByte()) return split32( hmac(key = keyDescriptor.chain, message = entropy.toByteArray()) @@ -39,7 +40,7 @@ object Derive { } private fun masterKey(seed: ByteArray): KeyDescriptor { - val descriptor = hmac(curve.toByteArray(), seed) + val descriptor = hmac(curve.encodeToByteArray(), seed) return split32(descriptor) } @@ -57,9 +58,7 @@ object Derive { data class KeyDescriptor(val key: ByteArray, val chain: ByteArray) { override fun equals(other: Any?): Boolean { if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as KeyDescriptor + if (other !is KeyDescriptor) return false if (!key.contentEquals(other.key)) return false if (!chain.contentEquals(other.chain)) return false @@ -73,4 +72,4 @@ object Derive { return result } } -} \ No newline at end of file +} diff --git a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/DerivePath.kt b/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/DerivePath.kt similarity index 74% rename from libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/DerivePath.kt rename to libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/DerivePath.kt index 9e876ef1df..16fe8756c8 100644 --- a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/DerivePath.kt +++ b/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/DerivePath.kt @@ -1,7 +1,5 @@ package com.getcode.crypt -import com.getcode.model.Domain - class DerivePath(val indexes: List, val password: String? = null) { fun stringRepresentation(): String { val components = indexes.joinToString(separator) { it.stringRepresentation() } @@ -10,9 +8,7 @@ class DerivePath(val indexes: List, val password: String? = null) { override fun equals(other: Any?): Boolean { if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as DerivePath + if (other !is DerivePath) return false if (indexes != other.indexes) return false @@ -45,23 +41,6 @@ class DerivePath(val indexes: List, val password: String? = null) { return DerivePath(indexes, password) } - // Primary - m/44'/501'/0/0 - // - // Incoming - m/44'/501'/0/0/i/2 - // Outgoing - m/44'/501'/0/0/i/3 - // - // Relationship - m/44'/501'/0/0/0/0 - // Swap - m/44'/501'/0/0/1/0 * - // etc. - m/44'/501'/0/0/2/0 * - // - // Bucket1 - m/44'/501'/0/0/0/1 - // Bucket10 - m/44'/501'/0/0/0/10 - // Bucket100 - m/44'/501'/0/0/0/100 - // Bucket1k - m/44'/501'/0/0/0/1000 - // Bucket10k - m/44'/501'/0/0/0/10000 - // Bucket100k - m/44'/501'/0/0/0/100000 - // Bucket1m - m/44'/501'/0/0/0/1000000 - val bucket1 = newInstance("m/44'/501'/0'/0'/0'/1")!! val bucket10 = newInstance("m/44'/501'/0'/0'/0'/10")!! val bucket100 = newInstance("m/44'/501'/0'/0'/0'/100")!! @@ -88,12 +67,8 @@ class DerivePath(val indexes: List, val password: String? = null) { return newInstance("m/44'/501'/0'/0'/2335'/$index'")!! } - fun relationship(domain: Domain): DerivePath { - return newInstance("m/44'/501'/0'/0'/0'/0", password = domain.relationshipHost)!! - } - private const val identifier = "m" private const val separator = "/" private const val hardener = "'" } -} \ No newline at end of file +} diff --git a/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/MnemonicCode.kt b/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/MnemonicCode.kt new file mode 100644 index 0000000000..6c193b31fe --- /dev/null +++ b/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/MnemonicCode.kt @@ -0,0 +1,138 @@ +package com.getcode.crypt + +/* + * Copyright 2013 Ken Sedgwick + * Copyright 2014 Andreas Schildbach + * + * 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. + */ + +/** + * Converts between binary seed values and lists of words per the + * [BIP 39 specification](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). + */ +object MnemonicCode { + + private val wordList: List = Bip39EnglishWordList.words + + /** UNIX time for when the BIP39 standard was finalised. Use as a default seed birthday. */ + var BIP39_STANDARDISATION_TIME_SECS: Long = 1381276800L + + private const val PBKDF2_ROUNDS = 2048 + + /** Returns the word list this instance uses. */ + fun getWordList(): List = wordList + + /** + * Converts mnemonic word list to original entropy bytes. + * + * @throws MnemonicException.MnemonicLengthException if the word count is not a multiple of 3 or the list is empty + * @throws MnemonicException.MnemonicWordException if a word is not in the word list + * @throws MnemonicException.MnemonicChecksumException if the checksum does not match + */ + @Throws(MnemonicException::class) + fun toEntropy(words: List): ByteArray { + if (words.size % 3 != 0) + throw MnemonicException.MnemonicLengthException("Word list size must be multiple of three words.") + if (words.isEmpty()) + throw MnemonicException.MnemonicLengthException("Word list is empty.") + + val concatLenBits = words.size * 11 + val concatBits = BooleanArray(concatLenBits) + var wordIndex = 0 + for (word in words) { + val ndx = wordList.binarySearch(word) + if (ndx < 0) throw MnemonicException.MnemonicWordException(word) + for (ii in 0 until 11) + concatBits[wordIndex * 11 + ii] = (ndx and (1 shl (10 - ii))) != 0 + wordIndex++ + } + + val checksumLengthBits = concatLenBits / 33 + val entropyLengthBits = concatLenBits - checksumLengthBits + + val entropy = ByteArray(entropyLengthBits / 8) + for (ii in entropy.indices) + for (jj in 0 until 8) + if (concatBits[ii * 8 + jj]) + entropy[ii] = (entropy[ii].toInt() or (1 shl (7 - jj))).toByte() + + val hash = Sha256Hash.hash(entropy) + val hashBits = bytesToBits(hash) + + for (i in 0 until checksumLengthBits) + if (concatBits[entropyLengthBits + i] != hashBits[i]) + throw MnemonicException.MnemonicChecksumException() + + return entropy + } + + /** + * Converts entropy bytes to a mnemonic word list. + * + * @throws MnemonicException.MnemonicLengthException if the entropy length is not a multiple of 4 or is empty + */ + @Throws(MnemonicException.MnemonicLengthException::class) + fun toMnemonic(entropy: ByteArray): List { + if (entropy.size % 4 != 0) + throw MnemonicException.MnemonicLengthException("Entropy length not multiple of 32 bits.") + if (entropy.isEmpty()) + throw MnemonicException.MnemonicLengthException("Entropy is empty.") + + val hash = Sha256Hash.hash(entropy) + val hashBits = bytesToBits(hash) + val entropyBits = bytesToBits(entropy) + val checksumLengthBits = entropyBits.size / 32 + + val concatBits = BooleanArray(entropyBits.size + checksumLengthBits) + entropyBits.copyInto(concatBits, destinationOffset = 0) + hashBits.copyInto(concatBits, destinationOffset = entropyBits.size, endIndex = checksumLengthBits) + + val words = ArrayList() + val nwords = concatBits.size / 11 + for (i in 0 until nwords) { + var index = 0 + for (j in 0 until 11) { + index = index shl 1 + if (concatBits[i * 11 + j]) index = index or 0x1 + } + words.add(wordList[index]) + } + return words + } + + /** + * Checks whether a mnemonic word list is valid. + * + * @throws MnemonicException if validation fails + */ + @Throws(MnemonicException::class) + fun check(words: List) { + toEntropy(words) + } + + /** Converts a mnemonic word list to a 64-byte PBKDF2-SHA512 seed. */ + fun toSeed(words: List, passphrase: String): ByteArray { + val pass = words.joinToString(" ") + val salt = "mnemonic$passphrase" + return PBKDF2SHA512.derive(pass, salt, PBKDF2_ROUNDS, 64) + } + + private fun bytesToBits(data: ByteArray): BooleanArray { + val bits = BooleanArray(data.size * 8) + for (i in data.indices) + for (j in 0 until 8) + bits[i * 8 + j] = (data[i].toInt() and (1 shl (7 - j))) != 0 + return bits + } +} diff --git a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicException.kt b/libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/MnemonicException.kt similarity index 100% rename from libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicException.kt rename to libs/encryption/mnemonic/src/commonMain/kotlin/com/getcode/crypt/MnemonicException.kt diff --git a/libs/encryption/mnemonic/src/test/kotlin/com/getcode/crypt/DerivePathTest.kt b/libs/encryption/mnemonic/src/commonTest/kotlin/com/getcode/crypt/DerivePathTest.kt similarity index 100% rename from libs/encryption/mnemonic/src/test/kotlin/com/getcode/crypt/DerivePathTest.kt rename to libs/encryption/mnemonic/src/commonTest/kotlin/com/getcode/crypt/DerivePathTest.kt diff --git a/libs/encryption/mnemonic/src/commonTest/kotlin/com/getcode/crypt/Slip10DerivationVectorTest.kt b/libs/encryption/mnemonic/src/commonTest/kotlin/com/getcode/crypt/Slip10DerivationVectorTest.kt new file mode 100644 index 0000000000..50b456534c --- /dev/null +++ b/libs/encryption/mnemonic/src/commonTest/kotlin/com/getcode/crypt/Slip10DerivationVectorTest.kt @@ -0,0 +1,62 @@ +package com.getcode.crypt + +import com.getcode.ed25519kmp.Ed25519Kmp +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * GATE: BIP39 seed -> SLIP-0010 ed25519 derivation must reproduce canonical vectors on BOTH + * Android (JVM host) and iOS (native). + * Fixture: src/commonTest/resources/slip10.json (canonical source: test-vectors/slip10.json). + */ +class Slip10DerivationVectorTest { + + @Test + fun derivation_matches_canonical_vectors() { + val vectors = loadVectors() + assertTrue(vectors.isNotEmpty(), "no vectors loaded from slip10.json") + + for (v in vectors) { + val seed = MnemonicCode.toSeed(v.mnemonic.split(" "), v.passphrase) + val path = DerivePath.newInstance(v.path)!! + val derivedKey = Derive.derivedKey(seed, path) + assertEquals(v.derivedKey, derivedKey.toHex(), "derivedKey mismatch for '${v.name}'") + + val keyPair = Ed25519Kmp.createKeyPair(derivedKey) + assertEquals(v.publicKey, keyPair.publicKey.toHex(), "publicKey mismatch for '${v.name}'") + } + } + + private data class Vector( + val name: String, + val mnemonic: String, + val passphrase: String, + val path: String, + val derivedKey: String, + val publicKey: String, + ) + + private fun loadVectors(): List { + val text = readTestResource("slip10.json") + val root = Json.parseToJsonElement(text).jsonObject + return root["vectors"]!!.jsonArray.map { el -> + val o = el.jsonObject + Vector( + name = o["name"]!!.jsonPrimitive.content, + mnemonic = o["mnemonic"]!!.jsonPrimitive.content, + passphrase = o["passphrase"]!!.jsonPrimitive.content, + path = o["path"]!!.jsonPrimitive.content, + derivedKey = o["derivedKey"]!!.jsonPrimitive.content, + publicKey = o["publicKey"]!!.jsonPrimitive.content, + ) + } + } + + private fun ByteArray.toHex(): String = + joinToString("") { (it.toInt() and 0xFF).toString(16).padStart(2, '0') } +} diff --git a/libs/encryption/mnemonic/src/commonTest/resources/slip10.json b/libs/encryption/mnemonic/src/commonTest/resources/slip10.json new file mode 100644 index 0000000000..fbb0e3331e --- /dev/null +++ b/libs/encryption/mnemonic/src/commonTest/resources/slip10.json @@ -0,0 +1,66 @@ +{ + "algorithm": "bip39+slip10-ed25519", + "note": "BIP39 seed -> SLIP-0010 ed25519 (all indices force-hardened) -> ed25519 keypair. Apps must reproduce publicKey/address for each mnemonic+path.", + "vectors": [ + { + "name": "abandon-x11-about m/44'/501'/0'/0'", + "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "passphrase": "", + "path": "m/44'/501'/0'/0'", + "seedBip39": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", + "derivedKey": "37df573b3ac4ad5b522e064e25b63ea16bcbe79d449e81a0268d1047948bb445", + "publicKey": "f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7", + "address": "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk" + }, + { + "name": "abandon-x11-about m/44'/501'/0'/0'/7665'/0", + "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "passphrase": "", + "path": "m/44'/501'/0'/0'/7665'/0", + "seedBip39": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", + "derivedKey": "1f317a98c39b1459a28fbc5d7e4ed9c9799dc69d194157ec7b9d645c4ed3a474", + "publicKey": "051d88bbb9c70cc1045090c3c01675620b060123f2d1f1700d8ef1f5dadcc5d8", + "address": "LyACZbC6QzPP3ixxyBjuCC1sKWuAi1bZU1xgTuFVpRD" + }, + { + "name": "abandon-x11-about m/44'/501'/0'/0'/2335'/5", + "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "passphrase": "", + "path": "m/44'/501'/0'/0'/2335'/5", + "seedBip39": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", + "derivedKey": "4b83957e1b5ca6b16e9f39fdd38c88a6a96c38e61f9b4cecb7fda43e5e249764", + "publicKey": "7bc1de0ce3459814c74dae0529df060261d896dea23264e298eb90d1d236c123", + "address": "9L6c3GSoNLLXbXAoxCgHR1fkhbX96jNaLqmQnsRYUCo4" + }, + { + "name": "legal-winner m/44'/501'/0'/0'", + "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank yellow", + "passphrase": "", + "path": "m/44'/501'/0'/0'", + "seedBip39": "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096", + "derivedKey": "6987bdb06aa8a243a3019f41489ffa8e609c953a885a748d1849a8df760aa479", + "publicKey": "999d46fb3d1256f7049c8ed09314d7268612e8a91b800e91934463848305c98c", + "address": "BLeUXTx9thHGT7VJUtF9vHEmfMDgW1nnKZ9UVer2CoLX" + }, + { + "name": "legal-winner m/44'/501'/0'/0'/7665'/0", + "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank yellow", + "passphrase": "", + "path": "m/44'/501'/0'/0'/7665'/0", + "seedBip39": "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096", + "derivedKey": "82c2531aae3c058eb076618c696c3d496c3249b066dd528cb83adc4f4bbe8294", + "publicKey": "9ed602ebfe2399e961e3a08535a50fd4391fdefc0341b1c790e0c4947ccd16a0", + "address": "Bh2gxt6UiDWjWkmsfK9qp46kRrjTPgvXn7DhvaPTxUr3" + }, + { + "name": "legal-winner m/44'/501'/0'/0'/2335'/5", + "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank yellow", + "passphrase": "", + "path": "m/44'/501'/0'/0'/2335'/5", + "seedBip39": "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096", + "derivedKey": "4eb3bb8632aa83691f51a4cff44021ff785df069432007bb023e858b6416f6ec", + "publicKey": "b5310f3190a4a39d5146f17072a767fb5c566593e4f2417d293d11fcd3b246f7", + "address": "DCJBY1iQroEzsNi2BMwu3zNmnB27rCV6iJHHJDAqTTmg" + } + ] +} diff --git a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicCache.kt b/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicCache.kt deleted file mode 100644 index 949740b19f..0000000000 --- a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicCache.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.getcode.crypt - -import android.content.Context - -object MnemonicCache { - lateinit var cachedCode: MnemonicCode - private set - - fun init(context: Context) { - cachedCode = MnemonicCode(context.resources) - } - - val cache = mutableMapOf, String>, ByteArray>() -} \ No newline at end of file diff --git a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicCode.kt b/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicCode.kt deleted file mode 100644 index 3a305e20da..0000000000 --- a/libs/encryption/mnemonic/src/main/kotlin/com/getcode/crypt/MnemonicCode.kt +++ /dev/null @@ -1,237 +0,0 @@ -package com.getcode.crypt - -/* - * Copyright 2013 Ken Sedgwick - * Copyright 2014 Andreas Schildbach - * - * 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. - */ - -import android.content.res.Resources -import com.getcode.encryption.mnemonic.R -import com.getcode.utils.Utils -import com.google.common.base.Stopwatch -import timber.log.Timber -import java.io.BufferedReader -import java.io.FileNotFoundException -import java.io.IOException -import java.io.InputStream -import java.io.InputStreamReader -import java.security.MessageDigest - -/** - * Converts between binary seed values and lists of words per the - * [BIP 39 specification](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). - */ -class MnemonicCode { - - var wordList: ArrayList = ArrayList() - private set - - /** - * Initialises from the included word list (requires an [android.content.res.Resources] - * instance to open the raw resource; not usable on bare JVM). - */ - @Throws(IOException::class) - constructor(resources: Resources?) { - val stream = openDefaultWords(resources) ?: return - init(stream, BIP39_ENGLISH_SHA256) - } - - /** - * Initialises with words read from [wordstream]. If [wordListDigest] is non-null the - * SHA-256 hex digest of the word list is verified against it. - */ - @Throws(IOException::class, IllegalArgumentException::class) - constructor(wordstream: InputStream?, wordListDigest: String?) { - wordstream ?: return - init(wordstream, wordListDigest) - } - - @Throws(IOException::class) - private fun init(wordstream: InputStream, wordListDigest: String?) { - val br = BufferedReader(InputStreamReader(wordstream, Charsets.UTF_8)) - val list = ArrayList(2048) - val md: MessageDigest = MessageDigest.getInstance("SHA-256") - br.forEachLine { word -> - md.update(word.toByteArray()) - list.add(word) - } - br.close() - - if (list.size != 2048) throw IllegalArgumentException("input stream did not contain 2048 words") - - if (wordListDigest != null) { - val hexDigest = md.digest().toHexString() - if (hexDigest != wordListDigest) throw IllegalArgumentException("wordlist digest mismatch") - } - - wordList = list - } - - /** Returns the word list this instance uses. */ - fun getWordList(): List = wordList - - /** - * Converts mnemonic word list to original entropy bytes. - * - * @throws MnemonicException.MnemonicLengthException if the word count is not a multiple of 3 or the list is empty - * @throws MnemonicException.MnemonicWordException if a word is not in the word list - * @throws MnemonicException.MnemonicChecksumException if the checksum does not match - */ - @Throws(MnemonicException::class) - fun toEntropy(words: List): ByteArray { - if (words.size % 3 != 0) - throw MnemonicException.MnemonicLengthException("Word list size must be multiple of three words.") - if (words.isEmpty()) - throw MnemonicException.MnemonicLengthException("Word list is empty.") - - val concatLenBits = words.size * 11 - val concatBits = BooleanArray(concatLenBits) - var wordIndex = 0 - for (word in words) { - val ndx = wordList.binarySearch(word) - if (ndx < 0) throw MnemonicException.MnemonicWordException(word) - for (ii in 0 until 11) - concatBits[wordIndex * 11 + ii] = (ndx and (1 shl (10 - ii))) != 0 - wordIndex++ - } - - val checksumLengthBits = concatLenBits / 33 - val entropyLengthBits = concatLenBits - checksumLengthBits - - val entropy = ByteArray(entropyLengthBits / 8) - for (ii in entropy.indices) - for (jj in 0 until 8) - if (concatBits[ii * 8 + jj]) - entropy[ii] = (entropy[ii].toInt() or (1 shl (7 - jj))).toByte() - - val hash = Sha256Hash.hash(entropy) - val hashBits = bytesToBits(hash) - - for (i in 0 until checksumLengthBits) - if (concatBits[entropyLengthBits + i] != hashBits[i]) - throw MnemonicException.MnemonicChecksumException() - - return entropy - } - - /** - * Converts entropy bytes to a mnemonic word list. - * - * @throws MnemonicException.MnemonicLengthException if the entropy length is not a multiple of 4 or is empty - */ - @Throws(MnemonicException.MnemonicLengthException::class) - fun toMnemonic(entropy: ByteArray): List { - if (entropy.size % 4 != 0) - throw MnemonicException.MnemonicLengthException("Entropy length not multiple of 32 bits.") - if (entropy.isEmpty()) - throw MnemonicException.MnemonicLengthException("Entropy is empty.") - - val hash = Sha256Hash.hash(entropy) - val hashBits = bytesToBits(hash) - val entropyBits = bytesToBits(entropy) - val checksumLengthBits = entropyBits.size / 32 - - val concatBits = BooleanArray(entropyBits.size + checksumLengthBits) - entropyBits.copyInto(concatBits, destinationOffset = 0) - hashBits.copyInto(concatBits, destinationOffset = entropyBits.size, endIndex = checksumLengthBits) - - val words = ArrayList() - val nwords = concatBits.size / 11 - for (i in 0 until nwords) { - var index = 0 - for (j in 0 until 11) { - index = index shl 1 - if (concatBits[i * 11 + j]) index = index or 0x1 - } - words.add(wordList[index]) - } - return words - } - - /** - * Checks whether a mnemonic word list is valid. - * - * @throws MnemonicException if validation fails - */ - @Throws(MnemonicException::class) - fun check(words: List) { - toEntropy(words) - } - - companion object { - const val TAG: String = "MnemonicCode" - - private const val BIP39_ENGLISH_RESOURCE_NAME = "english.txt" - private const val BIP39_ENGLISH_SHA256 = "ad90bf3beb7b0eb7e5acd74727dc0da96e0a280a258354e7293fb7e211ac03db" - - /** UNIX time for when the BIP39 standard was finalised. Use as a default seed birthday. */ - @JvmField - var BIP39_STANDARDISATION_TIME_SECS: Long = 1381276800L - - private const val PBKDF2_ROUNDS = 2048 - - /** Shared instance (null until set, e.g. via [MnemonicCache.init]). */ - @JvmField - var INSTANCE: MnemonicCode? = null - - init { - try { - INSTANCE = MnemonicCode(null as Resources?) - } catch (e: FileNotFoundException) { - if (!Utils.isAndroidRuntime()) Timber.e("Could not find word list") - } catch (e: IOException) { - Timber.e("Failed to load word list") - } - } - - /** Converts a mnemonic word list to a 64-byte PBKDF2-SHA512 seed. */ - @JvmStatic - fun toSeed(words: List, passphrase: String): ByteArray { - requireNotNull(passphrase) { "A null passphrase is not allowed." } - val pass = words.joinToString(" ") - val salt = "mnemonic$passphrase" - val watch = Stopwatch.createStarted() - val seed = PBKDF2SHA512.derive(pass, salt, PBKDF2_ROUNDS, 64) - watch.stop() - Timber.i("PBKDF2 took {} %s", watch) - return seed - } - - private fun openDefaultWords(resources: Resources?): InputStream? { - resources ?: return null - val stream = resources.openRawResource(R.raw.english) - ?: throw FileNotFoundException(BIP39_ENGLISH_RESOURCE_NAME) - return stream - } - - private fun bytesToBits(data: ByteArray): BooleanArray { - val bits = BooleanArray(data.size * 8) - for (i in data.indices) - for (j in 0 until 8) - bits[i * 8 + j] = (data[i].toInt() and (1 shl (7 - j))) != 0 - return bits - } - - private fun ByteArray.toHexString(): String { - val sb = StringBuilder(size * 2) - for (b in this) { - val v = b.toInt() and 0xFF - sb.append("0123456789abcdef"[v ushr 4]) - sb.append("0123456789abcdef"[v and 0x0F]) - } - return sb.toString() - } - } -} diff --git a/libs/encryption/mnemonic/src/main/res/raw/english.txt b/libs/encryption/mnemonic/src/main/res/raw/english.txt deleted file mode 100644 index 942040ed50..0000000000 --- a/libs/encryption/mnemonic/src/main/res/raw/english.txt +++ /dev/null @@ -1,2048 +0,0 @@ -abandon -ability -able -about -above -absent -absorb -abstract -absurd -abuse -access -accident -account -accuse -achieve -acid -acoustic -acquire -across -act -action -actor -actress -actual -adapt -add -addict -address -adjust -admit -adult -advance -advice -aerobic -affair -afford -afraid -again -age -agent -agree -ahead -aim -air -airport -aisle -alarm -album -alcohol -alert -alien -all -alley -allow -almost -alone -alpha -already -also -alter -always -amateur -amazing -among -amount -amused -analyst -anchor -ancient -anger -angle -angry -animal -ankle -announce -annual -another -answer -antenna -antique -anxiety -any -apart -apology -appear -apple -approve -april -arch -arctic -area -arena -argue -arm -armed -armor -army -around -arrange -arrest -arrive -arrow -art -artefact -artist -artwork -ask -aspect -assault -asset -assist -assume -asthma -athlete -atom -attack -attend -attitude -attract -auction -audit -august -aunt -author -auto -autumn -average -avocado -avoid -awake -aware -away -awesome -awful -awkward -axis -baby -bachelor -bacon -badge -bag -balance -balcony -ball -bamboo -banana -banner -bar -barely -bargain -barrel -base -basic -basket -battle -beach -bean -beauty -because -become -beef -before -begin -behave -behind -believe -below -belt -bench -benefit -best -betray -better -between -beyond -bicycle -bid -bike -bind -biology -bird -birth -bitter -black -blade -blame -blanket -blast -bleak -bless -blind -blood -blossom -blouse -blue -blur -blush -board -boat -body -boil -bomb -bone -bonus -book -boost -border -boring -borrow -boss -bottom -bounce -box -boy -bracket -brain -brand -brass -brave -bread -breeze -brick -bridge -brief -bright -bring -brisk -broccoli -broken -bronze -broom -brother -brown -brush -bubble -buddy -budget -buffalo -build -bulb -bulk -bullet -bundle -bunker -burden -burger -burst -bus -business -busy -butter -buyer -buzz -cabbage -cabin -cable -cactus -cage -cake -call -calm -camera -camp -can -canal -cancel -candy -cannon -canoe -canvas -canyon -capable -capital -captain -car -carbon -card -cargo -carpet -carry -cart -case -cash -casino -castle -casual -cat -catalog -catch -category -cattle -caught -cause -caution -cave -ceiling -celery -cement -census -century -cereal -certain -chair -chalk -champion -change -chaos -chapter -charge -chase -chat -cheap -check -cheese -chef -cherry -chest -chicken -chief -child -chimney -choice -choose -chronic -chuckle -chunk -churn -cigar -cinnamon -circle -citizen -city -civil -claim -clap -clarify -claw -clay -clean -clerk -clever -click -client -cliff -climb -clinic -clip -clock -clog -close -cloth -cloud -clown -club -clump -cluster -clutch -coach -coast -coconut -code -coffee -coil -coin -collect -color -column -combine -come -comfort -comic -common -company -concert -conduct -confirm -congress -connect -consider -control -convince -cook -cool -copper -copy -coral -core -corn -correct -cost -cotton -couch -country -couple -course -cousin -cover -coyote -crack -cradle -craft -cram -crane -crash -crater -crawl -crazy -cream -credit -creek -crew -cricket -crime -crisp -critic -crop -cross -crouch -crowd -crucial -cruel -cruise -crumble -crunch -crush -cry -crystal -cube -culture -cup -cupboard -curious -current -curtain -curve -cushion -custom -cute -cycle -dad -damage -damp -dance -danger -daring -dash -daughter -dawn -day -deal -debate -debris -decade -december -decide -decline -decorate -decrease -deer -defense -define -defy -degree -delay -deliver -demand -demise -denial -dentist -deny -depart -depend -deposit -depth -deputy -derive -describe -desert -design -desk -despair -destroy -detail -detect -develop -device -devote -diagram -dial -diamond -diary -dice -diesel -diet -differ -digital -dignity -dilemma -dinner -dinosaur -direct -dirt -disagree -discover -disease -dish -dismiss -disorder -display -distance -divert -divide -divorce -dizzy -doctor -document -dog -doll -dolphin -domain -donate -donkey -donor -door -dose -double -dove -draft -dragon -drama -drastic -draw -dream -dress -drift -drill -drink -drip -drive -drop -drum -dry -duck -dumb -dune -during -dust -dutch -duty -dwarf -dynamic -eager -eagle -early -earn -earth -easily -east -easy -echo -ecology -economy -edge -edit -educate -effort -egg -eight -either -elbow -elder -electric -elegant -element -elephant -elevator -elite -else -embark -embody -embrace -emerge -emotion -employ -empower -empty -enable -enact -end -endless -endorse -enemy -energy -enforce -engage -engine -enhance -enjoy -enlist -enough -enrich -enroll -ensure -enter -entire -entry -envelope -episode -equal -equip -era -erase -erode -erosion -error -erupt -escape -essay -essence -estate -eternal -ethics -evidence -evil -evoke -evolve -exact -example -excess -exchange -excite -exclude -excuse -execute -exercise -exhaust -exhibit -exile -exist -exit -exotic -expand -expect -expire -explain -expose -express -extend -extra -eye -eyebrow -fabric -face -faculty -fade -faint -faith -fall -false -fame -family -famous -fan -fancy -fantasy -farm -fashion -fat -fatal -father -fatigue -fault -favorite -feature -february -federal -fee -feed -feel -female -fence -festival -fetch -fever -few -fiber -fiction -field -figure -file -film -filter -final -find -fine -finger -finish -fire -firm -first -fiscal -fish -fit -fitness -fix -flag -flame -flash -flat -flavor -flee -flight -flip -float -flock -floor -flower -fluid -flush -fly -foam -focus -fog -foil -fold -follow -food -foot -force -forest -forget -fork -fortune -forum -forward -fossil -foster -found -fox -fragile -frame -frequent -fresh -friend -fringe -frog -front -frost -frown -frozen -fruit -fuel -fun -funny -furnace -fury -future -gadget -gain -galaxy -gallery -game -gap -garage -garbage -garden -garlic -garment -gas -gasp -gate -gather -gauge -gaze -general -genius -genre -gentle -genuine -gesture -ghost -giant -gift -giggle -ginger -giraffe -girl -give -glad -glance -glare -glass -glide -glimpse -globe -gloom -glory -glove -glow -glue -goat -goddess -gold -good -goose -gorilla -gospel -gossip -govern -gown -grab -grace -grain -grant -grape -grass -gravity -great -green -grid -grief -grit -grocery -group -grow -grunt -guard -guess -guide -guilt -guitar -gun -gym -habit -hair -half -hammer -hamster -hand -happy -harbor -hard -harsh -harvest -hat -have -hawk -hazard -head -health -heart -heavy -hedgehog -height -hello -helmet -help -hen -hero -hidden -high -hill -hint -hip -hire -history -hobby -hockey -hold -hole -holiday -hollow -home -honey -hood -hope -horn -horror -horse -hospital -host -hotel -hour -hover -hub -huge -human -humble -humor -hundred -hungry -hunt -hurdle -hurry -hurt -husband -hybrid -ice -icon -idea -identify -idle -ignore -ill -illegal -illness -image -imitate -immense -immune -impact -impose -improve -impulse -inch -include -income -increase -index -indicate -indoor -industry -infant -inflict -inform -inhale -inherit -initial -inject -injury -inmate -inner -innocent -input -inquiry -insane -insect -inside -inspire -install -intact -interest -into -invest -invite -involve -iron -island -isolate -issue -item -ivory -jacket -jaguar -jar -jazz -jealous -jeans -jelly -jewel -job -join -joke -journey -joy -judge -juice -jump -jungle -junior -junk -just -kangaroo -keen -keep -ketchup -key -kick -kid -kidney -kind -kingdom -kiss -kit -kitchen -kite -kitten -kiwi -knee -knife -knock -know -lab -label -labor -ladder -lady -lake -lamp -language -laptop -large -later -latin -laugh -laundry -lava -law -lawn -lawsuit -layer -lazy -leader -leaf -learn -leave -lecture -left -leg -legal -legend -leisure -lemon -lend -length -lens -leopard -lesson -letter -level -liar -liberty -library -license -life -lift -light -like -limb -limit -link -lion -liquid -list -little -live -lizard -load -loan -lobster -local -lock -logic -lonely -long -loop -lottery -loud -lounge -love -loyal -lucky -luggage -lumber -lunar -lunch -luxury -lyrics -machine -mad -magic -magnet -maid -mail -main -major -make -mammal -man -manage -mandate -mango -mansion -manual -maple -marble -march -margin -marine -market -marriage -mask -mass -master -match -material -math -matrix -matter -maximum -maze -meadow -mean -measure -meat -mechanic -medal -media -melody -melt -member -memory -mention -menu -mercy -merge -merit -merry -mesh -message -metal -method -middle -midnight -milk -million -mimic -mind -minimum -minor -minute -miracle -mirror -misery -miss -mistake -mix -mixed -mixture -mobile -model -modify -mom -moment -monitor -monkey -monster -month -moon -moral -more -morning -mosquito -mother -motion -motor -mountain -mouse -move -movie -much -muffin -mule -multiply -muscle -museum -mushroom -music -must -mutual -myself -mystery -myth -naive -name -napkin -narrow -nasty -nation -nature -near -neck -need -negative -neglect -neither -nephew -nerve -nest -net -network -neutral -never -news -next -nice -night -noble -noise -nominee -noodle -normal -north -nose -notable -note -nothing -notice -novel -now -nuclear -number -nurse -nut -oak -obey -object -oblige -obscure -observe -obtain -obvious -occur -ocean -october -odor -off -offer -office -often -oil -okay -old -olive -olympic -omit -once -one -onion -online -only -open -opera -opinion -oppose -option -orange -orbit -orchard -order -ordinary -organ -orient -original -orphan -ostrich -other -outdoor -outer -output -outside -oval -oven -over -own -owner -oxygen -oyster -ozone -pact -paddle -page -pair -palace -palm -panda -panel -panic -panther -paper -parade -parent -park -parrot -party -pass -patch -path -patient -patrol -pattern -pause -pave -payment -peace -peanut -pear -peasant -pelican -pen -penalty -pencil -people -pepper -perfect -permit -person -pet -phone -photo -phrase -physical -piano -picnic -picture -piece -pig -pigeon -pill -pilot -pink -pioneer -pipe -pistol -pitch -pizza -place -planet -plastic -plate -play -please -pledge -pluck -plug -plunge -poem -poet -point -polar -pole -police -pond -pony -pool -popular -portion -position -possible -post -potato -pottery -poverty -powder -power -practice -praise -predict -prefer -prepare -present -pretty -prevent -price -pride -primary -print -priority -prison -private -prize -problem -process -produce -profit -program -project -promote -proof -property -prosper -protect -proud -provide -public -pudding -pull -pulp -pulse -pumpkin -punch -pupil -puppy -purchase -purity -purpose -purse -push -put -puzzle -pyramid -quality -quantum -quarter -question -quick -quit -quiz -quote -rabbit -raccoon -race -rack -radar -radio -rail -rain -raise -rally -ramp -ranch -random -range -rapid -rare -rate -rather -raven -raw -razor -ready -real -reason -rebel -rebuild -recall -receive -recipe -record -recycle -reduce -reflect -reform -refuse -region -regret -regular -reject -relax -release -relief -rely -remain -remember -remind -remove -render -renew -rent -reopen -repair -repeat -replace -report -require -rescue -resemble -resist -resource -response -result -retire -retreat -return -reunion -reveal -review -reward -rhythm -rib -ribbon -rice -rich -ride -ridge -rifle -right -rigid -ring -riot -ripple -risk -ritual -rival -river -road -roast -robot -robust -rocket -romance -roof -rookie -room -rose -rotate -rough -round -route -royal -rubber -rude -rug -rule -run -runway -rural -sad -saddle -sadness -safe -sail -salad -salmon -salon -salt -salute -same -sample -sand -satisfy -satoshi -sauce -sausage -save -say -scale -scan -scare -scatter -scene -scheme -school -science -scissors -scorpion -scout -scrap -screen -script -scrub -sea -search -season -seat -second -secret -section -security -seed -seek -segment -select -sell -seminar -senior -sense -sentence -series -service -session -settle -setup -seven -shadow -shaft -shallow -share -shed -shell -sheriff -shield -shift -shine -ship -shiver -shock -shoe -shoot -shop -short -shoulder -shove -shrimp -shrug -shuffle -shy -sibling -sick -side -siege -sight -sign -silent -silk -silly -silver -similar -simple -since -sing -siren -sister -situate -six -size -skate -sketch -ski -skill -skin -skirt -skull -slab -slam -sleep -slender -slice -slide -slight -slim -slogan -slot -slow -slush -small -smart -smile -smoke -smooth -snack -snake -snap -sniff -snow -soap -soccer -social -sock -soda -soft -solar -soldier -solid -solution -solve -someone -song -soon -sorry -sort -soul -sound -soup -source -south -space -spare -spatial -spawn -speak -special -speed -spell -spend -sphere -spice -spider -spike -spin -spirit -split -spoil -sponsor -spoon -sport -spot -spray -spread -spring -spy -square -squeeze -squirrel -stable -stadium -staff -stage -stairs -stamp -stand -start -state -stay -steak -steel -stem -step -stereo -stick -still -sting -stock -stomach -stone -stool -story -stove -strategy -street -strike -strong -struggle -student -stuff -stumble -style -subject -submit -subway -success -such -sudden -suffer -sugar -suggest -suit -summer -sun -sunny -sunset -super -supply -supreme -sure -surface -surge -surprise -surround -survey -suspect -sustain -swallow -swamp -swap -swarm -swear -sweet -swift -swim -swing -switch -sword -symbol -symptom -syrup -system -table -tackle -tag -tail -talent -talk -tank -tape -target -task -taste -tattoo -taxi -teach -team -tell -ten -tenant -tennis -tent -term -test -text -thank -that -theme -then -theory -there -they -thing -this -thought -three -thrive -throw -thumb -thunder -ticket -tide -tiger -tilt -timber -time -tiny -tip -tired -tissue -title -toast -tobacco -today -toddler -toe -together -toilet -token -tomato -tomorrow -tone -tongue -tonight -tool -tooth -top -topic -topple -torch -tornado -tortoise -toss -total -tourist -toward -tower -town -toy -track -trade -traffic -tragic -train -transfer -trap -trash -travel -tray -treat -tree -trend -trial -tribe -trick -trigger -trim -trip -trophy -trouble -truck -true -truly -trumpet -trust -truth -try -tube -tuition -tumble -tuna -tunnel -turkey -turn -turtle -twelve -twenty -twice -twin -twist -two -type -typical -ugly -umbrella -unable -unaware -uncle -uncover -under -undo -unfair -unfold -unhappy -uniform -unique -unit -universe -unknown -unlock -until -unusual -unveil -update -upgrade -uphold -upon -upper -upset -urban -urge -usage -use -used -useful -useless -usual -utility -vacant -vacuum -vague -valid -valley -valve -van -vanish -vapor -various -vast -vault -vehicle -velvet -vendor -venture -venue -verb -verify -version -very -vessel -veteran -viable -vibrant -vicious -victory -video -view -village -vintage -violin -virtual -virus -visa -visit -visual -vital -vivid -vocal -voice -void -volcano -volume -vote -voyage -wage -wagon -wait -walk -wall -walnut -want -warfare -warm -warrior -wash -wasp -waste -water -wave -way -wealth -weapon -wear -weasel -weather -web -wedding -weekend -weird -welcome -west -wet -whale -what -wheat -wheel -when -where -whip -whisper -wide -width -wife -wild -will -win -window -wine -wing -wink -winner -winter -wire -wisdom -wise -wish -witness -wolf -woman -wonder -wood -wool -word -work -world -worry -worth -wrap -wreck -wrestle -wrist -write -wrong -yard -year -yellow -you -young -youth -zebra -zero -zone -zoo diff --git a/libs/encryption/sha256/build.gradle.kts b/libs/encryption/sha256/build.gradle.kts index 7c220c0bde..b6a586a0bb 100644 --- a/libs/encryption/sha256/build.gradle.kts +++ b/libs/encryption/sha256/build.gradle.kts @@ -14,6 +14,8 @@ kotlin { iosArm64() iosSimulatorArm64() iosX64() + macosArm64() + macosX64() sourceSets { commonMain { diff --git a/libs/encryption/sha512/build.gradle.kts b/libs/encryption/sha512/build.gradle.kts index b80649b67b..cc14323851 100644 --- a/libs/encryption/sha512/build.gradle.kts +++ b/libs/encryption/sha512/build.gradle.kts @@ -14,6 +14,8 @@ kotlin { iosArm64() iosSimulatorArm64() iosX64() + macosArm64() + macosX64() sourceSets { commonMain { diff --git a/libs/encryption/utils/build.gradle.kts b/libs/encryption/utils/build.gradle.kts index ebc11adb14..b6da12b8d8 100644 --- a/libs/encryption/utils/build.gradle.kts +++ b/libs/encryption/utils/build.gradle.kts @@ -14,6 +14,8 @@ kotlin { iosArm64() iosSimulatorArm64() iosX64() + macosArm64() + macosX64() sourceSets { commonMain { diff --git a/settings.gradle.kts b/settings.gradle.kts index 8d0c2b498d..3170afe6d0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -294,8 +294,14 @@ val kmpUnitTestModules = setOf( // parity is gated via the iOS cinterop path (macOS) instead — so it's excluded here. ":libs:encryption:utils", ) -// ed25519 excluded: its only test is a JNI host vector test that can't run on Linux CI (see kmpUnitTestModules). -val noUnitTestModules = setOf(":apps:flipcash:benchmark", ":kmp:shared-core", ":libs:encryption:ed25519") +// ed25519 and mnemonic excluded: both pull in the JNI-backed Ed25519Kmp Android actual for their +// host vector tests, which can't load on the Linux CI runner (see kmpUnitTestModules). +val noUnitTestModules = setOf( + ":apps:flipcash:benchmark", + ":kmp:shared-core", + ":libs:encryption:ed25519", + ":libs:encryption:mnemonic", +) val unitTestCandidates = includedProjectPaths.filter { path -> unitTestPaths.any { path == it || path.startsWith("$it:") } && path !in noUnitTestModules }