10 Commits

Author SHA1 Message Date
Fabrice Drouin
1a4c8b37cb Release 0.12.0 (#95) 2023-12-13 16:40:32 +01:00
Fabrice Drouin
f242b4ffe8 Check arguments passed to secp256k1 methods (#94)
* Check arguments passed to secp256k1 methods

Illegal arguments will trigger an internal callback that prints to stderr and calls abort.
We already check arguments in our JNI and kotlin native code but had missed 2 checks (recid in ecdsaRecover, empty arrays in pubkeyCombine).

* Implement the same "tweak" checks in the native code and JNI code

The native code was missing checks on the "tweak" size (which must be 32 bytes)
2023-12-13 13:42:14 +01:00
Fabrice Drouin
161da89ee1 Set version to 0.11.0 (#86) 2023-09-28 09:50:24 +02:00
Fabrice Drouin
3706a546a2 Use secp256k1 0.4.0 (#85) 2023-09-18 14:05:36 +02:00
Fabrice Drouin
ffcaaf1b64 Set version to 0.10.1 (#84) 2023-06-28 13:03:19 +02:00
Fabrice Drouin
6ef94df247 Use secp256k1 0.3.2 (#83) 2023-06-28 10:43:05 +02:00
Fabrice Drouin
5169073a92 Update README.md 2023-05-15 11:15:30 +02:00
Fabrice Drouin
317e086cba Set version to 0.10.0 (#82) 2023-05-11 18:29:50 +02:00
Fabrice Drouin
7c7aabba80 Upgrade to Kotlin 1.8 (#81)
* Upgrade to Kotlin 1.8

* Update snapshot deployment script

Kotlin 1.8 creates a new metadata jar for ios modules.
2023-05-11 17:53:41 +02:00
Fabrice Drouin
b6823cbda6 Update CI build (#80) 2023-04-25 09:55:48 +02:00
10 changed files with 112 additions and 59 deletions

View File

@@ -33,7 +33,7 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
- name: Cached Android NDK - name: Cached Android NDK
if: matrix.os != 'windows-latest' if: matrix.os != 'windows-latest'
uses: actions/cache@v2 uses: actions/cache@v2
@@ -96,10 +96,9 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: 27 api-level: 27
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ndk: ${{ env.ANDROID_NDK_VERSION }} ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.10.2.4988404 cmake: 3.22.1
script: ./gradlew connectedCheck script: ./gradlew connectedCheck
- name: Publish Linux - name: Publish Linux
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'

View File

@@ -42,7 +42,7 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
- name: Cached Android NDK - name: Cached Android NDK
if: matrix.os != 'windows-latest' if: matrix.os != 'windows-latest'
uses: actions/cache@v2 uses: actions/cache@v2
@@ -105,10 +105,9 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: 27 api-level: 27
-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ndk: ${{ env.ANDROID_NDK_VERSION }} ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.10.2.4988404 cmake: 3.22.1
script: ./gradlew connectedCheck script: ./gradlew connectedCheck
- name: Publish Linux - name: Publish Linux
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'

View File

@@ -48,7 +48,7 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
- name: Cached Android NDK - name: Cached Android NDK
if: matrix.os != 'windows-latest' if: matrix.os != 'windows-latest'
uses: actions/cache@v2 uses: actions/cache@v2
@@ -111,8 +111,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: 27 api-level: 27
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ndk: ${{ env.ANDROID_NDK_VERSION }} ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.10.2.4988404 cmake: 3.22.1
script: ./gradlew connectedCheck script: ./gradlew connectedCheck

View File

@@ -1,4 +1,4 @@
[![Kotlin](https://img.shields.io/badge/Kotlin-1.6.21-blue.svg?style=flat&logo=kotlin)](http://kotlinlang.org) [![Kotlin](https://img.shields.io/badge/Kotlin-1.8.21-blue.svg?style=flat&logo=kotlin)](http://kotlinlang.org)
[![Maven Central](https://img.shields.io/maven-central/v/fr.acinq.secp256k1/secp256k1-kmp)](https://search.maven.org/search?q=g:fr.acinq.secp256k1%20a:secp256k1-kmp*) [![Maven Central](https://img.shields.io/maven-central/v/fr.acinq.secp256k1/secp256k1-kmp)](https://search.maven.org/search?q=g:fr.acinq.secp256k1%20a:secp256k1-kmp*)
![Github Actions](https://github.com/ACINQ/secp256k1-kmp/actions/workflows/test.yml/badge.svg) ![Github Actions](https://github.com/ACINQ/secp256k1-kmp/actions/workflows/test.yml/badge.svg)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ACINQ/secp256k1-kmp/blob/master/LICENSE) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ACINQ/secp256k1-kmp/blob/master/LICENSE)
@@ -58,9 +58,9 @@ Native targets include libsecp256k1, called through KMP's c-interop, simply add
The JVM library uses JNI bindings for libsecp256k1, which is much faster than BouncyCastle. It will extract and load native bindings for your operating system in a temporary directory. The JVM library uses JNI bindings for libsecp256k1, which is much faster than BouncyCastle. It will extract and load native bindings for your operating system in a temporary directory.
JNI libraries are included for: JNI libraries are included for:
- Linux 64 bits - Linux 64 bits (x86_64 and arm64)
- Windows 64 bits - Windows 64 bits (x86_64)
- Macos 64 bits - Macos 64 bits (x86_64 and arm64)
Along this library, you **must** specify which JNI native library to use in your dependency manager: Along this library, you **must** specify which JNI native library to use in your dependency manager:

View File

@@ -3,8 +3,8 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.dokka.Platform import org.jetbrains.dokka.Platform
plugins { plugins {
kotlin("multiplatform") version "1.6.21" kotlin("multiplatform") version "1.8.21"
id("org.jetbrains.dokka") version "1.6.21" id("org.jetbrains.dokka") version "1.8.10"
`maven-publish` `maven-publish`
} }
@@ -16,13 +16,13 @@ buildscript {
dependencies { dependencies {
classpath("com.android.tools.build:gradle:7.3.1") classpath("com.android.tools.build:gradle:7.3.1")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.6.21") classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.8.10")
} }
} }
allprojects { allprojects {
group = "fr.acinq.secp256k1" group = "fr.acinq.secp256k1"
version = "0.9.0" version = "0.12.0"
repositories { repositories {
google() google()
@@ -157,6 +157,7 @@ allprojects {
Platform.js -> "js" Platform.js -> "js"
Platform.native -> "native" Platform.native -> "native"
Platform.common -> "common" Platform.common -> "common"
Platform.wasm -> "wasm"
} }
displayName.set(platformName) displayName.set(platformName)

View File

@@ -1,6 +1,9 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#ifdef WIN32
#define SECP256K1_STATIC // needed on windows when linking to a static version of secp256k1
#endif
#include "include/secp256k1.h" #include "include/secp256k1.h"
#include "include/secp256k1_ecdh.h" #include "include/secp256k1_ecdh.h"
#include "include/secp256k1_recovery.h" #include "include/secp256k1_recovery.h"
@@ -514,6 +517,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
if (jpubkeys == NULL) return NULL; if (jpubkeys == NULL) return NULL;
count = (*penv)->GetArrayLength(penv, jpubkeys); count = (*penv)->GetArrayLength(penv, jpubkeys);
CHECKRESULT(count < 1, "pubkey array cannot be empty")
pubkeys = calloc(count, sizeof(secp256k1_pubkey*)); pubkeys = calloc(count, sizeof(secp256k1_pubkey*));
for(i = 0; i < count; i++) { for(i = 0; i < count; i++) {
@@ -597,6 +601,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
if (jctx == 0) return NULL; if (jctx == 0) return NULL;
if (jsig == NULL) return NULL; if (jsig == NULL) return NULL;
if (jmsg == NULL) return NULL; if (jmsg == NULL) return NULL;
CHECKRESULT(recid < 0 || recid > 3, "invalid recovery id");
sigSize = (*penv)->GetArrayLength(penv, jsig); sigSize = (*penv)->GetArrayLength(penv, jsig);
int sigFormat = GetSignatureFormat(sigSize); int sigFormat = GetSignatureFormat(sigSize);

View File

@@ -2,52 +2,64 @@
GROUP_ID=fr.acinq.secp256k1 GROUP_ID=fr.acinq.secp256k1
ARTIFACT_ID_BASE=secp256k1-kmp ARTIFACT_ID_BASE=secp256k1-kmp
VERSION=0.9.0-SNAPSHOT
if [[ -z "${VERSION}" ]]; then
echo "VERSION is not defined"
exit 1
fi
cd snapshot cd snapshot
pushd . pushd .
cd fr/acinq/secp256k1/secp256k1-kmp/$VERSION cd fr/acinq/secp256k1/secp256k1-kmp/$VERSION
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \ mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
-DpomFile=$ARTIFACT_ID_BASE-$VERSION.pom \ -DpomFile=$ARTIFACT_ID_BASE-$VERSION.pom \
-Dfile=$ARTIFACT_ID_BASE-$VERSION.jar \ -Dfile=$ARTIFACT_ID_BASE-$VERSION.jar \
-Dfiles=$ARTIFACT_ID_BASE-$VERSION.module,$ARTIFACT_ID_BASE-$VERSION-kotlin-tooling-metadata.json \ -Dfiles=$ARTIFACT_ID_BASE-$VERSION.module,$ARTIFACT_ID_BASE-$VERSION-kotlin-tooling-metadata.json \
-Dtypes=module,json \ -Dtypes=module,json \
-Dclassifiers=,kotlin-tooling-metadata \ -Dclassifiers=,kotlin-tooling-metadata \
-Dsources=$ARTIFACT_ID_BASE-$VERSION-sources.jar \ -Dsources=$ARTIFACT_ID_BASE-$VERSION-sources.jar \
-Djavadoc=$ARTIFACT_ID_BASE-$VERSION-javadoc.jar -Djavadoc=$ARTIFACT_ID_BASE-$VERSION-javadoc.jar
popd popd
pushd . pushd .
for i in iosarm64 iosx64 jni-android jni-common jni-jvm-darwin jni-jvm-extract jni-jvm-linux jni-jvm-mingw jni-jvm jvm linux for i in iosarm64 iosx64 jni-android jni-common jni-jvm-darwin jni-jvm-extract jni-jvm-linux jni-jvm-mingw jni-jvm jvm linux; do
do cd fr/acinq/secp256k1/secp256k1-kmp-$i/$VERSION
cd fr/acinq/secp256k1/secp256k1-kmp-$i/$VERSION if [ $i == iosarm64 ] || [ $i == iosx64 ]; then
if [ $i == iosarm64 ] || [ $i == iosx64 ] || [ $i == linux ]; then mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \ -DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \ -Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.klib \
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.klib \ -Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION-metadata.jar,$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \ -Dtypes=jar,module,klib \
-Dtypes=module,klib \ -Dclassifiers=metadata,,cinterop-libsecp256k1 \
-Dclassifiers=,cinterop-libsecp256k1 \ -Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \ -Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar elif [ $i == linux ]; then
elif [ $i == jni-android ]; then mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \ -DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \ -Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.klib \
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.aar \ -Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \ -Dtypes=module,klib \
-Dtypes=module \ -Dclassifiers=,cinterop-libsecp256k1 \
-Dclassifiers= \ -Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \ -Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar elif [ $i == jni-android ]; then
else mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \ -DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \ -Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.aar \
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.jar \ -Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \ -Dtypes=module \
-Dtypes=module \ -Dclassifiers= \
-Dclassifiers= \ -Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \ -Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar else
fi mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
popd -DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
pushd . -Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.jar \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \
-Dtypes=module \
-Dclassifiers= \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
fi
popd
pushd .
done done

View File

@@ -125,6 +125,7 @@ public object Secp256k1Native : Secp256k1 {
public override fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray { public override fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray {
require(privkey.size == 32) require(privkey.size == 32)
require(tweak.size == 32)
memScoped { memScoped {
val added = privkey.copyOf() val added = privkey.copyOf()
val natAdd = toNat(added) val natAdd = toNat(added)
@@ -136,6 +137,7 @@ public object Secp256k1Native : Secp256k1 {
public override fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray { public override fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray {
require(privkey.size == 32) require(privkey.size == 32)
require(tweak.size == 32)
memScoped { memScoped {
val multiplied = privkey.copyOf() val multiplied = privkey.copyOf()
val natMul = toNat(multiplied) val natMul = toNat(multiplied)
@@ -156,6 +158,7 @@ public object Secp256k1Native : Secp256k1 {
public override fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray { public override fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65) require(pubkey.size == 33 || pubkey.size == 65)
require(tweak.size == 32)
memScoped { memScoped {
val nPubkey = allocPublicKey(pubkey) val nPubkey = allocPublicKey(pubkey)
val nTweak = toNat(tweak) val nTweak = toNat(tweak)
@@ -166,6 +169,7 @@ public object Secp256k1Native : Secp256k1 {
public override fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray { public override fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65) require(pubkey.size == 33 || pubkey.size == 65)
require(tweak.size == 32)
memScoped { memScoped {
val nPubkey = allocPublicKey(pubkey) val nPubkey = allocPublicKey(pubkey)
val nTweak = toNat(tweak) val nTweak = toNat(tweak)
@@ -175,6 +179,7 @@ public object Secp256k1Native : Secp256k1 {
} }
public override fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray { public override fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray {
require(pubkeys.isNotEmpty())
pubkeys.forEach { require(it.size == 33 || it.size == 65) } pubkeys.forEach { require(it.size == 33 || it.size == 65) }
memScoped { memScoped {
val nPubkeys = pubkeys.map { allocPublicKey(it).ptr } val nPubkeys = pubkeys.map { allocPublicKey(it).ptr }
@@ -199,6 +204,7 @@ public object Secp256k1Native : Secp256k1 {
public override fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray { public override fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray {
require(sig.size == 64) require(sig.size == 64)
require(message.size == 32) require(message.size == 32)
require(recid in 0..3)
memScoped { memScoped {
val nSig = toNat(sig) val nSig = toNat(sig)
val rSig = alloc<secp256k1_ecdsa_recoverable_signature>() val rSig = alloc<secp256k1_ecdsa_recoverable_signature>()

View File

@@ -352,6 +352,38 @@ class Secp256k1Test {
} }
} }
@Test
fun testInvalidArguments() {
assertFails {
Secp256k1.pubkeyCreate(ByteArray(32))
}
assertFails {
Secp256k1.pubkeyCreate(Hex.decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
}
assertFails {
Secp256k1.pubkeyParse(ByteArray(33))
}
assertFails {
Secp256k1.pubkeyParse(Hex.decode("03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
}
assertFails {
Secp256k1.pubKeyCombine(arrayOf())
}
assertFails {
Secp256k1.pubKeyCombine(arrayOf(ByteArray(0)))
}
assertFails {
Secp256k1.signSchnorr(ByteArray(0), Hex.decode("0101010101010101010101010101010101010101010101010101010101010101"), null)
}
assertFails {
Secp256k1.ecdsaRecover(
Hex.decode("01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"),
Hex.decode("0202020202020202020202020202020202020202020202020202020202020202"),
-1
)
}
}
@Test @Test
fun fuzzEcdsaSignVerify() { fun fuzzEcdsaSignVerify() {
val random = Random.Default val random = Random.Default