Skip to content

Commit 8a5fb0c

Browse files
committed
add js target for tweetnacl
1 parent 474026e commit 8a5fb0c

File tree

9 files changed

+154
-15
lines changed

9 files changed

+154
-15
lines changed

libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ ktorClientDarwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor"
3939
ktorClientLogging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
4040
ktorClientOkHttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
4141
ktorClientWinHttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" }
42+
ktorClientJs = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
4243
ktorSerializationKotlinxJson = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
4344
ktorUtils = { module = "io.ktor:ktor-utils", version.ref = "ktor" }
4445
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }

solana-kotlin/src/commonTest/kotlin/net/avianlabs/solana/domain/program/SystemProgramTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package net.avianlabs.solana.domain.program
22

33
import kotlinx.coroutines.delay
4-
import kotlinx.coroutines.runBlocking
4+
import kotlinx.coroutines.test.runTest
55
import net.avianlabs.solana.SolanaClient
66
import net.avianlabs.solana.client.RpcKtorClient
77
import net.avianlabs.solana.domain.core.Commitment
@@ -11,7 +11,6 @@ import net.avianlabs.solana.domain.core.decode
1111
import net.avianlabs.solana.methods.*
1212
import net.avianlabs.solana.tweetnacl.TweetNaCl
1313
import kotlin.random.Random
14-
import kotlin.test.Ignore
1514
import kotlin.test.Test
1615
import kotlin.time.Duration.Companion.seconds
1716

solana-kotlin/src/commonTest/kotlin/net/avianlabs/solana/vendor/IsOnCurveTest.kt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,11 @@ import kotlinx.coroutines.launch
55
import kotlinx.coroutines.test.runTest
66
import net.avianlabs.solana.domain.program.ProgramDerivedAddress
77
import net.avianlabs.solana.domain.program.associatedTokenAddress
8-
import net.avianlabs.solana.domain.randomKey
98
import net.avianlabs.solana.tweetnacl.ed25519.PublicKey
109
import kotlin.test.Test
1110
import kotlin.test.assertEquals
12-
import kotlin.test.assertFalse
13-
import kotlin.test.assertTrue
1411

1512
class IsOnCurveTest {
16-
@Test
17-
fun testIsOnCurve() {
18-
val offCurve = PublicKey.fromBase58("12rqwuEgBYiGhBrDJStCiqEtzQpTTiZbh7teNVLuYcFA")
19-
assertFalse(offCurve.isOnCurve())
20-
21-
val onCurve = randomKey().publicKey
22-
assertTrue(onCurve.isOnCurve())
23-
}
24-
2513
@Test
2614
fun testParallelAssociatedAddress() = runTest {
2715
val x = PublicKey.fromBase58("4rZoSK72jVaAW1ayZLrefdMPAAStRVhCfH1PSundaoNt")

tweetnacl-multiplatform/build.gradle.kts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import co.touchlab.cklib.gradle.CompileToBitcode.Language
2+
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
23
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
34

45
plugins {
@@ -40,6 +41,30 @@ kotlin {
4041
mingwX64()
4142
linuxX64()
4243

44+
js(KotlinJsCompilerType.IR) {
45+
browser {
46+
compilations.all {
47+
kotlinOptions {
48+
sourceMap = true
49+
sourceMapEmbedSources = "always"
50+
}
51+
}
52+
testTask {
53+
useMocha()
54+
}
55+
}
56+
nodejs {
57+
compilations.all {
58+
kotlinOptions {
59+
sourceMap = true
60+
sourceMapEmbedSources = "always"
61+
}
62+
}
63+
}
64+
generateTypeScriptDefinitions()
65+
useEsModules()
66+
}
67+
4368
sourceSets {
4469
val jvmMain by getting {
4570
dependencies {
@@ -57,6 +82,7 @@ kotlin {
5782
dependencies {
5883
implementation(libs.kotlinTest)
5984
implementation(libs.coroutinesTest)
85+
implementation(libs.ktorUtils)
6086
}
6187
}
6288

@@ -70,6 +96,13 @@ kotlin {
7096
val nativeMain by getting {
7197
}
7298

99+
val jsMain by getting {
100+
dependencies {
101+
implementation(npm("tweetnacl", "1.0.3"))
102+
implementation(npm("@noble/curves", "1.4.0"))
103+
}
104+
}
105+
73106
targets.withType<KotlinNativeTarget> {
74107
val main by compilations.getting
75108

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package net.avianlabs.solana.tweetnacl.net.avianlabs.solana.vendor
2+
3+
import net.avianlabs.solana.tweetnacl.TweetNaCl
4+
import net.avianlabs.solana.tweetnacl.ed25519.PublicKey
5+
import kotlin.random.Random
6+
import kotlin.test.Test
7+
import kotlin.test.assertFalse
8+
import kotlin.test.assertTrue
9+
10+
class IsOnCurveTest {
11+
@Test
12+
fun testIsOnCurve() {
13+
val offCurve = PublicKey.fromBase58("12rqwuEgBYiGhBrDJStCiqEtzQpTTiZbh7teNVLuYcFA")
14+
assertFalse(offCurve.isOnCurve())
15+
16+
val onCurve = TweetNaCl.Signature.generateKey(Random.nextBytes(32)).publicKey
17+
assertTrue(onCurve.isOnCurve())
18+
}
19+
}

solana-kotlin/src/commonTest/kotlin/net/avianlabs/solana/vendor/Sha256Test.kt renamed to tweetnacl-multiplatform/src/commonTest/kotlin/net/avianlabs/solana/vendor/Sha256Test.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package net.avianlabs.solana.vendor
1+
package net.avianlabs.solana.tweetnacl.net.avianlabs.solana.vendor
22

33
import io.ktor.util.*
44
import kotlinx.coroutines.Dispatchers
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package net.avianlabs.solana.tweetnacl
2+
3+
import org.khronos.webgl.Uint8Array
4+
5+
@JsModule("@noble/curves/ed25519")
6+
internal external object Curves {
7+
val ed25519: Ed25519
8+
}
9+
10+
internal external interface Ed25519 {
11+
@JsName("ExtendedPoint")
12+
val extendedPoint: ExtendedPoint
13+
}
14+
15+
internal external interface ExtendedPoint {
16+
fun fromHex(publicKey: Uint8Array): dynamic
17+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package net.avianlabs.solana.tweetnacl
2+
3+
import net.avianlabs.solana.tweetnacl.ed25519.Ed25519Keypair
4+
import org.khronos.webgl.Uint8Array
5+
import org.khronos.webgl.get
6+
7+
internal actual fun signInternal(message: ByteArray, secretKey: ByteArray): ByteArray =
8+
tweetNaclJs.sign.detached(message.asUint8Array(), secretKey.asUint8Array()).asByteArray()
9+
10+
internal actual fun isOnCurveInternal(publicKey: ByteArray): Boolean =
11+
try {
12+
Curves.ed25519.extendedPoint.fromHex(publicKey.asUint8Array())
13+
true
14+
} catch (e: Throwable) {
15+
println(e.stackTraceToString())
16+
false
17+
}
18+
19+
internal actual fun generateKeyInternal(seed: ByteArray): Ed25519Keypair {
20+
val bytes = tweetNaclJs.sign.keyPair.fromSeed(seed.asUint8Array())
21+
return Ed25519Keypair.fromSecretKeyBytes(bytes.secretKey.asByteArray())
22+
}
23+
24+
internal actual fun secretBoxInternal(secretKey: ByteArray): TweetNaCl.SecretBox =
25+
object : TweetNaCl.SecretBox {
26+
override fun box(message: ByteArray, nonce: ByteArray): ByteArray {
27+
return tweetNaclJs.secretbox(
28+
message.asUint8Array(),
29+
nonce.asUint8Array(),
30+
secretKey.asUint8Array()
31+
).asByteArray()
32+
}
33+
34+
override fun open(box: ByteArray, nonce: ByteArray): ByteArray {
35+
return tweetNaclJs.secretbox.open(
36+
box.asUint8Array(),
37+
nonce.asUint8Array(),
38+
secretKey.asUint8Array()
39+
).asByteArray()
40+
}
41+
}
42+
43+
private fun ByteArray.asUint8Array(): Uint8Array = Uint8Array(this.toTypedArray())
44+
45+
private fun Uint8Array.asByteArray(): ByteArray = ByteArray(this.length) { this[it] }
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package net.avianlabs.solana.tweetnacl
2+
3+
import org.khronos.webgl.Uint8Array
4+
5+
@JsModule("tweetnacl")
6+
internal external val tweetNaclJs: TweetNaClJs
7+
8+
internal external interface TweetNaClJs {
9+
val secretbox: SecretBox
10+
val sign: Sign
11+
}
12+
13+
internal external interface Sign {
14+
fun detached(message: Uint8Array, secretKey: Uint8Array): Uint8Array
15+
val keyPair: KeyPair
16+
}
17+
18+
internal external interface KeyPair {
19+
fun fromSeed(seed: Uint8Array): SignKeyPair
20+
}
21+
22+
internal external interface SignKeyPair {
23+
val secretKey: Uint8Array
24+
val publicKey: Uint8Array
25+
}
26+
27+
internal external interface SecretBox {
28+
fun open(box: Uint8Array, nonce: Uint8Array, secretKey: Uint8Array): Uint8Array
29+
}
30+
31+
32+
@Suppress("NOTHING_TO_INLINE")
33+
internal inline operator fun SecretBox.invoke(
34+
message: Uint8Array,
35+
nonce: Uint8Array,
36+
secretKey: Uint8Array
37+
): Uint8Array = asDynamic()(message, nonce, secretKey) as Uint8Array

0 commit comments

Comments
 (0)