Compare commits
2 Commits
f818b38b3d
...
abc8ee7553
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abc8ee7553 | ||
|
|
f46459b687 |
@@ -0,0 +1,138 @@
|
|||||||
|
package org.ccoin.services
|
||||||
|
|
||||||
|
import org.ccoin.database.Tables
|
||||||
|
import org.ccoin.exceptions.WalletNotFoundException
|
||||||
|
import org.ccoin.models.WalletResponse
|
||||||
|
import org.ccoin.utils.CryptoUtils
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
object WalletService {
|
||||||
|
|
||||||
|
/** Creates a new wallet with optional label */
|
||||||
|
fun createWallet(label: String? = null): WalletResponse {
|
||||||
|
val address = CryptoUtils.generateWalletAddress()
|
||||||
|
val timestamp = Instant.now().epochSecond
|
||||||
|
|
||||||
|
return transaction {
|
||||||
|
Tables.Wallets.insert {
|
||||||
|
it[Tables.Wallets.address] = address
|
||||||
|
it[Tables.Wallets.label] = label
|
||||||
|
it[createdAt] = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletResponse(address, 0.0, label, timestamp, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets wallet by address */
|
||||||
|
fun getWallet(address: String): WalletResponse? = transaction {
|
||||||
|
Tables.Wallets.selectAll().where { Tables.Wallets.address eq address }
|
||||||
|
.map {
|
||||||
|
WalletResponse(
|
||||||
|
it[Tables.Wallets.address],
|
||||||
|
it[Tables.Wallets.balance].toDouble(),
|
||||||
|
it[Tables.Wallets.label],
|
||||||
|
it[Tables.Wallets.createdAt],
|
||||||
|
it[Tables.Wallets.lastActivity]
|
||||||
|
)
|
||||||
|
}.singleOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets wallet balance */
|
||||||
|
fun getWalletBalance(address: String): Double = transaction {
|
||||||
|
Tables.Wallets.selectAll().where { Tables.Wallets.address eq address }
|
||||||
|
.map { it[Tables.Wallets.balance].toDouble() }
|
||||||
|
.singleOrNull() ?: throw WalletNotFoundException(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates wallet balance */
|
||||||
|
fun updateBalance(address: String, amount: BigDecimal): Boolean = transaction {
|
||||||
|
val currentBalance = Tables.Wallets.selectAll()
|
||||||
|
.where { Tables.Wallets.address eq address }
|
||||||
|
.map { it[Tables.Wallets.balance] }
|
||||||
|
.singleOrNull() ?: return@transaction false
|
||||||
|
|
||||||
|
val updated = Tables.Wallets.update({ Tables.Wallets.address eq address }) {
|
||||||
|
it[balance] = currentBalance.add(amount)
|
||||||
|
it[lastActivity] = Instant.now().epochSecond
|
||||||
|
}
|
||||||
|
updated > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets wallet balance to specific amount */
|
||||||
|
fun setBalance(address: String, amount: BigDecimal): Boolean = transaction {
|
||||||
|
val updated = Tables.Wallets.update({ Tables.Wallets.address eq address }) {
|
||||||
|
it[balance] = amount
|
||||||
|
it[lastActivity] = Instant.now().epochSecond
|
||||||
|
}
|
||||||
|
updated > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates wallet label */
|
||||||
|
fun updateLabel(address: String, label: String?): Boolean = transaction {
|
||||||
|
val updated = Tables.Wallets.update({ Tables.Wallets.address eq address }) {
|
||||||
|
it[Tables.Wallets.label] = label
|
||||||
|
}
|
||||||
|
updated > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checks if wallet exists */
|
||||||
|
fun walletExists(address: String): Boolean = transaction {
|
||||||
|
Tables.Wallets.selectAll().where { Tables.Wallets.address eq address }.count() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets all wallets with pagination */
|
||||||
|
fun getAllWallets(limit: Int = 50, offset: Int = 0): List<WalletResponse> = transaction {
|
||||||
|
Tables.Wallets.selectAll()
|
||||||
|
.orderBy(Tables.Wallets.createdAt, SortOrder.DESC)
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset.toLong())
|
||||||
|
.map {
|
||||||
|
WalletResponse(
|
||||||
|
it[Tables.Wallets.address],
|
||||||
|
it[Tables.Wallets.balance].toDouble(),
|
||||||
|
it[Tables.Wallets.label],
|
||||||
|
it[Tables.Wallets.createdAt],
|
||||||
|
it[Tables.Wallets.lastActivity]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets total number of wallets */
|
||||||
|
fun getTotalWalletCount(): Long = transaction {
|
||||||
|
Tables.Wallets.selectAll().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets wallets with balance greater than specified amount */
|
||||||
|
fun getWalletsWithBalance(minBalance: Double): List<WalletResponse> = transaction {
|
||||||
|
Tables.Wallets.selectAll().where { Tables.Wallets.balance greater BigDecimal.valueOf(minBalance) }
|
||||||
|
.orderBy(Tables.Wallets.balance, SortOrder.DESC)
|
||||||
|
.map {
|
||||||
|
WalletResponse(
|
||||||
|
it[Tables.Wallets.address],
|
||||||
|
it[Tables.Wallets.balance].toDouble(),
|
||||||
|
it[Tables.Wallets.label],
|
||||||
|
it[Tables.Wallets.createdAt],
|
||||||
|
it[Tables.Wallets.lastActivity]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates last activity timestamp */
|
||||||
|
fun updateLastActivity(address: String): Boolean = transaction {
|
||||||
|
val updated = Tables.Wallets.update({ Tables.Wallets.address eq address }) {
|
||||||
|
it[lastActivity] = Instant.now().epochSecond
|
||||||
|
}
|
||||||
|
updated > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets total supply across all wallets */
|
||||||
|
fun getTotalSupply(): Double = transaction {
|
||||||
|
Tables.Wallets.select(Tables.Wallets.balance.sum())
|
||||||
|
.single()[Tables.Wallets.balance.sum()]?.toDouble() ?: 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package org.ccoin.utils
|
||||||
|
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
|
object HashUtils {
|
||||||
|
private val secureRandom = SecureRandom()
|
||||||
|
|
||||||
|
/** Computes SHA-256 hash of a byte array */
|
||||||
|
fun sha256(data: ByteArray): ByteArray = MessageDigest.getInstance("SHA-256").digest(data)
|
||||||
|
|
||||||
|
/** Computes SHA-256 hash of a string and returns hex string */
|
||||||
|
fun sha256Hex(input: String): String = sha256(input.toByteArray()).toHexString()
|
||||||
|
|
||||||
|
/** Computes double SHA-256 hash (like Bitcoin) */
|
||||||
|
fun doubleSha256(data: ByteArray): ByteArray = sha256(sha256(data))
|
||||||
|
|
||||||
|
/** Computes double SHA-256 hash and returns hex string */
|
||||||
|
fun doubleSha256Hex(input: String): String = doubleSha256(input.toByteArray()).toHexString()
|
||||||
|
|
||||||
|
/** Converts byte array to hex string */
|
||||||
|
fun ByteArray.toHexString(): String = joinToString("") { "%02x".format(it) }
|
||||||
|
|
||||||
|
/** Converts hex string to byte array */
|
||||||
|
fun String.hexToByteArray(): ByteArray {
|
||||||
|
require(length % 2 == 0) { "Hex string must have even length" }
|
||||||
|
return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validates if string is valid hex */
|
||||||
|
fun isValidHex(hex: String): Boolean = hex.all { it.isDigit() || it.lowercaseChar() in 'a'..'f' }
|
||||||
|
|
||||||
|
/** Generates a random hash for testing purposes */
|
||||||
|
fun generateRandomHash(): String {
|
||||||
|
val randomBytes = ByteArray(32)
|
||||||
|
secureRandom.nextBytes(randomBytes)
|
||||||
|
return randomBytes.toHexString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Computes hash with salt for additional security */
|
||||||
|
fun hashWithSalt(input: String, salt: String): String = sha256Hex("$input:$salt")
|
||||||
|
|
||||||
|
/** Generates a random salt */
|
||||||
|
fun generateSalt(length: Int = 16): String {
|
||||||
|
val saltBytes = ByteArray(length)
|
||||||
|
secureRandom.nextBytes(saltBytes)
|
||||||
|
return saltBytes.toHexString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Computes HMAC-SHA256 */
|
||||||
|
fun hmacSha256(key: String, message: String): String {
|
||||||
|
val keyBytes = key.toByteArray()
|
||||||
|
val messageBytes = message.toByteArray()
|
||||||
|
|
||||||
|
val blockSize = 64
|
||||||
|
val adjustedKey = when {
|
||||||
|
keyBytes.size > blockSize -> sha256(keyBytes)
|
||||||
|
keyBytes.size < blockSize -> keyBytes + ByteArray(blockSize - keyBytes.size)
|
||||||
|
else -> keyBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
val outerPad = ByteArray(blockSize) { (adjustedKey[it].toInt() xor 0x5c).toByte() }
|
||||||
|
val innerPad = ByteArray(blockSize) { (adjustedKey[it].toInt() xor 0x36).toByte() }
|
||||||
|
|
||||||
|
val innerHash = sha256(innerPad + messageBytes)
|
||||||
|
val finalHash = sha256(outerPad + innerHash)
|
||||||
|
|
||||||
|
return finalHash.toHexString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validates hash format (64 character hex string for SHA-256) */
|
||||||
|
fun isValidSha256Hash(hash: String): Boolean = hash.length == 64 && isValidHex(hash)
|
||||||
|
|
||||||
|
/** Computes checksum for data integrity */
|
||||||
|
fun computeChecksum(data: String): String = sha256Hex(data).take(8)
|
||||||
|
|
||||||
|
/** Validates data against checksum */
|
||||||
|
fun validateChecksum(data: String, checksum: String): Boolean = computeChecksum(data) == checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user