Skip to content

Commit cdff1e9

Browse files
committed
fix(app): secp256k1 signature
1 parent 66ae02c commit cdff1e9

File tree

3 files changed

+82
-54
lines changed

3 files changed

+82
-54
lines changed

app/build.gradle

+1-2
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ dependencies {
6060
implementation 'io.ktor:ktor-client-core:2.3.10'
6161
implementation 'io.ktor:ktor-client-cio:2.3.10'
6262
implementation 'io.ktor:ktor-client-websockets:2.3.10'
63-
implementation 'fr.acinq.secp256k1:secp256k1-kmp-jni-android:0.15.0'
6463
implementation 'commons-codec:commons-codec:1.17.0'
65-
implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'
64+
implementation 'org.web3j:core:4.8.7-android'
6665
testImplementation 'junit:junit:4.13.2'
6766
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
6867
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

app/src/main/java/multiprooflabs/tee/MainActivity.kt

+8-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package multiprooflabs.tee
22

33
import android.content.Context
4-
import android.content.SharedPreferences
54
import android.os.Bundle
65
import android.util.Log
76
import kotlinx.coroutines.runBlocking
@@ -13,13 +12,11 @@ import io.ktor.websocket.*
1312
import multiprooflabs.tee.data.ProofAndroid
1413
import multiprooflabs.tee.data.Proof
1514
import multiprooflabs.tee.data.ProofAndroidValue
16-
import multiprooflabs.tee.security.Utils
1715
import multiprooflabs.tee.security.TrustedExecutor
18-
import multiprooflabs.tee.security.Utils.Companion.toBase64String
1916
import multiprooflabs.tee.security.Utils.Companion.toHexString
2017
import multiprooflabs.tee.security.Utils.Companion.toJson
2118
import java.lang.Exception
22-
import java.security.Security
19+
import java.nio.charset.StandardCharsets
2320

2421
class MainActivity : AppCompatActivity() {
2522
private external fun callRust(input: String): String
@@ -36,22 +33,23 @@ class MainActivity : AppCompatActivity() {
3633
}
3734

3835
private fun getProof(result: ByteArray): String {
39-
Log.d(TAG, "Getting proof, address: ${tee!!.getAddress().toHexString()}")
36+
Log.d(TAG, "Getting proof using address: ${tee!!.getAddress().toHexString()}")
4037
val type = tee!!.proofType
41-
val commitment = result.toHexString()
42-
val signature = tee!!.signWithSecp256k1PrivateKey(result).toHexString()
38+
val statement = result.toString(StandardCharsets.UTF_8)
39+
val commitment = tee!!.getEIP191SignedData(result)
40+
val signature = tee!!.signWithSecp256k1PrivateKey(commitment).toHexString()
4341
val publicKey = tee!!.getSecp256k1PublicKey().toHexString()
4442
val attestedPublicKey = tee!!.getSecp256k1Attestation().toHexString()
4543
val certChain = tee!!.getCertificateAttestation().toHexString()
4644
val value = ProofAndroidValue(
47-
commitment,
45+
commitment.toHexString(),
46+
signature,
4847
publicKey,
4948
attestedPublicKey,
50-
signature,
5149
certChain
5250
)
5351
val proof = ProofAndroid(type, value)
54-
return Proof(commitment, proof).toJson()
52+
return Proof(statement, proof).toJson()
5553
}
5654

5755
@OptIn(ExperimentalUnsignedTypes::class)
@@ -73,7 +71,6 @@ class MainActivity : AppCompatActivity() {
7371
while (true) {
7472
val request = incoming.receive() as? Frame.Text ?: continue
7573
val txt = request.readText()
76-
Log.i(TAG, "ws msg: $txt")
7774
val resp = try {
7875
val result = callRust(txt)
7976
getProof(result.toByteArray())

app/src/main/java/multiprooflabs/tee/security/TrustedExecutor.kt

+73-41
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
package multiprooflabs.tee.security
22

3-
import android.content.Context
43
import android.content.SharedPreferences
54
import android.security.keystore.KeyGenParameterSpec
65
import android.security.keystore.KeyInfo
76
import android.security.keystore.KeyProperties
87
import android.util.Log
9-
import fr.acinq.secp256k1.Secp256k1
10-
import io.ktor.network.tls.extensions.HashAlgorithm
118
import multiprooflabs.tee.security.Utils.Companion.fromHexString
129
import multiprooflabs.tee.security.Utils.Companion.toCbor
1310
import multiprooflabs.tee.security.Utils.Companion.toHexString
11+
import org.bouncycastle.asn1.x9.ECNamedCurveTable
12+
import org.bouncycastle.crypto.generators.ECKeyPairGenerator
13+
14+
import org.bouncycastle.crypto.params.ECDomainParameters
15+
import org.bouncycastle.crypto.params.ECKeyGenerationParameters
16+
import org.bouncycastle.crypto.params.ECPrivateKeyParameters
17+
import org.bouncycastle.crypto.params.ECPublicKeyParameters
1418
import org.bouncycastle.jcajce.provider.digest.Keccak
19+
import org.web3j.crypto.ECKeyPair
20+
import org.web3j.crypto.Sign
21+
import java.math.BigInteger
1522
import java.nio.charset.StandardCharsets
1623
import java.security.Key
1724
import java.security.KeyPairGenerator
1825
import java.security.KeyStore
19-
import java.security.MessageDigest
2026
import java.security.PrivateKey
2127
import java.security.PublicKey
2228
import java.security.SecureRandom
@@ -31,12 +37,12 @@ class TrustedExecutor(private val db: SharedPreferences, private val isStrongbox
3137
private val TAG = this.javaClass.name
3238
private val ETHEREUM_MSG_PREFIX = byteArrayOf(25)
3339
.plus("Ethereum Signed Message:\n".toByteArray())
34-
.plus(32)
3540
private val androidKeyStore = "AndroidKeyStore"
36-
private val aliasAttestationKey = "multiprooflabs.ecdsa"
37-
private val aliasSecretKey = "multiprooflabs.aes"
38-
private val PREF_KEY_SK = "secretKey"
39-
private val PREF_KEY_PK = "publicKey"
41+
private val ALIAS_ANDROID_ATTESTATION_KEY = "multiprooflabs.ecdsa"
42+
private val ALIAS_ANDROID_SECRET_KEY = "multiprooflabs.aes"
43+
private val PREFIX_SECP256K1_KEY = "multiprooflabs.secp256k1"
44+
private val PREFERENCES_PRIVATE_KEY = "$PREFIX_SECP256K1_KEY.PrivateKey"
45+
private val PREFERENCES_PUBLIC_KEY = "$PREFIX_SECP256K1_KEY.PublicKey"
4046
private val CIPHER_TRANSFORMATION = "AES/GCM/NoPadding"
4147

4248
val pubKey = null
@@ -70,7 +76,6 @@ class TrustedExecutor(private val db: SharedPreferences, private val isStrongbox
7076
}
7177

7278

73-
@OptIn(ExperimentalStdlibApi::class)
7479
private fun generateSecretKey(alias: String) {
7580
val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, androidKeyStore)
7681
val purposes = KeyProperties.PURPOSE_ENCRYPT.or(KeyProperties.PURPOSE_DECRYPT)
@@ -91,47 +96,74 @@ class TrustedExecutor(private val db: SharedPreferences, private val isStrongbox
9196
generateSecp256k1Key()
9297
}
9398

94-
fun generateSecp256k1Key(secretKeyAlias: String = aliasSecretKey) {
95-
Log.i(TAG, "Generating secp256k1 key...")
99+
fun generateSecp256k1Key() {
100+
val curve = ECNamedCurveTable.getByName("secp256k1")
101+
val domainParams = ECDomainParameters(
102+
curve.curve,
103+
curve.g,
104+
curve.n,
105+
curve.h,
106+
curve.seed
107+
)
96108

97-
// Generate secp256k1
98-
val secureRandom = SecureRandom()
99-
val privKey = ByteArray(32)
100-
secureRandom.nextBytes(privKey)
101-
// Write to shared preferences
102-
val pubKey = Secp256k1.pubkeyCreate(privKey)
103-
with (db.edit()) {
104-
putString(PREF_KEY_SK, encrypt(privKey, secretKeyAlias).toHexString())
105-
putString(PREF_KEY_PK, pubKey.toHexString())
106-
commit()
107-
}
109+
val secureRandom = SecureRandom()
110+
val keyParams = ECKeyGenerationParameters(domainParams, secureRandom)
111+
val generator = ECKeyPairGenerator()
112+
generator.init(keyParams)
113+
val keyPair = generator.generateKeyPair()
114+
115+
val privateKey = keyPair.private as ECPrivateKeyParameters
116+
val publicKey = keyPair.public as ECPublicKeyParameters
117+
118+
with (db.edit()) {
119+
val encryptedPrivateKey = encrypt(privateKey.d.toByteArray()).toHexString()
120+
val clearPublicKey = publicKey.q.getEncoded(false).toHexString()
121+
putString(PREFERENCES_PRIVATE_KEY, encryptedPrivateKey)
122+
putString(PREFERENCES_PUBLIC_KEY, clearPublicKey)
123+
commit()
124+
}
125+
Log.i(TAG,"New secp256k1 key pair stored successfully into app preferences")
108126
}
109127

110128
fun getSecp256k1PublicKey(): ByteArray {
111-
return db.getString(PREF_KEY_PK, null)!!.fromHexString()
129+
return db.getString(PREFERENCES_PUBLIC_KEY, null)!!.fromHexString()
112130
}
113131

114-
private fun keccak256(message: ByteArray): ByteArray {
132+
fun keccak256(message: ByteArray): ByteArray {
115133
val keccak256 = Keccak.Digest256()
116134
keccak256.update(message)
117135
return keccak256.digest()
118136
}
119-
fun signWithSecp256k1PrivateKey(message: ByteArray, secretKeyAlias: String = aliasSecretKey): ByteArray {
120-
val sk = decrypt(db.getString(PREF_KEY_SK, null)!!.fromHexString())
121-
137+
fun signWithSecp256k1PrivateKey(commitment: ByteArray): ByteArray {
138+
val privateKey = BigInteger(decrypt(db.getString(PREFERENCES_PRIVATE_KEY, null)!!.fromHexString()))
139+
val publicKey = Sign.publicKeyFromPrivate(privateKey)
140+
val keyPair = ECKeyPair(privateKey, publicKey)
141+
val needToHash = false
142+
val signature = Sign.signMessage(commitment, keyPair, needToHash)
143+
144+
return signature.r
145+
.plus(signature.s)
146+
.plus(signature.v)
147+
}
122148

123-
val hashedMessage = keccak256(ETHEREUM_MSG_PREFIX.plus(keccak256(message)))
124-
return Secp256k1.sign(hashedMessage, sk)
149+
fun getEIP191SignedData(data: ByteArray): ByteArray {
150+
val dataLength = data.size.toString().toByteArray()
151+
return keccak256(
152+
ETHEREUM_MSG_PREFIX
153+
.plus(dataLength)
154+
.plus(data)
155+
)
125156
}
126157

127158
fun getAddress(): ByteArray {
128159
val pubKey = getSecp256k1PublicKey()
129-
val hash = keccak256(pubKey)
130-
return hash.sliceArray(IntRange(0, 19))
160+
val pubKeyWithoutPrefix = pubKey.sliceArray(IntRange(1, pubKey.size - 1))
161+
val hash = keccak256(pubKeyWithoutPrefix)
162+
return hash.sliceArray(IntRange(hash.size - 20, hash.size - 1))
131163
}
132164

133-
fun getSecp256k1Attestation(secretKeyAlias: String = aliasSecretKey): ByteArray {
134-
val pubKey = db.getString(PREF_KEY_PK, null)!!.fromHexString()
165+
fun getSecp256k1Attestation(): ByteArray {
166+
val pubKey = db.getString(PREFERENCES_PUBLIC_KEY, null)!!.fromHexString()
135167
return signWithAttestationKey(pubKey)
136168
}
137169

@@ -140,7 +172,7 @@ class TrustedExecutor(private val db: SharedPreferences, private val isStrongbox
140172
return ks.getKey(alias, null)
141173
}
142174

143-
private fun encrypt(data: ByteArray, alias: String = aliasSecretKey): ByteArray {
175+
private fun encrypt(data: ByteArray, alias: String = ALIAS_ANDROID_SECRET_KEY): ByteArray {
144176
val key = getSecretKey(alias)
145177
val cipher = Cipher.getInstance(CIPHER_TRANSFORMATION)
146178
cipher.init(Cipher.ENCRYPT_MODE, key)
@@ -155,7 +187,7 @@ class TrustedExecutor(private val db: SharedPreferences, private val isStrongbox
155187
return result
156188
}
157189

158-
private fun decrypt(data: ByteArray, alias: String = aliasSecretKey): ByteArray {
190+
private fun decrypt(data: ByteArray, alias: String = ALIAS_ANDROID_SECRET_KEY): ByteArray {
159191
val key = getSecretKey(alias)
160192
val iv = ByteArray(12)
161193
val encryptedData = ByteArray(data.size - iv.size)
@@ -172,15 +204,15 @@ class TrustedExecutor(private val db: SharedPreferences, private val isStrongbox
172204
return cipher.doFinal(encryptedData)
173205
}
174206

175-
private fun maybeGenerateSecretKey(alias: String = aliasSecretKey) {
207+
private fun maybeGenerateSecretKey(alias: String = ALIAS_ANDROID_SECRET_KEY) {
176208
val ks = KeyStore.getInstance(androidKeyStore)
177209
.apply { load(null) }
178210
if (!ks.containsAlias(alias)) {
179211
generateSecretKey(alias)
180212
}
181213
}
182214

183-
private fun maybeGenerateSigningKey(alias: String = aliasAttestationKey): KeyStore {
215+
private fun maybeGenerateSigningKey(alias: String = ALIAS_ANDROID_ATTESTATION_KEY): KeyStore {
184216
val ks = KeyStore.getInstance(androidKeyStore).apply { load(null) }
185217

186218
if (!ks.containsAlias(alias)) {
@@ -225,9 +257,9 @@ class TrustedExecutor(private val db: SharedPreferences, private val isStrongbox
225257
return AttestationCertificate(leaf, intermediate, root).toCbor()
226258
}
227259

228-
fun getAttestationKeyPublicKey() = getPublicKey(aliasAttestationKey)
229-
fun getCertificateAttestation() = getCertificateAttestation(aliasAttestationKey)
230-
fun signWithAttestationKey(data: ByteArray) = sign(aliasAttestationKey, data)
260+
fun getAttestationKeyPublicKey() = getPublicKey(ALIAS_ANDROID_ATTESTATION_KEY)
261+
fun getCertificateAttestation() = getCertificateAttestation(ALIAS_ANDROID_ATTESTATION_KEY)
262+
fun signWithAttestationKey(data: ByteArray) = sign(ALIAS_ANDROID_ATTESTATION_KEY, data)
231263

232264

233265
}

0 commit comments

Comments
 (0)