From 144fa1efe9e7479a53f502f06a711106db83d5c9 Mon Sep 17 00:00:00 2001 From: Kevin Schildhorn Date: Tue, 11 Aug 2026 15:54:27 -0400 Subject: [PATCH 1/7] Adding initial sample project --- .gitignore | 3 + README.md | 2 + sample/README.md | 61 + sample/build.gradle.kts | 67 + sample/gradle.properties | 8 + .../gradle/wrapper/gradle-wrapper.properties | 7 + sample/settings.gradle.kts | 32 + sample/src/monocypher/c/monocypher.c | 2988 +++++++++++++++++ sample/src/monocypher/c/secure_random.c | 118 + sample/src/monocypher/headers/monocypher.h | 321 ++ sample/src/monocypher/headers/secure_random.h | 39 + .../src/nativeInterop/cinterop/monocypher.def | 16 + .../co/touchlab/cklib/sample/SecureNote.kt | 243 ++ .../touchlab/cklib/sample/SecureNoteTest.kt | 151 + 14 files changed, 4056 insertions(+) create mode 100644 sample/README.md create mode 100644 sample/build.gradle.kts create mode 100644 sample/gradle.properties create mode 100644 sample/gradle/wrapper/gradle-wrapper.properties create mode 100644 sample/settings.gradle.kts create mode 100644 sample/src/monocypher/c/monocypher.c create mode 100644 sample/src/monocypher/c/secure_random.c create mode 100644 sample/src/monocypher/headers/monocypher.h create mode 100644 sample/src/monocypher/headers/secure_random.h create mode 100644 sample/src/nativeInterop/cinterop/monocypher.def create mode 100644 sample/src/nativeMain/kotlin/co/touchlab/cklib/sample/SecureNote.kt create mode 100644 sample/src/nativeTest/kotlin/co/touchlab/cklib/sample/SecureNoteTest.kt diff --git a/.gitignore b/.gitignore index 48836e3..daab74d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ gen/ .gradle/ build/ + # Kotlin +.kotlin/ + local.properties Pods \ No newline at end of file diff --git a/README.md b/README.md index 84f4657..14e09fa 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ cklib { ## Examples +[`sample/`](sample) is a runnable build in this repo. It vendors [Monocypher](https://monocypher.org), compiles it with CKlib, binds it with cinterop, and exposes a passphrase-encrypted note API. Run it with `./gradlew -p sample macosArm64Test`. See [sample/README.md](sample/README.md). + You can find a [tutorial](https://hackernoon.com/how-to-extend-a-kmm-shared-module-with-cc-code) with a [GitHub Sample](https://github.com/ttypic/kmm-embedded-c) to get a brief understanding of how the library works. Additionally you can see multiple examples of C Klib in use here: diff --git a/sample/README.md b/sample/README.md new file mode 100644 index 0000000..ead7f21 --- /dev/null +++ b/sample/README.md @@ -0,0 +1,61 @@ +# C Klib sample: passphrase-encrypted notes with Monocypher + +A sample for C Klib that packages the C library [Monocypher](https://monocypher.org). It's a simple passphrase-encrypted note implementation. + +```kotlin +val sealed: ByteArray = SecureNote.seal("hunter2", "meet me at the docks at midnight") +val note: String? = SecureNote.open("hunter2", sealed) // null if the passphrase is wrong +``` + +C Klib compiles C to LLVM bitcode; cinterop reads the same headers and generates the Kotlin +declarations; the Kotlin/Native compiler links the two together. + +## Running the sample + +From the repository root, using the root wrapper: +```bash +./gradlew -p sample build # compile every declared target +./gradlew -p sample macosArm64Test # the host-runnable test suite +./gradlew -p sample allMonocypher # just the bitcode, for every target +``` + +`sample/settings.gradle.kts` uses `pluginManagement { includeBuild("..") }`, so the plugin is built +from this working tree rather than downloaded. No `version` on the plugin id, and no +`include(":sample")` in the root build. + +> **Note:** The first run downloads an LLVM toolchain into `~/.cklib` (about 1.6 GB) and Kotlin/Native into +`~/.konan`. + +## Notes + +* **`srcDirs` defaults to `srcRoot/cpp`, even for `Language.C`.** `headersDirs` defaults to +`srcDirs + srcRoot/headers`. If you want your C in a directory not named `cpp`, override both: + +```kotlin +create("monocypher", srcDir = file("src/monocypher")) { + language = CompileToBitcode.Language.C + srcDirs = files("src/monocypher/c") + headersDirs = files("src/monocypher/headers") +} +``` + +* **`mingwX64` is deliberately not declared: `secure_random.c` has no Windows branch, and adding one +means `BCryptGenRandom` plus linking `bcrypt.lib`. +> +* **`Language.C` compiles with `-std=gnu11 -O3 -Wall -Wextra -Werror`, hardcoded.** The only escape +hatch is `compilerArgs`, which is appended after those flags, so `-Wno-error=` works. +This sample needs **no suppressions at all**: Monocypher 4.0.3 and `secure_random.c` both build +clean. Vendored code that is not warning-clean will stop the build dead, so check before you commit +to a library. + +* **Shared `nativeMain` + cinterop needs `kotlin.mpp.enableCInteropCommonization=true`.** Without it, +per-target tasks like `compileKotlinMacosArm64` and `macosArm64Test` work fine, but +`compileNativeMainKotlinMetadata` — which `build` runs — fails with `Unresolved reference +'cinterop'` for every binding. It is in `gradle.properties`. + +* **`config.kotlinVersion` must match the Kotlin plugin version.** CKlib resolves the toolchain at +`~/.konan/kotlin-native-prebuilt---` and fails with a bare +`InvocationTargetException` if that directory is missing. On a machine with no Kotlin/Native +installed yet, the bitcode task can therefore fail before anything has had a chance to download it; +running any Kotlin/Native task first (`./gradlew -p sample cinteropMonocypherMacosArm64`) fetches +the distribution and unblocks it. \ No newline at end of file diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts new file mode 100644 index 0000000..a4e4062 --- /dev/null +++ b/sample/build.gradle.kts @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +import co.touchlab.cklib.gradle.CompileToBitcode + +plugins { + kotlin("multiplatform") version "2.0.0" + id("co.touchlab.cklib") +} + +kotlin { + val nativeTargets = listOf( + macosArm64(), + macosX64(), + iosArm64(), + iosSimulatorArm64(), + linuxX64(), + ) + + nativeTargets.forEach { target -> + target.compilations.getByName("main").cinterops.create("monocypher") { + defFile(project.file("src/nativeInterop/cinterop/monocypher.def")) + // cinterop only needs the headers. The compiled code arrives as bitcode from cklib, + // which is why the .def declares no staticLibraries or libraryPaths. + includeDirs(project.file("src/monocypher/headers")) + } + } + + sourceSets { + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +cklib { + config.kotlinVersion = "2.0.0" + + create("monocypher", srcDir = file("src/monocypher")) { + language = CompileToBitcode.Language.C + + // Both default to a `cpp` subdirectory (headersDirs additionally to `headers`) even for + // Language.C, so point them at the real layout instead of naming a C folder "cpp". + srcDirs = files("src/monocypher/c") + headersDirs = files("src/monocypher/headers") + + // No compilerArgs. Language.C compiles with a hardcoded + // `-std=gnu11 -O3 -Wall -Wextra -Werror`, and both Monocypher 4.0.3 and secure_random.c + // build clean under it, so no -Wno-error= escape hatch is needed. + // compilerArgs.addAll( + // listOf( + // ) + // ) + } +} diff --git a/sample/gradle.properties b/sample/gradle.properties new file mode 100644 index 0000000..9b09bf0 --- /dev/null +++ b/sample/gradle.properties @@ -0,0 +1,8 @@ +kotlin.code.style=official +org.gradle.jvmargs=-Xmx3g + +# Required because SecureNote.kt lives in the shared `nativeMain` source set and references cinterop +# declarations. Per-target compilation (`compileKotlinMacosArm64`) works without this, but +# `compileNativeMainKotlinMetadata`, which type-checks the shared source set and runs as part of +# `build`, fails with "Unresolved reference 'cinterop'" unless commonization is on. +kotlin.mpp.enableCInteropCommonization=true diff --git a/sample/gradle/wrapper/gradle-wrapper.properties b/sample/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..002b867 --- /dev/null +++ b/sample/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/sample/settings.gradle.kts b/sample/settings.gradle.kts new file mode 100644 index 0000000..a05e5d5 --- /dev/null +++ b/sample/settings.gradle.kts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +pluginManagement { + // Resolves `co.touchlab.cklib` from the plugin sources in this repository, so the sample + // always exercises the working tree rather than a published release. + includeBuild("..") + repositories { + gradlePluginPortal() + mavenCentral() + google() + } +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + google() + } +} + +rootProject.name = "cklib-sample" diff --git a/sample/src/monocypher/c/monocypher.c b/sample/src/monocypher/c/monocypher.c new file mode 100644 index 0000000..fe5bbdc --- /dev/null +++ b/sample/src/monocypher/c/monocypher.c @@ -0,0 +1,2988 @@ +// Monocypher version 4.0.3 +// +// This file is dual-licensed. Choose whichever licence you want from +// the two licences listed below. +// +// The first licence is a regular 2-clause BSD licence. The second licence +// is the CC-0 from Creative Commons. It is intended to release Monocypher +// to the public domain. The BSD licence serves as a fallback option. +// +// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0 +// +// ------------------------------------------------------------------------ +// +// Copyright (c) 2017-2020, Loup Vaillant +// All rights reserved. +// +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ------------------------------------------------------------------------ +// +// Written in 2017-2020 by Loup Vaillant +// +// To the extent possible under law, the author(s) have dedicated all copyright +// and related neighboring rights to this software to the public domain +// worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this software. If not, see +// + +#include "monocypher.h" + +#ifdef MONOCYPHER_CPP_NAMESPACE +namespace MONOCYPHER_CPP_NAMESPACE { +#endif + +///////////////// +/// Utilities /// +///////////////// +#define FOR_T(type, i, start, end) for (type i = (start); i < (end); i++) +#define FOR(i, start, end) FOR_T(size_t, i, start, end) +#define COPY(dst, src, size) FOR(_i_, 0, size) (dst)[_i_] = (src)[_i_] +#define ZERO(buf, size) FOR(_i_, 0, size) (buf)[_i_] = 0 +#define WIPE_CTX(ctx) crypto_wipe(ctx , sizeof(*(ctx))) +#define WIPE_BUFFER(buffer) crypto_wipe(buffer, sizeof(buffer)) +#define MIN(a, b) ((a) <= (b) ? (a) : (b)) +#define MAX(a, b) ((a) >= (b) ? (a) : (b)) + +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint32_t u32; +typedef int32_t i32; +typedef int64_t i64; +typedef uint64_t u64; + +static const u8 zero[128] = {0}; + +// returns the smallest positive integer y such that +// (x + y) % pow_2 == 0 +// Basically, y is the "gap" missing to align x. +// Only works when pow_2 is a power of 2. +// Note: we use ~x+1 instead of -x to avoid compiler warnings +static size_t gap(size_t x, size_t pow_2) +{ + return (~x + 1) & (pow_2 - 1); +} + +static u32 load24_le(const u8 s[3]) +{ + return + ((u32)s[0] << 0) | + ((u32)s[1] << 8) | + ((u32)s[2] << 16); +} + +static u32 load32_le(const u8 s[4]) +{ + return + ((u32)s[0] << 0) | + ((u32)s[1] << 8) | + ((u32)s[2] << 16) | + ((u32)s[3] << 24); +} + +static u64 load64_le(const u8 s[8]) +{ + return load32_le(s) | ((u64)load32_le(s+4) << 32); +} + +static void store32_le(u8 out[4], u32 in) +{ + out[0] = (u8)(in ); + out[1] = (u8)(in >> 8); + out[2] = (u8)(in >> 16); + out[3] = (u8)(in >> 24); +} + +static void store64_le(u8 out[8], u64 in) +{ + store32_le(out , (u32)(in )); + store32_le(out + 4, (u32)(in >> 32)); +} + +static void load32_le_buf (u32 *dst, const u8 *src, size_t size) { + FOR(i, 0, size) { dst[i] = load32_le(src + i*4); } +} +static void load64_le_buf (u64 *dst, const u8 *src, size_t size) { + FOR(i, 0, size) { dst[i] = load64_le(src + i*8); } +} +static void store32_le_buf(u8 *dst, const u32 *src, size_t size) { + FOR(i, 0, size) { store32_le(dst + i*4, src[i]); } +} +static void store64_le_buf(u8 *dst, const u64 *src, size_t size) { + FOR(i, 0, size) { store64_le(dst + i*8, src[i]); } +} + +static u64 rotr64(u64 x, u64 n) { return (x >> n) ^ (x << (64 - n)); } +static u32 rotl32(u32 x, u32 n) { return (x << n) ^ (x >> (32 - n)); } + +static int neq0(u64 diff) +{ + // constant time comparison to zero + // return diff != 0 ? -1 : 0 + u64 half = (diff >> 32) | ((u32)diff); // half < 2^32 + u64 eq0 = 1 & ((half - 1) >> 32); // half == 0 ? 1 : 0 + return (int)eq0 - 1; // half == 0 ? 0 : -1 +} + +static u64 x16(const u8 a[16], const u8 b[16]) +{ + return (load64_le(a + 0) ^ load64_le(b + 0)) + | (load64_le(a + 8) ^ load64_le(b + 8)); +} +static u64 x32(const u8 a[32],const u8 b[32]){return x16(a,b)| x16(a+16, b+16);} +static u64 x64(const u8 a[64],const u8 b[64]){return x32(a,b)| x32(a+32, b+32);} +int crypto_verify16(const u8 a[16], const u8 b[16]){ return neq0(x16(a, b)); } +int crypto_verify32(const u8 a[32], const u8 b[32]){ return neq0(x32(a, b)); } +int crypto_verify64(const u8 a[64], const u8 b[64]){ return neq0(x64(a, b)); } + +void crypto_wipe(void *secret, size_t size) +{ + volatile u8 *v_secret = (u8*)secret; + ZERO(v_secret, size); +} + +///////////////// +/// Chacha 20 /// +///////////////// +#define QUARTERROUND(a, b, c, d) \ + a += b; d = rotl32(d ^ a, 16); \ + c += d; b = rotl32(b ^ c, 12); \ + a += b; d = rotl32(d ^ a, 8); \ + c += d; b = rotl32(b ^ c, 7) + +static void chacha20_rounds(u32 out[16], const u32 in[16]) +{ + // The temporary variables make Chacha20 10% faster. + u32 t0 = in[ 0]; u32 t1 = in[ 1]; u32 t2 = in[ 2]; u32 t3 = in[ 3]; + u32 t4 = in[ 4]; u32 t5 = in[ 5]; u32 t6 = in[ 6]; u32 t7 = in[ 7]; + u32 t8 = in[ 8]; u32 t9 = in[ 9]; u32 t10 = in[10]; u32 t11 = in[11]; + u32 t12 = in[12]; u32 t13 = in[13]; u32 t14 = in[14]; u32 t15 = in[15]; + + FOR (i, 0, 10) { // 20 rounds, 2 rounds per loop. + QUARTERROUND(t0, t4, t8 , t12); // column 0 + QUARTERROUND(t1, t5, t9 , t13); // column 1 + QUARTERROUND(t2, t6, t10, t14); // column 2 + QUARTERROUND(t3, t7, t11, t15); // column 3 + QUARTERROUND(t0, t5, t10, t15); // diagonal 0 + QUARTERROUND(t1, t6, t11, t12); // diagonal 1 + QUARTERROUND(t2, t7, t8 , t13); // diagonal 2 + QUARTERROUND(t3, t4, t9 , t14); // diagonal 3 + } + out[ 0] = t0; out[ 1] = t1; out[ 2] = t2; out[ 3] = t3; + out[ 4] = t4; out[ 5] = t5; out[ 6] = t6; out[ 7] = t7; + out[ 8] = t8; out[ 9] = t9; out[10] = t10; out[11] = t11; + out[12] = t12; out[13] = t13; out[14] = t14; out[15] = t15; +} + +static const u8 *chacha20_constant = (const u8*)"expand 32-byte k"; // 16 bytes + +void crypto_chacha20_h(u8 out[32], const u8 key[32], const u8 in [16]) +{ + u32 block[16]; + load32_le_buf(block , chacha20_constant, 4); + load32_le_buf(block + 4, key , 8); + load32_le_buf(block + 12, in , 4); + + chacha20_rounds(block, block); + + // prevent reversal of the rounds by revealing only half of the buffer. + store32_le_buf(out , block , 4); // constant + store32_le_buf(out+16, block+12, 4); // counter and nonce + WIPE_BUFFER(block); +} + +u64 crypto_chacha20_djb(u8 *cipher_text, const u8 *plain_text, + size_t text_size, const u8 key[32], const u8 nonce[8], + u64 ctr) +{ + u32 input[16]; + load32_le_buf(input , chacha20_constant, 4); + load32_le_buf(input + 4, key , 8); + load32_le_buf(input + 14, nonce , 2); + input[12] = (u32) ctr; + input[13] = (u32)(ctr >> 32); + + // Whole blocks + u32 pool[16]; + size_t nb_blocks = text_size >> 6; + FOR (i, 0, nb_blocks) { + chacha20_rounds(pool, input); + if (plain_text != NULL) { + FOR (j, 0, 16) { + u32 p = pool[j] + input[j]; + store32_le(cipher_text, p ^ load32_le(plain_text)); + cipher_text += 4; + plain_text += 4; + } + } else { + FOR (j, 0, 16) { + u32 p = pool[j] + input[j]; + store32_le(cipher_text, p); + cipher_text += 4; + } + } + input[12]++; + if (input[12] == 0) { + input[13]++; + } + } + text_size &= 63; + + // Last (incomplete) block + if (text_size > 0) { + if (plain_text == NULL) { + plain_text = zero; + } + chacha20_rounds(pool, input); + u8 tmp[64]; + FOR (i, 0, 16) { + store32_le(tmp + i*4, pool[i] + input[i]); + } + FOR (i, 0, text_size) { + cipher_text[i] = tmp[i] ^ plain_text[i]; + } + WIPE_BUFFER(tmp); + } + ctr = input[12] + ((u64)input[13] << 32) + (text_size > 0); + + WIPE_BUFFER(pool); + WIPE_BUFFER(input); + return ctr; +} + +u32 crypto_chacha20_ietf(u8 *cipher_text, const u8 *plain_text, + size_t text_size, + const u8 key[32], const u8 nonce[12], u32 ctr) +{ + u64 big_ctr = ctr + ((u64)load32_le(nonce) << 32); + return (u32)crypto_chacha20_djb(cipher_text, plain_text, text_size, + key, nonce + 4, big_ctr); +} + +u64 crypto_chacha20_x(u8 *cipher_text, const u8 *plain_text, + size_t text_size, + const u8 key[32], const u8 nonce[24], u64 ctr) +{ + u8 sub_key[32]; + crypto_chacha20_h(sub_key, key, nonce); + ctr = crypto_chacha20_djb(cipher_text, plain_text, text_size, + sub_key, nonce + 16, ctr); + WIPE_BUFFER(sub_key); + return ctr; +} + +///////////////// +/// Poly 1305 /// +///////////////// + +// h = (h + c) * r +// preconditions: +// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff +// ctx->r <= 0ffffffc_0ffffffc_0ffffffc_0fffffff +// end <= 1 +// Postcondition: +// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff +static void poly_blocks(crypto_poly1305_ctx *ctx, const u8 *in, + size_t nb_blocks, unsigned end) +{ + // Local all the things! + const u32 r0 = ctx->r[0]; + const u32 r1 = ctx->r[1]; + const u32 r2 = ctx->r[2]; + const u32 r3 = ctx->r[3]; + const u32 rr0 = (r0 >> 2) * 5; // lose 2 bits... + const u32 rr1 = (r1 >> 2) + r1; // rr1 == (r1 >> 2) * 5 + const u32 rr2 = (r2 >> 2) + r2; // rr1 == (r2 >> 2) * 5 + const u32 rr3 = (r3 >> 2) + r3; // rr1 == (r3 >> 2) * 5 + const u32 rr4 = r0 & 3; // ...recover 2 bits + u32 h0 = ctx->h[0]; + u32 h1 = ctx->h[1]; + u32 h2 = ctx->h[2]; + u32 h3 = ctx->h[3]; + u32 h4 = ctx->h[4]; + + FOR (i, 0, nb_blocks) { + // h + c, without carry propagation + const u64 s0 = (u64)h0 + load32_le(in); in += 4; + const u64 s1 = (u64)h1 + load32_le(in); in += 4; + const u64 s2 = (u64)h2 + load32_le(in); in += 4; + const u64 s3 = (u64)h3 + load32_le(in); in += 4; + const u32 s4 = h4 + end; + + // (h + c) * r, without carry propagation + const u64 x0 = s0*r0+ s1*rr3+ s2*rr2+ s3*rr1+ s4*rr0; + const u64 x1 = s0*r1+ s1*r0 + s2*rr3+ s3*rr2+ s4*rr1; + const u64 x2 = s0*r2+ s1*r1 + s2*r0 + s3*rr3+ s4*rr2; + const u64 x3 = s0*r3+ s1*r2 + s2*r1 + s3*r0 + s4*rr3; + const u32 x4 = s4*rr4; + + // partial reduction modulo 2^130 - 5 + const u32 u5 = (u32)(x3 >> 32) + x4; // u5 <= 7ffffff5 + const u64 u0 = (u32)(u5 >> 2) * 5 + (x0 & 0xffffffff); + const u64 u1 = (u32)(u0 >> 32) + (x1 & 0xffffffff) + (x0 >> 32); + const u64 u2 = (u32)(u1 >> 32) + (x2 & 0xffffffff) + (x1 >> 32); + const u64 u3 = (u32)(u2 >> 32) + (x3 & 0xffffffff) + (x2 >> 32); + const u32 u4 = (u32)(u3 >> 32) + (u5 & 3); // u4 <= 4 + + // Update the hash + h0 = u0 & 0xffffffff; + h1 = u1 & 0xffffffff; + h2 = u2 & 0xffffffff; + h3 = u3 & 0xffffffff; + h4 = u4; + } + ctx->h[0] = h0; + ctx->h[1] = h1; + ctx->h[2] = h2; + ctx->h[3] = h3; + ctx->h[4] = h4; +} + +void crypto_poly1305_init(crypto_poly1305_ctx *ctx, const u8 key[32]) +{ + ZERO(ctx->h, 5); // Initial hash is zero + ctx->c_idx = 0; + // load r and pad (r has some of its bits cleared) + load32_le_buf(ctx->r , key , 4); + load32_le_buf(ctx->pad, key+16, 4); + FOR (i, 0, 1) { ctx->r[i] &= 0x0fffffff; } + FOR (i, 1, 4) { ctx->r[i] &= 0x0ffffffc; } +} + +void crypto_poly1305_update(crypto_poly1305_ctx *ctx, + const u8 *message, size_t message_size) +{ + // Avoid undefined NULL pointer increments with empty messages + if (message_size == 0) { + return; + } + + // Align ourselves with block boundaries + size_t aligned = MIN(gap(ctx->c_idx, 16), message_size); + FOR (i, 0, aligned) { + ctx->c[ctx->c_idx] = *message; + ctx->c_idx++; + message++; + message_size--; + } + + // If block is complete, process it + if (ctx->c_idx == 16) { + poly_blocks(ctx, ctx->c, 1, 1); + ctx->c_idx = 0; + } + + // Process the message block by block + size_t nb_blocks = message_size >> 4; + poly_blocks(ctx, message, nb_blocks, 1); + message += nb_blocks << 4; + message_size &= 15; + + // remaining bytes (we never complete a block here) + FOR (i, 0, message_size) { + ctx->c[ctx->c_idx] = message[i]; + ctx->c_idx++; + } +} + +void crypto_poly1305_final(crypto_poly1305_ctx *ctx, u8 mac[16]) +{ + // Process the last block (if any) + // We move the final 1 according to remaining input length + // (this will add less than 2^130 to the last input block) + if (ctx->c_idx != 0) { + ZERO(ctx->c + ctx->c_idx, 16 - ctx->c_idx); + ctx->c[ctx->c_idx] = 1; + poly_blocks(ctx, ctx->c, 1, 0); + } + + // check if we should subtract 2^130-5 by performing the + // corresponding carry propagation. + u64 c = 5; + FOR (i, 0, 4) { + c += ctx->h[i]; + c >>= 32; + } + c += ctx->h[4]; + c = (c >> 2) * 5; // shift the carry back to the beginning + // c now indicates how many times we should subtract 2^130-5 (0 or 1) + FOR (i, 0, 4) { + c += (u64)ctx->h[i] + ctx->pad[i]; + store32_le(mac + i*4, (u32)c); + c = c >> 32; + } + WIPE_CTX(ctx); +} + +void crypto_poly1305(u8 mac[16], const u8 *message, + size_t message_size, const u8 key[32]) +{ + crypto_poly1305_ctx ctx; + crypto_poly1305_init (&ctx, key); + crypto_poly1305_update(&ctx, message, message_size); + crypto_poly1305_final (&ctx, mac); +} + +//////////////// +/// BLAKE2 b /// +//////////////// +static const u64 iv[8] = { + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, +}; + +static void blake2b_compress(crypto_blake2b_ctx *ctx, int is_last_block) +{ + static const u8 sigma[12][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + }; + + // increment input offset + u64 *x = ctx->input_offset; + size_t y = ctx->input_idx; + x[0] += y; + if (x[0] < y) { + x[1]++; + } + + // init work vector + u64 v0 = ctx->hash[0]; u64 v8 = iv[0]; + u64 v1 = ctx->hash[1]; u64 v9 = iv[1]; + u64 v2 = ctx->hash[2]; u64 v10 = iv[2]; + u64 v3 = ctx->hash[3]; u64 v11 = iv[3]; + u64 v4 = ctx->hash[4]; u64 v12 = iv[4] ^ ctx->input_offset[0]; + u64 v5 = ctx->hash[5]; u64 v13 = iv[5] ^ ctx->input_offset[1]; + u64 v6 = ctx->hash[6]; u64 v14 = iv[6] ^ (u64)~(is_last_block - 1); + u64 v7 = ctx->hash[7]; u64 v15 = iv[7]; + + // mangle work vector + u64 *input = ctx->input; +#define BLAKE2_G(a, b, c, d, x, y) \ + a += b + x; d = rotr64(d ^ a, 32); \ + c += d; b = rotr64(b ^ c, 24); \ + a += b + y; d = rotr64(d ^ a, 16); \ + c += d; b = rotr64(b ^ c, 63) +#define BLAKE2_ROUND(i) \ + BLAKE2_G(v0, v4, v8 , v12, input[sigma[i][ 0]], input[sigma[i][ 1]]); \ + BLAKE2_G(v1, v5, v9 , v13, input[sigma[i][ 2]], input[sigma[i][ 3]]); \ + BLAKE2_G(v2, v6, v10, v14, input[sigma[i][ 4]], input[sigma[i][ 5]]); \ + BLAKE2_G(v3, v7, v11, v15, input[sigma[i][ 6]], input[sigma[i][ 7]]); \ + BLAKE2_G(v0, v5, v10, v15, input[sigma[i][ 8]], input[sigma[i][ 9]]); \ + BLAKE2_G(v1, v6, v11, v12, input[sigma[i][10]], input[sigma[i][11]]); \ + BLAKE2_G(v2, v7, v8 , v13, input[sigma[i][12]], input[sigma[i][13]]); \ + BLAKE2_G(v3, v4, v9 , v14, input[sigma[i][14]], input[sigma[i][15]]) + +#ifdef BLAKE2_NO_UNROLLING + FOR (i, 0, 12) { + BLAKE2_ROUND(i); + } +#else + BLAKE2_ROUND(0); BLAKE2_ROUND(1); BLAKE2_ROUND(2); BLAKE2_ROUND(3); + BLAKE2_ROUND(4); BLAKE2_ROUND(5); BLAKE2_ROUND(6); BLAKE2_ROUND(7); + BLAKE2_ROUND(8); BLAKE2_ROUND(9); BLAKE2_ROUND(10); BLAKE2_ROUND(11); +#endif + + // update hash + ctx->hash[0] ^= v0 ^ v8; ctx->hash[1] ^= v1 ^ v9; + ctx->hash[2] ^= v2 ^ v10; ctx->hash[3] ^= v3 ^ v11; + ctx->hash[4] ^= v4 ^ v12; ctx->hash[5] ^= v5 ^ v13; + ctx->hash[6] ^= v6 ^ v14; ctx->hash[7] ^= v7 ^ v15; +} + +void crypto_blake2b_keyed_init(crypto_blake2b_ctx *ctx, size_t hash_size, + const u8 *key, size_t key_size) +{ + // initial hash + COPY(ctx->hash, iv, 8); + ctx->hash[0] ^= 0x01010000 ^ (key_size << 8) ^ hash_size; + + ctx->input_offset[0] = 0; // beginning of the input, no offset + ctx->input_offset[1] = 0; // beginning of the input, no offset + ctx->hash_size = hash_size; + ctx->input_idx = 0; + ZERO(ctx->input, 16); + + // if there is a key, the first block is that key (padded with zeroes) + if (key_size > 0) { + u8 key_block[128] = {0}; + COPY(key_block, key, key_size); + // same as calling crypto_blake2b_update(ctx, key_block , 128) + load64_le_buf(ctx->input, key_block, 16); + ctx->input_idx = 128; + WIPE_BUFFER(key_block); + } +} + +void crypto_blake2b_init(crypto_blake2b_ctx *ctx, size_t hash_size) +{ + crypto_blake2b_keyed_init(ctx, hash_size, 0, 0); +} + +void crypto_blake2b_update(crypto_blake2b_ctx *ctx, + const u8 *message, size_t message_size) +{ + // Avoid undefined NULL pointer increments with empty messages + if (message_size == 0) { + return; + } + + // Align with word boundaries + if ((ctx->input_idx & 7) != 0) { + size_t nb_bytes = MIN(gap(ctx->input_idx, 8), message_size); + size_t word = ctx->input_idx >> 3; + size_t byte = ctx->input_idx & 7; + FOR (i, 0, nb_bytes) { + ctx->input[word] |= (u64)message[i] << ((byte + i) << 3); + } + ctx->input_idx += nb_bytes; + message += nb_bytes; + message_size -= nb_bytes; + } + + // Align with block boundaries (faster than byte by byte) + if ((ctx->input_idx & 127) != 0) { + size_t nb_words = MIN(gap(ctx->input_idx, 128), message_size) >> 3; + load64_le_buf(ctx->input + (ctx->input_idx >> 3), message, nb_words); + ctx->input_idx += nb_words << 3; + message += nb_words << 3; + message_size -= nb_words << 3; + } + + // Process block by block + size_t nb_blocks = message_size >> 7; + FOR (i, 0, nb_blocks) { + if (ctx->input_idx == 128) { + blake2b_compress(ctx, 0); + } + load64_le_buf(ctx->input, message, 16); + message += 128; + ctx->input_idx = 128; + } + message_size &= 127; + + if (message_size != 0) { + // Compress block & flush input buffer as needed + if (ctx->input_idx == 128) { + blake2b_compress(ctx, 0); + ctx->input_idx = 0; + } + if (ctx->input_idx == 0) { + ZERO(ctx->input, 16); + } + // Fill remaining words (faster than byte by byte) + size_t nb_words = message_size >> 3; + load64_le_buf(ctx->input, message, nb_words); + ctx->input_idx += nb_words << 3; + message += nb_words << 3; + message_size -= nb_words << 3; + + // Fill remaining bytes + FOR (i, 0, message_size) { + size_t word = ctx->input_idx >> 3; + size_t byte = ctx->input_idx & 7; + ctx->input[word] |= (u64)message[i] << (byte << 3); + ctx->input_idx++; + } + } +} + +void crypto_blake2b_final(crypto_blake2b_ctx *ctx, u8 *hash) +{ + blake2b_compress(ctx, 1); // compress the last block + size_t hash_size = MIN(ctx->hash_size, 64); + size_t nb_words = hash_size >> 3; + store64_le_buf(hash, ctx->hash, nb_words); + FOR (i, nb_words << 3, hash_size) { + hash[i] = (ctx->hash[i >> 3] >> (8 * (i & 7))) & 0xff; + } + WIPE_CTX(ctx); +} + +void crypto_blake2b_keyed(u8 *hash, size_t hash_size, + const u8 *key, size_t key_size, + const u8 *message, size_t message_size) +{ + crypto_blake2b_ctx ctx; + crypto_blake2b_keyed_init(&ctx, hash_size, key, key_size); + crypto_blake2b_update (&ctx, message, message_size); + crypto_blake2b_final (&ctx, hash); +} + +void crypto_blake2b(u8 *hash, size_t hash_size, const u8 *msg, size_t msg_size) +{ + crypto_blake2b_keyed(hash, hash_size, 0, 0, msg, msg_size); +} + +////////////// +/// Argon2 /// +////////////// +// references to R, Z, Q etc. come from the spec + +// Argon2 operates on 1024 byte blocks. +typedef struct { u64 a[128]; } blk; + +// updates a BLAKE2 hash with a 32 bit word, little endian. +static void blake_update_32(crypto_blake2b_ctx *ctx, u32 input) +{ + u8 buf[4]; + store32_le(buf, input); + crypto_blake2b_update(ctx, buf, 4); + WIPE_BUFFER(buf); +} + +static void blake_update_32_buf(crypto_blake2b_ctx *ctx, + const u8 *buf, u32 size) +{ + blake_update_32(ctx, size); + crypto_blake2b_update(ctx, buf, size); +} + + +static void copy_block(blk *o,const blk*in){FOR(i, 0, 128) o->a[i] = in->a[i];} +static void xor_block(blk *o,const blk*in){FOR(i, 0, 128) o->a[i] ^= in->a[i];} + +// Hash with a virtually unlimited digest size. +// Doesn't extract more entropy than the base hash function. +// Mainly used for filling a whole kilobyte block with pseudo-random bytes. +// (One could use a stream cipher with a seed hash as the key, but +// this would introduce another dependency —and point of failure.) +static void extended_hash(u8 *digest, u32 digest_size, + const u8 *input , u32 input_size) +{ + crypto_blake2b_ctx ctx; + crypto_blake2b_init (&ctx, MIN(digest_size, 64)); + blake_update_32 (&ctx, digest_size); + crypto_blake2b_update(&ctx, input, input_size); + crypto_blake2b_final (&ctx, digest); + + if (digest_size > 64) { + // the conversion to u64 avoids integer overflow on + // ludicrously big hash sizes. + u32 r = (u32)(((u64)digest_size + 31) >> 5) - 2; + u32 i = 1; + u32 in = 0; + u32 out = 32; + while (i < r) { + // Input and output overlap. This is intentional + crypto_blake2b(digest + out, 64, digest + in, 64); + i += 1; + in += 32; + out += 32; + } + crypto_blake2b(digest + out, digest_size - (32 * r), digest + in , 64); + } +} + +#define LSB(x) ((u64)(u32)x) +#define G(a, b, c, d) \ + a += b + ((LSB(a) * LSB(b)) << 1); d ^= a; d = rotr64(d, 32); \ + c += d + ((LSB(c) * LSB(d)) << 1); b ^= c; b = rotr64(b, 24); \ + a += b + ((LSB(a) * LSB(b)) << 1); d ^= a; d = rotr64(d, 16); \ + c += d + ((LSB(c) * LSB(d)) << 1); b ^= c; b = rotr64(b, 63) +#define ROUND(v0, v1, v2, v3, v4, v5, v6, v7, \ + v8, v9, v10, v11, v12, v13, v14, v15) \ + G(v0, v4, v8, v12); G(v1, v5, v9, v13); \ + G(v2, v6, v10, v14); G(v3, v7, v11, v15); \ + G(v0, v5, v10, v15); G(v1, v6, v11, v12); \ + G(v2, v7, v8, v13); G(v3, v4, v9, v14) + +// Core of the compression function G. Computes Z from R in place. +static void g_rounds(blk *b) +{ + // column rounds (work_block = Q) + for (int i = 0; i < 128; i += 16) { + ROUND(b->a[i ], b->a[i+ 1], b->a[i+ 2], b->a[i+ 3], + b->a[i+ 4], b->a[i+ 5], b->a[i+ 6], b->a[i+ 7], + b->a[i+ 8], b->a[i+ 9], b->a[i+10], b->a[i+11], + b->a[i+12], b->a[i+13], b->a[i+14], b->a[i+15]); + } + // row rounds (b = Z) + for (int i = 0; i < 16; i += 2) { + ROUND(b->a[i ], b->a[i+ 1], b->a[i+ 16], b->a[i+ 17], + b->a[i+32], b->a[i+33], b->a[i+ 48], b->a[i+ 49], + b->a[i+64], b->a[i+65], b->a[i+ 80], b->a[i+ 81], + b->a[i+96], b->a[i+97], b->a[i+112], b->a[i+113]); + } +} + +const crypto_argon2_extras crypto_argon2_no_extras = { 0, 0, 0, 0 }; + +void crypto_argon2(u8 *hash, u32 hash_size, void *work_area, + crypto_argon2_config config, + crypto_argon2_inputs inputs, + crypto_argon2_extras extras) +{ + const u32 segment_size = config.nb_blocks / config.nb_lanes / 4; + const u32 lane_size = segment_size * 4; + const u32 nb_blocks = lane_size * config.nb_lanes; // rounding down + + // work area seen as blocks (must be suitably aligned) + blk *blocks = (blk*)work_area; + { + u8 initial_hash[72]; // 64 bytes plus 2 words for future hashes + crypto_blake2b_ctx ctx; + crypto_blake2b_init (&ctx, 64); + blake_update_32 (&ctx, config.nb_lanes ); // p: number of "threads" + blake_update_32 (&ctx, hash_size); + blake_update_32 (&ctx, config.nb_blocks); + blake_update_32 (&ctx, config.nb_passes); + blake_update_32 (&ctx, 0x13); // v: version number + blake_update_32 (&ctx, config.algorithm); // y: Argon2i, Argon2d... + blake_update_32_buf (&ctx, inputs.pass, inputs.pass_size); + blake_update_32_buf (&ctx, inputs.salt, inputs.salt_size); + blake_update_32_buf (&ctx, extras.key, extras.key_size); + blake_update_32_buf (&ctx, extras.ad, extras.ad_size); + crypto_blake2b_final(&ctx, initial_hash); // fill 64 first bytes only + + // fill first 2 blocks of each lane + u8 hash_area[1024]; + FOR_T(u32, l, 0, config.nb_lanes) { + FOR_T(u32, i, 0, 2) { + store32_le(initial_hash + 64, i); // first additional word + store32_le(initial_hash + 68, l); // second additional word + extended_hash(hash_area, 1024, initial_hash, 72); + load64_le_buf(blocks[l * lane_size + i].a, hash_area, 128); + } + } + + WIPE_BUFFER(initial_hash); + WIPE_BUFFER(hash_area); + } + + // Argon2i and Argon2id start with constant time indexing + int constant_time = config.algorithm != CRYPTO_ARGON2_D; + + // Fill (and re-fill) the rest of the blocks + // + // Note: even though each segment within the same slice can be + // computed in parallel, (one thread per lane), we are computing + // them sequentially, because Monocypher doesn't support threads. + // + // Yet optimal performance (and therefore security) requires one + // thread per lane. The only reason Monocypher supports multiple + // lanes is compatibility. + blk tmp; + FOR_T(u32, pass, 0, config.nb_passes) { + FOR_T(u32, slice, 0, 4) { + // On the first slice of the first pass, + // blocks 0 and 1 are already filled, hence pass_offset. + u32 pass_offset = pass == 0 && slice == 0 ? 2 : 0; + u32 slice_offset = slice * segment_size; + + // Argon2id switches back to non-constant time indexing + // after the first two slices of the first pass + if (slice == 2 && config.algorithm == CRYPTO_ARGON2_ID) { + constant_time = 0; + } + + // Each iteration of the following loop may be performed in + // a separate thread. All segments must be fully completed + // before we start filling the next slice. + FOR_T(u32, segment, 0, config.nb_lanes) { + blk index_block; + u32 index_ctr = 1; + FOR_T (u32, block, pass_offset, segment_size) { + // Current and previous blocks + u32 lane_offset = segment * lane_size; + blk *segment_start = blocks + lane_offset + slice_offset; + blk *current = segment_start + block; + blk *previous = + block == 0 && slice_offset == 0 + ? segment_start + lane_size - 1 + : segment_start + block - 1; + + u64 index_seed; + if (constant_time) { + if (block == pass_offset || (block % 128) == 0) { + // Fill or refresh deterministic indices block + + // seed the beginning of the block... + ZERO(index_block.a, 128); + index_block.a[0] = pass; + index_block.a[1] = segment; + index_block.a[2] = slice; + index_block.a[3] = nb_blocks; + index_block.a[4] = config.nb_passes; + index_block.a[5] = config.algorithm; + index_block.a[6] = index_ctr; + index_ctr++; + + // ... then shuffle it + copy_block(&tmp, &index_block); + g_rounds (&index_block); + xor_block (&index_block, &tmp); + copy_block(&tmp, &index_block); + g_rounds (&index_block); + xor_block (&index_block, &tmp); + } + index_seed = index_block.a[block % 128]; + } else { + index_seed = previous->a[0]; + } + + // Establish the reference set. *Approximately* comprises: + // - The last 3 slices (if they exist yet) + // - The already constructed blocks in the current segment + u32 next_slice = ((slice + 1) % 4) * segment_size; + u32 window_start = pass == 0 ? 0 : next_slice; + u32 nb_segments = pass == 0 ? slice : 3; + u32 lane = + pass == 0 && slice == 0 + ? segment + : (u32)(index_seed >> 32) % config.nb_lanes; + u32 window_size = + nb_segments * segment_size + + (lane == segment ? block-1 : + block == 0 ? (u32)-1 : 0); + + // Find reference block + u64 j1 = index_seed & 0xffffffff; // block selector + u64 x = (j1 * j1) >> 32; + u64 y = (window_size * x) >> 32; + u64 z = (window_size - 1) - y; + u32 ref = (u32)((window_start + z) % lane_size); + u32 index = lane * lane_size + ref; + blk *reference = blocks + index; + + // Shuffle the previous & reference block + // into the current block + copy_block(&tmp, previous); + xor_block (&tmp, reference); + if (pass == 0) { copy_block(current, &tmp); } + else { xor_block (current, &tmp); } + g_rounds (&tmp); + xor_block (current, &tmp); + } + } + } + } + + // Wipe temporary block + volatile u64* p = tmp.a; + ZERO(p, 128); + + // XOR last blocks of each lane + blk *last_block = blocks + lane_size - 1; + FOR_T (u32, lane, 1, config.nb_lanes) { + blk *next_block = last_block + lane_size; + xor_block(next_block, last_block); + last_block = next_block; + } + + // Serialize last block + u8 final_block[1024]; + store64_le_buf(final_block, last_block->a, 128); + + // Wipe work area + p = (u64*)work_area; + ZERO(p, 128 * nb_blocks); + + // Hash the very last block with H' into the output hash + extended_hash(hash, hash_size, final_block, 1024); + WIPE_BUFFER(final_block); +} + +//////////////////////////////////// +/// Arithmetic modulo 2^255 - 19 /// +//////////////////////////////////// +// Originally taken from SUPERCOP's ref10 implementation. +// A bit bigger than TweetNaCl, over 4 times faster. + +// field element +typedef i32 fe[10]; + +// field constants +// +// fe_one : 1 +// sqrtm1 : sqrt(-1) +// d : -121665 / 121666 +// D2 : 2 * -121665 / 121666 +// lop_x, lop_y: low order point in Edwards coordinates +// ufactor : -sqrt(-1) * 2 +// A2 : 486662^2 (A squared) +static const fe fe_one = {1}; +static const fe sqrtm1 = { + -32595792, -7943725, 9377950, 3500415, 12389472, + -272473, -25146209, -2005654, 326686, 11406482, +}; +static const fe d = { + -10913610, 13857413, -15372611, 6949391, 114729, + -8787816, -6275908, -3247719, -18696448, -12055116, +}; +static const fe D2 = { + -21827239, -5839606, -30745221, 13898782, 229458, + 15978800, -12551817, -6495438, 29715968, 9444199, +}; +static const fe lop_x = { + 21352778, 5345713, 4660180, -8347857, 24143090, + 14568123, 30185756, -12247770, -33528939, 8345319, +}; +static const fe lop_y = { + -6952922, -1265500, 6862341, -7057498, -4037696, + -5447722, 31680899, -15325402, -19365852, 1569102, +}; +static const fe ufactor = { + -1917299, 15887451, -18755900, -7000830, -24778944, + 544946, -16816446, 4011309, -653372, 10741468, +}; +static const fe A2 = { + 12721188, 3529, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static void fe_0(fe h) { ZERO(h , 10); } +static void fe_1(fe h) { h[0] = 1; ZERO(h+1, 9); } + +static void fe_copy(fe h,const fe f ){FOR(i,0,10) h[i] = f[i]; } +static void fe_neg (fe h,const fe f ){FOR(i,0,10) h[i] = -f[i]; } +static void fe_add (fe h,const fe f,const fe g){FOR(i,0,10) h[i] = f[i] + g[i];} +static void fe_sub (fe h,const fe f,const fe g){FOR(i,0,10) h[i] = f[i] - g[i];} + +// Some compilers, when inlining fe_cswap() or fe_ccopy(), may introduce +// a timing leak. It happens when it notices `b` has only 2 possible +// values, and either replace the arithmetic by a secret dependent +// branch, or (as has been observed), swap pointers instead of values, +// which intruduces a secret dependent index. +// +// We apply two mitigations here: +// - Add `volatile` in the mask declaration. +// - Unroll the copy loop (costs couple hundred bytes of binary code). +// +// As of June 2026, those mitigation work when applied separately or +// together. Applying them both is currently overkill, but may help +// delay the day compilers grow clever enough to defeat it. (The true +// fix is in the semantics of the language itself: C currently has no +// way to specify constant time code). +// +// Note (Loup): as of June 2026, the problem has yet to surface in +// fe_cswap(), but since this is almost the same code as fe_ccopy() I +// believe it is more prudent to apply the precaution there too. +static void fe_cswap(fe f, fe g, int b) +{ + volatile i32 mask = -b; // -1 = 0xffffffff + i32 x0 = (f[0] ^ g[0]) & mask; f[0] = f[0] ^ x0; g[0] = g[0] ^ x0; + i32 x1 = (f[1] ^ g[1]) & mask; f[1] = f[1] ^ x1; g[1] = g[1] ^ x1; + i32 x2 = (f[2] ^ g[2]) & mask; f[2] = f[2] ^ x2; g[2] = g[2] ^ x2; + i32 x3 = (f[3] ^ g[3]) & mask; f[3] = f[3] ^ x3; g[3] = g[3] ^ x3; + i32 x4 = (f[4] ^ g[4]) & mask; f[4] = f[4] ^ x4; g[4] = g[4] ^ x4; + i32 x5 = (f[5] ^ g[5]) & mask; f[5] = f[5] ^ x5; g[5] = g[5] ^ x5; + i32 x6 = (f[6] ^ g[6]) & mask; f[6] = f[6] ^ x6; g[6] = g[6] ^ x6; + i32 x7 = (f[7] ^ g[7]) & mask; f[7] = f[7] ^ x7; g[7] = g[7] ^ x7; + i32 x8 = (f[8] ^ g[8]) & mask; f[8] = f[8] ^ x8; g[8] = g[8] ^ x8; + i32 x9 = (f[9] ^ g[9]) & mask; f[9] = f[9] ^ x9; g[9] = g[9] ^ x9; +} + +static void fe_ccopy(fe f, const fe g, int b) +{ + volatile i32 mask = -b; // -1 = 0xffffffff + i32 x0 = (f[0] ^ g[0]) & mask; f[0] = f[0] ^ x0; + i32 x1 = (f[1] ^ g[1]) & mask; f[1] = f[1] ^ x1; + i32 x2 = (f[2] ^ g[2]) & mask; f[2] = f[2] ^ x2; + i32 x3 = (f[3] ^ g[3]) & mask; f[3] = f[3] ^ x3; + i32 x4 = (f[4] ^ g[4]) & mask; f[4] = f[4] ^ x4; + i32 x5 = (f[5] ^ g[5]) & mask; f[5] = f[5] ^ x5; + i32 x6 = (f[6] ^ g[6]) & mask; f[6] = f[6] ^ x6; + i32 x7 = (f[7] ^ g[7]) & mask; f[7] = f[7] ^ x7; + i32 x8 = (f[8] ^ g[8]) & mask; f[8] = f[8] ^ x8; + i32 x9 = (f[9] ^ g[9]) & mask; f[9] = f[9] ^ x9; +} + + +// Signed carry propagation +// ------------------------ +// +// Let t be a number. It can be uniquely decomposed thus: +// +// t = h*2^26 + l +// such that -2^25 <= l < 2^25 +// +// Let c = (t + 2^25) / 2^26 (rounded down) +// c = (h*2^26 + l + 2^25) / 2^26 (rounded down) +// c = h + (l + 2^25) / 2^26 (rounded down) +// c = h (exactly) +// Because 0 <= l + 2^25 < 2^26 +// +// Let u = t - c*2^26 +// u = h*2^26 + l - h*2^26 +// u = l +// Therefore, -2^25 <= u < 2^25 +// +// Additionally, if |t| < x, then |h| < x/2^26 (rounded down) +// +// Notations: +// - In C, 1<<25 means 2^25. +// - In C, x>>25 means floor(x / (2^25)). +// - All of the above applies with 25 & 24 as well as 26 & 25. +// +// +// Note on negative right shifts +// ----------------------------- +// +// In C, x >> n, where x is a negative integer, is implementation +// defined. In practice, all platforms do arithmetic shift, which is +// equivalent to division by 2^26, rounded down. Some compilers, like +// GCC, even guarantee it. +// +// If we ever stumble upon a platform that does not propagate the sign +// bit (we won't), visible failures will show at the slightest test, and +// the signed shifts can be replaced by the following: +// +// typedef struct { i64 x:39; } s25; +// typedef struct { i64 x:38; } s26; +// i64 shift25(i64 x) { s25 s; s.x = ((u64)x)>>25; return s.x; } +// i64 shift26(i64 x) { s26 s; s.x = ((u64)x)>>26; return s.x; } +// +// Current compilers cannot optimise this, causing a 30% drop in +// performance. Fairly expensive for something that never happens. +// +// +// Precondition +// ------------ +// +// |t0| < 2^63 +// |t1|..|t9| < 2^62 +// +// Algorithm +// --------- +// c = t0 + 2^25 / 2^26 -- |c| <= 2^36 +// t0 -= c * 2^26 -- |t0| <= 2^25 +// t1 += c -- |t1| <= 2^63 +// +// c = t4 + 2^25 / 2^26 -- |c| <= 2^36 +// t4 -= c * 2^26 -- |t4| <= 2^25 +// t5 += c -- |t5| <= 2^63 +// +// c = t1 + 2^24 / 2^25 -- |c| <= 2^38 +// t1 -= c * 2^25 -- |t1| <= 2^24 +// t2 += c -- |t2| <= 2^63 +// +// c = t5 + 2^24 / 2^25 -- |c| <= 2^38 +// t5 -= c * 2^25 -- |t5| <= 2^24 +// t6 += c -- |t6| <= 2^63 +// +// c = t2 + 2^25 / 2^26 -- |c| <= 2^37 +// t2 -= c * 2^26 -- |t2| <= 2^25 < 1.1 * 2^25 (final t2) +// t3 += c -- |t3| <= 2^63 +// +// c = t6 + 2^25 / 2^26 -- |c| <= 2^37 +// t6 -= c * 2^26 -- |t6| <= 2^25 < 1.1 * 2^25 (final t6) +// t7 += c -- |t7| <= 2^63 +// +// c = t3 + 2^24 / 2^25 -- |c| <= 2^38 +// t3 -= c * 2^25 -- |t3| <= 2^24 < 1.1 * 2^24 (final t3) +// t4 += c -- |t4| <= 2^25 + 2^38 < 2^39 +// +// c = t7 + 2^24 / 2^25 -- |c| <= 2^38 +// t7 -= c * 2^25 -- |t7| <= 2^24 < 1.1 * 2^24 (final t7) +// t8 += c -- |t8| <= 2^63 +// +// c = t4 + 2^25 / 2^26 -- |c| <= 2^13 +// t4 -= c * 2^26 -- |t4| <= 2^25 < 1.1 * 2^25 (final t4) +// t5 += c -- |t5| <= 2^24 + 2^13 < 1.1 * 2^24 (final t5) +// +// c = t8 + 2^25 / 2^26 -- |c| <= 2^37 +// t8 -= c * 2^26 -- |t8| <= 2^25 < 1.1 * 2^25 (final t8) +// t9 += c -- |t9| <= 2^63 +// +// c = t9 + 2^24 / 2^25 -- |c| <= 2^38 +// t9 -= c * 2^25 -- |t9| <= 2^24 < 1.1 * 2^24 (final t9) +// t0 += c * 19 -- |t0| <= 2^25 + 2^38*19 < 2^44 +// +// c = t0 + 2^25 / 2^26 -- |c| <= 2^18 +// t0 -= c * 2^26 -- |t0| <= 2^25 < 1.1 * 2^25 (final t0) +// t1 += c -- |t1| <= 2^24 + 2^18 < 1.1 * 2^24 (final t1) +// +// Postcondition +// ------------- +// |t0|, |t2|, |t4|, |t6|, |t8| < 1.1 * 2^25 +// |t1|, |t3|, |t5|, |t7|, |t9| < 1.1 * 2^24 +#define FE_CARRY \ + i64 c; \ + c = (t0 + ((i64)1<<25)) >> 26; t0 -= c * ((i64)1 << 26); t1 += c; \ + c = (t4 + ((i64)1<<25)) >> 26; t4 -= c * ((i64)1 << 26); t5 += c; \ + c = (t1 + ((i64)1<<24)) >> 25; t1 -= c * ((i64)1 << 25); t2 += c; \ + c = (t5 + ((i64)1<<24)) >> 25; t5 -= c * ((i64)1 << 25); t6 += c; \ + c = (t2 + ((i64)1<<25)) >> 26; t2 -= c * ((i64)1 << 26); t3 += c; \ + c = (t6 + ((i64)1<<25)) >> 26; t6 -= c * ((i64)1 << 26); t7 += c; \ + c = (t3 + ((i64)1<<24)) >> 25; t3 -= c * ((i64)1 << 25); t4 += c; \ + c = (t7 + ((i64)1<<24)) >> 25; t7 -= c * ((i64)1 << 25); t8 += c; \ + c = (t4 + ((i64)1<<25)) >> 26; t4 -= c * ((i64)1 << 26); t5 += c; \ + c = (t8 + ((i64)1<<25)) >> 26; t8 -= c * ((i64)1 << 26); t9 += c; \ + c = (t9 + ((i64)1<<24)) >> 25; t9 -= c * ((i64)1 << 25); t0 += c * 19; \ + c = (t0 + ((i64)1<<25)) >> 26; t0 -= c * ((i64)1 << 26); t1 += c; \ + h[0]=(i32)t0; h[1]=(i32)t1; h[2]=(i32)t2; h[3]=(i32)t3; h[4]=(i32)t4; \ + h[5]=(i32)t5; h[6]=(i32)t6; h[7]=(i32)t7; h[8]=(i32)t8; h[9]=(i32)t9 + +// Decodes a field element from a byte buffer. +// mask specifies how many bits we ignore. +// Traditionally we ignore 1. It's useful for EdDSA, +// which uses that bit to denote the sign of x. +// Elligator however uses positive representatives, +// which means ignoring 2 bits instead. +static void fe_frombytes_mask(fe h, const u8 s[32], unsigned nb_mask) +{ + u32 mask = 0xffffff >> nb_mask; + i64 t0 = load32_le(s); // t0 < 2^32 + i64 t1 = load24_le(s + 4) << 6; // t1 < 2^30 + i64 t2 = load24_le(s + 7) << 5; // t2 < 2^29 + i64 t3 = load24_le(s + 10) << 3; // t3 < 2^27 + i64 t4 = load24_le(s + 13) << 2; // t4 < 2^26 + i64 t5 = load32_le(s + 16); // t5 < 2^32 + i64 t6 = load24_le(s + 20) << 7; // t6 < 2^31 + i64 t7 = load24_le(s + 23) << 5; // t7 < 2^29 + i64 t8 = load24_le(s + 26) << 4; // t8 < 2^28 + i64 t9 = (load24_le(s + 29) & mask) << 2; // t9 < 2^25 + FE_CARRY; // Carry precondition OK +} + +static void fe_frombytes(fe h, const u8 s[32]) +{ + fe_frombytes_mask(h, s, 1); +} + + +// Precondition +// |h[0]|, |h[2]|, |h[4]|, |h[6]|, |h[8]| < 1.1 * 2^25 +// |h[1]|, |h[3]|, |h[5]|, |h[7]|, |h[9]| < 1.1 * 2^24 +// +// Therefore, |h| < 2^255-19 +// There are two possibilities: +// +// - If h is positive, all we need to do is reduce its individual +// limbs down to their tight positive range. +// - If h is negative, we also need to add 2^255-19 to it. +// Or just remove 19 and chop off any excess bit. +static void fe_tobytes(u8 s[32], const fe h) +{ + i32 t[10]; + COPY(t, h, 10); + i32 q = (19 * t[9] + (((i32) 1) << 24)) >> 25; + // |t9| < 1.1 * 2^24 + // -1.1 * 2^24 < t9 < 1.1 * 2^24 + // -21 * 2^24 < 19 * t9 < 21 * 2^24 + // -2^29 < 19 * t9 + 2^24 < 2^29 + // -2^29 / 2^25 < (19 * t9 + 2^24) / 2^25 < 2^29 / 2^25 + // -16 < (19 * t9 + 2^24) / 2^25 < 16 + FOR (i, 0, 5) { + q += t[2*i ]; q >>= 26; // q = 0 or -1 + q += t[2*i+1]; q >>= 25; // q = 0 or -1 + } + // q = 0 iff h >= 0 + // q = -1 iff h < 0 + // Adding q * 19 to h reduces h to its proper range. + q *= 19; // Shift carry back to the beginning + FOR (i, 0, 5) { + t[i*2 ] += q; q = t[i*2 ] >> 26; t[i*2 ] -= q * ((i32)1 << 26); + t[i*2+1] += q; q = t[i*2+1] >> 25; t[i*2+1] -= q * ((i32)1 << 25); + } + // h is now fully reduced, and q represents the excess bit. + + store32_le(s + 0, ((u32)t[0] >> 0) | ((u32)t[1] << 26)); + store32_le(s + 4, ((u32)t[1] >> 6) | ((u32)t[2] << 19)); + store32_le(s + 8, ((u32)t[2] >> 13) | ((u32)t[3] << 13)); + store32_le(s + 12, ((u32)t[3] >> 19) | ((u32)t[4] << 6)); + store32_le(s + 16, ((u32)t[5] >> 0) | ((u32)t[6] << 25)); + store32_le(s + 20, ((u32)t[6] >> 7) | ((u32)t[7] << 19)); + store32_le(s + 24, ((u32)t[7] >> 13) | ((u32)t[8] << 12)); + store32_le(s + 28, ((u32)t[8] >> 20) | ((u32)t[9] << 6)); + + WIPE_BUFFER(t); +} + +// Precondition +// ------------- +// |f0|, |f2|, |f4|, |f6|, |f8| < 1.65 * 2^26 +// |f1|, |f3|, |f5|, |f7|, |f9| < 1.65 * 2^25 +// +// |g0|, |g2|, |g4|, |g6|, |g8| < 1.65 * 2^26 +// |g1|, |g3|, |g5|, |g7|, |g9| < 1.65 * 2^25 +static void fe_mul_small(fe h, const fe f, i32 g) +{ + i64 t0 = f[0] * (i64) g; i64 t1 = f[1] * (i64) g; + i64 t2 = f[2] * (i64) g; i64 t3 = f[3] * (i64) g; + i64 t4 = f[4] * (i64) g; i64 t5 = f[5] * (i64) g; + i64 t6 = f[6] * (i64) g; i64 t7 = f[7] * (i64) g; + i64 t8 = f[8] * (i64) g; i64 t9 = f[9] * (i64) g; + // |t0|, |t2|, |t4|, |t6|, |t8| < 1.65 * 2^26 * 2^31 < 2^58 + // |t1|, |t3|, |t5|, |t7|, |t9| < 1.65 * 2^25 * 2^31 < 2^57 + + FE_CARRY; // Carry precondition OK +} + +// Precondition +// ------------- +// |f0|, |f2|, |f4|, |f6|, |f8| < 1.65 * 2^26 +// |f1|, |f3|, |f5|, |f7|, |f9| < 1.65 * 2^25 +// +// |g0|, |g2|, |g4|, |g6|, |g8| < 1.65 * 2^26 +// |g1|, |g3|, |g5|, |g7|, |g9| < 1.65 * 2^25 +static void fe_mul(fe h, const fe f, const fe g) +{ + // Everything is unrolled and put in temporary variables. + // We could roll the loop, but that would make curve25519 twice as slow. + i32 f0 = f[0]; i32 f1 = f[1]; i32 f2 = f[2]; i32 f3 = f[3]; i32 f4 = f[4]; + i32 f5 = f[5]; i32 f6 = f[6]; i32 f7 = f[7]; i32 f8 = f[8]; i32 f9 = f[9]; + i32 g0 = g[0]; i32 g1 = g[1]; i32 g2 = g[2]; i32 g3 = g[3]; i32 g4 = g[4]; + i32 g5 = g[5]; i32 g6 = g[6]; i32 g7 = g[7]; i32 g8 = g[8]; i32 g9 = g[9]; + i32 F1 = f1*2; i32 F3 = f3*2; i32 F5 = f5*2; i32 F7 = f7*2; i32 F9 = f9*2; + i32 G1 = g1*19; i32 G2 = g2*19; i32 G3 = g3*19; + i32 G4 = g4*19; i32 G5 = g5*19; i32 G6 = g6*19; + i32 G7 = g7*19; i32 G8 = g8*19; i32 G9 = g9*19; + // |F1|, |F3|, |F5|, |F7|, |F9| < 1.65 * 2^26 + // |G0|, |G2|, |G4|, |G6|, |G8| < 2^31 + // |G1|, |G3|, |G5|, |G7|, |G9| < 2^30 + + i64 t0 = f0*(i64)g0 + F1*(i64)G9 + f2*(i64)G8 + F3*(i64)G7 + f4*(i64)G6 + + F5*(i64)G5 + f6*(i64)G4 + F7*(i64)G3 + f8*(i64)G2 + F9*(i64)G1; + i64 t1 = f0*(i64)g1 + f1*(i64)g0 + f2*(i64)G9 + f3*(i64)G8 + f4*(i64)G7 + + f5*(i64)G6 + f6*(i64)G5 + f7*(i64)G4 + f8*(i64)G3 + f9*(i64)G2; + i64 t2 = f0*(i64)g2 + F1*(i64)g1 + f2*(i64)g0 + F3*(i64)G9 + f4*(i64)G8 + + F5*(i64)G7 + f6*(i64)G6 + F7*(i64)G5 + f8*(i64)G4 + F9*(i64)G3; + i64 t3 = f0*(i64)g3 + f1*(i64)g2 + f2*(i64)g1 + f3*(i64)g0 + f4*(i64)G9 + + f5*(i64)G8 + f6*(i64)G7 + f7*(i64)G6 + f8*(i64)G5 + f9*(i64)G4; + i64 t4 = f0*(i64)g4 + F1*(i64)g3 + f2*(i64)g2 + F3*(i64)g1 + f4*(i64)g0 + + F5*(i64)G9 + f6*(i64)G8 + F7*(i64)G7 + f8*(i64)G6 + F9*(i64)G5; + i64 t5 = f0*(i64)g5 + f1*(i64)g4 + f2*(i64)g3 + f3*(i64)g2 + f4*(i64)g1 + + f5*(i64)g0 + f6*(i64)G9 + f7*(i64)G8 + f8*(i64)G7 + f9*(i64)G6; + i64 t6 = f0*(i64)g6 + F1*(i64)g5 + f2*(i64)g4 + F3*(i64)g3 + f4*(i64)g2 + + F5*(i64)g1 + f6*(i64)g0 + F7*(i64)G9 + f8*(i64)G8 + F9*(i64)G7; + i64 t7 = f0*(i64)g7 + f1*(i64)g6 + f2*(i64)g5 + f3*(i64)g4 + f4*(i64)g3 + + f5*(i64)g2 + f6*(i64)g1 + f7*(i64)g0 + f8*(i64)G9 + f9*(i64)G8; + i64 t8 = f0*(i64)g8 + F1*(i64)g7 + f2*(i64)g6 + F3*(i64)g5 + f4*(i64)g4 + + F5*(i64)g3 + f6*(i64)g2 + F7*(i64)g1 + f8*(i64)g0 + F9*(i64)G9; + i64 t9 = f0*(i64)g9 + f1*(i64)g8 + f2*(i64)g7 + f3*(i64)g6 + f4*(i64)g5 + + f5*(i64)g4 + f6*(i64)g3 + f7*(i64)g2 + f8*(i64)g1 + f9*(i64)g0; + // t0 < 0.67 * 2^61 + // t1 < 0.41 * 2^61 + // t2 < 0.52 * 2^61 + // t3 < 0.32 * 2^61 + // t4 < 0.38 * 2^61 + // t5 < 0.22 * 2^61 + // t6 < 0.23 * 2^61 + // t7 < 0.13 * 2^61 + // t8 < 0.09 * 2^61 + // t9 < 0.03 * 2^61 + + FE_CARRY; // Everything below 2^62, Carry precondition OK +} + +// Precondition +// ------------- +// |f0|, |f2|, |f4|, |f6|, |f8| < 1.65 * 2^26 +// |f1|, |f3|, |f5|, |f7|, |f9| < 1.65 * 2^25 +// +// Note: we could use fe_mul() for this, but this is significantly faster +static void fe_sq(fe h, const fe f) +{ + i32 f0 = f[0]; i32 f1 = f[1]; i32 f2 = f[2]; i32 f3 = f[3]; i32 f4 = f[4]; + i32 f5 = f[5]; i32 f6 = f[6]; i32 f7 = f[7]; i32 f8 = f[8]; i32 f9 = f[9]; + i32 f0_2 = f0*2; i32 f1_2 = f1*2; i32 f2_2 = f2*2; i32 f3_2 = f3*2; + i32 f4_2 = f4*2; i32 f5_2 = f5*2; i32 f6_2 = f6*2; i32 f7_2 = f7*2; + i32 f5_38 = f5*38; i32 f6_19 = f6*19; i32 f7_38 = f7*38; + i32 f8_19 = f8*19; i32 f9_38 = f9*38; + // |f0_2| , |f2_2| , |f4_2| , |f6_2| , |f8_2| < 1.65 * 2^27 + // |f1_2| , |f3_2| , |f5_2| , |f7_2| , |f9_2| < 1.65 * 2^26 + // |f5_38|, |f6_19|, |f7_38|, |f8_19|, |f9_38| < 2^31 + + i64 t0 = f0 *(i64)f0 + f1_2*(i64)f9_38 + f2_2*(i64)f8_19 + + f3_2*(i64)f7_38 + f4_2*(i64)f6_19 + f5 *(i64)f5_38; + i64 t1 = f0_2*(i64)f1 + f2 *(i64)f9_38 + f3_2*(i64)f8_19 + + f4 *(i64)f7_38 + f5_2*(i64)f6_19; + i64 t2 = f0_2*(i64)f2 + f1_2*(i64)f1 + f3_2*(i64)f9_38 + + f4_2*(i64)f8_19 + f5_2*(i64)f7_38 + f6 *(i64)f6_19; + i64 t3 = f0_2*(i64)f3 + f1_2*(i64)f2 + f4 *(i64)f9_38 + + f5_2*(i64)f8_19 + f6 *(i64)f7_38; + i64 t4 = f0_2*(i64)f4 + f1_2*(i64)f3_2 + f2 *(i64)f2 + + f5_2*(i64)f9_38 + f6_2*(i64)f8_19 + f7 *(i64)f7_38; + i64 t5 = f0_2*(i64)f5 + f1_2*(i64)f4 + f2_2*(i64)f3 + + f6 *(i64)f9_38 + f7_2*(i64)f8_19; + i64 t6 = f0_2*(i64)f6 + f1_2*(i64)f5_2 + f2_2*(i64)f4 + + f3_2*(i64)f3 + f7_2*(i64)f9_38 + f8 *(i64)f8_19; + i64 t7 = f0_2*(i64)f7 + f1_2*(i64)f6 + f2_2*(i64)f5 + + f3_2*(i64)f4 + f8 *(i64)f9_38; + i64 t8 = f0_2*(i64)f8 + f1_2*(i64)f7_2 + f2_2*(i64)f6 + + f3_2*(i64)f5_2 + f4 *(i64)f4 + f9 *(i64)f9_38; + i64 t9 = f0_2*(i64)f9 + f1_2*(i64)f8 + f2_2*(i64)f7 + + f3_2*(i64)f6 + f4 *(i64)f5_2; + // t0 < 0.67 * 2^61 + // t1 < 0.41 * 2^61 + // t2 < 0.52 * 2^61 + // t3 < 0.32 * 2^61 + // t4 < 0.38 * 2^61 + // t5 < 0.22 * 2^61 + // t6 < 0.23 * 2^61 + // t7 < 0.13 * 2^61 + // t8 < 0.09 * 2^61 + // t9 < 0.03 * 2^61 + + FE_CARRY; +} + +// Parity check. Returns 0 if even, 1 if odd +static int fe_isodd(const fe f) +{ + u8 s[32]; + fe_tobytes(s, f); + u8 isodd = s[0] & 1; + WIPE_BUFFER(s); + return isodd; +} + +// Returns 1 if equal, 0 if not equal +static int fe_isequal(const fe f, const fe g) +{ + u8 fs[32]; + u8 gs[32]; + fe_tobytes(fs, f); + fe_tobytes(gs, g); + int isdifferent = crypto_verify32(fs, gs); + WIPE_BUFFER(fs); + WIPE_BUFFER(gs); + return 1 + isdifferent; +} + +// Inverse square root. +// Returns true if x is a square, false otherwise. +// After the call: +// isr = sqrt(1/x) if x is a non-zero square. +// isr = sqrt(sqrt(-1)/x) if x is not a square. +// isr = 0 if x is zero. +// We do not guarantee the sign of the square root. +// +// Notes: +// Let quartic = x^((p-1)/4) +// +// x^((p-1)/2) = chi(x) +// quartic^2 = chi(x) +// quartic = sqrt(chi(x)) +// quartic = 1 or -1 or sqrt(-1) or -sqrt(-1) +// +// Note that x is a square if quartic is 1 or -1 +// There are 4 cases to consider: +// +// if quartic = 1 (x is a square) +// then x^((p-1)/4) = 1 +// x^((p-5)/4) * x = 1 +// x^((p-5)/4) = 1/x +// x^((p-5)/8) = sqrt(1/x) or -sqrt(1/x) +// +// if quartic = -1 (x is a square) +// then x^((p-1)/4) = -1 +// x^((p-5)/4) * x = -1 +// x^((p-5)/4) = -1/x +// x^((p-5)/8) = sqrt(-1) / sqrt(x) +// x^((p-5)/8) * sqrt(-1) = sqrt(-1)^2 / sqrt(x) +// x^((p-5)/8) * sqrt(-1) = -1/sqrt(x) +// x^((p-5)/8) * sqrt(-1) = -sqrt(1/x) or sqrt(1/x) +// +// if quartic = sqrt(-1) (x is not a square) +// then x^((p-1)/4) = sqrt(-1) +// x^((p-5)/4) * x = sqrt(-1) +// x^((p-5)/4) = sqrt(-1)/x +// x^((p-5)/8) = sqrt(sqrt(-1)/x) or -sqrt(sqrt(-1)/x) +// +// Note that the product of two non-squares is always a square: +// For any non-squares a and b, chi(a) = -1 and chi(b) = -1. +// Since chi(x) = x^((p-1)/2), chi(a)*chi(b) = chi(a*b) = 1. +// Therefore a*b is a square. +// +// Since sqrt(-1) and x are both non-squares, their product is a +// square, and we can compute their square root. +// +// if quartic = -sqrt(-1) (x is not a square) +// then x^((p-1)/4) = -sqrt(-1) +// x^((p-5)/4) * x = -sqrt(-1) +// x^((p-5)/4) = -sqrt(-1)/x +// x^((p-5)/8) = sqrt(-sqrt(-1)/x) +// x^((p-5)/8) = sqrt( sqrt(-1)/x) * sqrt(-1) +// x^((p-5)/8) * sqrt(-1) = sqrt( sqrt(-1)/x) * sqrt(-1)^2 +// x^((p-5)/8) * sqrt(-1) = sqrt( sqrt(-1)/x) * -1 +// x^((p-5)/8) * sqrt(-1) = -sqrt(sqrt(-1)/x) or sqrt(sqrt(-1)/x) +static int invsqrt(fe isr, const fe x) +{ + fe t0, t1, t2; + + // t0 = x^((p-5)/8) + // Can be achieved with a simple double & add ladder, + // but it would be slower. + fe_sq(t0, x); + fe_sq(t1,t0); fe_sq(t1, t1); fe_mul(t1, x, t1); + fe_mul(t0, t0, t1); + fe_sq(t0, t0); fe_mul(t0, t1, t0); + fe_sq(t1, t0); FOR (i, 1, 5) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); + fe_sq(t1, t0); FOR (i, 1, 10) { fe_sq(t1, t1); } fe_mul(t1, t1, t0); + fe_sq(t2, t1); FOR (i, 1, 20) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); + fe_sq(t1, t1); FOR (i, 1, 10) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); + fe_sq(t1, t0); FOR (i, 1, 50) { fe_sq(t1, t1); } fe_mul(t1, t1, t0); + fe_sq(t2, t1); FOR (i, 1, 100) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); + fe_sq(t1, t1); FOR (i, 1, 50) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); + fe_sq(t0, t0); FOR (i, 1, 2) { fe_sq(t0, t0); } fe_mul(t0, t0, x); + + // quartic = x^((p-1)/4) + i32 *quartic = t1; + fe_sq (quartic, t0); + fe_mul(quartic, quartic, x); + + i32 *check = t2; + fe_0 (check); int z0 = fe_isequal(x , check); + fe_1 (check); int p1 = fe_isequal(quartic, check); + fe_neg(check, check ); int m1 = fe_isequal(quartic, check); + fe_neg(check, sqrtm1); int ms = fe_isequal(quartic, check); + + // if quartic == -1 or sqrt(-1) + // then isr = x^((p-1)/4) * sqrt(-1) + // else isr = x^((p-1)/4) + fe_mul(isr, t0, sqrtm1); + fe_ccopy(isr, t0, 1 - (m1 | ms)); + + WIPE_BUFFER(t0); + WIPE_BUFFER(t1); + WIPE_BUFFER(t2); + return p1 | m1 | z0; +} + +// Inverse in terms of inverse square root. +// Requires two additional squarings to get rid of the sign. +// +// 1/x = x * (+invsqrt(x^2))^2 +// = x * (-invsqrt(x^2))^2 +// +// A fully optimised exponentiation by p-1 would save 6 field +// multiplications, but it would require more code. +static void fe_invert(fe out, const fe x) +{ + fe tmp; + fe_sq(tmp, x); + invsqrt(tmp, tmp); + fe_sq(tmp, tmp); + fe_mul(out, tmp, x); + WIPE_BUFFER(tmp); +} + +// trim a scalar for scalar multiplication +void crypto_eddsa_trim_scalar(u8 out[32], const u8 in[32]) +{ + COPY(out, in, 32); + out[ 0] &= 248; + out[31] &= 127; + out[31] |= 64; +} + +// get bit from scalar at position i +static int scalar_bit(const u8 s[32], int i) +{ + if (i < 0) { return 0; } // handle -1 for sliding windows + return (s[i>>3] >> (i&7)) & 1; +} + +/////////////// +/// X-25519 /// Taken from SUPERCOP's ref10 implementation. +/////////////// +static void scalarmult(u8 q[32], const u8 scalar[32], const u8 p[32], + int nb_bits) +{ + // computes the scalar product + fe x1; + fe_frombytes(x1, p); + + // computes the actual scalar product (the result is in x2 and z2) + fe x2, z2, x3, z3, t0, t1; + // Montgomery ladder + // In projective coordinates, to avoid divisions: x = X / Z + // We don't care about the y coordinate, it's only 1 bit of information + fe_1(x2); fe_0(z2); // "zero" point + fe_copy(x3, x1); fe_1(z3); // "one" point + int swap = 0; + for (int pos = nb_bits-1; pos >= 0; --pos) { + // constant time conditional swap before ladder step + int b = scalar_bit(scalar, pos); + swap ^= b; // xor trick avoids swapping at the end of the loop + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + swap = b; // anticipates one last swap after the loop + + // Montgomery ladder step: replaces (P2, P3) by (P2*2, P2+P3) + // with differential addition + fe_sub(t0, x3, z3); + fe_sub(t1, x2, z2); + fe_add(x2, x2, z2); + fe_add(z2, x3, z3); + fe_mul(z3, t0, x2); + fe_mul(z2, z2, t1); + fe_sq (t0, t1 ); + fe_sq (t1, x2 ); + fe_add(x3, z3, z2); + fe_sub(z2, z3, z2); + fe_mul(x2, t1, t0); + fe_sub(t1, t1, t0); + fe_sq (z2, z2 ); + fe_mul_small(z3, t1, 121666); + fe_sq (x3, x3 ); + fe_add(t0, t0, z3); + fe_mul(z3, x1, z2); + fe_mul(z2, t1, t0); + } + // last swap is necessary to compensate for the xor trick + // Note: after this swap, P3 == P2 + P1. + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + + // normalises the coordinates: x == X / Z + fe_invert(z2, z2); + fe_mul(x2, x2, z2); + fe_tobytes(q, x2); + + WIPE_BUFFER(x1); + WIPE_BUFFER(x2); WIPE_BUFFER(z2); WIPE_BUFFER(t0); + WIPE_BUFFER(x3); WIPE_BUFFER(z3); WIPE_BUFFER(t1); +} + +void crypto_x25519(u8 raw_shared_secret[32], + const u8 your_secret_key [32], + const u8 their_public_key [32]) +{ + // restrict the possible scalar values + u8 e[32]; + crypto_eddsa_trim_scalar(e, your_secret_key); + scalarmult(raw_shared_secret, e, their_public_key, 255); + WIPE_BUFFER(e); +} + +void crypto_x25519_public_key(u8 public_key[32], + const u8 secret_key[32]) +{ + static const u8 base_point[32] = {9}; + crypto_x25519(public_key, secret_key, base_point); +} + +/////////////////////////// +/// Arithmetic modulo L /// +/////////////////////////// +static const u32 L[8] = { + 0x5cf5d3ed, 0x5812631a, 0xa2f79cd6, 0x14def9de, + 0x00000000, 0x00000000, 0x00000000, 0x10000000, +}; + +// p = a*b + p +static void multiply(u32 p[16], const u32 a[8], const u32 b[8]) +{ + FOR (i, 0, 8) { + u64 carry = 0; + FOR (j, 0, 8) { + carry += p[i+j] + (u64)a[i] * b[j]; + p[i+j] = (u32)carry; + carry >>= 32; + } + p[i+8] = (u32)carry; + } +} + +static int is_above_l(const u32 x[8]) +{ + // We work with L directly, in a 2's complement encoding + // (-L == ~L + 1) + u64 carry = 1; + FOR (i, 0, 8) { + carry += (u64)x[i] + (~L[i] & 0xffffffff); + carry >>= 32; + } + return (int)carry; // carry is either 0 or 1 +} + +// Final reduction modulo L, by conditionally removing L. +// if x < l , then r = x +// if l <= x 2*l, then r = x-l +// otherwise the result will be wrong +static void remove_l(u32 r[8], const u32 x[8]) +{ + u64 carry = (u64)is_above_l(x); + u32 mask = ~(u32)carry + 1; // carry == 0 or 1 + FOR (i, 0, 8) { + carry += (u64)x[i] + (~L[i] & mask); + r[i] = (u32)carry; + carry >>= 32; + } +} + +// Full reduction modulo L (Barrett reduction) +static void mod_l(u8 reduced[32], const u32 x[16]) +{ + static const u32 r[9] = { + 0x0a2c131b,0xed9ce5a3,0x086329a7,0x2106215d, + 0xffffffeb,0xffffffff,0xffffffff,0xffffffff,0xf, + }; + // xr = x * r + u32 xr[25] = {0}; + FOR (i, 0, 9) { + u64 carry = 0; + FOR (j, 0, 16) { + carry += xr[i+j] + (u64)r[i] * x[j]; + xr[i+j] = (u32)carry; + carry >>= 32; + } + xr[i+16] = (u32)carry; + } + // xr = floor(xr / 2^512) * L + // Since the result is guaranteed to be below 2*L, + // it is enough to only compute the first 256 bits. + // The division is performed by saying xr[i+16]. (16 * 32 = 512) + ZERO(xr, 8); + FOR (i, 0, 8) { + u64 carry = 0; + FOR (j, 0, 8-i) { + carry += xr[i+j] + (u64)xr[i+16] * L[j]; + xr[i+j] = (u32)carry; + carry >>= 32; + } + } + // xr = x - xr + u64 carry = 1; + FOR (i, 0, 8) { + carry += (u64)x[i] + (~xr[i] & 0xffffffff); + xr[i] = (u32)carry; + carry >>= 32; + } + // Final reduction modulo L (conditional subtraction) + remove_l(xr, xr); + store32_le_buf(reduced, xr, 8); + + WIPE_BUFFER(xr); +} + +void crypto_eddsa_reduce(u8 reduced[32], const u8 expanded[64]) +{ + u32 x[16]; + load32_le_buf(x, expanded, 16); + mod_l(reduced, x); + WIPE_BUFFER(x); +} + +// r = (a * b) + c +void crypto_eddsa_mul_add(u8 r[32], + const u8 a[32], const u8 b[32], const u8 c[32]) +{ + u32 A[8]; load32_le_buf(A, a, 8); + u32 B[8]; load32_le_buf(B, b, 8); + u32 p[16]; load32_le_buf(p, c, 8); ZERO(p + 8, 8); + multiply(p, A, B); + mod_l(r, p); + WIPE_BUFFER(p); + WIPE_BUFFER(A); + WIPE_BUFFER(B); +} + +/////////////// +/// Ed25519 /// +/////////////// + +// Point (group element, ge) in a twisted Edwards curve, +// in extended projective coordinates. +// ge : x = X/Z, y = Y/Z, T = XY/Z +// ge_cached : Yp = X+Y, Ym = X-Y, T2 = T*D2 +// ge_precomp: Z = 1 +typedef struct { fe X; fe Y; fe Z; fe T; } ge; +typedef struct { fe Yp; fe Ym; fe Z; fe T2; } ge_cached; +typedef struct { fe Yp; fe Ym; fe T2; } ge_precomp; + +static void ge_zero(ge *p) +{ + fe_0(p->X); + fe_1(p->Y); + fe_1(p->Z); + fe_0(p->T); +} + +static void ge_tobytes(u8 s[32], const ge *h) +{ + fe recip, x, y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= (u8)fe_isodd(x) << 7; + + WIPE_BUFFER(recip); + WIPE_BUFFER(x); + WIPE_BUFFER(y); +} + +// h = -s, where s is a point encoded in 32 bytes +// +// Variable time! Inputs must not be secret! +// => Use only to *check* signatures. +// +// From the specifications: +// The encoding of s contains y and the sign of x +// x = sqrt((y^2 - 1) / (d*y^2 + 1)) +// In extended coordinates: +// X = x, Y = y, Z = 1, T = x*y +// +// Note that num * den is a square iff num / den is a square +// If num * den is not a square, the point was not on the curve. +// From the above: +// Let num = y^2 - 1 +// Let den = d*y^2 + 1 +// x = sqrt((y^2 - 1) / (d*y^2 + 1)) +// x = sqrt(num / den) +// x = sqrt(num^2 / (num * den)) +// x = num * sqrt(1 / (num * den)) +// +// Therefore, we can just compute: +// num = y^2 - 1 +// den = d*y^2 + 1 +// isr = invsqrt(num * den) // abort if not square +// x = num * isr +// Finally, negate x if its sign is not as specified. +static int ge_frombytes_neg_vartime(ge *h, const u8 s[32]) +{ + fe_frombytes(h->Y, s); + fe_1(h->Z); + fe_sq (h->T, h->Y); // t = y^2 + fe_mul(h->X, h->T, d ); // x = d*y^2 + fe_sub(h->T, h->T, h->Z); // t = y^2 - 1 + fe_add(h->X, h->X, h->Z); // x = d*y^2 + 1 + fe_mul(h->X, h->T, h->X); // x = (y^2 - 1) * (d*y^2 + 1) + int is_square = invsqrt(h->X, h->X); + if (!is_square) { + return -1; // Not on the curve, abort + } + fe_mul(h->X, h->T, h->X); // x = sqrt((y^2 - 1) / (d*y^2 + 1)) + if (fe_isodd(h->X) == (s[31] >> 7)) { + fe_neg(h->X, h->X); + } + fe_mul(h->T, h->X, h->Y); + return 0; +} + +static void ge_cache(ge_cached *c, const ge *p) +{ + fe_add (c->Yp, p->Y, p->X); + fe_sub (c->Ym, p->Y, p->X); + fe_copy(c->Z , p->Z ); + fe_mul (c->T2, p->T, D2 ); +} + +// Internal buffers are not wiped! Inputs must not be secret! +// => Use only to *check* signatures. +static void ge_add(ge *s, const ge *p, const ge_cached *q) +{ + fe a, b; + fe_add(a , p->Y, p->X ); + fe_sub(b , p->Y, p->X ); + fe_mul(a , a , q->Yp); + fe_mul(b , b , q->Ym); + fe_add(s->Y, a , b ); + fe_sub(s->X, a , b ); + + fe_add(s->Z, p->Z, p->Z ); + fe_mul(s->Z, s->Z, q->Z ); + fe_mul(s->T, p->T, q->T2); + fe_add(a , s->Z, s->T ); + fe_sub(b , s->Z, s->T ); + + fe_mul(s->T, s->X, s->Y); + fe_mul(s->X, s->X, b ); + fe_mul(s->Y, s->Y, a ); + fe_mul(s->Z, a , b ); +} + +// Internal buffers are not wiped! Inputs must not be secret! +// => Use only to *check* signatures. +static void ge_sub(ge *s, const ge *p, const ge_cached *q) +{ + ge_cached neg; + fe_copy(neg.Ym, q->Yp); + fe_copy(neg.Yp, q->Ym); + fe_copy(neg.Z , q->Z ); + fe_neg (neg.T2, q->T2); + ge_add(s, p, &neg); +} + +static void ge_madd(ge *s, const ge *p, const ge_precomp *q, fe a, fe b) +{ + fe_add(a , p->Y, p->X ); + fe_sub(b , p->Y, p->X ); + fe_mul(a , a , q->Yp); + fe_mul(b , b , q->Ym); + fe_add(s->Y, a , b ); + fe_sub(s->X, a , b ); + + fe_add(s->Z, p->Z, p->Z ); + fe_mul(s->T, p->T, q->T2); + fe_add(a , s->Z, s->T ); + fe_sub(b , s->Z, s->T ); + + fe_mul(s->T, s->X, s->Y); + fe_mul(s->X, s->X, b ); + fe_mul(s->Y, s->Y, a ); + fe_mul(s->Z, a , b ); +} + +// Internal buffers are not wiped! Inputs must not be secret! +// => Use only to *check* signatures. +static void ge_msub(ge *s, const ge *p, const ge_precomp *q, fe a, fe b) +{ + ge_precomp neg; + fe_copy(neg.Ym, q->Yp); + fe_copy(neg.Yp, q->Ym); + fe_neg (neg.T2, q->T2); + ge_madd(s, p, &neg, a, b); +} + +static void ge_double(ge *s, const ge *p, ge *q) +{ + fe_sq (q->X, p->X); + fe_sq (q->Y, p->Y); + fe_sq (q->Z, p->Z); // qZ = pZ^2 + fe_mul_small(q->Z, q->Z, 2); // qZ = pZ^2 * 2 + fe_add(q->T, p->X, p->Y); + fe_sq (s->T, q->T); + fe_add(q->T, q->Y, q->X); + fe_sub(q->Y, q->Y, q->X); + fe_sub(q->X, s->T, q->T); + fe_sub(q->Z, q->Z, q->Y); + + fe_mul(s->X, q->X , q->Z); + fe_mul(s->Y, q->T , q->Y); + fe_mul(s->Z, q->Y , q->Z); + fe_mul(s->T, q->X , q->T); +} + +// 5-bit signed window in cached format (Niels coordinates, Z=1) +static const ge_precomp b_window[8] = { + {{25967493,-14356035,29566456,3660896,-12694345, + 4014787,27544626,-11754271,-6079156,2047605,}, + {-12545711,934262,-2722910,3049990,-727428, + 9406986,12720692,5043384,19500929,-15469378,}, + {-8738181,4489570,9688441,-14785194,10184609, + -12363380,29287919,11864899,-24514362,-4438546,},}, + {{15636291,-9688557,24204773,-7912398,616977, + -16685262,27787600,-14772189,28944400,-1550024,}, + {16568933,4717097,-11556148,-1102322,15682896, + -11807043,16354577,-11775962,7689662,11199574,}, + {30464156,-5976125,-11779434,-15670865,23220365, + 15915852,7512774,10017326,-17749093,-9920357,},}, + {{10861363,11473154,27284546,1981175,-30064349, + 12577861,32867885,14515107,-15438304,10819380,}, + {4708026,6336745,20377586,9066809,-11272109, + 6594696,-25653668,12483688,-12668491,5581306,}, + {19563160,16186464,-29386857,4097519,10237984, + -4348115,28542350,13850243,-23678021,-15815942,},}, + {{5153746,9909285,1723747,-2777874,30523605, + 5516873,19480852,5230134,-23952439,-15175766,}, + {-30269007,-3463509,7665486,10083793,28475525, + 1649722,20654025,16520125,30598449,7715701,}, + {28881845,14381568,9657904,3680757,-20181635, + 7843316,-31400660,1370708,29794553,-1409300,},}, + {{-22518993,-6692182,14201702,-8745502,-23510406, + 8844726,18474211,-1361450,-13062696,13821877,}, + {-6455177,-7839871,3374702,-4740862,-27098617, + -10571707,31655028,-7212327,18853322,-14220951,}, + {4566830,-12963868,-28974889,-12240689,-7602672, + -2830569,-8514358,-10431137,2207753,-3209784,},}, + {{-25154831,-4185821,29681144,7868801,-6854661, + -9423865,-12437364,-663000,-31111463,-16132436,}, + {25576264,-2703214,7349804,-11814844,16472782, + 9300885,3844789,15725684,171356,6466918,}, + {23103977,13316479,9739013,-16149481,817875, + -15038942,8965339,-14088058,-30714912,16193877,},}, + {{-33521811,3180713,-2394130,14003687,-16903474, + -16270840,17238398,4729455,-18074513,9256800,}, + {-25182317,-4174131,32336398,5036987,-21236817, + 11360617,22616405,9761698,-19827198,630305,}, + {-13720693,2639453,-24237460,-7406481,9494427, + -5774029,-6554551,-15960994,-2449256,-14291300,},}, + {{-3151181,-5046075,9282714,6866145,-31907062, + -863023,-18940575,15033784,25105118,-7894876,}, + {-24326370,15950226,-31801215,-14592823,-11662737, + -5090925,1573892,-2625887,2198790,-15804619,}, + {-3099351,10324967,-2241613,7453183,-5446979, + -2735503,-13812022,-16236442,-32461234,-12290683,},}, +}; + +// Incremental sliding windows (left to right) +// Based on Roberto Maria Avanzi[2005] +typedef struct { + i16 next_index; // position of the next signed digit + i8 next_digit; // next signed digit (odd number below 2^window_width) + u8 next_check; // point at which we must check for a new window +} slide_ctx; + +static void slide_init(slide_ctx *ctx, const u8 scalar[32]) +{ + // scalar is guaranteed to be below L, either because we checked (s), + // or because we reduced it modulo L (h_ram). L is under 2^253, so + // so bits 253 to 255 are guaranteed to be zero. No need to test them. + // + // Note however that L is very close to 2^252, so bit 252 is almost + // always zero. If we were to start at bit 251, the tests wouldn't + // catch the off-by-one error (constructing one that does would be + // prohibitively expensive). + // + // We should still check bit 252, though. + int i = 252; + while (i > 0 && scalar_bit(scalar, i) == 0) { + i--; + } + ctx->next_check = (u8)(i + 1); + ctx->next_index = -1; + ctx->next_digit = -1; +} + +static int slide_step(slide_ctx *ctx, int width, int i, const u8 scalar[32]) +{ + if (i == ctx->next_check) { + if (scalar_bit(scalar, i) == scalar_bit(scalar, i - 1)) { + ctx->next_check--; + } else { + // compute digit of next window + int w = MIN(width, i + 1); + int v = -(scalar_bit(scalar, i) << (w-1)); + FOR_T (int, j, 0, w-1) { + v += scalar_bit(scalar, i-(w-1)+j) << j; + } + v += scalar_bit(scalar, i-w); + int lsb = v & (~v + 1); // smallest bit of v + int s = // log2(lsb) + (((lsb & 0xAA) != 0) << 0) | + (((lsb & 0xCC) != 0) << 1) | + (((lsb & 0xF0) != 0) << 2); + ctx->next_index = (i16)(i-(w-1)+s); + ctx->next_digit = (i8) (v >> s ); + ctx->next_check -= (u8) w; + } + } + return i == ctx->next_index ? ctx->next_digit: 0; +} + +#define P_W_WIDTH 3 // Affects the size of the stack +#define B_W_WIDTH 5 // Affects the size of the binary +#define P_W_SIZE (1<<(P_W_WIDTH-2)) + +int crypto_eddsa_check_equation(const u8 signature[64], const u8 public_key[32], + const u8 h[32]) +{ + ge minus_A; // -public_key + ge minus_R; // -first_half_of_signature + const u8 *s = signature + 32; + + // Check that A and R are on the curve + // Check that 0 <= S < L (prevents malleability) + // *Allow* non-cannonical encoding for A and R + { + u32 s32[8]; + load32_le_buf(s32, s, 8); + if (ge_frombytes_neg_vartime(&minus_A, public_key) || + ge_frombytes_neg_vartime(&minus_R, signature) || + is_above_l(s32)) { + return -1; + } + } + + // look-up table for minus_A + ge_cached lutA[P_W_SIZE]; + { + ge minus_A2, tmp; + ge_double(&minus_A2, &minus_A, &tmp); + ge_cache(&lutA[0], &minus_A); + FOR (i, 1, P_W_SIZE) { + ge_add(&tmp, &minus_A2, &lutA[i-1]); + ge_cache(&lutA[i], &tmp); + } + } + + // sum = [s]B - [h]A + // Merged double and add ladder, fused with sliding + slide_ctx h_slide; slide_init(&h_slide, h); + slide_ctx s_slide; slide_init(&s_slide, s); + int i = MAX(h_slide.next_check, s_slide.next_check); + ge *sum = &minus_A; // reuse minus_A for the sum + ge_zero(sum); + while (i >= 0) { + ge tmp; + ge_double(sum, sum, &tmp); + int h_digit = slide_step(&h_slide, P_W_WIDTH, i, h); + int s_digit = slide_step(&s_slide, B_W_WIDTH, i, s); + if (h_digit > 0) { ge_add(sum, sum, &lutA[ h_digit / 2]); } + if (h_digit < 0) { ge_sub(sum, sum, &lutA[-h_digit / 2]); } + fe t1, t2; + if (s_digit > 0) { ge_madd(sum, sum, b_window + s_digit/2, t1, t2); } + if (s_digit < 0) { ge_msub(sum, sum, b_window + -s_digit/2, t1, t2); } + i--; + } + + // Compare [8](sum-R) and the zero point + // The multiplication by 8 eliminates any low-order component + // and ensures consistency with batched verification. + ge_cached cached; + u8 check[32]; + static const u8 zero_point[32] = {1}; // Point of order 1 + ge_cache(&cached, &minus_R); + ge_add(sum, sum, &cached); + ge_double(sum, sum, &minus_R); // reuse minus_R as temporary + ge_double(sum, sum, &minus_R); // reuse minus_R as temporary + ge_double(sum, sum, &minus_R); // reuse minus_R as temporary + ge_tobytes(check, sum); + return crypto_verify32(check, zero_point); +} + +// 5-bit signed comb in cached format (Niels coordinates, Z=1) +static const ge_precomp b_comb_low[8] = { + {{-6816601,-2324159,-22559413,124364,18015490, + 8373481,19993724,1979872,-18549925,9085059,}, + {10306321,403248,14839893,9633706,8463310, + -8354981,-14305673,14668847,26301366,2818560,}, + {-22701500,-3210264,-13831292,-2927732,-16326337, + -14016360,12940910,177905,12165515,-2397893,},}, + {{-12282262,-7022066,9920413,-3064358,-32147467, + 2927790,22392436,-14852487,2719975,16402117,}, + {-7236961,-4729776,2685954,-6525055,-24242706, + -15940211,-6238521,14082855,10047669,12228189,}, + {-30495588,-12893761,-11161261,3539405,-11502464, + 16491580,-27286798,-15030530,-7272871,-15934455,},}, + {{17650926,582297,-860412,-187745,-12072900, + -10683391,-20352381,15557840,-31072141,-5019061,}, + {-6283632,-2259834,-4674247,-4598977,-4089240, + 12435688,-31278303,1060251,6256175,10480726,}, + {-13871026,2026300,-21928428,-2741605,-2406664, + -8034988,7355518,15733500,-23379862,7489131,},}, + {{6883359,695140,23196907,9644202,-33430614, + 11354760,-20134606,6388313,-8263585,-8491918,}, + {-7716174,-13605463,-13646110,14757414,-19430591, + -14967316,10359532,-11059670,-21935259,12082603,}, + {-11253345,-15943946,10046784,5414629,24840771, + 8086951,-6694742,9868723,15842692,-16224787,},}, + {{9639399,11810955,-24007778,-9320054,3912937, + -9856959,996125,-8727907,-8919186,-14097242,}, + {7248867,14468564,25228636,-8795035,14346339, + 8224790,6388427,-7181107,6468218,-8720783,}, + {15513115,15439095,7342322,-10157390,18005294, + -7265713,2186239,4884640,10826567,7135781,},}, + {{-14204238,5297536,-5862318,-6004934,28095835, + 4236101,-14203318,1958636,-16816875,3837147,}, + {-5511166,-13176782,-29588215,12339465,15325758, + -15945770,-8813185,11075932,-19608050,-3776283,}, + {11728032,9603156,-4637821,-5304487,-7827751, + 2724948,31236191,-16760175,-7268616,14799772,},}, + {{-28842672,4840636,-12047946,-9101456,-1445464, + 381905,-30977094,-16523389,1290540,12798615,}, + {27246947,-10320914,14792098,-14518944,5302070, + -8746152,-3403974,-4149637,-27061213,10749585,}, + {25572375,-6270368,-15353037,16037944,1146292, + 32198,23487090,9585613,24714571,-1418265,},}, + {{19844825,282124,-17583147,11004019,-32004269, + -2716035,6105106,-1711007,-21010044,14338445,}, + {8027505,8191102,-18504907,-12335737,25173494, + -5923905,15446145,7483684,-30440441,10009108,}, + {-14134701,-4174411,10246585,-14677495,33553567, + -14012935,23366126,15080531,-7969992,7663473,},}, +}; + +static const ge_precomp b_comb_high[8] = { + {{33055887,-4431773,-521787,6654165,951411, + -6266464,-5158124,6995613,-5397442,-6985227,}, + {4014062,6967095,-11977872,3960002,8001989, + 5130302,-2154812,-1899602,-31954493,-16173976,}, + {16271757,-9212948,23792794,731486,-25808309, + -3546396,6964344,-4767590,10976593,10050757,},}, + {{2533007,-4288439,-24467768,-12387405,-13450051, + 14542280,12876301,13893535,15067764,8594792,}, + {20073501,-11623621,3165391,-13119866,13188608, + -11540496,-10751437,-13482671,29588810,2197295,}, + {-1084082,11831693,6031797,14062724,14748428, + -8159962,-20721760,11742548,31368706,13161200,},}, + {{2050412,-6457589,15321215,5273360,25484180, + 124590,-18187548,-7097255,-6691621,-14604792,}, + {9938196,2162889,-6158074,-1711248,4278932, + -2598531,-22865792,-7168500,-24323168,11746309,}, + {-22691768,-14268164,5965485,9383325,20443693, + 5854192,28250679,-1381811,-10837134,13717818,},}, + {{-8495530,16382250,9548884,-4971523,-4491811, + -3902147,6182256,-12832479,26628081,10395408,}, + {27329048,-15853735,7715764,8717446,-9215518, + -14633480,28982250,-5668414,4227628,242148,}, + {-13279943,-7986904,-7100016,8764468,-27276630, + 3096719,29678419,-9141299,3906709,11265498,},}, + {{11918285,15686328,-17757323,-11217300,-27548967, + 4853165,-27168827,6807359,6871949,-1075745,}, + {-29002610,13984323,-27111812,-2713442,28107359, + -13266203,6155126,15104658,3538727,-7513788,}, + {14103158,11233913,-33165269,9279850,31014152, + 4335090,-1827936,4590951,13960841,12787712,},}, + {{1469134,-16738009,33411928,13942824,8092558, + -8778224,-11165065,1437842,22521552,-2792954,}, + {31352705,-4807352,-25327300,3962447,12541566, + -9399651,-27425693,7964818,-23829869,5541287,}, + {-25732021,-6864887,23848984,3039395,-9147354, + 6022816,-27421653,10590137,25309915,-1584678,},}, + {{-22951376,5048948,31139401,-190316,-19542447, + -626310,-17486305,-16511925,-18851313,-12985140,}, + {-9684890,14681754,30487568,7717771,-10829709, + 9630497,30290549,-10531496,-27798994,-13812825,}, + {5827835,16097107,-24501327,12094619,7413972, + 11447087,28057551,-1793987,-14056981,4359312,},}, + {{26323183,2342588,-21887793,-1623758,-6062284, + 2107090,-28724907,9036464,-19618351,-13055189,}, + {-29697200,14829398,-4596333,14220089,-30022969, + 2955645,12094100,-13693652,-5941445,7047569,}, + {-3201977,14413268,-12058324,-16417589,-9035655, + -7224648,9258160,1399236,30397584,-5684634,},}, +}; + +static void lookup_add(ge *p, ge_precomp *tmp_c, fe tmp_a, fe tmp_b, + const ge_precomp comb[8], const u8 scalar[32], int i) +{ + u8 teeth = (u8)((scalar_bit(scalar, i) ) + + (scalar_bit(scalar, i + 32) << 1) + + (scalar_bit(scalar, i + 64) << 2) + + (scalar_bit(scalar, i + 96) << 3)); + u8 high = teeth >> 3; + u8 index = (teeth ^ (high - 1)) & 7; + FOR (j, 0, 8) { + i32 select = 1 & (((j ^ index) - 1) >> 8); + fe_ccopy(tmp_c->Yp, comb[j].Yp, select); + fe_ccopy(tmp_c->Ym, comb[j].Ym, select); + fe_ccopy(tmp_c->T2, comb[j].T2, select); + } + fe_neg(tmp_a, tmp_c->T2); + fe_cswap(tmp_c->T2, tmp_a , high ^ 1); + fe_cswap(tmp_c->Yp, tmp_c->Ym, high ^ 1); + ge_madd(p, p, tmp_c, tmp_a, tmp_b); +} + +// p = [scalar]B, where B is the base point +static void ge_scalarmult_base(ge *p, const u8 scalar[32]) +{ + // twin 4-bits signed combs, from Mike Hamburg's + // Fast and compact elliptic-curve cryptography (2012) + // 1 / 2 modulo L + static const u8 half_mod_L[32] = { + 247,233,122,46,141,49,9,44,107,206,123,81,239,124,111,10, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8, + }; + // (2^256 - 1) / 2 modulo L + static const u8 half_ones[32] = { + 142,74,204,70,186,24,118,107,184,231,190,57,250,173,119,99, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,7, + }; + + // All bits set form: 1 means 1, 0 means -1 + u8 s_scalar[32]; + crypto_eddsa_mul_add(s_scalar, scalar, half_mod_L, half_ones); + + // Double and add ladder + fe tmp_a, tmp_b; // temporaries for addition + ge_precomp tmp_c; // temporary for comb lookup + ge tmp_d; // temporary for doubling + fe_1(tmp_c.Yp); + fe_1(tmp_c.Ym); + fe_0(tmp_c.T2); + + // Save a double on the first iteration + ge_zero(p); + lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_low , s_scalar, 31); + lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_high, s_scalar, 31+128); + // Regular double & add for the rest + for (int i = 30; i >= 0; i--) { + ge_double(p, p, &tmp_d); + lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_low , s_scalar, i); + lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_high, s_scalar, i+128); + } + // Note: we could save one addition at the end if we assumed the + // scalar fit in 252 bits. Which it does in practice if it is + // selected at random. However, non-random, non-hashed scalars + // *can* overflow 252 bits in practice. Better account for that + // than leaving that kind of subtle corner case. + + WIPE_BUFFER(tmp_a); WIPE_CTX(&tmp_d); + WIPE_BUFFER(tmp_b); WIPE_CTX(&tmp_c); + WIPE_BUFFER(s_scalar); +} + +void crypto_eddsa_scalarbase(u8 point[32], const u8 scalar[32]) +{ + ge P; + ge_scalarmult_base(&P, scalar); + ge_tobytes(point, &P); + WIPE_CTX(&P); +} + +void crypto_eddsa_key_pair(u8 secret_key[64], u8 public_key[32], u8 seed[32]) +{ + // To allow overlaps, observable writes happen in this order: + // 1. seed + // 2. secret_key + // 3. public_key + u8 a[64]; + COPY(a, seed, 32); + crypto_wipe(seed, 32); + COPY(secret_key, a, 32); + crypto_blake2b(a, 64, a, 32); + crypto_eddsa_trim_scalar(a, a); + crypto_eddsa_scalarbase(secret_key + 32, a); + COPY(public_key, secret_key + 32, 32); + WIPE_BUFFER(a); +} + +static void hash_reduce(u8 h[32], + const u8 *a, size_t a_size, + const u8 *b, size_t b_size, + const u8 *c, size_t c_size) +{ + u8 hash[64]; + crypto_blake2b_ctx ctx; + crypto_blake2b_init (&ctx, 64); + crypto_blake2b_update(&ctx, a, a_size); + crypto_blake2b_update(&ctx, b, b_size); + crypto_blake2b_update(&ctx, c, c_size); + crypto_blake2b_final (&ctx, hash); + crypto_eddsa_reduce(h, hash); +} + +// Digital signature of a message with from a secret key. +// +// The secret key comprises two parts: +// - The seed that generates the key (secret_key[ 0..31]) +// - The public key (secret_key[32..63]) +// +// The seed and the public key are bundled together to make sure users +// don't use mismatched seeds and public keys, which would instantly +// leak the secret scalar and allow forgeries (allowing this to happen +// has resulted in critical vulnerabilities in the wild). +// +// The seed is hashed to derive the secret scalar and a secret prefix. +// The sole purpose of the prefix is to generate a secret random nonce. +// The properties of that nonce must be as follows: +// - Unique: we need a different one for each message. +// - Secret: third parties must not be able to predict it. +// - Random: any detectable bias would break all security. +// +// There are two ways to achieve these properties. The obvious one is +// to simply generate a random number. Here that would be a parameter +// (Monocypher doesn't have an RNG). It works, but then users may reuse +// the nonce by accident, which _also_ leaks the secret scalar and +// allows forgeries. This has happened in the wild too. +// +// This is no good, so instead we generate that nonce deterministically +// by reducing modulo L a hash of the secret prefix and the message. +// The secret prefix makes the nonce unpredictable, the message makes it +// unique, and the hash/reduce removes all bias. +// +// The cost of that safety is hashing the message twice. If that cost +// is unacceptable, there are two alternatives: +// +// - Signing a hash of the message instead of the message itself. This +// is fine as long as the hash is collision resistant. It is not +// compatible with existing "pure" signatures, but at least it's safe. +// +// - Using a random nonce. Please exercise **EXTREME CAUTION** if you +// ever do that. It is absolutely **critical** that the nonce is +// really an unbiased random number between 0 and L-1, never reused, +// and wiped immediately. +// +// To lower the likelihood of complete catastrophe if the RNG is +// either flawed or misused, you can hash the RNG output together with +// the secret prefix and the beginning of the message, and use the +// reduction of that hash instead of the RNG output itself. It's not +// foolproof (you'd need to hash the whole message) but it helps. +// +// Signing a message involves the following operations: +// +// scalar, prefix = HASH(secret_key) +// r = HASH(prefix || message) % L +// R = [r]B +// h = HASH(R || public_key || message) % L +// S = ((h * a) + r) % L +// signature = R || S +void crypto_eddsa_sign(u8 signature [64], const u8 secret_key[64], + const u8 *message, size_t message_size) +{ + u8 a[64]; // secret scalar and prefix + u8 r[32]; // secret deterministic "random" nonce + u8 h[32]; // publically verifiable hash of the message (not wiped) + u8 R[32]; // first half of the signature (allows overlapping inputs) + + crypto_blake2b(a, 64, secret_key, 32); + crypto_eddsa_trim_scalar(a, a); + hash_reduce(r, a + 32, 32, message, message_size, 0, 0); + crypto_eddsa_scalarbase(R, r); + hash_reduce(h, R, 32, secret_key + 32, 32, message, message_size); + COPY(signature, R, 32); + crypto_eddsa_mul_add(signature + 32, h, a, r); + + WIPE_BUFFER(a); + WIPE_BUFFER(r); +} + +// To check the signature R, S of the message M with the public key A, +// there are 3 steps: +// +// compute h = HASH(R || A || message) % L +// check that A is on the curve. +// check that R == [s]B - [h]A +// +// The last two steps are done in crypto_eddsa_check_equation() +int crypto_eddsa_check(const u8 signature[64], const u8 public_key[32], + const u8 *message, size_t message_size) +{ + u8 h[32]; + hash_reduce(h, signature, 32, public_key, 32, message, message_size); + return crypto_eddsa_check_equation(signature, public_key, h); +} + +///////////////////////// +/// EdDSA <--> X25519 /// +///////////////////////// +void crypto_eddsa_to_x25519(u8 x25519[32], const u8 eddsa[32]) +{ + // (u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x) + // Only converting y to u, the sign of x is ignored. + fe t1, t2; + fe_frombytes(t2, eddsa); + fe_add(t1, fe_one, t2); + fe_sub(t2, fe_one, t2); + fe_invert(t2, t2); + fe_mul(t1, t1, t2); + fe_tobytes(x25519, t1); + WIPE_BUFFER(t1); + WIPE_BUFFER(t2); +} + +void crypto_x25519_to_eddsa(u8 eddsa[32], const u8 x25519[32]) +{ + // (x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1)) + // Only converting u to y, x is assumed positive. + fe t1, t2; + fe_frombytes(t2, x25519); + fe_sub(t1, t2, fe_one); + fe_add(t2, t2, fe_one); + fe_invert(t2, t2); + fe_mul(t1, t1, t2); + fe_tobytes(eddsa, t1); + WIPE_BUFFER(t1); + WIPE_BUFFER(t2); +} + +///////////////////////////////////////////// +/// Dirty ephemeral public key generation /// +///////////////////////////////////////////// + +// Those functions generates a public key, *without* clearing the +// cofactor. Sending that key over the network leaks 3 bits of the +// private key. Use only to generate ephemeral keys that will be hidden +// with crypto_curve_to_hidden(). +// +// The public key is otherwise compatible with crypto_x25519(), which +// properly clears the cofactor. +// +// Note that the distribution of the resulting public keys is almost +// uniform. Flipping the sign of the v coordinate (not provided by this +// function), covers the entire key space almost perfectly, where +// "almost" means a 2^-128 bias (undetectable). This uniformity is +// needed to ensure the proper randomness of the resulting +// representatives (once we apply crypto_curve_to_hidden()). +// +// Recall that Curve25519 has order C = 2^255 + e, with e < 2^128 (not +// to be confused with the prime order of the main subgroup, L, which is +// 8 times less than that). +// +// Generating all points would require us to multiply a point of order C +// (the base point plus any point of order 8) by all scalars from 0 to +// C-1. Clamping limits us to scalars between 2^254 and 2^255 - 1. But +// by negating the resulting point at random, we also cover scalars from +// -2^255 + 1 to -2^254 (which modulo C is congruent to e+1 to 2^254 + e). +// +// In practice: +// - Scalars from 0 to e + 1 are never generated +// - Scalars from 2^255 to 2^255 + e are never generated +// - Scalars from 2^254 + 1 to 2^254 + e are generated twice +// +// Since e < 2^128, detecting this bias requires observing over 2^100 +// representatives from a given source (this will never happen), *and* +// recovering enough of the private key to determine that they do, or do +// not, belong to the biased set (this practically requires solving +// discrete logarithm, which is conjecturally intractable). +// +// In practice, this means the bias is impossible to detect. + +// s + (x*L) % 8*L +// Guaranteed to fit in 256 bits iff s fits in 255 bits. +// L < 2^253 +// x%8 < 2^3 +// L * (x%8) < 2^255 +// s < 2^255 +// s + L * (x%8) < 2^256 +static void add_xl(u8 s[32], u8 x) +{ + u64 mod8 = x & 7; + u64 carry = 0; + FOR (i , 0, 8) { + carry = carry + load32_le(s + 4*i) + L[i] * mod8; + store32_le(s + 4*i, (u32)carry); + carry >>= 32; + } +} + +// "Small" dirty ephemeral key. +// Use if you need to shrink the size of the binary, and can afford to +// slow down by a factor of two (compared to the fast version) +// +// This version works by decoupling the cofactor from the main factor. +// +// - The trimmed scalar determines the main factor +// - The clamped bits of the scalar determine the cofactor. +// +// Cofactor and main factor are combined into a single scalar, which is +// then multiplied by a point of order 8*L (unlike the base point, which +// has prime order). That "dirty" base point is the addition of the +// regular base point (9), and a point of order 8. +void crypto_x25519_dirty_small(u8 public_key[32], const u8 secret_key[32]) +{ + // Base point of order 8*L + // Raw scalar multiplication with it does not clear the cofactor, + // and the resulting public key will reveal 3 bits of the scalar. + // + // The low order component of this base point has been chosen + // to yield the same results as crypto_x25519_dirty_fast(). + static const u8 dirty_base_point[32] = { + 0xd8, 0x86, 0x1a, 0xa2, 0x78, 0x7a, 0xd9, 0x26, + 0x8b, 0x74, 0x74, 0xb6, 0x82, 0xe3, 0xbe, 0xc3, + 0xce, 0x36, 0x9a, 0x1e, 0x5e, 0x31, 0x47, 0xa2, + 0x6d, 0x37, 0x7c, 0xfd, 0x20, 0xb5, 0xdf, 0x75, + }; + // separate the main factor & the cofactor of the scalar + u8 scalar[32]; + crypto_eddsa_trim_scalar(scalar, secret_key); + + // Separate the main factor and the cofactor + // + // The scalar is trimmed, so its cofactor is cleared. The three + // least significant bits however still have a main factor. We must + // remove it for X25519 compatibility. + // + // cofactor = lsb * L (modulo 8*L) + // combined = scalar + cofactor (modulo 8*L) + add_xl(scalar, secret_key[0]); + scalarmult(public_key, scalar, dirty_base_point, 256); + WIPE_BUFFER(scalar); +} + +// Select low order point +// We're computing the [cofactor]lop scalar multiplication, where: +// +// cofactor = tweak & 7. +// lop = (lop_x, lop_y) +// lop_x = sqrt((sqrt(d + 1) + 1) / d) +// lop_y = -lop_x * sqrtm1 +// +// The low order point has order 8. There are 4 such points. We've +// chosen the one whose both coordinates are positive (below p/2). +// The 8 low order points are as follows: +// +// [0]lop = ( 0 , 1 ) +// [1]lop = ( lop_x , lop_y) +// [2]lop = ( sqrt(-1), -0 ) +// [3]lop = ( lop_x , -lop_y) +// [4]lop = (-0 , -1 ) +// [5]lop = (-lop_x , -lop_y) +// [6]lop = (-sqrt(-1), 0 ) +// [7]lop = (-lop_x , lop_y) +// +// The x coordinate is either 0, sqrt(-1), lop_x, or their opposite. +// The y coordinate is either 0, -1 , lop_y, or their opposite. +// The pattern for both is the same, except for a rotation of 2 (modulo 8) +// +// This helper function captures the pattern, and we can use it thus: +// +// select_lop(x, lop_x, sqrtm1, cofactor); +// select_lop(y, lop_y, fe_one, cofactor + 2); +// +// This is faster than an actual scalar multiplication, +// and requires less code than naive constant time look up. +static void select_lop(fe out, const fe x, const fe k, u8 cofactor) +{ + fe tmp; + fe_0(out); + fe_ccopy(out, k , (cofactor >> 1) & 1); // bit 1 + fe_ccopy(out, x , (cofactor >> 0) & 1); // bit 0 + fe_neg (tmp, out); + fe_ccopy(out, tmp, (cofactor >> 2) & 1); // bit 2 + WIPE_BUFFER(tmp); +} + +// "Fast" dirty ephemeral key +// We use this one by default. +// +// This version works by performing a regular scalar multiplication, +// then add a low order point. The scalar multiplication is done in +// Edwards space for more speed (*2 compared to the "small" version). +// The cost is a bigger binary for programs that don't also sign messages. +void crypto_x25519_dirty_fast(u8 public_key[32], const u8 secret_key[32]) +{ + // Compute clean scalar multiplication + u8 scalar[32]; + ge pk; + crypto_eddsa_trim_scalar(scalar, secret_key); + ge_scalarmult_base(&pk, scalar); + + // Compute low order point + fe t1, t2; + select_lop(t1, lop_x, sqrtm1, secret_key[0]); + select_lop(t2, lop_y, fe_one, secret_key[0] + 2); + ge_precomp low_order_point; + fe_add(low_order_point.Yp, t2, t1); + fe_sub(low_order_point.Ym, t2, t1); + fe_mul(low_order_point.T2, t2, t1); + fe_mul(low_order_point.T2, low_order_point.T2, D2); + + // Add low order point to the public key + ge_madd(&pk, &pk, &low_order_point, t1, t2); + + // Convert to Montgomery u coordinate (we ignore the sign) + fe_add(t1, pk.Z, pk.Y); + fe_sub(t2, pk.Z, pk.Y); + fe_invert(t2, t2); + fe_mul(t1, t1, t2); + + fe_tobytes(public_key, t1); + + WIPE_BUFFER(t1); WIPE_CTX(&pk); + WIPE_BUFFER(t2); WIPE_CTX(&low_order_point); + WIPE_BUFFER(scalar); +} + +/////////////////// +/// Elligator 2 /// +/////////////////// +static const fe A = {486662}; + +// Elligator direct map +// +// Computes the point corresponding to a representative, encoded in 32 +// bytes (little Endian). Since positive representatives fits in 254 +// bits, The two most significant bits are ignored. +// +// From the paper: +// w = -A / (fe(1) + non_square * r^2) +// e = chi(w^3 + A*w^2 + w) +// u = e*w - (fe(1)-e)*(A//2) +// v = -e * sqrt(u^3 + A*u^2 + u) +// +// We ignore v because we don't need it for X25519 (the Montgomery +// ladder only uses u). +// +// Note that e is either 0, 1 or -1 +// if e = 0 u = 0 and v = 0 +// if e = 1 u = w +// if e = -1 u = -w - A = w * non_square * r^2 +// +// Let r1 = non_square * r^2 +// Let r2 = 1 + r1 +// Note that r2 cannot be zero, -1/non_square is not a square. +// We can (tediously) verify that: +// w^3 + A*w^2 + w = (A^2*r1 - r2^2) * A / r2^3 +// Therefore: +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3)) +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3)) * 1 +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3)) * chi(r2^6) +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3) * r2^6) +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * A * r2^3) +// Corollary: +// e = 1 if (A^2*r1 - r2^2) * A * r2^3) is a non-zero square +// e = -1 if (A^2*r1 - r2^2) * A * r2^3) is not a square +// Note that w^3 + A*w^2 + w (and therefore e) can never be zero: +// w^3 + A*w^2 + w = w * (w^2 + A*w + 1) +// w^3 + A*w^2 + w = w * (w^2 + A*w + A^2/4 - A^2/4 + 1) +// w^3 + A*w^2 + w = w * (w + A/2)^2 - A^2/4 + 1) +// which is zero only if: +// w = 0 (impossible) +// (w + A/2)^2 = A^2/4 - 1 (impossible, because A^2/4-1 is not a square) +// +// Let isr = invsqrt((A^2*r1 - r2^2) * A * r2^3) +// isr = sqrt(1 / ((A^2*r1 - r2^2) * A * r2^3)) if e = 1 +// isr = sqrt(sqrt(-1) / ((A^2*r1 - r2^2) * A * r2^3)) if e = -1 +// +// if e = 1 +// let u1 = -A * (A^2*r1 - r2^2) * A * r2^2 * isr^2 +// u1 = w +// u1 = u +// +// if e = -1 +// let ufactor = -non_square * sqrt(-1) * r^2 +// let vfactor = sqrt(ufactor) +// let u2 = -A * (A^2*r1 - r2^2) * A * r2^2 * isr^2 * ufactor +// u2 = w * -1 * -non_square * r^2 +// u2 = w * non_square * r^2 +// u2 = u +void crypto_elligator_map(u8 curve[32], const u8 hidden[32]) +{ + fe r, u, t1, t2, t3; + fe_frombytes_mask(r, hidden, 2); // r is encoded in 254 bits. + fe_sq(r, r); + fe_add(t1, r, r); + fe_add(u, t1, fe_one); + fe_sq (t2, u); + fe_mul(t3, A2, t1); + fe_sub(t3, t3, t2); + fe_mul(t3, t3, A); + fe_mul(t1, t2, u); + fe_mul(t1, t3, t1); + int is_square = invsqrt(t1, t1); + fe_mul(u, r, ufactor); + fe_ccopy(u, fe_one, is_square); + fe_sq (t1, t1); + fe_mul(u, u, A); + fe_mul(u, u, t3); + fe_mul(u, u, t2); + fe_mul(u, u, t1); + fe_neg(u, u); + fe_tobytes(curve, u); + + WIPE_BUFFER(t1); WIPE_BUFFER(r); + WIPE_BUFFER(t2); WIPE_BUFFER(u); + WIPE_BUFFER(t3); +} + +// Elligator inverse map +// +// Computes the representative of a point, if possible. If not, it does +// nothing and returns -1. Note that the success of the operation +// depends only on the point (more precisely its u coordinate). The +// tweak parameter is used only upon success +// +// The tweak should be a random byte. Beyond that, its contents are an +// implementation detail. Currently, the tweak comprises: +// - Bit 1 : sign of the v coordinate (0 if positive, 1 if negative) +// - Bit 2-5: not used +// - Bits 6-7: random padding +// +// From the paper: +// Let sq = -non_square * u * (u+A) +// if sq is not a square, or u = -A, there is no mapping +// Assuming there is a mapping: +// if v is positive: r = sqrt(-u / (non_square * (u+A))) +// if v is negative: r = sqrt(-(u+A) / (non_square * u )) +// +// We compute isr = invsqrt(-non_square * u * (u+A)) +// if it wasn't a square, abort. +// else, isr = sqrt(-1 / (non_square * u * (u+A)) +// +// If v is positive, we return isr * u: +// isr * u = sqrt(-1 / (non_square * u * (u+A)) * u +// isr * u = sqrt(-u / (non_square * (u+A)) +// +// If v is negative, we return isr * (u+A): +// isr * (u+A) = sqrt(-1 / (non_square * u * (u+A)) * (u+A) +// isr * (u+A) = sqrt(-(u+A) / (non_square * u) +int crypto_elligator_rev(u8 hidden[32], const u8 public_key[32], u8 tweak) +{ + fe t1, t2, t3; + fe_frombytes(t1, public_key); // t1 = u + + fe_add(t2, t1, A); // t2 = u + A + fe_mul(t3, t1, t2); + fe_mul_small(t3, t3, -2); + int is_square = invsqrt(t3, t3); // t3 = sqrt(-1 / non_square * u * (u+A)) + if (is_square) { + // The only variable time bit. This ultimately reveals how many + // tries it took us to find a representable key. + // This does not affect security as long as we try keys at random. + + fe_ccopy (t1, t2, tweak & 1); // multiply by u if v is positive, + fe_mul (t3, t1, t3); // multiply by u+A otherwise + fe_mul_small(t1, t3, 2); + fe_neg (t2, t3); + fe_ccopy (t3, t2, fe_isodd(t1)); + fe_tobytes(hidden, t3); + + // Pad with two random bits + hidden[31] |= tweak & 0xc0; + } + + WIPE_BUFFER(t1); + WIPE_BUFFER(t2); + WIPE_BUFFER(t3); + return is_square - 1; +} + +void crypto_elligator_key_pair(u8 hidden[32], u8 secret_key[32], u8 seed[32]) +{ + u8 pk [32]; // public key + u8 buf[64]; // seed + representative + COPY(buf + 32, seed, 32); + do { + crypto_chacha20_djb(buf, 0, 64, buf+32, zero, 0); + crypto_x25519_dirty_fast(pk, buf); // or the "small" version + } while(crypto_elligator_rev(buf+32, pk, buf[32])); + // Note that the return value of crypto_elligator_rev() is + // independent from its tweak parameter. + // Therefore, buf[32] is not actually reused. Either we loop one + // more time and buf[32] is used for the new seed, or we succeeded, + // and buf[32] becomes the tweak parameter. + + crypto_wipe(seed, 32); + COPY(hidden , buf + 32, 32); + COPY(secret_key, buf , 32); + WIPE_BUFFER(buf); + WIPE_BUFFER(pk); +} + +/////////////////////// +/// Scalar division /// +/////////////////////// + +// Montgomery reduction. +// Divides x by (2^256), and reduces the result modulo L +// +// Precondition: +// x < L * 2^256 +// Constants: +// r = 2^256 (makes division by r trivial) +// k = (r * (1/r) - 1) // L (1/r is computed modulo L ) +// Algorithm: +// s = (x * k) % r +// t = x + s*L (t is always a multiple of r) +// u = (t/r) % L (u is always below 2*L, conditional subtraction is enough) +static void redc(u32 u[8], u32 x[16]) +{ + static const u32 k[8] = { + 0x12547e1b, 0xd2b51da3, 0xfdba84ff, 0xb1a206f2, + 0xffa36bea, 0x14e75438, 0x6fe91836, 0x9db6c6f2, + }; + + // s = x * k (modulo 2^256) + // This is cheaper than the full multiplication. + u32 s[8] = {0}; + FOR (i, 0, 8) { + u64 carry = 0; + FOR (j, 0, 8-i) { + carry += s[i+j] + (u64)x[i] * k[j]; + s[i+j] = (u32)carry; + carry >>= 32; + } + } + u32 t[16] = {0}; + multiply(t, s, L); + + // t = t + x + u64 carry = 0; + FOR (i, 0, 16) { + carry += (u64)t[i] + x[i]; + t[i] = (u32)carry; + carry >>= 32; + } + + // u = (t / 2^256) % L + // Note that t / 2^256 is always below 2*L, + // So a constant time conditional subtraction is enough + remove_l(u, t+8); + + WIPE_BUFFER(s); + WIPE_BUFFER(t); +} + +void crypto_x25519_inverse(u8 blind_salt [32], const u8 private_key[32], + const u8 curve_point[32]) +{ + static const u8 Lm2[32] = { // L - 2 + 0xeb, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, + 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + }; + // 1 in Montgomery form + u32 m_inv [8] = { + 0x8d98951d, 0xd6ec3174, 0x737dcf70, 0xc6ef5bf4, + 0xfffffffe, 0xffffffff, 0xffffffff, 0x0fffffff, + }; + + u8 scalar[32]; + crypto_eddsa_trim_scalar(scalar, private_key); + + // Convert the scalar in Montgomery form + // m_scl = scalar * 2^256 (modulo L) + u32 m_scl[8]; + { + u32 tmp[16]; + ZERO(tmp, 8); + load32_le_buf(tmp+8, scalar, 8); + mod_l(scalar, tmp); + load32_le_buf(m_scl, scalar, 8); + WIPE_BUFFER(tmp); // Wipe ASAP to save stack space + } + + // Compute the inverse + u32 product[16]; + for (int i = 252; i >= 0; i--) { + ZERO(product, 16); + multiply(product, m_inv, m_inv); + redc(m_inv, product); + if (scalar_bit(Lm2, i)) { + ZERO(product, 16); + multiply(product, m_inv, m_scl); + redc(m_inv, product); + } + } + // Convert the inverse *out* of Montgomery form + // scalar = m_inv / 2^256 (modulo L) + COPY(product, m_inv, 8); + ZERO(product + 8, 8); + redc(m_inv, product); + store32_le_buf(scalar, m_inv, 8); // the *inverse* of the scalar + + // Clear the cofactor of scalar: + // cleared = scalar * (3*L + 1) (modulo 8*L) + // cleared = scalar + scalar * 3 * L (modulo 8*L) + // Note that (scalar * 3) is reduced modulo 8, so we only need the + // first byte. + add_xl(scalar, scalar[0] * 3); + + // Recall that 8*L < 2^256. However it is also very close to + // 2^255. If we spanned the ladder over 255 bits, random tests + // wouldn't catch the off-by-one error. + scalarmult(blind_salt, scalar, curve_point, 256); + + WIPE_BUFFER(scalar); WIPE_BUFFER(m_scl); + WIPE_BUFFER(product); WIPE_BUFFER(m_inv); +} + +//////////////////////////////// +/// Authenticated encryption /// +//////////////////////////////// +static void lock_auth(u8 mac[16], const u8 auth_key[32], + const u8 *ad , size_t ad_size, + const u8 *cipher_text, size_t text_size) +{ + u8 sizes[16]; // Not secret, not wiped + store64_le(sizes + 0, ad_size); + store64_le(sizes + 8, text_size); + crypto_poly1305_ctx poly_ctx; // auto wiped... + crypto_poly1305_init (&poly_ctx, auth_key); + crypto_poly1305_update(&poly_ctx, ad , ad_size); + crypto_poly1305_update(&poly_ctx, zero , gap(ad_size, 16)); + crypto_poly1305_update(&poly_ctx, cipher_text, text_size); + crypto_poly1305_update(&poly_ctx, zero , gap(text_size, 16)); + crypto_poly1305_update(&poly_ctx, sizes , 16); + crypto_poly1305_final (&poly_ctx, mac); // ...here +} + +void crypto_aead_init_x(crypto_aead_ctx *ctx, + u8 const key[32], const u8 nonce[24]) +{ + crypto_chacha20_h(ctx->key, key, nonce); + COPY(ctx->nonce, nonce + 16, 8); + ctx->counter = 0; +} + +void crypto_aead_init_djb(crypto_aead_ctx *ctx, + const u8 key[32], const u8 nonce[8]) +{ + COPY(ctx->key , key , 32); + COPY(ctx->nonce, nonce, 8); + ctx->counter = 0; +} + +void crypto_aead_init_ietf(crypto_aead_ctx *ctx, + const u8 key[32], const u8 nonce[12]) +{ + COPY(ctx->key , key , 32); + COPY(ctx->nonce, nonce + 4, 8); + ctx->counter = (u64)load32_le(nonce) << 32; +} + +void crypto_aead_write(crypto_aead_ctx *ctx, u8 *cipher_text, u8 mac[16], + const u8 *ad, size_t ad_size, + const u8 *plain_text, size_t text_size) +{ + u8 auth_key[64]; // the last 32 bytes are used for rekeying. + crypto_chacha20_djb(auth_key, 0, 64, ctx->key, ctx->nonce, ctx->counter); + crypto_chacha20_djb(cipher_text, plain_text, text_size, + ctx->key, ctx->nonce, ctx->counter + 1); + lock_auth(mac, auth_key, ad, ad_size, cipher_text, text_size); + COPY(ctx->key, auth_key + 32, 32); + WIPE_BUFFER(auth_key); +} + +int crypto_aead_read(crypto_aead_ctx *ctx, u8 *plain_text, const u8 mac[16], + const u8 *ad, size_t ad_size, + const u8 *cipher_text, size_t text_size) +{ + u8 auth_key[64]; // the last 32 bytes are used for rekeying. + u8 real_mac[16]; + crypto_chacha20_djb(auth_key, 0, 64, ctx->key, ctx->nonce, ctx->counter); + lock_auth(real_mac, auth_key, ad, ad_size, cipher_text, text_size); + int mismatch = crypto_verify16(mac, real_mac); + if (!mismatch) { + crypto_chacha20_djb(plain_text, cipher_text, text_size, + ctx->key, ctx->nonce, ctx->counter + 1); + COPY(ctx->key, auth_key + 32, 32); + } + WIPE_BUFFER(auth_key); + WIPE_BUFFER(real_mac); + return mismatch; +} + +void crypto_aead_lock(u8 *cipher_text, u8 mac[16], const u8 key[32], + const u8 nonce[24], const u8 *ad, size_t ad_size, + const u8 *plain_text, size_t text_size) +{ + crypto_aead_ctx ctx; + crypto_aead_init_x(&ctx, key, nonce); + crypto_aead_write(&ctx, cipher_text, mac, ad, ad_size, + plain_text, text_size); + crypto_wipe(&ctx, sizeof(ctx)); +} + +int crypto_aead_unlock(u8 *plain_text, const u8 mac[16], const u8 key[32], + const u8 nonce[24], const u8 *ad, size_t ad_size, + const u8 *cipher_text, size_t text_size) +{ + crypto_aead_ctx ctx; + crypto_aead_init_x(&ctx, key, nonce); + int mismatch = crypto_aead_read(&ctx, plain_text, mac, ad, ad_size, + cipher_text, text_size); + crypto_wipe(&ctx, sizeof(ctx)); + return mismatch; +} + +#ifdef MONOCYPHER_CPP_NAMESPACE +} +#endif diff --git a/sample/src/monocypher/c/secure_random.c b/sample/src/monocypher/c/secure_random.c new file mode 100644 index 0000000..4e659e9 --- /dev/null +++ b/sample/src/monocypher/c/secure_random.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2021 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +/* + * glibc hides getentropy() behind _DEFAULT_SOURCE, and the macro has to be set before the first + * libc header is pulled in, hence the placement above the includes. + */ +#if defined(__linux__) && !defined(_DEFAULT_SOURCE) +#define _DEFAULT_SOURCE 1 +#endif + +#include "secure_random.h" + +/* + * Choosing an entropy source is the one genuinely platform-specific thing in this sample: + * + * macOS getentropy(), declared in since 10.12. + * + * iOS / simulator libSystem exports getentropy() (it is marked __IOS_AVAILABLE(10.0) in the + * macOS SDK), but the iOS SDKs ship no at all. arc4random_buf() + * is declared in on every Apple SDK, is the same CSPRNG underneath, + * and has no 256 byte request limit. + * + * Linux getentropy() needs glibc 2.25 or newer, and the sysroot Kotlin/Native cross + * compiles against is glibc 2.19, so read /dev/urandom instead. This branch + * also covers any other Unix without getentropy(). + */ +#if defined(__APPLE__) && __has_include() +#include +#define SAMPLE_SOURCE_GETENTROPY 1 +#elif defined(__APPLE__) +#include +#define SAMPLE_SOURCE_ARC4RANDOM 1 +#elif defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)) +#include +#define SAMPLE_SOURCE_GETENTROPY 1 +#else +#include +#include +#include +#define SAMPLE_SOURCE_URANDOM 1 +#endif + +#if defined(SAMPLE_SOURCE_GETENTROPY) + +/* getentropy() rejects requests larger than 256 bytes, so loop. */ +#define SAMPLE_ENTROPY_CHUNK 256 + +static int fill_random(uint8_t *buf, size_t size) +{ + size_t offset = 0; + while (offset < size) { + size_t chunk = size - offset; + if (chunk > SAMPLE_ENTROPY_CHUNK) { + chunk = SAMPLE_ENTROPY_CHUNK; + } + if (getentropy(buf + offset, chunk) != 0) { + return -1; + } + offset += chunk; + } + return 0; +} + +#elif defined(SAMPLE_SOURCE_ARC4RANDOM) + +static int fill_random(uint8_t *buf, size_t size) +{ + arc4random_buf(buf, size); + return 0; +} + +#else + +static int fill_random(uint8_t *buf, size_t size) +{ + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + return -1; + } + size_t offset = 0; + while (offset < size) { + ssize_t read_bytes = read(fd, buf + offset, size - offset); + if (read_bytes <= 0) { + if (read_bytes < 0 && errno == EINTR) { + continue; + } + close(fd); + return -1; + } + offset += (size_t)read_bytes; + } + close(fd); + return 0; +} + +#endif + +int sample_random_bytes(uint8_t *buf, size_t size) +{ + if (size == 0) { + return 0; + } + if (buf == NULL) { + return -1; + } + return fill_random(buf, size); +} diff --git a/sample/src/monocypher/headers/monocypher.h b/sample/src/monocypher/headers/monocypher.h new file mode 100644 index 0000000..291b8b9 --- /dev/null +++ b/sample/src/monocypher/headers/monocypher.h @@ -0,0 +1,321 @@ +// Monocypher version 4.0.3 +// +// This file is dual-licensed. Choose whichever licence you want from +// the two licences listed below. +// +// The first licence is a regular 2-clause BSD licence. The second licence +// is the CC-0 from Creative Commons. It is intended to release Monocypher +// to the public domain. The BSD licence serves as a fallback option. +// +// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0 +// +// ------------------------------------------------------------------------ +// +// Copyright (c) 2017-2019, Loup Vaillant +// All rights reserved. +// +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ------------------------------------------------------------------------ +// +// Written in 2017-2019 by Loup Vaillant +// +// To the extent possible under law, the author(s) have dedicated all copyright +// and related neighboring rights to this software to the public domain +// worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this software. If not, see +// + +#ifndef MONOCYPHER_H +#define MONOCYPHER_H + +#include +#include + +#ifdef MONOCYPHER_CPP_NAMESPACE +namespace MONOCYPHER_CPP_NAMESPACE { +#elif defined(__cplusplus) +extern "C" { +#endif + +// Constant time comparisons +// ------------------------- + +// Return 0 if a and b are equal, -1 otherwise +int crypto_verify16(const uint8_t a[16], const uint8_t b[16]); +int crypto_verify32(const uint8_t a[32], const uint8_t b[32]); +int crypto_verify64(const uint8_t a[64], const uint8_t b[64]); + + +// Erase sensitive data +// -------------------- +void crypto_wipe(void *secret, size_t size); + + +// Authenticated encryption +// ------------------------ +void crypto_aead_lock(uint8_t *cipher_text, + uint8_t mac [16], + const uint8_t key [32], + const uint8_t nonce[24], + const uint8_t *ad, size_t ad_size, + const uint8_t *plain_text, size_t text_size); +int crypto_aead_unlock(uint8_t *plain_text, + const uint8_t mac [16], + const uint8_t key [32], + const uint8_t nonce[24], + const uint8_t *ad, size_t ad_size, + const uint8_t *cipher_text, size_t text_size); + +// Authenticated stream +// -------------------- +typedef struct { + uint64_t counter; + uint8_t key[32]; + uint8_t nonce[8]; +} crypto_aead_ctx; + +void crypto_aead_init_x(crypto_aead_ctx *ctx, + const uint8_t key[32], const uint8_t nonce[24]); +void crypto_aead_init_djb(crypto_aead_ctx *ctx, + const uint8_t key[32], const uint8_t nonce[8]); +void crypto_aead_init_ietf(crypto_aead_ctx *ctx, + const uint8_t key[32], const uint8_t nonce[12]); + +void crypto_aead_write(crypto_aead_ctx *ctx, + uint8_t *cipher_text, + uint8_t mac[16], + const uint8_t *ad , size_t ad_size, + const uint8_t *plain_text, size_t text_size); +int crypto_aead_read(crypto_aead_ctx *ctx, + uint8_t *plain_text, + const uint8_t mac[16], + const uint8_t *ad , size_t ad_size, + const uint8_t *cipher_text, size_t text_size); + + +// General purpose hash (BLAKE2b) +// ------------------------------ + +// Direct interface +void crypto_blake2b(uint8_t *hash, size_t hash_size, + const uint8_t *message, size_t message_size); + +void crypto_blake2b_keyed(uint8_t *hash, size_t hash_size, + const uint8_t *key, size_t key_size, + const uint8_t *message, size_t message_size); + +// Incremental interface +typedef struct { + // Do not rely on the size or contents of this type, + // for they may change without notice. + uint64_t hash[8]; + uint64_t input_offset[2]; + uint64_t input[16]; + size_t input_idx; + size_t hash_size; +} crypto_blake2b_ctx; + +void crypto_blake2b_init(crypto_blake2b_ctx *ctx, size_t hash_size); +void crypto_blake2b_keyed_init(crypto_blake2b_ctx *ctx, size_t hash_size, + const uint8_t *key, size_t key_size); +void crypto_blake2b_update(crypto_blake2b_ctx *ctx, + const uint8_t *message, size_t message_size); +void crypto_blake2b_final(crypto_blake2b_ctx *ctx, uint8_t *hash); + + +// Password key derivation (Argon2) +// -------------------------------- +#define CRYPTO_ARGON2_D 0 +#define CRYPTO_ARGON2_I 1 +#define CRYPTO_ARGON2_ID 2 + +typedef struct { + uint32_t algorithm; // Argon2d, Argon2i, Argon2id + uint32_t nb_blocks; // memory hardness, >= 8 * nb_lanes + uint32_t nb_passes; // CPU hardness, >= 1 (>= 3 recommended for Argon2i) + uint32_t nb_lanes; // parallelism level (single threaded anyway) +} crypto_argon2_config; + +typedef struct { + const uint8_t *pass; + const uint8_t *salt; + uint32_t pass_size; + uint32_t salt_size; // 16 bytes recommended +} crypto_argon2_inputs; + +typedef struct { + const uint8_t *key; // may be NULL if no key + const uint8_t *ad; // may be NULL if no additional data + uint32_t key_size; // 0 if no key (32 bytes recommended otherwise) + uint32_t ad_size; // 0 if no additional data +} crypto_argon2_extras; + +extern const crypto_argon2_extras crypto_argon2_no_extras; + +void crypto_argon2(uint8_t *hash, uint32_t hash_size, void *work_area, + crypto_argon2_config config, + crypto_argon2_inputs inputs, + crypto_argon2_extras extras); + + +// Key exchange (X-25519) +// ---------------------- + +// Shared secrets are not quite random. +// Hash them to derive an actual shared key. +void crypto_x25519_public_key(uint8_t public_key[32], + const uint8_t secret_key[32]); +void crypto_x25519(uint8_t raw_shared_secret[32], + const uint8_t your_secret_key [32], + const uint8_t their_public_key [32]); + +// Conversion to EdDSA +void crypto_x25519_to_eddsa(uint8_t eddsa[32], const uint8_t x25519[32]); + +// scalar "division" +// Used for OPRF. Be aware that exponential blinding is less secure +// than Diffie-Hellman key exchange. +void crypto_x25519_inverse(uint8_t blind_salt [32], + const uint8_t private_key[32], + const uint8_t curve_point[32]); + +// "Dirty" versions of x25519_public_key(). +// Use with crypto_elligator_rev(). +// Leaks 3 bits of the private key. +void crypto_x25519_dirty_small(uint8_t pk[32], const uint8_t sk[32]); +void crypto_x25519_dirty_fast (uint8_t pk[32], const uint8_t sk[32]); + + +// Signatures +// ---------- + +// EdDSA with curve25519 + BLAKE2b +void crypto_eddsa_key_pair(uint8_t secret_key[64], + uint8_t public_key[32], + uint8_t seed[32]); +void crypto_eddsa_sign(uint8_t signature [64], + const uint8_t secret_key[64], + const uint8_t *message, size_t message_size); +int crypto_eddsa_check(const uint8_t signature [64], + const uint8_t public_key[32], + const uint8_t *message, size_t message_size); + +// Conversion to X25519 +void crypto_eddsa_to_x25519(uint8_t x25519[32], const uint8_t eddsa[32]); + +// EdDSA building blocks +void crypto_eddsa_trim_scalar(uint8_t out[32], const uint8_t in[32]); +void crypto_eddsa_reduce(uint8_t reduced[32], const uint8_t expanded[64]); +void crypto_eddsa_mul_add(uint8_t r[32], + const uint8_t a[32], + const uint8_t b[32], + const uint8_t c[32]); +void crypto_eddsa_scalarbase(uint8_t point[32], const uint8_t scalar[32]); +int crypto_eddsa_check_equation(const uint8_t signature[64], + const uint8_t public_key[32], + const uint8_t h_ram[32]); + + +// Chacha20 +// -------- + +// Specialised hash. +// Used to hash X25519 shared secrets. +void crypto_chacha20_h(uint8_t out[32], + const uint8_t key[32], + const uint8_t in [16]); + +// Unauthenticated stream cipher. +// Don't forget to add authentication. +uint64_t crypto_chacha20_djb(uint8_t *cipher_text, + const uint8_t *plain_text, + size_t text_size, + const uint8_t key[32], + const uint8_t nonce[8], + uint64_t ctr); +uint32_t crypto_chacha20_ietf(uint8_t *cipher_text, + const uint8_t *plain_text, + size_t text_size, + const uint8_t key[32], + const uint8_t nonce[12], + uint32_t ctr); +uint64_t crypto_chacha20_x(uint8_t *cipher_text, + const uint8_t *plain_text, + size_t text_size, + const uint8_t key[32], + const uint8_t nonce[24], + uint64_t ctr); + + +// Poly 1305 +// --------- + +// This is a *one time* authenticator. +// Disclosing the mac reveals the key. +// See crypto_lock() on how to use it properly. + +// Direct interface +void crypto_poly1305(uint8_t mac[16], + const uint8_t *message, size_t message_size, + const uint8_t key[32]); + +// Incremental interface +typedef struct { + // Do not rely on the size or contents of this type, + // for they may change without notice. + uint8_t c[16]; // chunk of the message + size_t c_idx; // How many bytes are there in the chunk. + uint32_t r [4]; // constant multiplier (from the secret key) + uint32_t pad[4]; // random number added at the end (from the secret key) + uint32_t h [5]; // accumulated hash +} crypto_poly1305_ctx; + +void crypto_poly1305_init (crypto_poly1305_ctx *ctx, const uint8_t key[32]); +void crypto_poly1305_update(crypto_poly1305_ctx *ctx, + const uint8_t *message, size_t message_size); +void crypto_poly1305_final (crypto_poly1305_ctx *ctx, uint8_t mac[16]); + + +// Elligator 2 +// ----------- + +// Elligator mappings proper +void crypto_elligator_map(uint8_t curve [32], const uint8_t hidden[32]); +int crypto_elligator_rev(uint8_t hidden[32], const uint8_t curve [32], + uint8_t tweak); + +// Easy to use key pair generation +void crypto_elligator_key_pair(uint8_t hidden[32], uint8_t secret_key[32], + uint8_t seed[32]); + +#ifdef __cplusplus +} +#endif + +#endif // MONOCYPHER_H diff --git a/sample/src/monocypher/headers/secure_random.h b/sample/src/monocypher/headers/secure_random.h new file mode 100644 index 0000000..b30d77f --- /dev/null +++ b/sample/src/monocypher/headers/secure_random.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +#ifndef SAMPLE_SECURE_RANDOM_H +#define SAMPLE_SECURE_RANDOM_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Fill `buf` with `size` cryptographically secure random bytes. + * + * Monocypher deliberately ships no RNG, so the sample provides one here in the + * C layer that cklib already compiles, rather than as a Kotlin expect/actual. + * + * Returns 0 on success and a non-zero value on failure. On failure the contents + * of `buf` are unspecified and must not be used. + */ +int sample_random_bytes(uint8_t *buf, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* SAMPLE_SECURE_RANDOM_H */ diff --git a/sample/src/nativeInterop/cinterop/monocypher.def b/sample/src/nativeInterop/cinterop/monocypher.def new file mode 100644 index 0000000..ab135be --- /dev/null +++ b/sample/src/nativeInterop/cinterop/monocypher.def @@ -0,0 +1,16 @@ +package = co.touchlab.cklib.sample.cinterop + +headers = monocypher.h secure_random.h + +# Only generate bindings for these two headers; without the filter every transitively included +# system header (stdint.h, stddef.h, ...) ends up in the generated package as well. +headerFilter = monocypher.h secure_random.h + +# Header search path, relative to the Gradle project directory (that is cinterop's working +# directory). This line alone is enough to resolve the headers; build.gradle.kts additionally +# passes the same directory via `includeDirs(...)`, which does not depend on the working directory. +compilerOpts = -Isrc/monocypher/headers + +# Deliberately no `staticLibraries` and no `libraryPaths`: cinterop only reads the headers here. +# The machine code comes from cklib, which compiles the .c files to LLVM bitcode and passes the +# result to the Kotlin/Native compiler with -native-library. diff --git a/sample/src/nativeMain/kotlin/co/touchlab/cklib/sample/SecureNote.kt b/sample/src/nativeMain/kotlin/co/touchlab/cklib/sample/SecureNote.kt new file mode 100644 index 0000000..6002f8a --- /dev/null +++ b/sample/src/nativeMain/kotlin/co/touchlab/cklib/sample/SecureNote.kt @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2021 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +@file:OptIn(ExperimentalForeignApi::class) + +package co.touchlab.cklib.sample + +import co.touchlab.cklib.sample.cinterop.CRYPTO_ARGON2_I +import co.touchlab.cklib.sample.cinterop.crypto_aead_lock +import co.touchlab.cklib.sample.cinterop.crypto_aead_unlock +import co.touchlab.cklib.sample.cinterop.crypto_argon2 +import co.touchlab.cklib.sample.cinterop.crypto_argon2_config +import co.touchlab.cklib.sample.cinterop.crypto_argon2_extras +import co.touchlab.cklib.sample.cinterop.crypto_argon2_inputs +import co.touchlab.cklib.sample.cinterop.crypto_wipe +import co.touchlab.cklib.sample.cinterop.sample_random_bytes +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.MemScope +import kotlinx.cinterop.UByteVar +import kotlinx.cinterop.allocArray +import kotlinx.cinterop.cValue +import kotlinx.cinterop.convert +import kotlinx.cinterop.get +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.set + +/** + * Passphrase-encrypted notes, built on the vendored Monocypher sources that cklib compiles to + * bitcode and the cinterop bindings generated from the same headers. + * + * The sealed envelope is laid out as: + * + * ``` + * | "SNOTE1" (6) | salt (16) | nonce (24) | MAC (16) | ciphertext (n) | + * ``` + * + * This is sample code. Read the caveats in the sample README before reusing any of it: the Argon2 + * parameters are tuned to keep the tests fast, not to resist a real attacker. + */ +object SecureNote { + + private const val MAGIC = "SNOTE1" + private const val MAGIC_SIZE = 6 + private const val SALT_SIZE = 16 + private const val NONCE_SIZE = 24 + private const val MAC_SIZE = 16 + private const val KEY_SIZE = 32 + + private const val SALT_OFFSET = MAGIC_SIZE + private const val NONCE_OFFSET = SALT_OFFSET + SALT_SIZE + private const val MAC_OFFSET = NONCE_OFFSET + NONCE_SIZE + private const val HEADER_SIZE = MAC_OFFSET + MAC_SIZE + + // Deliberately weak, so the test suite stays fast. 1024 blocks is 1 MB of work area; real + // applications should use orders of magnitude more (see the README). + private const val ARGON2_BLOCKS = 1024u + private const val ARGON2_PASSES = 3u + private const val ARGON2_LANES = 1u + + private val magicBytes = MAGIC.encodeToByteArray() + + /** Encrypts [note] under [passphrase] with a fresh random salt and nonce. */ + fun seal(passphrase: String, note: String): ByteArray = + sealWith(passphrase, note, randomBytes(SALT_SIZE), randomBytes(NONCE_SIZE)) + + /** + * Decrypts an envelope produced by [seal]. + * + * Returns `null` for a wrong passphrase, tampered contents, or any input that is not a + * well-formed envelope. Nothing here throws on bad input. + */ + fun open(passphrase: String, sealed: ByteArray): String? { + if (sealed.size < HEADER_SIZE) return null + for (i in magicBytes.indices) { + if (sealed[i] != magicBytes[i]) return null + } + + val salt = sealed.copyOfRange(SALT_OFFSET, SALT_OFFSET + SALT_SIZE) + val nonce = sealed.copyOfRange(NONCE_OFFSET, NONCE_OFFSET + NONCE_SIZE) + val mac = sealed.copyOfRange(MAC_OFFSET, MAC_OFFSET + MAC_SIZE) + val cipherText = sealed.copyOfRange(HEADER_SIZE, sealed.size) + + return memScoped { + val key = allocArray(KEY_SIZE) + deriveKey(passphrase, salt, key) + + val plain = allocArray(bufferSize(cipherText.size)) + val status = crypto_aead_unlock( + plain, + allocFilled(mac), + key, + allocFilled(nonce), + null, + 0uL, + allocFilled(cipherText), + cipherText.size.convert(), + ) + crypto_wipe(key, KEY_SIZE.convert()) + + // -1 means the MAC did not verify: wrong passphrase or tampered bytes. + if (status != 0) { + null + } else { + val plainText = plain.toByteArray(cipherText.size) + crypto_wipe(plain, bufferSize(cipherText.size).convert()) + val note = plainText.decodeToString() + plainText.fill(0) + note + } + } + } + + /** + * [seal] with the salt and nonce supplied by the caller, so a test can pin them and assert + * exact ciphertext bytes. Reusing a nonce with the same key destroys the security of the + * scheme, which is why this is not public. + */ + internal fun sealWith( + passphrase: String, + note: String, + salt: ByteArray, + nonce: ByteArray, + ): ByteArray { + require(salt.size == SALT_SIZE) { "salt must be $SALT_SIZE bytes, was ${salt.size}" } + require(nonce.size == NONCE_SIZE) { "nonce must be $NONCE_SIZE bytes, was ${nonce.size}" } + + val plainText = note.encodeToByteArray() + val sealed = ByteArray(HEADER_SIZE + plainText.size) + magicBytes.copyInto(sealed, 0) + salt.copyInto(sealed, SALT_OFFSET) + nonce.copyInto(sealed, NONCE_OFFSET) + + memScoped { + val key = allocArray(KEY_SIZE) + deriveKey(passphrase, salt, key) + + val plain = allocFilled(plainText) + val cipher = allocArray(bufferSize(plainText.size)) + val mac = allocArray(MAC_SIZE) + + crypto_aead_lock( + cipher, + mac, + key, + allocFilled(nonce), + null, + 0uL, + plain, + plainText.size.convert(), + ) + + crypto_wipe(key, KEY_SIZE.convert()) + crypto_wipe(plain, bufferSize(plainText.size).convert()) + + for (i in 0 until MAC_SIZE) { + sealed[MAC_OFFSET + i] = mac[i].toByte() + } + for (i in plainText.indices) { + sealed[HEADER_SIZE + i] = cipher[i].toByte() + } + } + plainText.fill(0) + return sealed + } + + /** Stretches [passphrase] into a [KEY_SIZE] byte key with Argon2i, writing it to [keyOut]. */ + private fun MemScope.deriveKey( + passphrase: String, + salt: ByteArray, + keyOut: CPointer, + ) { + val passBytes = passphrase.encodeToByteArray() + val passPtr = allocFilled(passBytes) + val saltPtr = allocFilled(salt) + + // Argon2 needs nb_blocks * 1024 bytes of scratch. memScoped hands out native heap memory + // rather than stack memory, so a megabyte here is fine, and it is freed with the scope. + val workArea = allocArray(ARGON2_BLOCKS.toInt() * 1024) + + // Monocypher 4.x takes these three structs by value, hence cValue { } rather than plain + // arguments. Monocypher 3.x had a flat crypto_argon2i(...) instead. + crypto_argon2( + keyOut, + KEY_SIZE.convert(), + workArea, + cValue { + algorithm = CRYPTO_ARGON2_I.convert() + nb_blocks = ARGON2_BLOCKS + nb_passes = ARGON2_PASSES + nb_lanes = ARGON2_LANES + }, + cValue { + this.pass = passPtr + this.salt = saltPtr + this.pass_size = passBytes.size.convert() + this.salt_size = salt.size.convert() + }, + // The same thing the crypto_argon2_no_extras global holds: no key, no associated data. + cValue { + this.key = null + this.ad = null + this.key_size = 0u + this.ad_size = 0u + }, + ) + + crypto_wipe(passPtr, passBytes.size.convert()) + passBytes.fill(0) + } + + private fun randomBytes(size: Int): ByteArray = memScoped { + val buffer = allocArray(size) + val status = sample_random_bytes(buffer, size.convert()) + check(status == 0) { "sample_random_bytes() failed with status $status" } + buffer.toByteArray(size) + } +} + +/** allocArray(0) is not useful, and an empty note is a legitimate input. */ +private fun bufferSize(size: Int): Int = if (size == 0) 1 else size + +private fun MemScope.allocFilled(bytes: ByteArray): CPointer { + val pointer = allocArray(bufferSize(bytes.size)) + for (i in bytes.indices) { + pointer[i] = bytes[i].toUByte() + } + return pointer +} + +/** Copies C memory into a Kotlin-owned array, so nothing outlives the enclosing [memScoped]. */ +private fun CPointer.toByteArray(size: Int): ByteArray = + ByteArray(size) { this[it].toByte() } diff --git a/sample/src/nativeTest/kotlin/co/touchlab/cklib/sample/SecureNoteTest.kt b/sample/src/nativeTest/kotlin/co/touchlab/cklib/sample/SecureNoteTest.kt new file mode 100644 index 0000000..7508111 --- /dev/null +++ b/sample/src/nativeTest/kotlin/co/touchlab/cklib/sample/SecureNoteTest.kt @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2021 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package co.touchlab.cklib.sample + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class SecureNoteTest { + + @Test + fun roundTrips() { + val sealed = SecureNote.seal(PASSPHRASE, "meet me at the docks at midnight") + assertEquals("meet me at the docks at midnight", SecureNote.open(PASSPHRASE, sealed)) + } + + @Test + fun sealIsRandomized() { + // Fresh salt and nonce per call, so the same note never seals to the same bytes. + val first = SecureNote.seal(PASSPHRASE, "same note") + val second = SecureNote.seal(PASSPHRASE, "same note") + assertNotEquals(first.toHex(), second.toHex()) + assertEquals("same note", SecureNote.open(PASSPHRASE, second)) + } + + @Test + fun wrongPassphraseReturnsNull() { + val sealed = SecureNote.seal(PASSPHRASE, "meet me at the docks at midnight") + assertNull(SecureNote.open("hunter3", sealed)) + } + + @Test + fun flippedCipherTextByteReturnsNull() { + val sealed = SecureNote.seal(PASSPHRASE, "meet me at the docks at midnight") + val tampered = sealed.copyOf() + tampered[HEADER_SIZE] = (tampered[HEADER_SIZE].toInt() xor 0x01).toByte() + assertNull(SecureNote.open(PASSPHRASE, tampered)) + } + + @Test + fun flippedMacByteReturnsNull() { + val sealed = SecureNote.seal(PASSPHRASE, "meet me at the docks at midnight") + val tampered = sealed.copyOf() + tampered[MAC_OFFSET] = (tampered[MAC_OFFSET].toInt() xor 0x80).toByte() + assertNull(SecureNote.open(PASSPHRASE, tampered)) + } + + @Test + fun emptyNoteRoundTrips() { + val sealed = SecureNote.seal(PASSPHRASE, "") + assertEquals(HEADER_SIZE, sealed.size) + assertEquals("", SecureNote.open(PASSPHRASE, sealed)) + } + + @Test + fun multiBlockNoteRoundTrips() { + // Several kilobytes, well past ChaCha20's 64 byte block, to catch off-by-one buffer sizing. + val note = buildString { + repeat(200) { append("line $it: the quick brown fox jumps over the lazy dog\n") } + } + assertTrue(note.length > 4096) + val sealed = SecureNote.seal(PASSPHRASE, note) + assertEquals(note, SecureNote.open(PASSPHRASE, sealed)) + } + + @Test + fun nonAsciiNoteRoundTrips() { + val note = "ここは秘密です — naïve café, 🔐 100% sûr" + val sealed = SecureNote.seal(PASSPHRASE, note) + assertEquals(note, SecureNote.open(PASSPHRASE, sealed)) + } + + @Test + fun malformedInputReturnsNull() { + assertNull(SecureNote.open(PASSPHRASE, ByteArray(0))) + assertNull(SecureNote.open(PASSPHRASE, ByteArray(HEADER_SIZE - 1))) + assertNull(SecureNote.open(PASSPHRASE, ByteArray(HEADER_SIZE + 8) { 0x41 })) + assertNull(SecureNote.open(PASSPHRASE, ByteArray(1024) { it.toByte() })) + + // Right magic, right length, garbage everywhere else. + val garbage = ByteArray(HEADER_SIZE + 16) { 0x7f } + "SNOTE1".encodeToByteArray().copyInto(garbage, 0) + assertNull(SecureNote.open(PASSPHRASE, garbage)) + + // A valid envelope truncated mid-ciphertext. + val sealed = SecureNote.seal(PASSPHRASE, "meet me at the docks at midnight") + assertNull(SecureNote.open(PASSPHRASE, sealed.copyOf(sealed.size - 5))) + assertNull(SecureNote.open(PASSPHRASE, sealed.copyOf(HEADER_SIZE / 2))) + } + + @Test + fun knownAnswer() { + // The point of this test: the salt, nonce, passphrase and note are all fixed, so the + // envelope is fully deterministic. Every target that compiles the same vendored + // Monocypher sources must produce these exact bytes. If this passes on macosArm64 and + // linuxX64 alike, the C really is being compiled and linked identically everywhere. + val sealed = SecureNote.sealWith( + passphrase = "correct horse battery staple", + note = "attack at dawn", + salt = ByteArray(16) { it.toByte() }, + nonce = ByteArray(24) { (0xa0 + it).toByte() }, + ) + assertEquals(GOLDEN, sealed.toHex()) + assertEquals( + "attack at dawn", + SecureNote.open("correct horse battery staple", sealed), + ) + } + + private companion object { + const val PASSPHRASE = "hunter2" + + const val MAGIC_SIZE = 6 + const val SALT_SIZE = 16 + const val NONCE_SIZE = 24 + const val MAC_SIZE = 16 + const val MAC_OFFSET = MAGIC_SIZE + SALT_SIZE + NONCE_SIZE + const val HEADER_SIZE = MAC_OFFSET + MAC_SIZE + + // "SNOTE1" | salt 00..0f | nonce a0..b7 | MAC | ciphertext of "attack at dawn" + const val GOLDEN = "534e4f544531" + + "000102030405060708090a0b0c0d0e0f" + + "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7" + + "aba1e52c9024785fa4bdf90f729b34da" + + "7d9757b3e43a89cdb2b21953a174" + } +} + +private fun ByteArray.toHex(): String { + val hex = "0123456789abcdef" + return buildString(size * 2) { + for (byte in this@toHex) { + val value = byte.toInt() and 0xff + append(hex[value shr 4]) + append(hex[value and 0x0f]) + } + } +} From efed021843e23491b5ddc6bb37befaf8ae04a837 Mon Sep 17 00:00:00 2001 From: Kevin Schildhorn Date: Tue, 11 Aug 2026 16:02:59 -0400 Subject: [PATCH 2/7] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 14e09fa..6f4b128 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ cklib { ## Examples -[`sample/`](sample) is a runnable build in this repo. It vendors [Monocypher](https://monocypher.org), compiles it with CKlib, binds it with cinterop, and exposes a passphrase-encrypted note API. Run it with `./gradlew -p sample macosArm64Test`. See [sample/README.md](sample/README.md). +[`sample/`](sample) is a small sample that packages [Monocypher](https://monocypher.org), compiles it with CKlib, binds it with cinterop, and exposes a passphrase-encrypted note API. -You can find a [tutorial](https://hackernoon.com/how-to-extend-a-kmm-shared-module-with-cc-code) with a [GitHub Sample](https://github.com/ttypic/kmm-embedded-c) to get a brief understanding of how the library works. +You can also find a [tutorial](https://hackernoon.com/how-to-extend-a-kmm-shared-module-with-cc-code) with a [GitHub Sample](https://github.com/ttypic/kmm-embedded-c) to get a brief understanding of how the library works. Additionally you can see multiple examples of C Klib in use here: 1. [zstd-kmp](https://github.com/square/zstd-kmp) - Packages [ztsd](https://github.com/facebook/zstd), a fast real-time compression algorithm. From 3def0157597a485f8a736bff2f2a3d7107782087 Mon Sep 17 00:00:00 2001 From: Kevin Schildhorn Date: Fri, 14 Aug 2026 14:06:39 -0400 Subject: [PATCH 3/7] Update README.md --- sample/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sample/README.md b/sample/README.md index ead7f21..00ec666 100644 --- a/sample/README.md +++ b/sample/README.md @@ -39,9 +39,9 @@ create("monocypher", srcDir = file("src/monocypher")) { } ``` -* **`mingwX64` is deliberately not declared: `secure_random.c` has no Windows branch, and adding one +* **`mingwX64`** is deliberately not declared: `secure_random.c` has no Windows branch, and adding one means `BCryptGenRandom` plus linking `bcrypt.lib`. -> + * **`Language.C` compiles with `-std=gnu11 -O3 -Wall -Wextra -Werror`, hardcoded.** The only escape hatch is `compilerArgs`, which is appended after those flags, so `-Wno-error=` works. This sample needs **no suppressions at all**: Monocypher 4.0.3 and `secure_random.c` both build @@ -58,4 +58,4 @@ per-target tasks like `compileKotlinMacosArm64` and `macosArm64Test` work fine, `InvocationTargetException` if that directory is missing. On a machine with no Kotlin/Native installed yet, the bitcode task can therefore fail before anything has had a chance to download it; running any Kotlin/Native task first (`./gradlew -p sample cinteropMonocypherMacosArm64`) fetches -the distribution and unblocks it. \ No newline at end of file +the distribution and unblocks it. From 2a42c07cfc1aa6eb227347e1f371d0bd87b242c5 Mon Sep 17 00:00:00 2001 From: Kevin Schildhorn Date: Fri, 14 Aug 2026 15:36:52 -0400 Subject: [PATCH 4/7] Update .gitignore --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index daab74d..218c120 100644 --- a/.gitignore +++ b/.gitignore @@ -7,16 +7,16 @@ *.iml *.ipr - # Generated files +# Generated files bin/ gen/ - # Gradle files +# Gradle files .gradle .gradle/ build/ - # Kotlin +# Kotlin .kotlin/ local.properties From eeac196a97b686955a5b423a6c8602ce71ade4c5 Mon Sep 17 00:00:00 2001 From: Kevin Schildhorn Date: Fri, 14 Aug 2026 15:37:09 -0400 Subject: [PATCH 5/7] Adding gradle wrapper --- sample/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 46175 bytes sample/gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 sample/gradle/wrapper/gradle-wrapper.jar diff --git a/sample/gradle/wrapper/gradle-wrapper.jar b/sample/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..61285a659d17295f1de7c53e24fdf13ad755c379 GIT binary patch literal 46175 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

pVX@HRij*jw$g4?}Pn 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 literal 0 HcmV?d00001 diff --git a/sample/gradle/wrapper/gradle-wrapper.properties b/sample/gradle/wrapper/gradle-wrapper.properties index 002b867..1e922f4 100644 --- a/sample/gradle/wrapper/gradle-wrapper.properties +++ b/sample/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From be7ecf3d1b3b23328022f01867a21891233cf32f Mon Sep 17 00:00:00 2001 From: Kevin Schildhorn Date: Fri, 14 Aug 2026 15:37:40 -0400 Subject: [PATCH 6/7] Updating Copyright --- sample/build.gradle.kts | 7 +++---- sample/settings.gradle.kts | 2 +- sample/src/monocypher/c/secure_random.c | 2 +- sample/src/monocypher/headers/secure_random.h | 2 +- .../kotlin/co/touchlab/cklib/sample/SecureNote.kt | 2 +- .../kotlin/co/touchlab/cklib/sample/SecureNoteTest.kt | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index a4e4062..a857691 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Touchlab + * Copyright (c) 2026 Touchlab * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * @@ -14,7 +14,7 @@ import co.touchlab.cklib.gradle.CompileToBitcode plugins { - kotlin("multiplatform") version "2.0.0" + kotlin("multiplatform") version "2.1.10" id("co.touchlab.cklib") } @@ -29,7 +29,6 @@ kotlin { nativeTargets.forEach { target -> target.compilations.getByName("main").cinterops.create("monocypher") { - defFile(project.file("src/nativeInterop/cinterop/monocypher.def")) // cinterop only needs the headers. The compiled code arrives as bitcode from cklib, // which is why the .def declares no staticLibraries or libraryPaths. includeDirs(project.file("src/monocypher/headers")) @@ -46,7 +45,7 @@ kotlin { } cklib { - config.kotlinVersion = "2.0.0" + config.kotlinVersion = "2.1.10" create("monocypher", srcDir = file("src/monocypher")) { language = CompileToBitcode.Language.C diff --git a/sample/settings.gradle.kts b/sample/settings.gradle.kts index a05e5d5..ac84386 100644 --- a/sample/settings.gradle.kts +++ b/sample/settings.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Touchlab + * Copyright (c) 2026 Touchlab * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * diff --git a/sample/src/monocypher/c/secure_random.c b/sample/src/monocypher/c/secure_random.c index 4e659e9..dd2c830 100644 --- a/sample/src/monocypher/c/secure_random.c +++ b/sample/src/monocypher/c/secure_random.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Touchlab + * Copyright (c) 2026 Touchlab * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * diff --git a/sample/src/monocypher/headers/secure_random.h b/sample/src/monocypher/headers/secure_random.h index b30d77f..eec37e3 100644 --- a/sample/src/monocypher/headers/secure_random.h +++ b/sample/src/monocypher/headers/secure_random.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Touchlab + * Copyright (c) 2026 Touchlab * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * diff --git a/sample/src/nativeMain/kotlin/co/touchlab/cklib/sample/SecureNote.kt b/sample/src/nativeMain/kotlin/co/touchlab/cklib/sample/SecureNote.kt index 6002f8a..3bf8806 100644 --- a/sample/src/nativeMain/kotlin/co/touchlab/cklib/sample/SecureNote.kt +++ b/sample/src/nativeMain/kotlin/co/touchlab/cklib/sample/SecureNote.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Touchlab + * Copyright (c) 2026 Touchlab * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * diff --git a/sample/src/nativeTest/kotlin/co/touchlab/cklib/sample/SecureNoteTest.kt b/sample/src/nativeTest/kotlin/co/touchlab/cklib/sample/SecureNoteTest.kt index 7508111..77c4fea 100644 --- a/sample/src/nativeTest/kotlin/co/touchlab/cklib/sample/SecureNoteTest.kt +++ b/sample/src/nativeTest/kotlin/co/touchlab/cklib/sample/SecureNoteTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Touchlab + * Copyright (c) 2026 Touchlab * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * From 2317c9c3bc189341ede3b7c943c219473df8662a Mon Sep 17 00:00:00 2001 From: Kevin Schildhorn Date: Fri, 14 Aug 2026 16:41:52 -0400 Subject: [PATCH 7/7] Updating versions to use latest kotlin and gradle --- build.gradle.kts | 5 +- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- plugin/build.gradle.kts | 6 +- .../touchlab/cklib/gradle/CompileToBitcode.kt | 8 +- .../cklib/gradle/CompileToBitcodeExtension.kt | 38 +-- .../gradle/reflection/PlatformManager.kt | 2 +- sample/build.gradle.kts | 6 +- sample/gradle/wrapper/gradle-wrapper.jar | Bin 46175 -> 47505 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 + sample/gradlew | 248 ++++++++++++++++++ sample/gradlew.bat | 82 ++++++ 12 files changed, 372 insertions(+), 29 deletions(-) create mode 100755 sample/gradlew create mode 100644 sample/gradlew.bat diff --git a/build.gradle.kts b/build.gradle.kts index d0a1b30..3dc71a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,9 +22,8 @@ plugins { alias(libs.plugins.kotlin.jvm) apply false } -val GROUP: String by project - -val CKLIB_VERSION: String by project +val GROUP = property("GROUP") as String +val CKLIB_VERSION = property("CKLIB_VERSION") as String allprojects { group = GROUP diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0cd22da..5beb404 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.0.0" +kotlin = "2.4.10" [libraries] kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 002b867..1e922f4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 2bd50ed..d1ac69f 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { id("java-gradle-plugin") kotlin("jvm") @@ -9,7 +7,7 @@ plugins { } kotlin { - jvmToolchain(11) + jvmToolchain(17) } dependencies { @@ -42,7 +40,7 @@ repositories { mavenPublishing { publishToMavenCentral(automaticRelease = true) val releaseSigningEnabled = - project.properties["RELEASE_SIGNING_ENABLED"]?.toString()?.equals("false", ignoreCase = true) != true + findProperty("RELEASE_SIGNING_ENABLED")?.toString()?.equals("false", ignoreCase = true) != true if (releaseSigningEnabled) signAllPublications() pomFromGradleProperties() } diff --git a/plugin/src/main/kotlin/co/touchlab/cklib/gradle/CompileToBitcode.kt b/plugin/src/main/kotlin/co/touchlab/cklib/gradle/CompileToBitcode.kt index e24e8d9..7b16c55 100644 --- a/plugin/src/main/kotlin/co/touchlab/cklib/gradle/CompileToBitcode.kt +++ b/plugin/src/main/kotlin/co/touchlab/cklib/gradle/CompileToBitcode.kt @@ -15,9 +15,11 @@ import org.gradle.api.DefaultTask import org.gradle.api.file.FileCollection import org.gradle.api.tasks.* import org.gradle.process.ExecOperations +import org.gradle.work.DisableCachingByDefault import java.io.File import javax.inject.Inject +@DisableCachingByDefault(because = "Invokes host clang and llvm-link with absolute toolchain paths") open class CompileToBitcode @Inject constructor( srcRoot: File, @Input val compileName: String, @@ -56,9 +58,11 @@ open class CompileToBitcode @Inject constructor( // Source files and headers are registered as inputs by the `inputFiles` and `headers` properties. @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) var srcDirs: FileCollection = project.files(srcRoot.resolve("cpp")) @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) var headersDirs: FileCollection = srcDirs + project.files(srcRoot.resolve("headers")) @Input @@ -73,7 +77,7 @@ open class CompileToBitcode @Inject constructor( @get:Internal internal val targetDir: File get() { - return project.buildDir.resolve("$PLUGIN_NAME/$compileName/$target") + return project.layout.buildDirectory.get().asFile.resolve("$PLUGIN_NAME/$compileName/$target") } @get:OutputDirectory @@ -133,6 +137,7 @@ open class CompileToBitcode @Inject constructor( @get:SkipWhenEmpty @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) val inputFiles: Iterable get() { return srcDirs.flatMap { srcDir -> @@ -147,6 +152,7 @@ open class CompileToBitcode @Inject constructor( private fun bitcodeFileForInputFile(file: File) = outputFileForInputFile(file, "bc") @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) protected val headers: Iterable get() { // Not using clang's -M* flags because there's a problem with our current include system: diff --git a/plugin/src/main/kotlin/co/touchlab/cklib/gradle/CompileToBitcodeExtension.kt b/plugin/src/main/kotlin/co/touchlab/cklib/gradle/CompileToBitcodeExtension.kt index 6771248..1d1306c 100644 --- a/plugin/src/main/kotlin/co/touchlab/cklib/gradle/CompileToBitcodeExtension.kt +++ b/plugin/src/main/kotlin/co/touchlab/cklib/gradle/CompileToBitcodeExtension.kt @@ -27,16 +27,16 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) { ) { val kmpExt = project.kmpExt - val allBitcode = project.tasks.register("all${name.snakeCaseToCamelCase().capitalize()}") { + val allBitcode = project.tasks.register("all${name.snakeCaseToCamelCase().capitalized()}") { it.group = GROUP_NAME it.description = "Compiles '$name' to bitcode for all targets" }.get() val allTaskProviders = kmpExt.kotlinNativeTargets.map { knTarget -> - val compileKotlinTask = project.tasks.getByPath("compileKotlin${knTarget.name.capitalize()}") + val compileKotlinTask = project.tasks.getByPath("compileKotlin${knTarget.name.capitalized()}") - val taskName = "${knTarget.name}${name.snakeCaseToCamelCase().capitalize()}" + val taskName = "${knTarget.name}${name.snakeCaseToCamelCase().capitalized()}" val taskProvider = project.tasks.register( taskName, @@ -44,22 +44,27 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) { srcDir, name, knTarget.konanTarget.name, { project.platformManager.isEnabled(knTarget.konanTarget) } ) - //tasks.getByName("compileKotlin${targetName.capitalize()}").dependsOn("${it.second}Quickjs") + compileKotlinTask.dependsOn(taskName) - compileKotlinTask - .dependsOn(taskName) + val bitcodeFile = project.layout.buildDirectory + .file("${CompileToBitcodePlugin.PLUGIN_NAME}/$name/${knTarget.konanTarget.name}/$name.bc") + val nativeLibraryArgs = bitcodeFile.map { listOf("-native-library", it.asFile.absolutePath) } - taskProvider.configure { compileToBitcodeTask -> - compileToBitcodeTask.group = GROUP_NAME - compileToBitcodeTask.description = "Compiles '$name' to bitcode for ${knTarget.name}" - - compilations.forEach { compilation -> - val knCompilation = knTarget.compilations.getByName(compilation) + compilations.forEach { compilation -> + @Suppress("DEPRECATION") + knTarget.compilations.getByName(compilation) + .compilerOptions.options.freeCompilerArgs.addAll(nativeLibraryArgs) + } - knCompilation.kotlinOptions.freeCompilerArgs += - listOf("-native-library", compileToBitcodeTask.outFile.absolutePath) + knTarget.binaries.configureEach { binary -> + binary.linkTaskProvider.configure { linkTask -> + linkTask.toolOptions.freeCompilerArgs.addAll(nativeLibraryArgs) } + } + taskProvider.configure { compileToBitcodeTask -> + compileToBitcodeTask.group = GROUP_NAME + compileToBitcodeTask.description = "Compiles '$name' to bitcode for ${knTarget.name}" compileToBitcodeTask.configurationBlock() } @@ -70,7 +75,10 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) { companion object { private fun String.snakeCaseToCamelCase() = - split('_').joinToString(separator = "") { it.capitalize() } + split('_').joinToString(separator = "") { it.capitalized() } + + private fun String.capitalized(): String = + replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } const val GROUP_NAME = CompileToBitcodePlugin.PLUGIN_NAME } diff --git a/plugin/src/main/kotlin/co/touchlab/cklib/gradle/reflection/PlatformManager.kt b/plugin/src/main/kotlin/co/touchlab/cklib/gradle/reflection/PlatformManager.kt index cac5f03..549bea9 100644 --- a/plugin/src/main/kotlin/co/touchlab/cklib/gradle/reflection/PlatformManager.kt +++ b/plugin/src/main/kotlin/co/touchlab/cklib/gradle/reflection/PlatformManager.kt @@ -24,7 +24,7 @@ class PlatformManager(dist: Distribution, konanHome:String) { private val blindDelegate:Any init { - val child = URLClassLoader(arrayOf(File(kotlinNativeJar).toURL()), this.javaClass.classLoader) + val child = URLClassLoader(arrayOf(File(kotlinNativeJar).toURI().toURL()), this.javaClass.classLoader) pmClass = Class.forName("org.jetbrains.kotlin.konan.target.PlatformManager", true, child) blindDelegate = pmClass.declaredConstructors.find { it.parameters.size == 2 && it.parameters[0].type == Distribution::class.java diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index a857691..3627b78 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -14,7 +14,7 @@ import co.touchlab.cklib.gradle.CompileToBitcode plugins { - kotlin("multiplatform") version "2.1.10" + kotlin("multiplatform") version "2.4.10" id("co.touchlab.cklib") } @@ -36,7 +36,7 @@ kotlin { } sourceSets { - val commonTest by getting { + getByName("commonTest") { dependencies { implementation(kotlin("test")) } @@ -45,7 +45,7 @@ kotlin { } cklib { - config.kotlinVersion = "2.1.10" + config.kotlinVersion = "2.4.10" create("monocypher", srcDir = file("src/monocypher")) { language = CompileToBitcode.Language.C diff --git a/sample/gradle/wrapper/gradle-wrapper.jar b/sample/gradle/wrapper/gradle-wrapper.jar index 61285a659d17295f1de7c53e24fdf13ad755c379..eddabd2eef8d94a5437d6168ff9c87a78ff725b3 100644 GIT binary patch delta 39228 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/sample/gradlew.bat b/sample/gradlew.bat new file mode 100644 index 0000000..8508ef6 --- /dev/null +++ b/sample/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL%