Skip to content

Commit 2d9c735

Browse files
committed
Merge remote-tracking branch 'ProjectOpenSea/master'
2 parents 012428d + f67314f commit 2d9c735

File tree

11 files changed

+307
-134
lines changed

11 files changed

+307
-134
lines changed

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
buildscript {
22
ext {
33
versions = [
4-
kotlin : "1.4.31",
5-
moshi : '1.8.0',
6-
okhttp : '3.11.0',
4+
kotlin : "1.7.0",
5+
moshi : '1.13.0',
6+
okhttp : '4.10.0',
77
jupiter : '5.7.0',
88

99
'minSdk' : 14,

lib/build.gradle

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
plugins {
2+
id "com.google.devtools.ksp" version "1.7.0-1.0.6"
3+
}
14
apply plugin: "kotlin"
25
apply plugin: 'maven'
36

@@ -11,12 +14,10 @@ dependencies {
1114

1215
implementation "com.github.komputing:khex:1.1.0"
1316

14-
implementation "org.bouncycastle:bcprov-jdk15to18:1.68"
17+
implementation "org.bouncycastle:bcprov-jdk15to18:1.71"
1518

16-
implementation 'com.squareup.moshi:moshi:1.8.0'
17-
// TODO: it would be better to use the generated adapter by moshi
18-
// but for that we should move the implementations in different modules
19-
//kapt "com.squareup.moshi:moshi-kotlin-codegen:$versions.moshi"
19+
implementation "com.squareup.moshi:moshi:$versions.moshi"
20+
ksp "com.squareup.moshi:moshi-kotlin-codegen:$versions.moshi"
2021

2122
implementation "com.squareup.okhttp3:okhttp:$versions.okhttp"
2223

lib/src/main/kotlin/org/walletconnect/Session.kt

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.walletconnect
22

3+
import com.squareup.moshi.JsonClass
34
import java.net.URLDecoder
45
import java.net.URLEncoder
56

@@ -17,6 +18,7 @@ interface Session {
1718

1819
fun peerMeta(): PeerMeta?
1920
fun approvedAccounts(): List<String>?
21+
fun chainId(): Long?
2022

2123
fun approveRequest(id: Long, response: Any)
2224
fun rejectRequest(id: Long, errorCode: Long, errorMsg: String)
@@ -26,6 +28,7 @@ interface Session {
2628
fun removeCallback(cb: Callback)
2729
fun clearCallbacks()
2830

31+
@JsonClass(generateAdapter = true)
2932
data class FullyQualifiedConfig(
3033
val handshakeTopic: String,
3134
val bridge: String,
@@ -70,6 +73,10 @@ interface Session {
7073
}
7174
}
7275

76+
interface MessageLogger {
77+
fun log(message: Session.Transport.Message, isOwnMessage: Boolean)
78+
}
79+
7380
interface Callback {
7481
fun onStatus(status: Status)
7582
fun onMethodCall(call: MethodCall)
@@ -79,18 +86,31 @@ interface Session {
7986
object Connected : Status()
8087
object Disconnected : Status()
8188
object Approved : Status()
89+
object Updated : Status()
8290
object Closed : Status()
8391
data class Error(val throwable: Throwable) : Status()
8492
}
8593

8694
data class TransportError(override val cause: Throwable) : RuntimeException("Transport exception caused by $cause", cause)
8795

8896
interface PayloadAdapter {
89-
fun parse(payload: String, key: String): MethodCall
90-
fun prepare(data: MethodCall, key: String): String
97+
/**
98+
* Takes in the decrypted payload JSON and returns the parsed [MethodCall].
99+
*/
100+
fun parse(decryptedPayload: String): MethodCall
101+
102+
/**
103+
* Takes in a [MethodCall] and returns the unencrypted payload JSON.
104+
*/
105+
fun prepare(data: MethodCall): String
91106

92107
}
93108

109+
interface PayloadEncryption {
110+
fun encrypt(unencryptedPayloadJson: String, key: String): String
111+
fun decrypt(encryptedPayloadJson: String, key: String): String
112+
}
113+
94114
interface Transport {
95115

96116
fun connect(): Boolean
@@ -117,7 +137,7 @@ interface Session {
117137
fun build(
118138
url: String,
119139
statusHandler: (Status) -> Unit,
120-
messageHandler: (Message) -> Unit
140+
messageHandler: (Message) -> Unit,
121141
): Transport
122142
}
123143

@@ -154,7 +174,10 @@ interface Session {
154174
data class Response(val id: Long, val result: Any?, val error: Error? = null) : MethodCall(id)
155175
}
156176

177+
@JsonClass(generateAdapter = true)
157178
data class PeerData(val id: String, val meta: PeerMeta?)
179+
180+
@JsonClass(generateAdapter = true)
158181
data class PeerMeta(
159182
val url: String? = null,
160183
val name: String? = null,

lib/src/main/kotlin/org/walletconnect/impls/MoshiPayloadAdapter.kt

Lines changed: 10 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
11
package org.walletconnect.impls
22

33
import com.squareup.moshi.Json
4+
import com.squareup.moshi.JsonClass
45
import com.squareup.moshi.Moshi
56
import com.squareup.moshi.Types
6-
import org.bouncycastle.crypto.digests.SHA256Digest
7-
import org.bouncycastle.crypto.engines.AESEngine
8-
import org.bouncycastle.crypto.macs.HMac
9-
import org.bouncycastle.crypto.modes.CBCBlockCipher
10-
import org.bouncycastle.crypto.paddings.PKCS7Padding
11-
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher
12-
import org.bouncycastle.crypto.params.KeyParameter
13-
import org.bouncycastle.crypto.params.ParametersWithIV
14-
import org.komputing.khex.decode
15-
import org.komputing.khex.extensions.toNoPrefixHexString
167
import org.walletconnect.Session
178
import org.walletconnect.types.*
18-
import java.security.SecureRandom
199

2010
class MoshiPayloadAdapter(moshi: Moshi) : Session.PayloadAdapter {
2111

22-
private val payloadAdapter = moshi.adapter(EncryptedPayload::class.java)
2312
private val mapAdapter = moshi.adapter<Map<String, Any?>>(
2413
Types.newParameterizedType(
2514
Map::class.java,
@@ -28,74 +17,19 @@ class MoshiPayloadAdapter(moshi: Moshi) : Session.PayloadAdapter {
2817
)
2918
)
3019

31-
private fun createRandomBytes(i: Int) = ByteArray(i).also { SecureRandom().nextBytes(it) }
32-
33-
override fun parse(payload: String, key: String): Session.MethodCall {
34-
val encryptedPayload = payloadAdapter.fromJson(payload) ?: throw IllegalArgumentException("Invalid json payload!")
35-
36-
// TODO verify hmac
37-
38-
val padding = PKCS7Padding()
39-
val aes = PaddedBufferedBlockCipher(
40-
CBCBlockCipher(AESEngine()),
41-
padding
42-
)
43-
val ivAndKey = ParametersWithIV(
44-
KeyParameter(decode(key)),
45-
decode(encryptedPayload.iv)
46-
)
47-
aes.init(false, ivAndKey)
48-
49-
val encryptedData = decode(encryptedPayload.data)
50-
val minSize = aes.getOutputSize(encryptedData.size)
51-
val outBuf = ByteArray(minSize)
52-
var len = aes.processBytes(encryptedData, 0, encryptedData.size, outBuf, 0)
53-
len += aes.doFinal(outBuf, len)
54-
55-
return outBuf.copyOf(len).toMethodCall()
20+
override fun parse(decryptedPayloadJson: String): Session.MethodCall {
21+
return decryptedPayloadJson.toMethodCall()
5622
}
5723

58-
override fun prepare(data: Session.MethodCall, key: String): String {
59-
val bytesData = data.toBytes()
60-
val hexKey = decode(key)
61-
val iv = createRandomBytes(16)
62-
63-
val padding = PKCS7Padding()
64-
val aes = PaddedBufferedBlockCipher(
65-
CBCBlockCipher(AESEngine()),
66-
padding
67-
)
68-
aes.init(true, ParametersWithIV(KeyParameter(hexKey), iv))
69-
70-
val minSize = aes.getOutputSize(bytesData.size)
71-
val outBuf = ByteArray(minSize)
72-
val length1 = aes.processBytes(bytesData, 0, bytesData.size, outBuf, 0)
73-
aes.doFinal(outBuf, length1)
74-
75-
76-
val hmac = HMac(SHA256Digest())
77-
hmac.init(KeyParameter(hexKey))
78-
79-
val hmacResult = ByteArray(hmac.macSize)
80-
hmac.update(outBuf, 0, outBuf.size)
81-
hmac.update(iv, 0, iv.size)
82-
hmac.doFinal(hmacResult, 0)
83-
84-
return payloadAdapter.toJson(
85-
EncryptedPayload(
86-
outBuf.toNoPrefixHexString(),
87-
hmac = hmacResult.toNoPrefixHexString(),
88-
iv = iv.toNoPrefixHexString()
89-
)
90-
)
24+
override fun prepare(data: Session.MethodCall): String {
25+
return data.toJson()
9126
}
9227

9328
/**
9429
* Convert FROM request bytes
9530
*/
96-
private fun ByteArray.toMethodCall(): Session.MethodCall =
97-
String(this).let { json ->
98-
mapAdapter.fromJson(json)?.let {
31+
private fun String.toMethodCall(): Session.MethodCall =
32+
mapAdapter.fromJson(this)?.let {
9933
try {
10034
val method = it["method"]
10135
when (method) {
@@ -107,10 +41,9 @@ class MoshiPayloadAdapter(moshi: Moshi) : Session.PayloadAdapter {
10741
else -> it.toCustom()
10842
}
10943
} catch (e: Exception) {
110-
throw Session.MethodCallException.InvalidRequest(it.getId(), "$json (${e.message ?: "Unknown error"})")
44+
throw Session.MethodCallException.InvalidRequest(it.getId(), "$this (${e.message ?: "Unknown error"})")
11145
}
11246
} ?: throw IllegalArgumentException("Invalid json")
113-
}
11447

11548
private fun Map<String, *>.toSessionUpdate(): Session.MethodCall.SessionUpdate {
11649
val params = this["params"] as? List<*> ?: throw IllegalArgumentException("params missing")
@@ -162,7 +95,7 @@ class MoshiPayloadAdapter(moshi: Moshi) : Session.PayloadAdapter {
16295
/**
16396
* Convert INTO request bytes
16497
*/
165-
private fun Session.MethodCall.toBytes() =
98+
private fun Session.MethodCall.toJson() =
16699
mapAdapter.toJson(
167100
when (this) {
168101
is Session.MethodCall.SessionRequest -> this.toMap()
@@ -172,7 +105,7 @@ class MoshiPayloadAdapter(moshi: Moshi) : Session.PayloadAdapter {
172105
is Session.MethodCall.SignMessage -> this.toMap()
173106
is Session.MethodCall.Custom -> this.toMap()
174107
}
175-
).toByteArray()
108+
)
176109

177110
private fun Session.MethodCall.SessionRequest.toMap() =
178111
jsonRpc(id, "wc_sessionRequest", peer.intoMap())
@@ -222,11 +155,4 @@ class MoshiPayloadAdapter(moshi: Moshi) : Session.PayloadAdapter {
222155
"method" to method,
223156
"params" to params
224157
)
225-
226-
// TODO: @JsonClass(generateAdapter = true)
227-
data class EncryptedPayload(
228-
@Json(name = "data") val data: String,
229-
@Json(name = "iv") val iv: String,
230-
@Json(name = "hmac") val hmac: String
231-
)
232158
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package org.walletconnect.impls
2+
3+
import com.squareup.moshi.Json
4+
import com.squareup.moshi.JsonClass
5+
import com.squareup.moshi.Moshi
6+
import org.bouncycastle.crypto.digests.SHA256Digest
7+
import org.bouncycastle.crypto.engines.AESEngine
8+
import org.bouncycastle.crypto.macs.HMac
9+
import org.bouncycastle.crypto.modes.CBCBlockCipher
10+
import org.bouncycastle.crypto.paddings.PKCS7Padding
11+
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher
12+
import org.bouncycastle.crypto.params.KeyParameter
13+
import org.bouncycastle.crypto.params.ParametersWithIV
14+
import org.komputing.khex.decode
15+
import org.komputing.khex.extensions.toNoPrefixHexString
16+
import org.walletconnect.Session
17+
import java.security.SecureRandom
18+
19+
class MoshiPayloadEncryption(moshi: Moshi) : Session.PayloadEncryption {
20+
21+
private val encryptedPayloadAdapter = moshi.adapter(EncryptedPayload::class.java)
22+
23+
override fun encrypt(unencryptedPayloadJson: String, key: String): String {
24+
val bytesData = unencryptedPayloadJson.toByteArray()
25+
val hexKey = decode(key)
26+
val iv = createRandomBytes(16)
27+
28+
val padding = PKCS7Padding()
29+
val aes = PaddedBufferedBlockCipher(
30+
CBCBlockCipher(AESEngine()),
31+
padding
32+
)
33+
aes.init(true, ParametersWithIV(KeyParameter(hexKey), iv))
34+
35+
val minSize = aes.getOutputSize(bytesData.size)
36+
val outBuf = ByteArray(minSize)
37+
val length1 = aes.processBytes(bytesData, 0, bytesData.size, outBuf, 0)
38+
aes.doFinal(outBuf, length1)
39+
40+
41+
val hmac = HMac(SHA256Digest())
42+
hmac.init(KeyParameter(hexKey))
43+
44+
val hmacResult = ByteArray(hmac.macSize)
45+
hmac.update(outBuf, 0, outBuf.size)
46+
hmac.update(iv, 0, iv.size)
47+
hmac.doFinal(hmacResult, 0)
48+
49+
return encryptedPayloadAdapter.toJson(
50+
EncryptedPayload(
51+
outBuf.toNoPrefixHexString(),
52+
hmac = hmacResult.toNoPrefixHexString(),
53+
iv = iv.toNoPrefixHexString()
54+
)
55+
)
56+
}
57+
58+
override fun decrypt(encryptedPayloadJson: String, key: String): String {
59+
val encryptedPayload = encryptedPayloadAdapter.fromJson(encryptedPayloadJson) ?: throw IllegalArgumentException("Invalid json payload!")
60+
61+
val hexKey = decode(key)
62+
val iv = decode(encryptedPayload.iv)
63+
val encryptedData = decode(encryptedPayload.data)
64+
val providedHmac = decode(encryptedPayload.hmac)
65+
66+
// verify hmac
67+
with(HMac(SHA256Digest())) {
68+
val hmacResult = ByteArray(macSize)
69+
init(KeyParameter(hexKey))
70+
update(encryptedData, 0, encryptedData.size)
71+
update(iv, 0, iv.size)
72+
doFinal(hmacResult, 0)
73+
74+
require(hmacResult.contentEquals(providedHmac)) { "HMAC does not match - expected: $hmacResult received: $providedHmac" }
75+
}
76+
77+
// decrypt payload
78+
val padding = PKCS7Padding()
79+
val aes = PaddedBufferedBlockCipher(
80+
CBCBlockCipher(AESEngine()),
81+
padding
82+
)
83+
val ivAndKey = ParametersWithIV(
84+
KeyParameter(hexKey),
85+
iv
86+
)
87+
aes.init(false, ivAndKey)
88+
89+
val minSize = aes.getOutputSize(encryptedData.size)
90+
val outBuf = ByteArray(minSize)
91+
var len = aes.processBytes(encryptedData, 0, encryptedData.size, outBuf, 0)
92+
len += aes.doFinal(outBuf, len)
93+
94+
return String(outBuf.copyOf(len))
95+
}
96+
97+
private fun createRandomBytes(i: Int) = ByteArray(i).also { SecureRandom().nextBytes(it) }
98+
}
99+
100+
@JsonClass(generateAdapter = true)
101+
data class EncryptedPayload(
102+
@Json(name = "data") val data: String,
103+
@Json(name = "iv") val iv: String,
104+
@Json(name = "hmac") val hmac: String
105+
)

lib/src/main/kotlin/org/walletconnect/impls/OkHttpTransport.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,12 @@ class OkHttpTransport(
121121
}
122122
}
123123

124-
class Builder(val client: OkHttpClient, val moshi: Moshi) :
124+
class Builder(private val client: OkHttpClient, private val moshi: Moshi) :
125125
Session.Transport.Builder {
126126
override fun build(
127127
url: String,
128128
statusHandler: (Session.Transport.Status) -> Unit,
129-
messageHandler: (Session.Transport.Message) -> Unit
129+
messageHandler: (Session.Transport.Message) -> Unit,
130130
): Session.Transport =
131131
OkHttpTransport(client, url, statusHandler, messageHandler, moshi)
132132

0 commit comments

Comments
 (0)