Skip to content

Commit

Permalink
Implement message signing for android demo (#177)
Browse files Browse the repository at this point in the history
* add android small demo
* update for comment suggestion
* consolidate configuration info
* Update android/app/build.gradle
* implement personal / typed sign
* Add unknown method
Co-authored-by: Dougie <[email protected]>
Co-authored-by: Viktor Radchenko <[email protected]>
  • Loading branch information
hewigovens authored Sep 12, 2021
1 parent 0d0f9a0 commit 105ad0b
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 41 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Step 2. Add the dependency

```groovy
dependencies {
implementation 'com.github.TrustWallet:trust-web3-provider:1.0.4'
implementation 'com.github.trustwallet:trust-web3-provider:TAG'
}
```

Expand Down
9 changes: 6 additions & 3 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ android {

defaultConfig {
applicationId "com.trust.web3.demo"
minSdkVersion 21
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0"
Expand All @@ -33,13 +33,16 @@ android {
}

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation project(path: ':lib')

implementation "com.louiscad.splitties:splitties-alertdialog-material:3.0.0"
implementation "com.trustwallet:wallet-core:2.6.23"

implementation 'com.github.trustwallet:trust-web3-provider:1.0.5'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
Expand Down
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".App"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
Expand Down
9 changes: 9 additions & 0 deletions android/app/src/main/java/com/trust/web3/demo/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.trust.web3.demo

import android.app.Application

class App: Application() {
init {
System.loadLibrary("TrustWalletCore")
}
}
29 changes: 29 additions & 0 deletions android/app/src/main/java/com/trust/web3/demo/DAppMethod.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.trust.web3.demo

enum class DAppMethod {
SIGNTRANSACTION,
SIGNPERSONALMESSAGE,
SIGNMESSAGE,
SIGNTYPEDMESSAGE,
ECRECOVER,
REQUESTACCOUNTS,
WATCHASSET,
ADDETHEREUMCHAIN,
UNKNOWN;

companion object {
fun fromValue(value: String): DAppMethod {
return when (value) {
"signTransaction" -> SIGNTRANSACTION
"signPersonalMessage" -> SIGNPERSONALMESSAGE
"signMessage" -> SIGNMESSAGE
"signTypedMessage" -> SIGNTYPEDMESSAGE
"ecRecover" -> ECRECOVER
"requestAccounts" -> REQUESTACCOUNTS
"watchAsset" -> WATCHASSET
"addEthereumChain" -> ADDETHEREUMCHAIN
else -> UNKNOWN
}
}
}
}
46 changes: 28 additions & 18 deletions android/app/src/main/java/com/trust/web3/demo/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,43 +1,53 @@
package com.trust.web3.demo

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
companion object {
private const val DAPP_URL = "https://js-eth-sign.surge.sh"
private const val CHAIN_ID = 56
private const val RPC_URL = "https://bsc-dataseed2.binance.org"
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

val provderJs = loadProviderJs()
val initJs = loadInitJs(
1,
"https://mainnet.infura.io/v3/6e822818ec644335be6f0ed231f48310"
CHAIN_ID,
RPC_URL
)
println("file lenght: ${provderJs.length}")
WebView.setWebContentsDebuggingEnabled(true)
val webview: WebView = findViewById(R.id.webview)
webview.settings.javaScriptEnabled = true
webview.addJavascriptInterface(WebAppInterface(webview), "_tw_")

val webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
println("loaded: ${url}")
view?.evaluateJavascript(provderJs, null)
view?.evaluateJavascript(initJs, null)
webview.settings.run {
javaScriptEnabled = true
domStorageEnabled = true
}
WebAppInterface(this, webview, DAPP_URL).run {
webview.addJavascriptInterface(this, "_tw_")

val webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
view?.evaluateJavascript(provderJs, null)
view?.evaluateJavascript(initJs, null)
}
}
webview.webViewClient = webViewClient
webview.loadUrl(DAPP_URL)
}
webview.webViewClient = webViewClient
webview.loadUrl("https://js-eth-sign.surge.sh")
}

fun loadProviderJs(): String {
return resources.openRawResource(R.raw.trust).bufferedReader().use { it.readText() }
private fun loadProviderJs(): String {
return resources.openRawResource(R.raw.trust_min).bufferedReader().use { it.readText() }
}

fun loadInitJs(chainId: Int, rpcUrl: String): String {
private fun loadInitJs(chainId: Int, rpcUrl: String): String {
val source = """
(function() {
var config = {
Expand Down
65 changes: 65 additions & 0 deletions android/app/src/main/java/com/trust/web3/demo/Numeric.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.trust.web3.demo

import kotlin.experimental.and

object Numeric {
fun containsHexPrefix(input: String): Boolean {
return input.length > 1 && input[0] == '0' && input[1] == 'x'
}

fun cleanHexPrefix(input: String): String {
return if (containsHexPrefix(input)) {
input.substring(2)
} else {
input
}
}

fun hexStringToByteArray(input: String): ByteArray {
val cleanInput = cleanHexPrefix(input)

val len = cleanInput.length

if (len == 0) {
return byteArrayOf()
}

val data: ByteArray
val startIdx: Int
if (len % 2 != 0) {
data = ByteArray(len / 2 + 1)
data[0] = Character.digit(cleanInput[0], 16).toByte()
startIdx = 1
} else {
data = ByteArray(len / 2)
startIdx = 0
}

var i = startIdx
while (i < len) {
data[(i + 1) / 2] =
((Character.digit(cleanInput[i], 16) shl 4) + Character.digit(
cleanInput[i + 1],
16
)).toByte()
i += 2
}
return data
}

fun toHexString(input: ByteArray?, offset: Int, length: Int, withPrefix: Boolean): String {
val stringBuilder = StringBuilder()
if (withPrefix) {
stringBuilder.append("0x")
}
for (i in offset until offset + length) {
stringBuilder.append(String.format("%02x", input!![i] and 0xFF.toByte()))
}

return stringBuilder.toString()
}

fun toHexString(input: ByteArray?): String {
return toHexString(input, 0, input!!.size, true)
}
}
127 changes: 113 additions & 14 deletions android/app/src/main/java/com/trust/web3/demo/WebAppInterface.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,127 @@ package com.trust.web3.demo
import android.content.Context
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.widget.Toast
import org.json.JSONObject
import splitties.alertdialog.appcompat.cancelButton
import splitties.alertdialog.appcompat.message
import splitties.alertdialog.appcompat.okButton
import splitties.alertdialog.appcompat.title
import splitties.alertdialog.material.materialAlertDialog
import wallet.core.jni.CoinType
import wallet.core.jni.Curve
import wallet.core.jni.PrivateKey

class WebAppInterface(
private val context: Context,
private val webView: WebView,
private val dappUrl: String
) {
private val privateKey =
PrivateKey("0x4646464646464646464646464646464646464646464646464646464646464646".toHexByteArray())
private val addr = CoinType.ETHEREUM.deriveAddress(privateKey).toLowerCase()

class WebAppInterface(private val context: WebView) {
@JavascriptInterface
fun postMessage(json: String) {
val obj = JSONObject(json)
println(obj)
val id = obj["id"]
val addr = "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1"

when(obj["name"]) {
"requestAccounts" -> {
val callback = "window.ethereum.sendResponse($id, [\"$addr\"])"
context.post {
context.evaluateJavascript(callback) { value ->
println(value)
val id = obj.getLong("id")
val method = DAppMethod.fromValue(obj.getString("name"))
when (method) {
DAppMethod.REQUESTACCOUNTS -> {
context.materialAlertDialog {
title = "Request Accounts"
message = "${dappUrl} requests your address"
okButton {
val setAddress = "window.ethereum.setAddress(\"$addr\");"
val callback = "window.ethereum.sendResponse($id, [\"$addr\"])"
webView.post {
webView.evaluateJavascript(setAddress) {
// ignore
}
webView.evaluateJavascript(callback) { value ->
println(value)
}
}
}
cancelButton()
}.show()
}
DAppMethod.SIGNMESSAGE -> {
val data = extractMessage(obj)
handleSignMessage(id, data, addPrefix = false)
}
DAppMethod.SIGNPERSONALMESSAGE -> {
val data = extractMessage(obj)
handleSignMessage(id, data, addPrefix = true)
}
DAppMethod.SIGNTYPEDMESSAGE -> {
val data = extractMessage(obj)
val raw = extractRaw(obj)
handleSignTypedMessage(id, data, raw)
}
else -> {
context.materialAlertDialog {
title = "Error"
message = "$method not implemented"
okButton {
}
}
}.show()
}
// handle other methods here
// signTransaction, signMessage, ecRecover, watchAsset, addEthereumChain
}
}

private fun extractMessage(json: JSONObject): ByteArray {
val param = json.getJSONObject("object")
val data = param.getString("data")
return Numeric.hexStringToByteArray(data)
}

private fun extractRaw(json: JSONObject): String {
val param = json.getJSONObject("object")
return param.getString("raw")
}

private fun handleSignMessage(id: Long, data: ByteArray, addPrefix: Boolean) {
context.materialAlertDialog {
title = "Sign Message"
message = if (addPrefix) String(data, Charsets.UTF_8) else Numeric.toHexString(data)
cancelButton {
webView.sendError("Cancel", id)
}
okButton {
webView.sendResult(signEthereumMessage(data, addPrefix), id)
}
}.show()
}

private fun handleSignTypedMessage(id: Long, data: ByteArray, raw: String) {
context.materialAlertDialog {
title = "Sign Typed Message"
message = raw
cancelButton {
webView.sendError("Cancel", id)
}
okButton {
webView.sendResult(signEthereumMessage(data, false), id)
}
}.show()
}

private fun signEthereumMessage(message: ByteArray, addPrefix: Boolean): String {
var data = message
if (addPrefix) {
val messagePrefix = "\u0019Ethereum Signed Message:\n"
val prefix = (messagePrefix + message.size).toByteArray()
val result = ByteArray(prefix.size + message.size)
System.arraycopy(prefix, 0, result, 0, prefix.size)
System.arraycopy(message, 0, result, prefix.size, message.size)
data = wallet.core.jni.Hash.keccak256(result)
}

val signatureData = privateKey.sign(data, Curve.SECP256K1)
.apply {
(this[this.size - 1]) = (this[this.size - 1] + 27).toByte()
}
return Numeric.toHexString(signatureData)
}
}
29 changes: 29 additions & 0 deletions android/app/src/main/java/com/trust/web3/demo/WebViewExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.trust.web3.demo

import android.webkit.WebView

fun WebView.sendError(message: String, methodId: Long) {
val script = "window.ethereum.sendError($methodId, \"$message\")"
this.post {
this.evaluateJavascript(script) {}
}
}

fun WebView.sendResult(message: String, methodId: Long) {
val script = "window.ethereum.sendResponse($methodId, \"$message\")"
this.post {
this.evaluateJavascript(script) {}
}
}

fun WebView.sendResults(messages: List<String>, methodId: Long) {
val message = messages.joinToString(separator = ",")
val script = "window.ethereum.sendResponse($methodId, \"$message\")"
this.post {
this.evaluateJavascript(script) {}
}
}

fun String.toHexByteArray(): ByteArray {
return Numeric.hexStringToByteArray(this)
}
Loading

0 comments on commit 105ad0b

Please sign in to comment.