-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dev' of https://github.com/tonkeeper/android into featu…
…re/cd
- Loading branch information
Showing
47 changed files
with
1,361 additions
and
710 deletions.
There are no files selected for viewing
35 changes: 0 additions & 35 deletions
35
apps/signer/src/main/java/com/tonapps/signer/core/entities/PrivateKeyEntity.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
214 changes: 214 additions & 0 deletions
214
apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/CommentEncryption.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
package com.tonapps.wallet.data.events | ||
|
||
import com.tonapps.security.AesCbcState | ||
import com.tonapps.security.Security | ||
import io.ktor.util.hex | ||
import org.ton.api.pk.PrivateKeyEd25519 | ||
import org.ton.api.pub.PublicKeyEd25519 | ||
import org.ton.block.AddrStd | ||
import org.ton.cell.Cell | ||
import org.ton.cell.CellBuilder | ||
import org.ton.crypto.Ed25519 | ||
import kotlin.experimental.xor | ||
import kotlin.math.ceil | ||
|
||
object CommentEncryption { | ||
private const val ROOT_CELL_BYTE_LENGTH = 35 + 4 | ||
private const val CELL_BYTE_LENGTH = 127 | ||
|
||
fun decryptComment( | ||
publicKey: PublicKeyEd25519, | ||
privateKey: PrivateKeyEd25519, | ||
cipherText: String, | ||
senderAddress: String | ||
): String { | ||
val address = AddrStd(senderAddress).toString(bounceable = true, urlSafe = true) | ||
|
||
val decryptedData = decryptData( | ||
hex(cipherText), | ||
publicKey.key.toByteArray(), | ||
privateKey.key.toByteArray(), | ||
address.toByteArray() | ||
) | ||
|
||
return decryptedData.decodeToString() | ||
} | ||
fun encryptComment( | ||
comment: String, | ||
myPublicKey: PublicKeyEd25519, | ||
theirPublicKey: PublicKeyEd25519, | ||
myPrivateKey: PrivateKeyEd25519, | ||
senderAddress: String | ||
): Cell { | ||
val myPublicKeyBytes = myPublicKey.key.toByteArray() | ||
val theirPublicKeyBytes = theirPublicKey.key.toByteArray() | ||
val myPrivateKeyBytes = myPrivateKey.key.toByteArray() | ||
return encryptComment(comment, myPublicKeyBytes, theirPublicKeyBytes, myPrivateKeyBytes, senderAddress) | ||
} | ||
|
||
fun encryptComment( | ||
comment: String, | ||
myPublicKey: ByteArray, | ||
theirPublicKey: ByteArray, | ||
myPrivateKey: ByteArray, | ||
senderAddress: String | ||
): Cell { | ||
if (comment.isEmpty()) throw IllegalArgumentException("empty comment") | ||
|
||
val privateKey = if (myPrivateKey.size == 64) { | ||
myPrivateKey.sliceArray(0 until 32) | ||
} else { | ||
myPrivateKey | ||
} | ||
|
||
val commentBytes = comment.toByteArray() | ||
|
||
val address = AddrStd(senderAddress).toString(bounceable = true, urlSafe = true) | ||
val salt = address.toByteArray() | ||
|
||
val encryptedBytes = | ||
encryptData(commentBytes, myPublicKey, theirPublicKey, privateKey, salt) | ||
|
||
val payload = ByteArray(encryptedBytes.size + 4) | ||
payload[0] = 0x21 // encrypted text prefix | ||
payload[1] = 0x67 | ||
payload[2] = 0xda.toByte() | ||
payload[3] = 0x4b | ||
encryptedBytes.copyInto(payload, 4) | ||
|
||
return makeSnakeCells(payload) | ||
} | ||
|
||
private fun makeSnakeCells(bytes: ByteArray): Cell { | ||
val rootCellBuilder = CellBuilder.beginCell() | ||
.storeBytes(bytes.sliceArray(0 until minOf(bytes.size, ROOT_CELL_BYTE_LENGTH))) | ||
|
||
val cellCount = | ||
ceil((bytes.size - ROOT_CELL_BYTE_LENGTH).toDouble() / CELL_BYTE_LENGTH).toInt() | ||
if (cellCount > 16) { | ||
throw IllegalArgumentException("Text too long") | ||
} | ||
|
||
rootCellBuilder.storeRef(storeDeepRef(bytes.sliceArray(ROOT_CELL_BYTE_LENGTH until bytes.size))) | ||
|
||
return rootCellBuilder.endCell() | ||
} | ||
|
||
private fun storeDeepRef(bytes: ByteArray): Cell { | ||
return if (bytes.size <= CELL_BYTE_LENGTH) { | ||
CellBuilder.beginCell().storeBytes(bytes.sliceArray(0 until bytes.size)).endCell() | ||
} else { | ||
CellBuilder.beginCell().storeBytes(bytes.sliceArray(0 until CELL_BYTE_LENGTH)) | ||
.storeRef(storeDeepRef(bytes.sliceArray(CELL_BYTE_LENGTH until bytes.size))) | ||
.endCell() | ||
} | ||
} | ||
|
||
private fun encryptData( | ||
data: ByteArray, | ||
myPublicKey: ByteArray, | ||
theirPublicKey: ByteArray, | ||
privateKey: ByteArray, | ||
salt: ByteArray | ||
): ByteArray { | ||
val sharedSecret = Ed25519.sharedKey(privateKey, theirPublicKey) | ||
|
||
val encrypted = encryptDataImpl(data, sharedSecret, salt) | ||
val prefixedEncrypted = ByteArray(myPublicKey.size + encrypted.size) | ||
|
||
for (i in myPublicKey.indices) { | ||
prefixedEncrypted[i] = (theirPublicKey[i] xor myPublicKey[i]).toByte() | ||
} | ||
|
||
encrypted.copyInto(prefixedEncrypted, myPublicKey.size) | ||
|
||
return prefixedEncrypted | ||
} | ||
|
||
private fun encryptDataImpl( | ||
data: ByteArray, | ||
sharedSecret: ByteArray, | ||
salt: ByteArray | ||
): ByteArray { | ||
val prefix = getRandomPrefix(data.size, 16) | ||
val combined = ByteArray(prefix.size + data.size) | ||
System.arraycopy(prefix, 0, combined, 0, prefix.size) | ||
System.arraycopy(data, 0, combined, prefix.size, data.size) | ||
return encryptDataWithPrefix(combined, sharedSecret, salt) | ||
} | ||
|
||
private fun encryptDataWithPrefix( | ||
data: ByteArray, | ||
sharedSecret: ByteArray, | ||
salt: ByteArray | ||
): ByteArray { | ||
if (data.size % 16 != 0) throw IllegalArgumentException("Data length is not divisible by 16") | ||
val dataHash = Security.hmacSha512(salt, data) | ||
val msgKey = dataHash.sliceArray(0 until 16) | ||
val cbcStateSecret = Security.hmacSha512(sharedSecret, msgKey) | ||
val encrypted = AesCbcState(cbcStateSecret).encrypt(data) | ||
return msgKey + encrypted | ||
} | ||
|
||
private fun getRandomPrefix(dataLength: Int, minPadding: Int): ByteArray { | ||
val prefixLength = ((minPadding + 15 + dataLength) and -16) - dataLength | ||
val prefix = Security.randomBytes(prefixLength) | ||
prefix[0] = prefixLength.toByte() | ||
if ((prefixLength + dataLength) % 16 != 0) throw IllegalArgumentException("Prefix length is invalid") | ||
return prefix | ||
} | ||
|
||
private fun decryptData( | ||
data: ByteArray, | ||
publicKey: ByteArray, | ||
privateKey: ByteArray, | ||
salt: ByteArray | ||
): ByteArray { | ||
val theirPublicKey = ByteArray(publicKey.size) | ||
for (i in publicKey.indices) { | ||
theirPublicKey[i] = data[i] xor publicKey[i] | ||
} | ||
val sharedSecret = Ed25519.sharedKey(privateKey, theirPublicKey) | ||
return decryptDataImpl(data.sliceArray(publicKey.size until data.size), sharedSecret, salt) | ||
} | ||
|
||
private fun decryptDataImpl( | ||
encryptedData: ByteArray, | ||
sharedSecret: ByteArray, | ||
salt: ByteArray | ||
): ByteArray { | ||
if (encryptedData.size < 16) throw IllegalArgumentException("Failed to decrypt: data is too small") | ||
if (encryptedData.size % 16 != 0) throw IllegalArgumentException("Failed to decrypt: data size is not divisible by 16") | ||
|
||
val msgKey = encryptedData.sliceArray(0 until 16) | ||
val data = encryptedData.sliceArray(16 until encryptedData.size) | ||
val cbcStateSecret = Security.hmacSha512(sharedSecret, msgKey) | ||
|
||
return doDecrypt(cbcStateSecret, msgKey, data, salt) | ||
} | ||
|
||
private fun doDecrypt( | ||
cbcStateSecret: ByteArray, | ||
msgKey: ByteArray, | ||
encryptedData: ByteArray, | ||
salt: ByteArray | ||
): ByteArray { | ||
val decryptedData = AesCbcState(cbcStateSecret).decrypt(encryptedData) | ||
val dataHash = Security.hmacSha512(salt, decryptedData) | ||
|
||
val gotMsgKey = dataHash.sliceArray(0 until 16) | ||
|
||
if (!msgKey.contentEquals(gotMsgKey)) { | ||
throw IllegalArgumentException("Failed to decrypt: hash mismatch") | ||
} | ||
|
||
val prefixLength = decryptedData[0].toInt() | ||
if (prefixLength > decryptedData.size || prefixLength < 16) { | ||
throw IllegalArgumentException("Failed to decrypt: invalid prefix size") | ||
} | ||
|
||
return decryptedData.sliceArray(prefixLength until decryptedData.size) | ||
} | ||
|
||
|
||
} |
Oops, something went wrong.