Initial commit: rosetta-android-prime
This commit is contained in:
201
app/src/main/java/com/rosetta/messenger/crypto/CryptoManager.kt
Normal file
201
app/src/main/java/com/rosetta/messenger/crypto/CryptoManager.kt
Normal file
@@ -0,0 +1,201 @@
|
||||
package com.rosetta.messenger.crypto
|
||||
|
||||
import org.bitcoinj.crypto.MnemonicCode
|
||||
import org.bitcoinj.crypto.MnemonicException
|
||||
import org.bouncycastle.jce.ECNamedCurveTable
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.jce.spec.ECPrivateKeySpec
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import android.util.Base64
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.zip.Deflater
|
||||
import java.util.zip.Inflater
|
||||
|
||||
/**
|
||||
* Cryptography module for Rosetta Messenger
|
||||
* Implements BIP39 seed phrase generation and secp256k1 key derivation
|
||||
*/
|
||||
object CryptoManager {
|
||||
|
||||
private const val PBKDF2_ITERATIONS = 1000
|
||||
private const val KEY_SIZE = 256
|
||||
private const val SALT = "rosetta"
|
||||
|
||||
init {
|
||||
// Add BouncyCastle provider for secp256k1 support
|
||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new 12-word BIP39 seed phrase
|
||||
*/
|
||||
fun generateSeedPhrase(): List<String> {
|
||||
val secureRandom = SecureRandom()
|
||||
val entropy = ByteArray(16) // 128 bits = 12 words
|
||||
secureRandom.nextBytes(entropy)
|
||||
|
||||
val mnemonicCode = MnemonicCode.INSTANCE
|
||||
return mnemonicCode.toMnemonic(entropy)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a seed phrase
|
||||
*/
|
||||
fun validateSeedPhrase(words: List<String>): Boolean {
|
||||
return try {
|
||||
val mnemonicCode = MnemonicCode.INSTANCE
|
||||
mnemonicCode.check(words)
|
||||
true
|
||||
} catch (e: MnemonicException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert seed phrase to private key (64 bytes hex string)
|
||||
*/
|
||||
fun seedPhraseToPrivateKey(seedPhrase: List<String>): String {
|
||||
val mnemonicCode = MnemonicCode.INSTANCE
|
||||
val seed = MnemonicCode.toSeed(seedPhrase, "")
|
||||
|
||||
// Convert to hex string (128 characters for 64 bytes)
|
||||
return seed.joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate key pair from private key using secp256k1 curve
|
||||
*/
|
||||
fun generateKeyPairFromSeed(privateKeyHex: String): KeyPairData {
|
||||
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
|
||||
// Use first 32 bytes of private key for secp256k1
|
||||
val privateKeyBytes = privateKeyHex.take(64).chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
|
||||
val privateKeyBigInt = BigInteger(1, privateKeyBytes)
|
||||
|
||||
// Generate public key from private key
|
||||
val publicKeyPoint = ecSpec.g.multiply(privateKeyBigInt)
|
||||
val publicKeyHex = publicKeyPoint.getEncoded(false)
|
||||
.joinToString("") { "%02x".format(it) }
|
||||
|
||||
return KeyPairData(
|
||||
privateKey = privateKeyHex.take(64),
|
||||
publicKey = publicKeyHex
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate private key hash for protocol (SHA256(privateKey + "rosetta"))
|
||||
*/
|
||||
fun generatePrivateKeyHash(privateKey: String): String {
|
||||
val data = (privateKey + SALT).toByteArray()
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
val hash = digest.digest(data)
|
||||
return hash.joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt data with password using PBKDF2 + AES
|
||||
*/
|
||||
fun encryptWithPassword(password: String, data: String): String {
|
||||
// Compress data
|
||||
val compressed = compress(data.toByteArray())
|
||||
|
||||
// Derive key using PBKDF2
|
||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
val spec = PBEKeySpec(password.toCharArray(), SALT.toByteArray(), PBKDF2_ITERATIONS, KEY_SIZE)
|
||||
val secretKey = factory.generateSecret(spec)
|
||||
val key = SecretKeySpec(secretKey.encoded, "AES")
|
||||
|
||||
// Generate random IV
|
||||
val iv = ByteArray(16)
|
||||
SecureRandom().nextBytes(iv)
|
||||
val ivSpec = IvParameterSpec(iv)
|
||||
|
||||
// Encrypt with AES
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
|
||||
val encrypted = cipher.doFinal(compressed)
|
||||
|
||||
// Return iv:ciphertext in Base64
|
||||
val ivBase64 = Base64.encodeToString(iv, Base64.NO_WRAP)
|
||||
val ctBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP)
|
||||
|
||||
return "$ivBase64:$ctBase64"
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt data with password
|
||||
*/
|
||||
fun decryptWithPassword(password: String, encryptedData: String): String? {
|
||||
return try {
|
||||
val parts = encryptedData.split(":")
|
||||
if (parts.size != 2) return null
|
||||
|
||||
val iv = Base64.decode(parts[0], Base64.NO_WRAP)
|
||||
val ciphertext = Base64.decode(parts[1], Base64.NO_WRAP)
|
||||
|
||||
// Derive key using PBKDF2
|
||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
val spec = PBEKeySpec(password.toCharArray(), SALT.toByteArray(), PBKDF2_ITERATIONS, KEY_SIZE)
|
||||
val secretKey = factory.generateSecret(spec)
|
||||
val key = SecretKeySpec(secretKey.encoded, "AES")
|
||||
|
||||
// Decrypt
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
|
||||
val decrypted = cipher.doFinal(ciphertext)
|
||||
|
||||
// Decompress
|
||||
String(decompress(decrypted))
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compress(data: ByteArray): ByteArray {
|
||||
val deflater = Deflater()
|
||||
deflater.setInput(data)
|
||||
deflater.finish()
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val buffer = ByteArray(1024)
|
||||
while (!deflater.finished()) {
|
||||
val count = deflater.deflate(buffer)
|
||||
outputStream.write(buffer, 0, count)
|
||||
}
|
||||
outputStream.close()
|
||||
return outputStream.toByteArray()
|
||||
}
|
||||
|
||||
private fun decompress(data: ByteArray): ByteArray {
|
||||
val inflater = Inflater()
|
||||
inflater.setInput(data)
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val buffer = ByteArray(1024)
|
||||
while (!inflater.finished()) {
|
||||
val count = inflater.inflate(buffer)
|
||||
outputStream.write(buffer, 0, count)
|
||||
}
|
||||
outputStream.close()
|
||||
return outputStream.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
data class KeyPairData(
|
||||
val privateKey: String,
|
||||
val publicKey: String
|
||||
)
|
||||
Reference in New Issue
Block a user