diff --git a/PhoenixCrypto/PhoenixCrypto.xcodeproj/project.pbxproj b/PhoenixCrypto/PhoenixCrypto.xcodeproj/project.pbxproj index 3be719534..4b50e8f12 100644 --- a/PhoenixCrypto/PhoenixCrypto.xcodeproj/project.pbxproj +++ b/PhoenixCrypto/PhoenixCrypto.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + DC35ED842630A381002E441D /* NativeWeakRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC35ED832630A381002E441D /* NativeWeakRandom.swift */; }; DC9B8EC625D6D41400E13818 /* PhoenixCrypto.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9B8EC525D6D41400E13818 /* PhoenixCrypto.m */; }; DC9B8EC725D6D41400E13818 /* PhoenixCrypto.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DC9B8EC425D6D41400E13818 /* PhoenixCrypto.h */; }; DC9B8ED425D6D7A100E13818 /* NativeChaChaPoly.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9B8ED325D6D7A100E13818 /* NativeChaChaPoly.swift */; }; @@ -26,6 +27,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + DC35ED832630A381002E441D /* NativeWeakRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeWeakRandom.swift; sourceTree = ""; }; DC9B8EC125D6D41400E13818 /* libPhoenixCrypto.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPhoenixCrypto.a; sourceTree = BUILT_PRODUCTS_DIR; }; DC9B8EC425D6D41400E13818 /* PhoenixCrypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhoenixCrypto.h; sourceTree = ""; }; DC9B8EC525D6D41400E13818 /* PhoenixCrypto.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PhoenixCrypto.m; sourceTree = ""; }; @@ -74,6 +76,7 @@ DC9B8EC425D6D41400E13818 /* PhoenixCrypto.h */, DC9B8EC525D6D41400E13818 /* PhoenixCrypto.m */, DC9B8ED325D6D7A100E13818 /* NativeChaChaPoly.swift */, + DC35ED832630A381002E441D /* NativeWeakRandom.swift */, DC9B8ED225D6D7A000E13818 /* PhoenixCrypto-Bridging-Header.h */, ); path = Classes; @@ -159,6 +162,7 @@ buildActionMask = 2147483647; files = ( DC9B8ED425D6D7A100E13818 /* NativeChaChaPoly.swift in Sources */, + DC35ED842630A381002E441D /* NativeWeakRandom.swift in Sources */, DC9B8EC625D6D41400E13818 /* PhoenixCrypto.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/PhoenixCrypto/PhoenixCrypto/Classes/NativeChaChaPoly.swift b/PhoenixCrypto/PhoenixCrypto/Classes/NativeChaChaPoly.swift index a34688163..397b9824b 100644 --- a/PhoenixCrypto/PhoenixCrypto/Classes/NativeChaChaPoly.swift +++ b/PhoenixCrypto/PhoenixCrypto/Classes/NativeChaChaPoly.swift @@ -2,20 +2,16 @@ import Foundation import CryptoKit import os.log -//#if DEBUG && true fileprivate var log = Logger( subsystem: Bundle.main.bundleIdentifier!, category: "NativeChaChaPoly" ) -//#else -//fileprivate var log = Logger(OSLog.disabled) -//#endif @objc public class NativeChaChaPoly: NSObject { @objc - public class func chachapoly_encrypt( + public class func encrypt( key: Data, nonce: Data, authenticatedData: Data, @@ -50,7 +46,7 @@ public class NativeChaChaPoly: NSObject { } @objc - public class func chachapoly_decrypt( + public class func decrypt( key: Data, nonce: Data, authenticatedData: Data, diff --git a/PhoenixCrypto/PhoenixCrypto/Classes/NativeWeakRandom.swift b/PhoenixCrypto/PhoenixCrypto/Classes/NativeWeakRandom.swift new file mode 100644 index 000000000..267998e57 --- /dev/null +++ b/PhoenixCrypto/PhoenixCrypto/Classes/NativeWeakRandom.swift @@ -0,0 +1,69 @@ +import Foundation +import CryptoKit +import os.log + +fileprivate var log = Logger( + subsystem: Bundle.main.bundleIdentifier!, + category: "NativeWeakRandom" +) + +@objc +public class NativeWeakRandom: NSObject { + + @objc + private class func toByteArr(i: UInt64) -> [UInt8] { + let count = MemoryLayout.size + var _i = i + return withUnsafePointer(to: &_i) { + $0.withMemoryRebound(to: UInt8.self, capacity: count) { + [UInt8](UnsafeBufferPointer(start: $0, count: count)) + } + } + } + + @objc + public class func sample() -> Data { + + // Sample some entropy from the running process metrics. + let pid = UInt64(getpid()) + let usage = rusage_info_current() + var entropy : [UInt8] = toByteArr(i: pid) + entropy += toByteArr(i: usage.ri_user_time) + entropy += toByteArr(i: usage.ri_system_time) + entropy += toByteArr(i: usage.ri_pkg_idle_wkups) + entropy += toByteArr(i: usage.ri_interrupt_wkups) + entropy += toByteArr(i: usage.ri_pageins) + entropy += toByteArr(i: usage.ri_wired_size) + entropy += toByteArr(i: usage.ri_resident_size) + entropy += toByteArr(i: usage.ri_phys_footprint) + entropy += toByteArr(i: usage.ri_proc_start_abstime) + entropy += toByteArr(i: usage.ri_proc_exit_abstime) + entropy += toByteArr(i: usage.ri_child_user_time) + entropy += toByteArr(i: usage.ri_child_system_time) + entropy += toByteArr(i: usage.ri_child_pkg_idle_wkups) + entropy += toByteArr(i: usage.ri_child_interrupt_wkups) + entropy += toByteArr(i: usage.ri_child_pageins) + entropy += toByteArr(i: usage.ri_child_elapsed_abstime) + entropy += toByteArr(i: usage.ri_diskio_bytesread) + entropy += toByteArr(i: usage.ri_diskio_byteswritten) + entropy += toByteArr(i: usage.ri_cpu_time_qos_default) + entropy += toByteArr(i: usage.ri_cpu_time_qos_maintenance) + entropy += toByteArr(i: usage.ri_cpu_time_qos_background) + entropy += toByteArr(i: usage.ri_cpu_time_qos_utility) + entropy += toByteArr(i: usage.ri_cpu_time_qos_legacy) + entropy += toByteArr(i: usage.ri_cpu_time_qos_user_initiated) + entropy += toByteArr(i: usage.ri_cpu_time_qos_user_interactive) + entropy += toByteArr(i: usage.ri_billed_system_time) + entropy += toByteArr(i: usage.ri_serviced_system_time) + entropy += toByteArr(i: usage.ri_logical_writes) + entropy += toByteArr(i: usage.ri_lifetime_max_phys_footprint) + entropy += toByteArr(i: usage.ri_instructions) + entropy += toByteArr(i: usage.ri_cycles) + entropy += toByteArr(i: usage.ri_billed_energy) + entropy += toByteArr(i: usage.ri_serviced_energy) + entropy += toByteArr(i: usage.ri_interval_max_phys_footprint) + entropy += toByteArr(i: usage.ri_runnable_time) + + return Data(entropy) + } +} diff --git a/PhoenixCrypto/PhoenixCrypto/Classes/PhoenixCrypto.h b/PhoenixCrypto/PhoenixCrypto/Classes/PhoenixCrypto.h index d356af018..e29cb59dc 100644 --- a/PhoenixCrypto/PhoenixCrypto/Classes/PhoenixCrypto.h +++ b/PhoenixCrypto/PhoenixCrypto/Classes/PhoenixCrypto.h @@ -1,10 +1,3 @@ -// -// PhoenixCrypto.h -// PhoenixCrypto -// -// Created by Robbie Hanson on 2/12/21. -// - #import #import "PhoenixCrypto-Swift.h" diff --git a/PhoenixCrypto/PhoenixCrypto/Classes/PhoenixCrypto.m b/PhoenixCrypto/PhoenixCrypto/Classes/PhoenixCrypto.m index 991073e77..b37eacd9a 100644 --- a/PhoenixCrypto/PhoenixCrypto/Classes/PhoenixCrypto.m +++ b/PhoenixCrypto/PhoenixCrypto/Classes/PhoenixCrypto.m @@ -1,10 +1,3 @@ -// -// PhoenixCrypto.m -// PhoenixCrypto -// -// Created by Robbie Hanson on 2/12/21. -// - #import "PhoenixCrypto.h" @implementation PhoenixCrypto diff --git a/src/commonMain/kotlin/fr/acinq/lightning/crypto/LocalKeyManager.kt b/src/commonMain/kotlin/fr/acinq/lightning/crypto/LocalKeyManager.kt index 8bce00456..7ff63ad6a 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/crypto/LocalKeyManager.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/crypto/LocalKeyManager.kt @@ -3,9 +3,9 @@ package fr.acinq.lightning.crypto import fr.acinq.bitcoin.* import fr.acinq.bitcoin.DeterministicWallet.derivePrivateKey import fr.acinq.bitcoin.DeterministicWallet.hardened -import fr.acinq.lightning.Lightning.secureRandom import fr.acinq.lightning.channel.ChannelKeys import fr.acinq.lightning.channel.RecoveredChannelKeys +import fr.acinq.lightning.Lightning.randomLong import fr.acinq.lightning.transactions.Transactions data class LocalKeyManager(val seed: ByteVector, val chainHash: ByteVector32) : KeyManager { @@ -58,7 +58,7 @@ data class LocalKeyManager(val seed: ByteVector, val chainHash: ByteVector32) : override fun newFundingKeyPath(isFunder: Boolean): KeyPath { val last = hardened(if (isFunder) 1 else 0) - fun next() = secureRandom.nextInt().toLong() and 0xFFFFFFFF + fun next() = randomLong() and 0xFFFFFFFF return KeyPath(listOf(next(), next(), next(), next(), next(), next(), next(), next(), last)) } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/crypto/WeakRandom.kt b/src/commonMain/kotlin/fr/acinq/lightning/crypto/WeakRandom.kt new file mode 100644 index 000000000..fb4fdd569 --- /dev/null +++ b/src/commonMain/kotlin/fr/acinq/lightning/crypto/WeakRandom.kt @@ -0,0 +1,54 @@ +package fr.acinq.lightning.crypto + +import fr.acinq.bitcoin.Crypto.sha256 +import fr.acinq.bitcoin.crypto.Pack +import fr.acinq.lightning.utils.currentTimestampMillis +import fr.acinq.lightning.utils.runtimeEntropy +import fr.acinq.lightning.utils.xor +import kotlin.native.concurrent.ThreadLocal + +/** + * A weak pseudo-random number generator that regularly samples a few entropy sources to build a hash chain. + * This should never be used alone but can be xor-ed with the OS random number generator in case it completely breaks. + */ +@ThreadLocal +object WeakRandom { + + private var seed = ByteArray(32) + private var stream = ChaCha20(seed, ByteArray(12), 0) + private var lastByte: Byte = 0 + private var opsSinceLastSample: Int = 0 + + private fun sampleEntropy() { + opsSinceLastSample = 0 + val commonEntropy = Pack.writeInt64BE(currentTimestampMillis()) + Pack.writeInt32BE(ByteArray(0).hashCode()) + val runtimeEntropy = runtimeEntropy() + seed = seed.xor(sha256(commonEntropy + runtimeEntropy)) + stream = ChaCha20(seed, ByteArray(12), 0) + } + + /** We sample new entropy approximately every 8 operations and at most every 16 operations. */ + private fun shouldSample(): Boolean { + opsSinceLastSample += 1 + val condition1 = -16 <= lastByte && lastByte <= 16 + val condition2 = opsSinceLastSample >= 16 + return condition1 || condition2 + } + + fun nextBytes(array: ByteArray): ByteArray { + if (shouldSample()) { + sampleEntropy() + } + + stream.encrypt(array, array, array.size) + lastByte = array.last() + + return array + } + + fun nextLong(): Long { + val bytes = ByteArray(8) + nextBytes(bytes) + return Pack.int64BE(bytes) + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/fr/acinq/lightning/crypto/noise/Noise.kt b/src/commonMain/kotlin/fr/acinq/lightning/crypto/noise/Noise.kt index 7daa1b71a..49f7fe7c4 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/crypto/noise/Noise.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/crypto/noise/Noise.kt @@ -1,6 +1,6 @@ package fr.acinq.lightning.crypto.noise -import kotlin.random.Random +import fr.acinq.lightning.Lightning.randomBytes interface DHFunctions { fun name(): String @@ -296,7 +296,7 @@ interface ByteStream { } object RandomBytes : ByteStream { - override fun nextBytes(length: Int) = Random.nextBytes(length) + override fun nextBytes(length: Int) = randomBytes(length) } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/eclair.kt b/src/commonMain/kotlin/fr/acinq/lightning/eclair.kt index 2aaccabc1..30551b65d 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/eclair.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/eclair.kt @@ -4,18 +4,22 @@ import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.ByteVector64 import fr.acinq.bitcoin.KeyPath import fr.acinq.bitcoin.PrivateKey +import fr.acinq.lightning.crypto.WeakRandom import fr.acinq.lightning.utils.secure +import fr.acinq.lightning.utils.xor import kotlin.experimental.xor import kotlin.random.Random object Lightning { - val secureRandom = Random.secure() + private val secureRandom = Random.secure() fun randomBytes(length: Int): ByteArray { val buffer = ByteArray(length) secureRandom.nextBytes(buffer) - return buffer + val weakBuffer = ByteArray(length) + WeakRandom.nextBytes(weakBuffer) + return buffer.xor(weakBuffer) } fun randomBytes32(): ByteVector32 = ByteVector32(randomBytes(32)) @@ -24,10 +28,14 @@ object Lightning { fun randomKeyPath(length: Int): KeyPath { val path = mutableListOf() - repeat(length) { path.add(secureRandom.nextLong()) } + repeat(length) { path.add(randomLong()) } return KeyPath(path) } + fun randomLong(): Long { + return secureRandom.nextLong().xor(WeakRandom.nextLong()) + } + fun toLongId(fundingTxHash: ByteVector32, fundingOutputIndex: Int): ByteVector32 { require(fundingOutputIndex < 65536) { "fundingOutputIndex must not be greater than FFFF" } val x1 = fundingTxHash[30] xor (fundingOutputIndex.shr(8)).toByte() diff --git a/src/commonMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt b/src/commonMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt index 6cca30891..4ee29e6fd 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt @@ -4,3 +4,9 @@ import kotlin.random.Random // https://github.com/Kotlin/KEEP/issues/184 expect fun Random.Default.secure(): Random + +/** + * This function should return some entropy based on application-specific runtime data. + * It doesn't need to be very strong entropy as it's only used as a backup, but it should be as good as possible. + */ +expect fun runtimeEntropy(): ByteArray \ No newline at end of file diff --git a/src/commonTest/kotlin/fr/acinq/lightning/crypto/SchaChainTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/crypto/ShaChainTestsCommon.kt similarity index 99% rename from src/commonTest/kotlin/fr/acinq/lightning/crypto/SchaChainTestsCommon.kt rename to src/commonTest/kotlin/fr/acinq/lightning/crypto/ShaChainTestsCommon.kt index 29e8bf482..f1c96fd94 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/crypto/SchaChainTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/crypto/ShaChainTestsCommon.kt @@ -9,7 +9,7 @@ import kotlin.test.assertFailsWith import kotlin.test.assertTrue -class SchaChainTestsCommon : LightningTestSuite() { +class ShaChainTestsCommon : LightningTestSuite() { private val expected = listOf( Hex.decode("f85a0f7f237ed2139855703db4264de380ec731f64a3d832c22a5f2ef615f1d5"), Hex.decode("a07acb1203f8d7a761eb43e109e46fd877031a6fd2a8e6840f064a49ba826aec"), diff --git a/src/commonTest/kotlin/fr/acinq/lightning/crypto/WeakRandomTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/crypto/WeakRandomTestsCommon.kt new file mode 100644 index 000000000..1879eb495 --- /dev/null +++ b/src/commonTest/kotlin/fr/acinq/lightning/crypto/WeakRandomTestsCommon.kt @@ -0,0 +1,75 @@ +package fr.acinq.lightning.crypto + +import fr.acinq.bitcoin.crypto.Pack +import fr.acinq.lightning.tests.utils.LightningTestSuite +import fr.acinq.lightning.utils.BitField +import kotlin.math.log2 +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotSame +import kotlin.test.assertTrue + +class WeakRandomTestsCommon : LightningTestSuite() { + + @Test + fun `random long generation`() { + val randomNumbers = (1..1000).map { WeakRandom.nextLong() } + assertEquals(1000, randomNumbers.toSet().size) + val entropy = randomNumbers.sumOf { entropyScore(it) } / 1000 + assertTrue(entropy >= 0.98) + } + + @Test + fun `random bytes generation (small length)`() { + val b1 = ByteArray(32) + WeakRandom.nextBytes(b1) + val b2 = ByteArray(32) + WeakRandom.nextBytes(b2) + val b3 = ByteArray(32) + WeakRandom.nextBytes(b3) + assertNotSame(b1, b2) + assertNotSame(b1, b3) + assertNotSame(b2, b3) + } + + @Test + fun `random bytes generation (same length)`() { + var randomBytes = ByteArray(0) + for (i in 1..1000) { + val buffer = ByteArray(64) + WeakRandom.nextBytes(buffer) + randomBytes += buffer + } + val entropy = entropyScore(randomBytes) + assertTrue(entropy >= 0.99) + } + + @Test + fun `random bytes generation (variable length)`() { + var randomBytes = ByteArray(0) + for (i in 10..500) { + val buffer = ByteArray(i) + WeakRandom.nextBytes(buffer) + randomBytes += buffer + } + val entropy = entropyScore(randomBytes) + assertTrue(entropy >= 0.99) + } + + companion object { + // See https://en.wikipedia.org/wiki/Binary_entropy_function + private fun entropyScore(bits: BitField): Double { + val p = bits.asLeftSequence().fold(0) { acc, bit -> if (bit) acc + 1 else acc }.toDouble() / bits.bitCount + return (-p) * log2(p) - (1 - p) * log2(1 - p) + } + + fun entropyScore(l: Long): Double { + return entropyScore(BitField.from(Pack.writeInt64BE(l))) + } + + fun entropyScore(bytes: ByteArray): Double { + return entropyScore(BitField.from(bytes)) + } + } + +} \ No newline at end of file diff --git a/src/iosMain/kotlin/fr/acinq/lightning/crypto/noise/Chacha20Poly1305CipherFunctions.kt b/src/iosMain/kotlin/fr/acinq/lightning/crypto/noise/Chacha20Poly1305CipherFunctions.kt index beab2a28a..269c9d63f 100644 --- a/src/iosMain/kotlin/fr/acinq/lightning/crypto/noise/Chacha20Poly1305CipherFunctions.kt +++ b/src/iosMain/kotlin/fr/acinq/lightning/crypto/noise/Chacha20Poly1305CipherFunctions.kt @@ -1,36 +1,10 @@ package fr.acinq.lightning.crypto.noise import swift.phoenix_crypto.* +import fr.acinq.lightning.utils.toByteArray +import fr.acinq.lightning.utils.toNSData import fr.acinq.lightning.crypto.ChaCha20Poly1305 -import kotlinx.cinterop.addressOf import kotlinx.cinterop.autoreleasepool -import kotlinx.cinterop.pin -import kotlinx.cinterop.usePinned -import platform.Foundation.create -import platform.Foundation.NSData -import platform.posix.memcpy - -fun NSData.toByteArray(): ByteArray { - val data = this - return ByteArray(data.length.toInt()).apply { - if (data.length > 0uL) { - usePinned { pinned -> - memcpy(pinned.addressOf(0), data.bytes, data.length) - } - } - } -} - -fun ByteArray.toNSData(): NSData { - if (isEmpty()) return NSData() - val pinned = pin() - return NSData.create( - bytesNoCopy = pinned.addressOf(0), - length = size.toULong(), - deallocator = { _, _ -> pinned.unpin() } - ) -} - actual object Chacha20Poly1305CipherFunctions : CipherFunctions { override fun name() = "ChaChaPoly" @@ -41,7 +15,7 @@ actual object Chacha20Poly1305CipherFunctions : CipherFunctions { // Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique. override fun encrypt(k: ByteArray, n: Long, ad: ByteArray, plaintext: ByteArray): ByteArray { autoreleasepool { - val ciphertextAndMac = NativeChaChaPoly.chachapoly_encryptWithKey( + val ciphertextAndMac = NativeChaChaPoly.encryptWithKey( key = k.toNSData(), nonce = nonce(n).toNSData(), authenticatedData = ad.toNSData(), @@ -55,7 +29,7 @@ actual object Chacha20Poly1305CipherFunctions : CipherFunctions { @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") override fun decrypt(k: ByteArray, n: Long, ad: ByteArray, ciphertextAndMac: ByteArray): ByteArray { autoreleasepool { - val plaintext = NativeChaChaPoly.chachapoly_decryptWithKey( + val plaintext = NativeChaChaPoly.decryptWithKey( key = k.toNSData(), nonce = nonce(n).toNSData(), authenticatedData = ad.toNSData(), diff --git a/src/iosMain/kotlin/fr/acinq/lightning/utils/IosBridgeUtils.kt b/src/iosMain/kotlin/fr/acinq/lightning/utils/IosBridgeUtils.kt new file mode 100644 index 000000000..1a39aac08 --- /dev/null +++ b/src/iosMain/kotlin/fr/acinq/lightning/utils/IosBridgeUtils.kt @@ -0,0 +1,29 @@ +package fr.acinq.lightning.utils + +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.pin +import kotlinx.cinterop.usePinned +import platform.Foundation.create +import platform.Foundation.NSData +import platform.posix.memcpy + +fun NSData.toByteArray(): ByteArray { + val data = this + return ByteArray(data.length.toInt()).apply { + if (data.length > 0uL) { + usePinned { pinned -> + memcpy(pinned.addressOf(0), data.bytes, data.length) + } + } + } +} + +fun ByteArray.toNSData(): NSData { + if (isEmpty()) return NSData() + val pinned = pin() + return NSData.create( + bytesNoCopy = pinned.addressOf(0), + length = size.toULong(), + deallocator = { _, _ -> pinned.unpin() } + ) +} \ No newline at end of file diff --git a/src/iosMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt b/src/iosMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt new file mode 100644 index 000000000..29cca6c9f --- /dev/null +++ b/src/iosMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt @@ -0,0 +1,11 @@ +package fr.acinq.lightning.utils + +import swift.phoenix_crypto.* +import kotlinx.cinterop.autoreleasepool + +actual fun runtimeEntropy(): ByteArray { + autoreleasepool { + val result = NativeWeakRandom.sample() + return result.toByteArray() + } +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/fr/acinq/lightning/utils/SecureRandomJvm.kt b/src/jvmMain/kotlin/fr/acinq/lightning/utils/SecureRandomJvm.kt index 7616d6be8..4b5e7f7b2 100644 --- a/src/jvmMain/kotlin/fr/acinq/lightning/utils/SecureRandomJvm.kt +++ b/src/jvmMain/kotlin/fr/acinq/lightning/utils/SecureRandomJvm.kt @@ -1,5 +1,6 @@ package fr.acinq.lightning.utils +import java.lang.management.ManagementFactory import java.security.SecureRandom import kotlin.random.Random @@ -28,3 +29,27 @@ class SecureRandomJvm : Random() { } actual fun Random.Default.secure(): Random = SecureRandomJvm() + +actual fun runtimeEntropy(): ByteArray { + val memoryMXBean = ManagementFactory.getMemoryMXBean() + val runtimeMXBean = ManagementFactory.getRuntimeMXBean() + val threadMXBean = ManagementFactory.getThreadMXBean() + + fun toBytes(l: Long): List { + return buildList { + add(l.toByte()) + add((l shr 8).toByte()) + add((l shr 16).toByte()) + add((l shr 24).toByte()) + } + } + + return toBytes(memoryMXBean.getHeapMemoryUsage().getUsed()) + .plus(toBytes(memoryMXBean.getNonHeapMemoryUsage().getUsed())) + .plus(toBytes(runtimeMXBean.getPid())) + .plus(toBytes(runtimeMXBean.getUptime())) + .plus(toBytes(threadMXBean.getCurrentThreadCpuTime())) + .plus(toBytes(threadMXBean.getCurrentThreadUserTime())) + .plus(toBytes(threadMXBean.getPeakThreadCount().toLong())) + .toByteArray() +} \ No newline at end of file diff --git a/src/linuxMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt b/src/linuxMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt new file mode 100644 index 000000000..250b78561 --- /dev/null +++ b/src/linuxMain/kotlin/fr/acinq/lightning/utils/SecureRandom.kt @@ -0,0 +1,5 @@ +package fr.acinq.lightning.utils + +actual fun runtimeEntropy(): ByteArray { + return ByteArray(32) +} \ No newline at end of file