Skip to content

Commit

Permalink
Fix/dapp message signing (#1750)
Browse files Browse the repository at this point in the history
* Wrap polkadot signing message in bytes tag if not wrapped already

* Code style
  • Loading branch information
valentunn authored Jan 10, 2025
1 parent 35b0bb6 commit e87b52f
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ fun ByteArray.startsWith(prefix: ByteArray): Boolean {
return true
}

fun ByteArray.endsWith(suffix: ByteArray): Boolean {
if (suffix.size > size) return false

val offset = size - suffix.size

suffix.forEachIndexed { index, byte ->
if (get(offset + index) != byte) return false
}

return true
}

fun ByteArray.windowed(windowSize: Int): List<ByteArray> {
require(windowSize > 0) {
"Window size should be positive"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.novafoundation.nova.common.utils

import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class KotlinExtTest {

@Test
fun `endsWith should return false for suffix bigger than content`() {
assertFalse(byteArrayOf(0, 1).endsWith(byteArrayOf(0, 1, 2)))
}

@Test
fun `endsWith should return true for suffix equal to the content`() {
assertTrue(byteArrayOf(0, 1, 2).endsWith(byteArrayOf(0, 1, 2)))
}

@Test
fun `endsWith should return true for correct suffix`() {
assertTrue(byteArrayOf(0, 1, 2).endsWith(byteArrayOf(1, 2)))
}

@Test
fun `endsWith should return true for incorrect suffix`() {
assertFalse(byteArrayOf(0, 1, 2, 3).endsWith(byteArrayOf(1, 2)))
assertFalse(byteArrayOf(0, 1, 2, 3).endsWith(byteArrayOf(2)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import io.novafoundation.nova.common.address.AddressIconGenerator
import io.novafoundation.nova.common.address.AddressModel
import io.novafoundation.nova.common.utils.asHexString
import io.novafoundation.nova.common.utils.bigIntegerFromHex
import io.novafoundation.nova.common.utils.endsWith
import io.novafoundation.nova.common.utils.intFromHex
import io.novafoundation.nova.common.utils.singleReplaySharedFlow
import io.novafoundation.nova.common.utils.startsWith
import io.novafoundation.nova.common.validation.EmptyValidationSystem
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi
Expand Down Expand Up @@ -49,7 +51,6 @@ import io.novasama.substrate_sdk_android.runtime.extrinsic.Nonce
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SendableExtrinsic
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignerPayloadRaw
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.fromHex
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.fromUtf8
import io.novasama.substrate_sdk_android.wsrpc.request.runtime.chain.RuntimeVersion
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -178,11 +179,7 @@ class PolkadotExternalSignInteractor(
val accountId = signBytesPayload.address.anyAddressToAccountId()

val signer = resolveWalletSigner()
val payload = runCatching {
SignerPayloadRaw.fromHex(signBytesPayload.data, accountId)
}.getOrElse {
SignerPayloadRaw.fromUtf8(signBytesPayload.data, accountId)
}
val payload = SignerPayloadRaw.fromUnsafeString(signBytesPayload.data, accountId)

val signature = signer.signRaw(payload).asHexString()
return SignedResult(signature, modifiedTransaction = null)
Expand Down Expand Up @@ -244,6 +241,26 @@ class PolkadotExternalSignInteractor(
)
}

private fun SignerPayloadRaw.Companion.fromUnsafeString(data: String, signer: AccountId): SignerPayloadRaw {
val unsafeMessage = decodeSigningMessage(data)
val safeMessage = protectSigningMessage(unsafeMessage)

return SignerPayloadRaw(safeMessage, signer)
}

private fun decodeSigningMessage(data: String): ByteArray {
return kotlin.runCatching { data.fromHex() }.getOrElse { data.encodeToByteArray() }
}

private fun protectSigningMessage(message: ByteArray): ByteArray {
val prefix = "<Bytes>".encodeToByteArray()
val suffix = "</Bytes>".encodeToByteArray()

if (message.startsWith(prefix) && message.endsWith(suffix)) return message

return prefix + message + suffix
}

private suspend fun PolkadotSignPayload.Json.actualMetadataHash(chain: Chain, signer: NovaSigner): ActualMetadataHash {
// If a dapp haven't declared a permission to modify extrinsic - return whatever metadataHash present in payload
if (withSignedTransaction != true) {
Expand Down

0 comments on commit e87b52f

Please sign in to comment.