Merge branch 'odin-lang:master' into master

This commit is contained in:
WP. Yingamphol
2026-07-17 23:06:28 +07:00
committed by GitHub
1062 changed files with 340794 additions and 38479 deletions

23
.gitattributes vendored
View File

@@ -10,3 +10,26 @@ vendor/sdl3/SDL3.dll filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/SDL3.lib filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/mixer/*.dll filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/mixer/*.lib filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.dll filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.dll.lib filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.lib filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.pdb filter=lfs diff=lfs merge=lfs -text
vendor/raylib/linux/libraygui.so filter=lfs diff=lfs merge=lfs -text
vendor/raylib/linux/libraygui.a filter=lfs diff=lfs merge=lfs -text
vendor/raylib/linux/libraylib.a filter=lfs diff=lfs merge=lfs -text
vendor/raylib/linux/libraylib.so.600 filter=lfs diff=lfs merge=lfs -text
vendor/raylib/linux-arm64/libraylib.a filter=lfs diff=lfs merge=lfs -text
vendor/raylib/linux-arm64/libraylib.so.600 filter=lfs diff=lfs merge=lfs -text
vendor/raylib/macos/libraygui-arm64.dylib filter=lfs diff=lfs merge=lfs -text
vendor/raylib/macos/libraygui.dylib filter=lfs diff=lfs merge=lfs -text
vendor/raylib/macos/libraylib.a filter=lfs diff=lfs merge=lfs -text
vendor/raylib/macos/libraylib.600.dylib filter=lfs diff=lfs merge=lfs -text
vendor/raylib/wasm/libraylib.web.a filter=lfs diff=lfs merge=lfs -text
vendor/raylib/windows/raylib.dll filter=lfs diff=lfs merge=lfs -text
vendor/raylib/windows/raylib.lib filter=lfs diff=lfs merge=lfs -text
vendor/raylib/windows/raylibdll.lib filter=lfs diff=lfs merge=lfs -text
vendor/raylib/windows/raygui.dll filter=lfs diff=lfs merge=lfs -text
vendor/raylib/windows/raygui.lib filter=lfs diff=lfs merge=lfs -text
vendor/raylib/windows/rayguidll.lib filter=lfs diff=lfs merge=lfs -text
vendor/box3d/lib/linux-amd64/libbox3d.a filter=lfs diff=lfs merge=lfs -text
vendor/box3d/lib/darwin/libbox3d.a filter=lfs diff=lfs merge=lfs -text

View File

@@ -27,9 +27,9 @@ jobs:
gmake release
./odin version
./odin report
gmake -C vendor/stb/src
gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src
(cd ./vendor/stb/src/; ./build_stb.sh)
(cd ./vendor/cgltf/src/; ./build_cgltf.sh)
(cd ./vendor/miniaudio/src/; ./build_miniaudio.sh)
./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64 -no-entry-point
@@ -60,9 +60,9 @@ jobs:
gmake release
./odin version
./odin report
gmake -C vendor/stb/src
gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src
(cd ./vendor/stb/src/; ./build_stb.sh)
(cd ./vendor/cgltf/src/; ./build_cgltf.sh)
(cd ./vendor/miniaudio/src/; ./build_miniaudio.sh)
./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64 -no-entry-point
./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
@@ -122,9 +122,9 @@ jobs:
brew install curl mbedtls
- name: Compile needed Vendor
run: |
make -C vendor/stb/src
make -C vendor/cgltf/src
make -C vendor/miniaudio/src
(cd ./vendor/stb/src/; ./build_stb.sh)
(cd ./vendor/cgltf/src/; ./build_cgltf.sh)
(cd ./vendor/miniaudio/src/; ./build_miniaudio.sh)
- name: Odin check
run: ./odin check examples/demo -vet
- name: Odin run
@@ -138,10 +138,13 @@ jobs:
- name: Normal Core library tests
run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized Core library tests
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
- name: Wycheproof tests
run: ./odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed
- name: Noise Protocol Framework tests
run: ./odin test tests/core/crypto/noise -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed
- name: X.509 limbo tests
run: ./odin test tests/core/crypto/x509_limbo -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed
- name: Vendor library tests
run: ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
if: matrix.os != 'macos-15-intel' && matrix.os != 'macos-latest'
@@ -238,12 +241,22 @@ jobs:
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
- name: Wycheproof tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native
- name: Noise Protocol Framework tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin test tests/core/crypto/noise -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native
- name: X.509 limbo tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin test tests/core/crypto/x509_limbo -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native
- name: Vendor library tests
shell: cmd
run: |
@@ -307,9 +320,9 @@ jobs:
- name: Compile needed Vendor
run: |
make -C vendor/stb/src
make -C vendor/cgltf/src
make -C vendor/miniaudio/src
(cd ./vendor/stb/src/; ./build_stb.sh)
(cd ./vendor/cgltf/src/; ./build_cgltf.sh)
(cd ./vendor/miniaudio/src/; ./build_miniaudio.sh)
- name: Odin check examples/all
run: ./odin check examples/all -target:linux_riscv64 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do

View File

@@ -44,27 +44,34 @@ jobs:
name: windows_artifacts
path: dist
build_linux:
name: Linux Build
strategy:
matrix:
arch: [amd64, arm64]
env:
ARCH: ${{ matrix.arch }}
DOCKER_IMAGE: ${{ matrix.arch == 'amd64' && 'alpine:3.24' || 'arm64v8/alpine:3.24' }}
name: Linux Build ${{ matrix.arch }}
if: github.repository == 'odin-lang/Odin'
runs-on: ubuntu-latest
runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: (Linux) Download LLVM and Build Odin
run: |
docker run --rm -v "$PWD:/src" -w /src alpine sh -c '
docker run --rm -v "$PWD:/src" -w /src $DOCKER_IMAGE sh -c '
apk add --no-cache \
musl-dev llvm20-dev clang20 git mold lz4 \
musl-dev llvm20-dev clang20 build-base git mold lz4 \
libxml2-static llvm20-static zlib-static zstd-static \
make &&
git config --global --add safe.directory /src &&
./ci/build_linux_static.sh
'
- name: Odin run
run: ./odin run examples/demo
- name: Copy artifacts
run: |
FILE="odin-linux-amd64-nightly+$(date -I)"
FILE="odin-linux-$ARCH-nightly+$(date -I)"
mkdir $FILE
cp odin $FILE
cp LICENSE $FILE
@@ -78,59 +85,22 @@ jobs:
tar -czvf dist.tar.gz $FILE
- name: Odin run
run: |
FILE="odin-linux-amd64-nightly+$(date -I)"
FILE="odin-linux-$ARCH-nightly+$(date -I)"
$FILE/odin run examples/demo
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: linux_artifacts
path: dist.tar.gz
build_linux_arm:
name: Linux ARM Build
if: github.repository == 'odin-lang/Odin'
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: (Linux ARM) Download LLVM and Build Odin
run: |
docker run --rm -v "$PWD:/src" -w /src arm64v8/alpine sh -c '
apk add --no-cache \
musl-dev llvm20-dev clang20 git mold lz4 \
libxml2-static llvm20-static zlib-static zstd-static \
make &&
./ci/build_linux_static.sh
'
- name: Odin run
run: ./odin run examples/demo
- name: Copy artifacts
run: |
FILE="odin-linux-arm64-nightly+$(date -I)"
mkdir $FILE
cp odin $FILE
cp LICENSE $FILE
cp -r shared $FILE
cp -r base $FILE
cp -r core $FILE
cp -r vendor $FILE
cp -r examples $FILE
./ci/remove_windows_binaries.sh $FILE
# Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
tar -czvf dist.tar.gz $FILE
- name: Odin run
run: |
FILE="odin-linux-arm64-nightly+$(date -I)"
$FILE/odin run examples/demo
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: linux_arm_artifacts
name: ${{ matrix.arch == 'amd64' && 'linux_artifacts' || 'linux_arm_artifacts' }}
path: dist.tar.gz
build_macos:
name: MacOS Build
strategy:
matrix:
arch: [amd64, arm64]
env:
ARCH: ${{ matrix.arch }}
name: Macos Build ${{ matrix.arch }}
if: github.repository == 'odin-lang/Odin'
runs-on: macos-15-intel
runs-on: ${{ matrix.arch == 'amd64' && 'macos-15-intel' || 'macos-latest' }}
steps:
- uses: actions/checkout@v4
with:
@@ -139,6 +109,7 @@ jobs:
run: |
brew update
brew install llvm@20 dylibbundler lld@20
brew link llvm@20
- name: build odin
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
@@ -146,7 +117,7 @@ jobs:
run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
- name: Bundle
run: |
FILE="odin-macos-amd64-nightly+$(date -I)"
FILE="odin-macos-$ARCH-nightly+$(date -I)"
mkdir $FILE
cp odin $FILE
cp LICENSE $FILE
@@ -161,57 +132,16 @@ jobs:
tar -czvf dist.tar.gz $FILE
- name: Odin run
run: |
FILE="odin-macos-amd64-nightly+$(date -I)"
FILE="odin-macos-$ARCH-nightly+$(date -I)"
$FILE/odin run examples/demo
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: macos_artifacts
path: dist.tar.gz
build_macos_arm:
name: MacOS ARM Build
if: github.repository == 'odin-lang/Odin'
runs-on: macos-latest # ARM machine
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Download LLVM and setup PATH
run: |
brew update
brew install llvm@20 dylibbundler lld@20
- name: build odin
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
# not link with libunwind bundled with LLVM but link with libunwind on the system.
run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
- name: Bundle
run: |
FILE="odin-macos-arm64-nightly+$(date -I)"
mkdir $FILE
cp odin $FILE
cp LICENSE $FILE
cp -r shared $FILE
cp -r base $FILE
cp -r core $FILE
cp -r vendor $FILE
cp -r examples $FILE
./ci/remove_windows_binaries.sh $FILE
dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs
# Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
tar -czvf dist.tar.gz $FILE
- name: Odin run
run: |
FILE="odin-macos-arm64-nightly+$(date -I)"
$FILE/odin run examples/demo
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: macos_arm_artifacts
name: ${{ matrix.arch == 'amd64' && 'macos_artifacts' || 'macos_arm_artifacts' }}
path: dist.tar.gz
upload_b2:
runs-on: [ubuntu-latest]
needs: [build_windows, build_macos, build_macos_arm, build_linux, build_linux_arm]
needs: [build_windows, build_macos, build_linux]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5

10
.gitignore vendored
View File

@@ -277,7 +277,15 @@ odin
*.bin
demo.bin
libLLVM*.so*
*.a
# core:rexcode commits its generated encode/decode tables: each arch's tablegen
# emits human-readable Odin, serialized to tables/*.bin, which the library
# #loads at compile time. ISA packages live under core/rexcode/isa/<arch>; the
# broad x86/ rule above otherwise shadows the isa/x86 source package, so
# un-ignore that directory tree and every arch's committed table blobs. Stray
# *.bin build output inside the tree stays ignored -- only tables/*.bin is kept.
!/core/rexcode/isa/x86/
!core/rexcode/isa/*/tables/*.bin
# WASM
*.wasm

View File

@@ -4,6 +4,7 @@ package intrinsics
import "base:runtime"
// Package-Related
is_package_imported :: proc(package_name: string) -> bool ---
@@ -184,6 +185,8 @@ type_is_simd_vector :: proc($T: typeid) -> bool ---
type_is_matrix :: proc($T: typeid) -> bool ---
type_is_fixed_capacity_dynamic_array :: proc($T: typeid) -> bool ---
type_is_internally_pointer_like :: proc($T: typeid) -> bool ---
type_has_nil :: proc($T: typeid) -> bool ---
type_is_matrix_row_major :: proc($T: typeid) -> bool where type_is_matrix(T) ---
@@ -205,12 +208,17 @@ type_bit_set_underlying_type :: proc($T: typeid) -> typeid where type_is_bit_set
type_has_field :: proc($T: typeid, $name: string) -> bool ---
type_field_type :: proc($T: typeid, $name: string) -> typeid ---
type_field_bit_size :: proc($T: typeid, $name: string) -> int where type_is_bit_field(T) ---
type_field_bit_offset :: proc($T: typeid, $name: string) -> int where type_is_bit_field(T) ---
type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) ---
type_proc_parameter_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
type_proc_return_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
type_proc_calling_convention :: proc($T: typeid) -> Odin_Calling_Convention where type_is_proc(T) ---
type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
type_struct_has_implicit_padding :: proc($T: typeid) -> bool where type_is_struct(T) ---
@@ -245,6 +253,8 @@ type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), t
type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
// Returns the canonicalized name of the type, of which is used to produce the pseudo-unique 'typeid'
type_canonical_name :: proc($T: typeid) -> string ---
@@ -345,7 +355,11 @@ simd_trunc :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
// rounding to the nearest integral value; if two values are equally near, rounds to the even one
simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) ---
simd_approx_recip :: proc(x: #simd[N]T) -> #simd[N]T where type_is_float(T)) ---
simd_approx_recip_sqrt :: proc(x: #simd[N]T) -> #simd[N]T where type_is_float(T)) ---
simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) ---
simd_to_bits_signed :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), !type_is_unsigned(Integer) ---
// equivalent to a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0)
simd_lanes_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
@@ -353,6 +367,19 @@ simd_lanes_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
simd_lanes_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
simd_lanes_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
// return {b[0], a[1], b[2], a[3], ...}
simd_odd_even :: proc(a, b: #simd[N]T) -> #simd[N]T ---
// Returns the sums of N consecutive lanes
simd_sums_of_n :: proc(a: #simd[LANES]T, $N: uint) -> #simd[LANES/N]T where is_power_of_two(N) ---
simd_pairwise_add :: proc(a, b: #simd[LANES]T) -> #simd[LANES]T where LANES % 2 == 0 ---
simd_pairwise_sub :: proc(a, b: #simd[LANES]T) -> #simd[LANES]T where LANES % 2 == 0 ---
simd_interleave :: proc(a, ..#simd[LANES/N]T) -> #simd[LANES]T where N >= 1 ---
simd_deinterleave :: proc(a: #simd[LANES]T, $N: uint) -> (..#simd[LANES/N]T) where N >= 1, LANES % N == 0 --- // returns N multiple vectors
// Checks if the current target supports the given target features.
//
// Takes a constant comma-seperated string (eg: "sha512,sse4.1"), or a procedure type which has either
@@ -387,6 +414,14 @@ x86_cpuid :: proc(ax, cx: u32) -> (eax, ebx, ecx, edx: u32) ---
x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
// C specific things
c_va_list :: struct{/*platform specific implementation*/}
c_va_start :: proc(list: ^c_va_list, /*#c_vararg parameter*/ args: ..$T) ---
c_va_end :: proc(list: ^c_va_list) ---
c_va_copy :: proc(dst, src: ^c_va_list) ---
c_va_arg :: proc(list: ^c_va_list, $T: typeid) -> T ---
// Darwin targets only
objc_object :: struct{}

View File

@@ -23,8 +23,27 @@ package runtime
import "base:intrinsics"
/*
Fast_Math_Flag :: enum u8 {
Allow_Reassoc = 0,
No_NaNs = 1,
No_Infs = 2,
No_Signed_Zeros = 3,
Allow_Reciprocal = 4,
Allow_Contract = 5,
Approx_Func = 6,
}
*/
Fast_Math_Flag :: intrinsics.Fast_Math_Flag
// Fast_Math_Flags :: distinct bit_set[Fast_Math_Flag; u32]
Fast_Math_Flags :: intrinsics.Fast_Math_Flags
// NOTE(bill): This must match the compiler's
Calling_Convention :: enum u8 {
/*
enum u8 {
Invalid = 0,
Odin = 1,
Contextless = 2,
@@ -44,6 +63,8 @@ Calling_Convention :: enum u8 {
Preserve_Most = 12,
Preserve_All = 13,
}
*/
Calling_Convention :: type_of(ODIN_DEFAULT_CALLING_CONVENTION)
Type_Info_Enum_Value :: distinct i64
@@ -279,7 +300,79 @@ when ODIN_OS == .Windows {
dll_instance: rawptr
}
// IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it)
// This is safe to change. The log2 size of a cache-line. At minimum it has to
// be six though. Higher cache line sizes are permitted.
MAP_CACHE_LINE_LOG2 :: 6
// The size of a cache-line.
MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2
// The minimum cache-line size allowed by this implementation is 64 bytes since
// we need 6 bits in the base pointer to store the integer log2 capacity, which
// at maximum is 63. Odin uses signed integers to represent length and capacity,
// so only 63 bits are needed in the maximum case.
#assert(MAP_CACHE_LINE_SIZE >= 64)
// Map_Cell type that packs multiple T in such a way to ensure that each T stays
// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0
//
// This means a value of type T will never straddle a cache-line.
//
// When multiple Ts can fit in a single cache-line the data array will have more
// than one element. When it cannot, the data array will have one element and
// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE.
//
// We rely on the type system to do all the arithmetic and padding for us here.
//
// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit
// more involved as there now may be internal padding. The indexing now becomes
//
// N :: len(Map_Cell(T){}.data)
// i := index / N
// j := index % N
// cell[i].data[j]
//
// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some
// optimizations we can do to eliminate the need for any divisions as N will
// be bounded by [1, 64).
//
// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated
// as a regular array of T, which is the case for hashes.
Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) {
data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T,
}
// So we can operate on a cell data structure at runtime without any type
// information, we have a simple table that stores some traits about the cell.
//
// 32-bytes on 64-bit
// 16-bytes on 32-bit
Map_Cell_Info :: struct {
size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
}
Map_Hash :: uintptr
// When working with the type-erased structure at runtime we need information
// about the map to make working with it possible. This info structure stores
// that.
//
// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be
// modified after creation
//
// 32-bytes on 64-bit
// 16-bytes on 32-bit
Map_Info :: struct {
ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit
key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit
}
Source_Code_Location :: struct {
@@ -509,11 +602,9 @@ Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64}
Windows,
Darwin,
Linux,
Essence,
FreeBSD,
OpenBSD,
NetBSD,
Haiku,
WASI,
JS,
Orca,
@@ -565,7 +656,6 @@ Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE)
/*
// Defined internally by the compiler
Odin_Endian_Type :: enum int {
Unknown,
Little,
Big,
}
@@ -578,11 +668,9 @@ ALL_ODIN_OS_TYPES :: Odin_OS_Types{
.Windows,
.Darwin,
.Linux,
.Essence,
.FreeBSD,
.OpenBSD,
.NetBSD,
.Haiku,
.WASI,
.JS,
.Orca,

View File

@@ -2,6 +2,8 @@ package runtime
import "base:intrinsics"
MAP_ENABLED :: !ODIN_BEDROCK
@builtin
Maybe :: union($T: typeid) {T}
@@ -65,7 +67,7 @@ when !NO_DEFAULT_TEMP_ALLOCATOR {
// Initializes the global temporary allocator used as the default `context.temp_allocator`.
// This is ignored when `NO_DEFAULT_TEMP_ALLOCATOR` is true.
@(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR)
init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) {
init_global_temporary_allocator :: proc "odin" (size: int, backup_allocator := context.allocator) {
when !NO_DEFAULT_TEMP_ALLOCATOR {
default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator)
}
@@ -387,7 +389,7 @@ pop_front_safe :: proc {
@builtin
clear :: proc{
clear_dynamic_array,
clear_map,
clear_map where MAP_ENABLED,
clear_fixed_capacity_dynamic_array,
clear_soa_dynamic_array,
@@ -397,7 +399,7 @@ clear :: proc{
@builtin
reserve :: proc{
reserve_dynamic_array,
reserve_map,
reserve_map where MAP_ENABLED,
reserve_soa,
}
@@ -430,7 +432,7 @@ non_zero_resize :: proc{
@builtin
shrink :: proc{
shrink_dynamic_array,
shrink_map,
shrink_map where MAP_ENABLED,
}
// `free` will try to free the passed pointer, with the given `allocator` if the allocator supports this operation.
@@ -471,14 +473,6 @@ delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) ->
delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
return mem_free_with_size(raw_data(array), len(array)*size_of(E), allocator, loc)
}
// `delete_map` will try to free the underlying data of the passed map, with the given `allocator` if the allocator supports this operation.
//
// Note: Prefer the procedure group `delete`.
@builtin
delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error {
return map_free_dynamic(transmute(Raw_Map)m, map_info(T), loc)
}
@builtin
delete_string16 :: proc(str: string16, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
@@ -489,6 +483,16 @@ delete_cstring16 :: proc(str: cstring16, allocator := context.allocator, loc :=
return mem_free((^u16)(str), allocator, loc)
}
when MAP_ENABLED {
// `delete_map` will try to free the underlying data of the passed map, with the given `allocator` if the allocator supports this operation.
//
// Note: Prefer the procedure group `delete`.
@builtin
delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error {
return map_free_dynamic(transmute(Raw_Map)m, map_info(T), loc)
}
}
// `delete` will try to free the underlying data of the passed built-in data structure (string, cstring, dynamic array, slice, or map), with the given `allocator` if the allocator supports this operation.
//
// Note: Prefer `delete` over the specific `delete_*` procedures where possible.
@@ -498,7 +502,7 @@ delete :: proc{
delete_cstring,
delete_dynamic_array,
delete_slice,
delete_map,
delete_map where MAP_ENABLED,
delete_soa_slice,
delete_soa_dynamic_array,
delete_string16,
@@ -513,7 +517,7 @@ new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location)
t = (^T)(raw_data(mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return))
return
}
@(require_results)
@(builtin, require_results)
new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) {
t = (^T)(raw_data(mem_alloc_bytes(size_of(T), alignment, allocator, loc) or_return))
return
@@ -530,7 +534,7 @@ new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_locat
DEFAULT_DYNAMIC_ARRAY_CAPACITY :: 8
@(require_results)
@(builtin, require_results)
make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (res: T, err: Allocator_Error) #optional_allocator_error {
err = _make_aligned_type_erased(&res, size_of(E), len, alignment, allocator, loc)
return
@@ -597,29 +601,32 @@ _make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, ali
return
}
// `make_map` initializes a map with an allocator. Like `new`, the first argument is a type, not a value.
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
//
// Note: Prefer using the procedure group `make`.
@(builtin, require_results)
make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator, loc := #caller_location) -> (m: T) {
m.allocator = allocator
return m
when MAP_ENABLED {
// `make_map` initializes a map with an allocator. Like `new`, the first argument is a type, not a value.
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
//
// Note: Prefer using the procedure group `make`.
@(builtin, require_results)
make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator, loc := #caller_location) -> (m: T) {
m.allocator = allocator
return m
}
// `make_map_cap` initializes a map with an allocator and allocates space using `capacity`.
// Like `new`, the first argument is a type, not a value.
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
//
// Note: Prefer using the procedure group `make`.
@(builtin, require_results)
make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
make_map_expr_error_loc(loc, capacity)
context.allocator = allocator
err = reserve_map(&m, capacity, loc)
return
}
}
// `make_map_cap` initializes a map with an allocator and allocates space using `capacity`.
// Like `new`, the first argument is a type, not a value.
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
//
// Note: Prefer using the procedure group `make`.
@(builtin, require_results)
make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
make_map_expr_error_loc(loc, capacity)
context.allocator = allocator
err = reserve_map(&m, capacity, loc)
return
}
// `make_multi_pointer` allocates and initializes a multi-pointer. Like `new`, the first argument is a type, not a value.
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
//
@@ -649,8 +656,8 @@ make :: proc{
make_dynamic_array,
make_dynamic_array_len,
make_dynamic_array_len_cap,
make_map,
make_map_cap,
make_map where MAP_ENABLED,
make_map_cap where MAP_ENABLED,
make_multi_pointer,
make_soa_slice,
@@ -659,64 +666,67 @@ make :: proc{
make_soa_dynamic_array_len_cap,
}
when MAP_ENABLED {
// `clear_map` will set the length of a passed map to `0`
//
// Note: Prefer the procedure group `clear`
@builtin
clear_map :: proc "contextless" (m: ^$T/map[$K]$V) {
if m == nil {
return
}
map_clear_dynamic((^Raw_Map)(m), map_info(T))
}
// `clear_map` will set the length of a passed map to `0`
//
// Note: Prefer the procedure group `clear`
@builtin
clear_map :: proc "contextless" (m: ^$T/map[$K]$V) {
if m == nil {
// `reserve_map` will try to reserve memory of a passed map to the requested element count (setting the `cap`).
//
// Note: Prefer the procedure group `reserve`
@builtin
reserve_map :: proc(m: ^$T/map[$K]$V, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc)
}
// Shrinks the capacity of a map down to the current length.
//
// Note: Prefer the procedure group `shrink`
@builtin
shrink_map :: proc(m: ^$T/map[$K]$V, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
if m != nil {
return map_shrink_dynamic((^Raw_Map)(m), map_info(T), loc)
}
return
}
map_clear_dynamic((^Raw_Map)(m), map_info(T))
}
// `reserve_map` will try to reserve memory of a passed map to the requested element count (setting the `cap`).
//
// Note: Prefer the procedure group `reserve`
@builtin
reserve_map :: proc(m: ^$T/map[$K]$V, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc)
}
// Shrinks the capacity of a map down to the current length.
//
// Note: Prefer the procedure group `shrink`
@builtin
shrink_map :: proc(m: ^$T/map[$K]$V, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
if m != nil {
return map_shrink_dynamic((^Raw_Map)(m), map_info(T), loc)
}
return
}
// The delete_key built-in procedure deletes the element with the specified key (m[key]) from the map.
// If m is nil, or there is no such element, this procedure is a no-op
@builtin
delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: V) {
if m != nil {
key := key
old_k, old_v, ok := map_erase_dynamic((^Raw_Map)(m), map_info(T), uintptr(&key))
if ok {
deleted_key = (^K)(old_k)^
deleted_value = (^V)(old_v)^
// The delete_key built-in procedure deletes the element with the specified key (m[key]) from the map.
// If m is nil, or there is no such element, this procedure is a no-op
// It is safe to use `delete_key` while iterating a map.
// But if you iterate across a map and insert a new key, it could resize which means you are not iterating across all of the elements.
@builtin
delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: V) {
if m != nil {
key := key
old_k, old_v, ok := map_erase_dynamic((^Raw_Map)(m), map_info(T), uintptr(&key))
if ok {
deleted_key = (^K)(old_k)^
deleted_value = (^V)(old_v)^
}
}
return
}
return
}
_append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
_append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
if array == nil {
return
}
if array.cap < array.len+1 {
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY
cap := max(2 * array.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
// do not 'or_return' here as it could be a partial success
err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
err = _reserve_dynamic_array_unsafe(array, size_of_elem, align_of_elem, cap, should_zero, loc)
}
if array.cap-array.len > 0 {
data := ([^]byte)(array.data)
@@ -724,20 +734,71 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a
data = data[array.len*size_of_elem:]
intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem)
array.len += 1
n = 1
num_appended = 1
}
return
}
_append_elem_ptr :: #force_no_inline proc(array: ^Raw_Dynamic_Array, arg: rawptr, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
if array == nil {
return
}
if array.cap < array.len+1 {
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
cap := max(2 * array.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
// do not 'or_return' here as it could be a partial success
err = _reserve_dynamic_array_unsafe(array, size_of(rawptr), align_of(rawptr), cap, should_zero, loc)
}
if array.cap-array.len > 0 {
data := ([^]rawptr)(array.data)
assert(data != nil, loc=loc)
data[array.len] = arg
array.len += 1
num_appended = 1
}
return
}
// `append_elem` appends an element to the end of a dynamic array.
@builtin
append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
when size_of(E) == 0 {
if array == nil {
return
}
(^Raw_Dynamic_Array)(array).len += 1
return 1, nil
} else {
} else when intrinsics.type_is_internally_pointer_like(E) {
return _append_elem_ptr((^Raw_Dynamic_Array)(array), rawptr(arg), should_zero=true, loc=loc)
} else when ODIN_OPTIMIZATION_MODE <= .Size {
arg := arg
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc)
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, should_zero=true, loc=loc)
} else {
if array == nil {
return
}
arg := arg
arr := (^Raw_Dynamic_Array)(array)
if arr.cap < arr.len+1 {
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
cap := max(2 * arr.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
// do not 'or_return' here as it could be a partial success
err = _reserve_dynamic_array_unsafe(arr, size_of(E), align_of(E), cap, should_zero=true, loc=loc)
}
if arr.cap-arr.len > 0 {
// NOTE(bill, 2026-06-19): When this is in the hot path with -o:speed or -o:aggressive enabled,
// this code path cannot rely on type erasure and `mem_copy_non_overlapping`.
// So directly inlining the call and storing the argument like this helps the optimize a lot
assert(arr.data != nil, loc=loc)
([^]E)(arr.data)[arr.len] = arg
arr.len += 1
num_appended = 1
}
return
}
}
@@ -745,17 +806,42 @@ append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller
//
// Note: Prefer using the procedure group `non_zero_append
@builtin
non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
when size_of(E) == 0 {
(^Raw_Dynamic_Array)(array).len += 1
return 1, nil
} else {
} else when intrinsics.type_is_internally_pointer_like(E) {
return _append_elem_ptr((^Raw_Dynamic_Array)(array), rawptr(arg), should_zero=false, loc=loc)
} else when ODIN_OPTIMIZATION_MODE <= .Size {
arg := arg
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, false, loc=loc)
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, should_zero=false, loc=loc)
} else {
if array == nil {
return
}
arg := arg
arr := (^Raw_Dynamic_Array)(array)
if arr.cap < arr.len+1 {
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
cap := max(2 * arr.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
// do not 'or_return' here as it could be a partial success
err = _reserve_dynamic_array_unsafe(arr, size_of(E), align_of(E), cap, should_zero=false, loc=loc)
}
if arr.cap-arr.len > 0 {
// NOTE(bill, 2026-06-19): When this is in the hot path with -o:speed or -o:aggressive enabled,
// this code path cannot rely on type erasure and `mem_copy_non_overlapping`.
// So directly inlining the call and storing the argument like this helps the optimize a lot
assert(arr.data != nil, loc=loc)
([^]E)(arr.data)[arr.len] = arg
arr.len += 1
num_appended = 1
}
return
}
}
_append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (n: int, err: Allocator_Error) #optional_allocator_error {
_append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
if array == nil {
return 0, nil
}
@@ -786,7 +872,7 @@ _append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem,
//
// Note: Prefer using the procedure group `append`.
@builtin
append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
when size_of(E) == 0 {
a := (^Raw_Dynamic_Array)(array)
a.len += len(args)
@@ -800,7 +886,7 @@ append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #ca
//
// Note: Prefer using the procedure group `non_zero_append
@builtin
non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
when size_of(E) == 0 {
a := (^Raw_Dynamic_Array)(array)
a.len += len(args)
@@ -811,7 +897,7 @@ non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, l
}
// The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type
_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg))
}
@@ -819,14 +905,14 @@ _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_ze
//
// Note: Prefer using the procedure group `append`.
@builtin
append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
return _append_elem_string(array, arg, true, loc)
}
// `non_zero_append_elem_string` appends a string to the end of a dynamic array of bytes, without zeroing any reserved memory
//
// Note: Prefer using the procedure group `non_zero_append`.
@builtin
non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
return _append_elem_string(array, arg, false, loc)
}
@@ -834,7 +920,7 @@ non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, l
//
// Note: Prefer using the procedure group `non_zero_append`.
@builtin
non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, arg: $A/string) -> (n: int) {
non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, arg: $A/string) -> (num_appended: int) {
return append_fixed_capacity_elem(array, transmute([]byte)arg)
}
@@ -844,11 +930,11 @@ non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dy
//
// Note: Prefer using the procedure group `append`.
@builtin
append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
n_arg: int
for arg in args {
n_arg, err = append(array, ..transmute([]E)(arg), loc=loc)
n += n_arg
num_appended += n_arg
if err != nil {
return
}
@@ -859,7 +945,7 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_
// `append_fixed_capacity_elem` appends an element to the end of a fixed capacity dynamic array. Returns 0 on failure
@builtin
append_fixed_capacity_elem :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast arg: E) -> (n: int) {
append_fixed_capacity_elem :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast arg: E) -> (num_appended: int) {
Raw :: Raw_Fixed_Capacity_Dynamic_Array(N, E)
if (^Raw)(array).len >= N {
@@ -876,29 +962,29 @@ append_fixed_capacity_elem :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #n
// `append_fixed_capacity_elem` appends an element to the end of a fixed capacity dynamic array. Returns 0 on failure
@builtin
append_fixed_capacity_elems :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast args: ..E) -> (n: int) {
append_fixed_capacity_elems :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast args: ..E) -> (num_appended: int) {
Raw :: Raw_Fixed_Capacity_Dynamic_Array(N, E)
raw := (^Raw)(array)
n = min(N - len(array), len(args))
num_appended = min(N - len(array), len(args))
#no_bounds_check when size_of(E) != 0 {
intrinsics.mem_copy(&raw.data[raw.len], raw_data(args), n*size_of(E))
intrinsics.mem_copy(&raw.data[raw.len], raw_data(args), num_appended*size_of(E))
}
raw.len += n
return n
raw.len += num_appended
return num_appended
}
// The append_fixed_capacity_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type
//
// Note: Prefer using the procedure group `append`.
@builtin
append_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, args: ..string) -> (n: int) {
append_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, args: ..string) -> (num_appended: int) {
n_arg: int
for arg in args {
n_arg = append_fixed_capacity_elems(array, ..transmute([]E)(arg))
n += n_arg
num_appended += n_arg
if n_arg < len(arg) {
return
}
@@ -1191,7 +1277,7 @@ assign_at_elem_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[d
array[index] = arg
ok = true
} else {
resize(array, index+1, loc) or_return
resize(array, index+1) or_return
array[index] = arg
ok = true
}
@@ -1210,7 +1296,7 @@ assign_at_elems_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[
copy(array[index:], args)
ok = true
} else {
resize(array, new_size, loc) or_return
resize(array, new_size) or_return
copy(array[index:], args)
ok = true
}
@@ -1228,7 +1314,7 @@ assign_at_elem_string_fixed_capacity_dynamic_array :: proc "contextless" (array:
copy(array[index:], arg)
ok = true
} else {
resize(array, new_size, loc) or_return
resize(array, new_size) or_return
copy(array[index:], arg)
ok = true
}
@@ -1309,6 +1395,35 @@ _reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_e
return nil
}
_reserve_dynamic_array_unsafe :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
if capacity <= a.cap {
return nil
}
if a.allocator.procedure == nil {
a.allocator = context.allocator
assert(a.allocator.procedure != nil)
}
old_size := a.cap * size_of_elem
new_size := capacity * size_of_elem
allocator := a.allocator
new_data: []byte
if should_zero {
new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
} else {
new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
}
if new_data == nil && new_size > 0 {
return .Out_Of_Memory
}
a.data = raw_data(new_data)
a.cap = capacity
return nil
}
// `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
//
// When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`).
@@ -1468,52 +1583,54 @@ _shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem
return true, nil
}
@builtin
map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (ptr: ^V) {
key, value := key, value
return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc))
}
// Explicitly inserts a key and value into a map `m`, the same as `map_insert`, but the return values differ.
// - `prev_key` will return the previous pointer of a key if it exists, check `found_previous` if was previously found
// - `value_ptr` will return the pointer of the memory where the insertion happens, and `nil` if the map failed to resize
// - `found_previous` will be true a previous key was found
@(builtin, require_results)
map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (prev_key: K, value_ptr: ^V, found_previous: bool) {
key, value := key, value
kp, vp := __dynamic_map_set_extra_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)
if kp != nil {
prev_key = (^K)(kp)^
found_previous = true
when MAP_ENABLED {
@builtin
map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (ptr: ^V) {
key, value := key, value
return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc))
}
value_ptr = (^V)(vp)
return
}
/*
Retrieves a pointer to the key and value for a possibly just inserted entry into the map.
// Explicitly inserts a key and value into a map `m`, the same as `map_insert`, but the return values differ.
// - `prev_key` will return the previous pointer of a key if it exists, check `found_previous` if was previously found
// - `value_ptr` will return the pointer of the memory where the insertion happens, and `nil` if the map failed to resize
// - `found_previous` will be true a previous key was found
@(builtin, require_results)
map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (prev_key: K, value_ptr: ^V, found_previous: bool) {
key, value := key, value
kp, vp := __dynamic_map_set_extra_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)
if kp != nil {
prev_key = (^K)(kp)^
found_previous = true
}
value_ptr = (^V)(vp)
return
}
If the `key` was not in the map `m`, an entry is inserted with the zero value and `just_inserted` will be `true`.
Otherwise the existing entry is left untouched and pointers to its key and value are returned.
/*
Retrieves a pointer to the key and value for a possibly just inserted entry into the map.
If the map has to grow in order to insert the entry and the allocation fails, `err` is set and returned.
If the `key` was not in the map `m`, an entry is inserted with the zero value and `just_inserted` will be `true`.
Otherwise the existing entry is left untouched and pointers to its key and value are returned.
If `err` is `nil`, `key_ptr` and `value_ptr` are valid pointers and will not be `nil`.
If the map has to grow in order to insert the entry and the allocation fails, `err` is set and returned.
WARN: User modification of the key pointed at by `key_ptr` should only be done if the new key is equal to (in hash) the old key.
If that is not the case you will corrupt the map.
*/
@(builtin, require_results)
map_entry :: proc(m: ^$T/map[$K]$V, key: K, loc := #caller_location) -> (key_ptr: ^K, value_ptr: ^V, just_inserted: bool, err: Allocator_Error) {
key := key
zero: V
If `err` is `nil`, `key_ptr` and `value_ptr` are valid pointers and will not be `nil`.
_key_ptr, _value_ptr: rawptr
_key_ptr, _value_ptr, just_inserted, err = __dynamic_map_entry((^Raw_Map)(m), map_info(T), &key, &zero, loc)
WARN: User modification of the key pointed at by `key_ptr` should only be done if the new key is equal to (in hash) the old key.
If that is not the case you will corrupt the map.
*/
@(builtin, require_results)
map_entry :: proc(m: ^$T/map[$K]$V, key: K, loc := #caller_location) -> (key_ptr: ^K, value_ptr: ^V, just_inserted: bool, err: Allocator_Error) {
key := key
zero: V
key_ptr = (^K)(_key_ptr)
value_ptr = (^V)(_value_ptr)
return
_key_ptr, _value_ptr: rawptr
_key_ptr, _value_ptr, just_inserted, err = __dynamic_map_entry((^Raw_Map)(m), map_info(T), &key, &zero, loc)
key_ptr = (^K)(_key_ptr)
value_ptr = (^V)(_value_ptr)
return
}
}
@@ -1622,14 +1739,14 @@ ensure_contextless :: proc "contextless" (condition: bool, message := #caller_ex
}
}
// Panics the program with a message to indicate something has yet to be implemented.
// Panics the program with a message.
// This uses the `default_assertion_contextless_failure_proc` to assert.
@builtin
panic_contextless :: proc "contextless" (message: string, loc := #caller_location) -> ! {
default_assertion_contextless_failure_proc("panic", message, loc)
}
// Panics the program with a message.
// Panics the program with a message to indicate something has yet to be implemented.
// This uses the `default_assertion_contextless_failure_proc` to assert.
@builtin
unimplemented_contextless :: proc "contextless" (message := "", loc := #caller_location) -> ! {

View File

@@ -97,15 +97,6 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
@(require_results)
arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint {
p := ptr
modulo := p & (align-1)
if modulo != 0 {
p += align - modulo
}
return p
}
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
size := size

View File

@@ -7,7 +7,7 @@ when NO_DEFAULT_TEMP_ALLOCATOR {
// `Default_Temp_Allocator` is a `nil_allocator` when `NO_DEFAULT_TEMP_ALLOCATOR` is `true`.
Default_Temp_Allocator :: struct {}
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {}
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator: Allocator) {}
default_temp_allocator_destroy :: proc "contextless" (s: ^Default_Temp_Allocator) {}
@@ -30,7 +30,7 @@ when NO_DEFAULT_TEMP_ALLOCATOR {
arena: Arena,
}
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator: Allocator) {
_ = arena_init(&s.arena, uint(size), backing_allocator)
}

View File

@@ -1,3 +1,4 @@
#+build !bedrock
package runtime
import "base:intrinsics"
@@ -47,60 +48,6 @@ MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements
// Has to be less than 100% though.
#assert(MAP_LOAD_FACTOR < 100)
// This is safe to change. The log2 size of a cache-line. At minimum it has to
// be six though. Higher cache line sizes are permitted.
MAP_CACHE_LINE_LOG2 :: 6
// The size of a cache-line.
MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2
// The minimum cache-line size allowed by this implementation is 64 bytes since
// we need 6 bits in the base pointer to store the integer log2 capacity, which
// at maximum is 63. Odin uses signed integers to represent length and capacity,
// so only 63 bits are needed in the maximum case.
#assert(MAP_CACHE_LINE_SIZE >= 64)
// Map_Cell type that packs multiple T in such a way to ensure that each T stays
// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0
//
// This means a value of type T will never straddle a cache-line.
//
// When multiple Ts can fit in a single cache-line the data array will have more
// than one element. When it cannot, the data array will have one element and
// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE.
//
// We rely on the type system to do all the arithmetic and padding for us here.
//
// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit
// more involved as there now may be internal padding. The indexing now becomes
//
// N :: len(Map_Cell(T){}.data)
// i := index / N
// j := index % N
// cell[i].data[j]
//
// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some
// optimizations we can do to eliminate the need for any divisions as N will
// be bounded by [1, 64).
//
// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated
// as a regular array of T, which is the case for hashes.
Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) {
data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T,
}
// So we can operate on a cell data structure at runtime without any type
// information, we have a simple table that stores some traits about the cell.
//
// 32-bytes on 64-bit
// 16-bytes on 32-bit
Map_Cell_Info :: struct {
size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
}
// map_cell_info :: proc "contextless" ($T: typeid) -> ^Map_Cell_Info {...}
map_cell_info :: intrinsics.type_map_cell_info
@@ -226,8 +173,6 @@ map_data :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr {
}
Map_Hash :: uintptr
TOMBSTONE_MASK :: 1<<(size_of(Map_Hash)*8 - 1)
// Procedure to check if a slot is empty for a given hash. This is represented
@@ -288,23 +233,6 @@ map_probe_distance :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Ha
return (slot - uintptr(hash)) & (capacity - 1) // NOTE(bill): this is equivalent to the above, but less operations
}
// When working with the type-erased structure at runtime we need information
// about the map to make working with it possible. This info structure stores
// that.
//
// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be
// modified after creation
//
// 32-bytes on 64-bit
// 16-bytes on 32-bit
Map_Info :: struct {
ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit
key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit
}
// The Map_Info structure is basically a pseudo-table of information for a given K and V pair.
// map_info :: proc "contextless" ($T: typeid/map[$K]$V) -> ^Map_Info {...}
map_info :: intrinsics.type_map_info
@@ -905,22 +833,22 @@ __dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info:
return nil, false
}
__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> rawptr {
__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> (value_ptr: rawptr, err: Allocator_Error) #optional_allocator_error {
return __dynamic_map_set(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc)
}
// IMPORTANT: USED WITHIN THE COMPILER
__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> rawptr {
__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> (value_ptr: rawptr, err: Allocator_Error) #optional_allocator_error {
if found := __dynamic_map_get(m, info, hash, key); found != nil {
intrinsics.mem_copy_non_overlapping(found, value, info.vs.size_of_type)
return found
return found, nil
}
hash := hash
err, has_grown := __dynamic_map_check_grow(m, info, loc)
if err != nil {
return nil
err_grow, has_grown := __dynamic_map_check_grow(m, info, loc)
if err_grow != nil {
return nil, err_grow
}
if has_grown {
hash = info.key_hasher(key, map_seed(m^))
@@ -930,7 +858,7 @@ __dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_In
if result != 0 {
m.len += 1
}
return rawptr(result)
return rawptr(result), nil
}
__dynamic_map_set_extra_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> (prev_key_ptr, value_ptr: rawptr) {
return __dynamic_map_set_extra(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc)
@@ -1060,4 +988,4 @@ default_hasher_quaternion256 :: proc "contextless" (x, y, z, w: f64, seed: uintp
seed = default_hasher_f64(z, seed)
seed = default_hasher_f64(w, seed)
return seed
}
}

View File

@@ -1,5 +1,5 @@
#+private
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
#+build linux, darwin, freebsd, openbsd, netbsd
#+no-instrumentation
package runtime
@@ -9,13 +9,15 @@ when ODIN_BUILD_MODE == .Dynamic {
@(link_name="_odin_entry_point", linkage="strong", require/*, link_section=".init"*/)
_odin_entry_point :: proc "c" () {
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
}
@(link_name="_odin_exit_point", linkage="strong", require/*, link_section=".fini"*/)
_odin_exit_point :: proc "c" () {
context = default_context()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK {
#force_no_inline _cleanup_runtime()
}
}
@(link_name="main", linkage="strong", require)
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
@@ -42,9 +44,9 @@ when ODIN_BUILD_MODE == .Dynamic {
_start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! {
args__ = argv[:argc]
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
intrinsics.syscall(SYS_exit, 0)
unreachable()
}
@@ -53,9 +55,9 @@ when ODIN_BUILD_MODE == .Dynamic {
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
args__ = argv[:argc]
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
return 0
}
}

View File

@@ -16,10 +16,10 @@ when ODIN_BUILD_MODE == .Dynamic {
switch dll_forward_reason {
case .Process_Attach:
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
case .Process_Detach:
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
case .Thread_Attach:
break
case .Thread_Detach:
@@ -35,18 +35,18 @@ when ODIN_BUILD_MODE == .Dynamic {
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
args__ = argv[:argc]
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
return 0
}
} else when ODIN_NO_CRT {
@(link_name="mainCRTStartup", linkage="strong", require)
mainCRTStartup :: proc "system" () -> i32 {
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
return 0
}
} else {
@@ -54,9 +54,9 @@ when ODIN_BUILD_MODE == .Dynamic {
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
args__ = argv[:argc]
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
return 0
}
}

View File

@@ -1,4 +1,4 @@
#+build js, wasi, freestanding, essence
#+build js, wasi, freestanding
#+private
package runtime

View File

@@ -1,4 +1,4 @@
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
#+build linux, darwin, freebsd, openbsd, netbsd
#+private
package runtime

View File

@@ -8,10 +8,10 @@ IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
@(private)
RUNTIME_LINKAGE :: "strong" when ODIN_USE_SEPARATE_MODULES else
"internal" when ODIN_NO_ENTRY_POINT && (ODIN_BUILD_MODE == .Static || ODIN_BUILD_MODE == .Dynamic || ODIN_BUILD_MODE == .Object) else
"strong" when ODIN_BUILD_MODE == .Dynamic else
"strong" when !ODIN_NO_CRT else
"internal"
"internal" when ODIN_NO_ENTRY_POINT && (ODIN_BUILD_MODE == .Static || ODIN_BUILD_MODE == .Dynamic || ODIN_BUILD_MODE == .Object) else
"strong" when ODIN_BUILD_MODE == .Dynamic else
"strong" when !ODIN_NO_CRT else
"internal"
RUNTIME_REQUIRE :: false // !ODIN_TILDE
@(private)
@@ -23,12 +23,42 @@ HAS_HARDWARE_SIMD :: false when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !
false when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else
true
// Size of a native SIMD register for the current compilation target
NATIVE_SIMD_BIT_WIDTH ::
512 when (ODIN_ARCH == .amd64) && intrinsics.has_target_feature("avx512f") else
256 when (ODIN_ARCH == .amd64) && (intrinsics.has_target_feature("avx2") || intrinsics.has_target_feature("avx")) else
// Fallback for no hardware SIMD, but also SSE, NEON, SVE, RVV and WASM SIMD128.
128
@(private)
byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check {
return ([^]byte)(data)[:max(len, 0)]
}
@(require_results)
align_forward_uint :: #force_inline proc "odin" (ptr, align: uint) -> uint {
assert(is_power_of_two_uint(align))
return (ptr + align-1) & ~(align-1)
}
@(require_results)
align_forward_int :: #force_inline proc "odin" (ptr, align: int) -> int {
assert(is_power_of_two_int(align))
return int(align_forward_uint(uint(ptr), uint(align)))
}
@(require_results)
align_forward_uintptr :: #force_inline proc "odin" (ptr, align: uintptr) -> uintptr {
return uintptr(align_forward_uint(uint(ptr), uint(align)))
}
align_forward :: proc {
align_forward_int,
align_forward_uint,
align_forward_uintptr,
}
@(require_results)
is_power_of_two_int :: #force_inline proc "contextless" (x: int) -> bool {
if x <= 0 {
return false
@@ -36,51 +66,17 @@ is_power_of_two_int :: #force_inline proc "contextless" (x: int) -> bool {
return (x & (x-1)) == 0
}
align_forward_int :: #force_inline proc "odin" (ptr, align: int) -> int {
assert(is_power_of_two_int(align))
p := ptr
modulo := p & (align-1)
if modulo != 0 {
p += align - modulo
}
return p
}
@(require_results)
is_power_of_two_uint :: #force_inline proc "contextless" (x: uint) -> bool {
if x <= 0 {
if x == 0 {
return false
}
return (x & (x-1)) == 0
}
align_forward_uint :: #force_inline proc "odin" (ptr, align: uint) -> uint {
assert(is_power_of_two_uint(align))
p := ptr
modulo := p & (align-1)
if modulo != 0 {
p += align - modulo
}
return p
}
@(require_results)
is_power_of_two_uintptr :: #force_inline proc "contextless" (x: uintptr) -> bool {
if x <= 0 {
return false
}
return (x & (x-1)) == 0
}
align_forward_uintptr :: #force_inline proc "odin" (ptr, align: uintptr) -> uintptr {
assert(is_power_of_two_uintptr(align))
p := ptr
modulo := p & (align-1)
if modulo != 0 {
p += align - modulo
}
return p
return is_power_of_two_uint(uint(x))
}
is_power_of_two :: proc {
@@ -89,12 +85,6 @@ is_power_of_two :: proc {
is_power_of_two_uintptr,
}
align_forward :: proc {
align_forward_int,
align_forward_uint,
align_forward_uintptr,
}
mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr {
if data == nil {
return nil
@@ -1174,236 +1164,6 @@ extendhfsf2 :: proc "c" (value: __float16) -> f32 {
return gnu_h2f_ieee(value)
}
@(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
floattidf :: proc "c" (a: i128) -> f64 {
DBL_MANT_DIG :: 53
if a == 0 {
return 0.0
}
a := a
N :: size_of(i128) * 8
s := a >> (N-1)
a = (a ~ s) - s
sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits
e := i32(sd - 1) // exponent
if sd > DBL_MANT_DIG {
switch sd {
case DBL_MANT_DIG + 1:
a <<= 1
case DBL_MANT_DIG + 2:
// okay
case:
a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0)
}
a |= i128((a & 4) != 0)
a += 1
a >>= 2
if a & (i128(1) << DBL_MANT_DIG) != 0 {
a >>= 1
e += 1
}
} else {
a <<= u128(DBL_MANT_DIG - sd) & 127
}
fb: [2]u32
fb[1] = (u32(s) & 0x80000000) | // sign
(u32(e + 1023) << 20) | // exponent
u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high
fb[0] = u32(a) // mantissa-low
return transmute(f64)fb
}
@(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
floattidf_unsigned :: proc "c" (a: u128) -> f64 {
DBL_MANT_DIG :: 53
if a == 0 {
return 0.0
}
a := a
N :: size_of(u128) * 8
sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits
e := i32(sd - 1) // exponent
if sd > DBL_MANT_DIG {
switch sd {
case DBL_MANT_DIG + 1:
a <<= 1
case DBL_MANT_DIG + 2:
// okay
case:
a = u128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
u128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0)
}
a |= u128((a & 4) != 0)
a += 1
a >>= 2
if a & (1 << DBL_MANT_DIG) != 0 {
a >>= 1
e += 1
}
} else {
a <<= u128(DBL_MANT_DIG - sd)
}
fb: [2]u32
fb[1] = (0) | // sign
u32((e + 1023) << 20) | // exponent
u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high
fb[0] = u32(a) // mantissa-low
return transmute(f64)fb
}
@(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 {
// TODO(bill): implement `fixunsdfti` correctly
x := u64(a)
return u128(x)
}
@(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 {
// TODO(bill): implement `fixunsdfdi` correctly
x := i64(a)
return i128(x)
}
@(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
umodti3 :: proc "c" (a, b: u128) -> u128 {
r: u128 = ---
_ = udivmod128(a, b, &r)
return r
}
@(link_name="__udivmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
return udivmod128(a, b, rem)
}
when !IS_WASM {
@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
udivti3 :: proc "c" (a, b: u128) -> u128 {
return udivmodti4(a, b, nil)
}
}
@(link_name="__modti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
modti3 :: proc "c" (a, b: i128) -> i128 {
s_a := a >> (128 - 1)
s_b := b >> (128 - 1)
an := (a ~ s_a) - s_a
bn := (b ~ s_b) - s_b
r: u128 = ---
_ = udivmod128(u128(an), u128(bn), &r)
return (i128(r) ~ s_a) - s_a
}
@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
s_a := a >> (128 - 1) // -1 if negative or 0
s_b := b >> (128 - 1)
an := (a ~ s_a) - s_a // absolute
bn := (b ~ s_b) - s_b
s_b ~= s_a // quotient sign
u_s_b := u128(s_b)
u_s_a := u128(s_a)
r: u128 = ---
u := i128((udivmodti4(u128(an), u128(bn), &r) ~ u_s_b) - u_s_b) // negate if negative
rem^ = i128((r ~ u_s_a) - u_s_a)
return u
}
@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
divti3 :: proc "c" (a, b: i128) -> i128 {
s_a := a >> (128 - 1) // -1 if negative or 0
s_b := b >> (128 - 1)
an := (a ~ s_a) - s_a // absolute
bn := (b ~ s_b) - s_b
s_a ~= s_b // quotient sign
u_s_a := u128(s_a)
return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative
}
@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
fixdfti :: proc "c" (a: u64) -> i128 {
significandBits :: 52
typeWidth :: (size_of(u64)*8)
exponentBits :: (typeWidth - significandBits - 1)
maxExponent :: ((1 << exponentBits) - 1)
exponentBias :: (maxExponent >> 1)
implicitBit :: (u64(1) << significandBits)
significandMask :: (implicitBit - 1)
signBit :: (u64(1) << (significandBits + exponentBits))
absMask :: (signBit - 1)
exponentMask :: (absMask ~ significandMask)
// Break a into sign, exponent, significand
aRep := a
aAbs := aRep & absMask
sign := i128(-1 if aRep & signBit != 0 else 1)
exponent := u64((aAbs >> significandBits) - exponentBias)
significand := u64((aAbs & significandMask) | implicitBit)
// If exponent is negative, the result is zero.
if exponent < 0 {
return 0
}
// If the value is too large for the integer type, saturate.
if exponent >= size_of(i128) * 8 {
return max(i128) if sign == 1 else min(i128)
}
// If 0 <= exponent < significandBits, right shift to get the result.
// Otherwise, shift left.
if exponent < significandBits {
return sign * i128(significand >> (significandBits - exponent))
} else {
return sign * (i128(significand) << (exponent - significandBits))
}
}
__write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
for i in 0..<size {
j := offset+i
the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
dst[j>>3] &~= 1<<(j&7)
dst[j>>3] |= the_bit<<(j&7)
}
}
__read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
for j in 0..<size {
i := offset+j
the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
dst[j>>3] &~= 1<<(j&7)
dst[j>>3] |= the_bit<<(j&7)
}
}
when .Address in ODIN_SANITIZER_FLAGS {
foreign {
@(require)

View File

@@ -0,0 +1,217 @@
#+vet !cast
#+build !bedrock
package runtime
import "base:intrinsics"
@(private="file")
IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
@(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
floattidf :: proc "c" (a: i128) -> f64 {
DBL_MANT_DIG :: 53
if a == 0 {
return 0.0
}
a := a
N :: size_of(i128) * 8
s := a >> (N-1)
a = (a ~ s) - s
sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits
e := i32(sd - 1) // exponent
if sd > DBL_MANT_DIG {
switch sd {
case DBL_MANT_DIG + 1:
a <<= 1
case DBL_MANT_DIG + 2:
// okay
case:
a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0)
}
a |= i128((a & 4) != 0)
a += 1
a >>= 2
if a & (i128(1) << DBL_MANT_DIG) != 0 {
a >>= 1
e += 1
}
} else {
a <<= u128(DBL_MANT_DIG - sd) & 127
}
fb: [2]u32
fb[1] = (u32(s) & 0x80000000) | // sign
(u32(e + 1023) << 20) | // exponent
u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high
fb[0] = u32(a) // mantissa-low
return transmute(f64)fb
}
@(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
floattidf_unsigned :: proc "c" (a: u128) -> f64 {
DBL_MANT_DIG :: 53
if a == 0 {
return 0.0
}
a := a
N :: size_of(u128) * 8
sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits
e := i32(sd - 1) // exponent
if sd > DBL_MANT_DIG {
switch sd {
case DBL_MANT_DIG + 1:
a <<= 1
case DBL_MANT_DIG + 2:
// okay
case:
a = u128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
u128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0)
}
a |= u128((a & 4) != 0)
a += 1
a >>= 2
if a & (1 << DBL_MANT_DIG) != 0 {
a >>= 1
e += 1
}
} else {
a <<= u128(DBL_MANT_DIG - sd)
}
fb: [2]u32
fb[1] = (0) | // sign
u32((e + 1023) << 20) | // exponent
u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high
fb[0] = u32(a) // mantissa-low
return transmute(f64)fb
}
@(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 {
// TODO(bill): implement `fixunsdfti` correctly
x := u64(a)
return u128(x)
}
@(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 {
// TODO(bill): implement `fixunsdfdi` correctly
x := i64(a)
return i128(x)
}
@(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
umodti3 :: proc "c" (a, b: u128) -> u128 {
r: u128 = ---
_ = udivmod128(a, b, &r)
return r
}
@(link_name="__udivmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
return udivmod128(a, b, rem)
}
when !IS_WASM {
@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
udivti3 :: proc "c" (a, b: u128) -> u128 {
return udivmodti4(a, b, nil)
}
}
@(link_name="__modti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
modti3 :: proc "c" (a, b: i128) -> i128 {
s_a := a >> (128 - 1)
s_b := b >> (128 - 1)
an := (a ~ s_a) - s_a
bn := (b ~ s_b) - s_b
r: u128 = ---
_ = udivmod128(u128(an), u128(bn), &r)
return (i128(r) ~ s_a) - s_a
}
@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
s_a := a >> (128 - 1) // -1 if negative or 0
s_b := b >> (128 - 1)
an := (a ~ s_a) - s_a // absolute
bn := (b ~ s_b) - s_b
s_b ~= s_a // quotient sign
u_s_b := u128(s_b)
u_s_a := u128(s_a)
r: u128 = ---
u := i128((udivmodti4(u128(an), u128(bn), &r) ~ u_s_b) - u_s_b) // negate if negative
rem^ = i128((r ~ u_s_a) - u_s_a)
return u
}
@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
divti3 :: proc "c" (a, b: i128) -> i128 {
s_a := a >> (128 - 1) // -1 if negative or 0
s_b := b >> (128 - 1)
an := (a ~ s_a) - s_a // absolute
bn := (b ~ s_b) - s_b
s_a ~= s_b // quotient sign
u_s_a := u128(s_a)
return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative
}
@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
fixdfti :: proc "c" (a: u64) -> i128 {
significandBits :: 52
typeWidth :: (size_of(u64)*8)
exponentBits :: (typeWidth - significandBits - 1)
maxExponent :: ((1 << exponentBits) - 1)
exponentBias :: (maxExponent >> 1)
implicitBit :: (u64(1) << significandBits)
significandMask :: (implicitBit - 1)
signBit :: (u64(1) << (significandBits + exponentBits))
absMask :: (signBit - 1)
exponentMask :: (absMask ~ significandMask)
// Break a into sign, exponent, significand
aRep := a
aAbs := aRep & absMask
sign := i128(-1 if aRep & signBit != 0 else 1)
exponent := u64((aAbs >> significandBits) - exponentBias)
significand := u64((aAbs & significandMask) | implicitBit)
// If exponent is negative, the result is zero.
if exponent < 0 {
return 0
}
// If the value is too large for the integer type, saturate.
if exponent >= size_of(i128) * 8 {
return max(i128) if sign == 1 else min(i128)
}
// If 0 <= exponent < significandBits, right shift to get the result.
// Otherwise, shift left.
if exponent < significandBits {
return sign * i128(significand >> (significandBits - exponent))
} else {
return sign * (i128(significand) << (exponent - significandBits))
}
}

View File

@@ -1,14 +0,0 @@
#+build essence
#+private
package runtime
_HAS_RAND_BYTES :: false
// TODO(bill): reimplement `os.write`
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
return 0, -1
}
_exit :: proc "contextless" (code: int) -> ! {
trap()
}

View File

@@ -1,33 +0,0 @@
#+build haiku
#+private
package runtime
foreign import libc "system:c"
_HAS_RAND_BYTES :: true
foreign libc {
@(link_name="write")
_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
_errnop :: proc() -> ^i32 ---
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
}
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
ret := _unix_write(2, raw_data(data), len(data))
if ret < len(data) {
err := _errnop()
return int(ret), _OS_Errno(err^ if err != nil else 0)
}
return int(ret), 0
}
_rand_bytes :: proc "contextless" (dst: []byte) {
arc4random_buf(raw_data(dst), len(dst))
}
_exit :: proc "contextless" (code: int) -> ! {
trap()
}

View File

@@ -1,3 +1,4 @@
#+build !bedrock
package runtime
import "base:intrinsics"

View File

@@ -21,7 +21,7 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
where /Q git.exe || goto skip_git_hash
if not exist .git\ goto skip_git_hash
for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
for /f "tokens=1,2" %%i IN ('git -c "log.showSignature=false" show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
set CURR_DATE_TIME=%%i
set GIT_SHA=%%j
)
@@ -107,16 +107,6 @@ set libs= ^
set odin_res=misc\odin.res
set odin_rc=misc\odin.rc
rem DO NOT TOUCH!
rem THIS TILDE STUFF IS FOR DEVELOPMENT ONLY!
set tilde_backend=0
if %tilde_backend% EQU 1 (
set libs=%libs% src\tilde\tb.lib
set compiler_defines=%compiler_defines% -DODIN_TILDE_BACKEND
)
rem DO NOT TOUCH!
set linker_flags= -incremental:no -opt:ref -subsystem:console -MANIFEST:EMBED
if %release_mode% EQU 0 ( rem Debug
@@ -148,4 +138,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat
del *.obj > NUL 2> NUL
:end_of_build
:end_of_build

View File

@@ -1,6 +1,10 @@
#!/usr/bin/env sh
set -eu
SUPPORTED_LLVM_VERSIONS="22 21 20 19 18 17"
SUGGESTED_LLVM_VERSION="22"
MINIMUM_LLVM_VERSION="17"
: ${CPPFLAGS=}
: ${CXXFLAGS=}
: ${LDFLAGS=}
@@ -13,8 +17,10 @@ OS_ARCH="$(uname -m)"
OS_NAME="$(uname -s)"
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
# Counter the user's git config to show the signature in logs.
gitnosig="-c log.showSignature=false"
GIT_SHA=$(git $gitnosig show --pretty='%h' --no-patch --no-notes HEAD)
GIT_DATE=$(git $gitnosig show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
else
GIT_DATE=$(date +"%Y-%m")
@@ -26,8 +32,6 @@ error() {
exit 1
}
SUPPORTED_LLVM_VERSIONS="22 21 20 19 18 17 14"
# Brew advises people not to add llvm to their $PATH, so try and use brew to find it.
if [ -z "$LLVM_CONFIG" ] && [ -n "$(command -v brew)" ]; then
for V in $SUPPORTED_LLVM_VERSIONS; do
@@ -61,7 +65,22 @@ if [ -z "$LLVM_CONFIG" ]; then
done
if [ -z "$LLVM_CONFIG" ]; then
error "No supported llvm-config command found. Set LLVM_CONFIG to proceed."
if [ -f "/etc/os-release" ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian|linuxmint|pop|zorin|kali|elementary|raspbian)
echo "ERROR: No supported llvm-config command found. Set LLVM_CONFIG to proceed."
echo "We suggest installing LLVM $SUGGESTED_LLVM_VERSION from https://apt.llvm.org"
exit 1
;;
fedora|centos|rocky|almalinux|rhel|amzn|ol|centos-stream)
echo "ERROR: No supported llvm-config command found. Set LLVM_CONFIG to proceed."
echo "We suggest installing LLVM $SUGGESTED_LLVM_VERSION via COPR, e.g. dnf copr enable fedora-llvm-team/llvm-snapshots"
exit 1
;;
esac
fi
error "No supported llvm-config command found. Set LLVM_CONFIG to proceed. We suggest LLVM $SUGGESTED_LLVM_VERSION."
fi
fi
@@ -78,8 +97,8 @@ LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')"
LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')"
if [ $LLVM_VERSION_MAJOR -lt 14 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 22 ]; then
error "Invalid LLVM version $LLVM_VERSION: must be 14, 17, 18, 19, 20, 21 or 22"
if [ $LLVM_VERSION_MAJOR -lt $MINIMUM_LLVM_VERSION ]; then
error "Unsupported LLVM version $LLVM_VERSION: must be 17, 18, 19, 20, 21 or 22"
fi
case "$OS_NAME" in
@@ -114,12 +133,7 @@ Linux)
;;
OpenBSD)
CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)"
LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -liconv"
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
;;
Haiku)
CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel"
LDFLAGS="$LDFLAGS -lstdc++ -liconv"
LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -Wl,-rpath,$($LLVM_CONFIG --libdir) -liconv"
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
;;
*)

View File

@@ -49,12 +49,8 @@ if "%1" == "freestanding" (
)
if "%1" == "rare" (
echo Checking essence_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:essence_amd64
echo Checking freebsd_i386
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
echo Checking haiku_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
)
if "%1" == "wasm" (
@@ -72,4 +68,4 @@ if "%1" == "wasm" (
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:orca_wasm32
echo Checking js_wasm64p32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm64p32
)
)

View File

@@ -19,12 +19,8 @@ freestanding)
;;
rare)
echo Checking essence_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:essence_amd64
echo Checking freebsd_i386
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
echo Checking haiku_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
;;
wasm)
@@ -75,4 +71,4 @@ wasm)
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
;;
esac
esac

View File

@@ -6,7 +6,14 @@ LLVM_CONFIG="llvm-config-20"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
CPPFLAGS="-DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
else
GIT_DATE=$(date +"%Y-%m")
fi
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
CXXFLAGS="-std=c++14 $($LLVM_CONFIG --cxxflags --ldflags)"
LDFLAGS="-static -lm -lzstd -lz -lffi -pthread -ldl -fuse-ld=mold"

View File

@@ -355,16 +355,15 @@ _reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
//
// reader_read_slice reads until the first occurrence of delim from the reader
// It returns a slice pointing at the bytes in the buffer
// The bytes stop being valid at the next read
// If reader_read_slice encounters an error before finding a delimiter
// reader_read_slice fails with error .Buffer_Full if the buffer fills without a delim
// Because the data returned from reader_read_slice will be overwritten on the
// next IO operation, reader_read_bytes or reader_read_string is usually preferred
//
// reader_read_slice returns err != nil if and only if line does not end in delim
// reader_read_slice reads until the first occurrence of delim in the input,
// returning a slice pointing at the bytes in the internal buffer.
// The returned slice is only valid until the next read call.
// If the buffer fills without finding delim, it returns .Buffer_Full.
// If the underlying reader returns an error before finding delim, that error is returned.
// Because the returned data will be overwritten by the next I/O operation,
// reader_read_bytes or reader_read_string is usually preferred.
//
// reader_read_slice returns err != nil if and only if line does not end in delim.
reader_read_slice :: proc(b: ^Reader, delim: byte) -> (line: []byte, err: io.Error) {
s := 0
for {

View File

@@ -1,7 +1,8 @@
// Defines the basic types used by `C` programs for foreign function and data structure interop.
package c
import builtin "base:builtin"
import builtin "base:builtin"
import intrinsics "base:intrinsics"
char :: builtin.u8 // assuming -funsigned-char
@@ -115,14 +116,6 @@ NDEBUG :: !ODIN_DEBUG
CHAR_BIT :: 8
// Since there are no types in C with an alignment larger than that of
// max_align_t, which cannot be larger than sizeof(long double) as any other
// exposed type wouldn't be valid C, the maximum alignment possible in a
// strictly conformant C implementation is 16 on the platforms we care about.
// The choice of 4096 bytes for storage of this type is more than enough on all
// relevant platforms.
va_list :: struct #align(16) {
_: [4096]u8,
}
va_list :: intrinsics.c_va_list
FILE :: struct {}

View File

@@ -80,25 +80,6 @@ when ODIN_OS == .Darwin {
ERANGE :: 34
}
when ODIN_OS == .Haiku {
@(private="file")
@(default_calling_convention="c")
foreign libc {
@(link_name="_errnop")
_get_errno :: proc() -> ^int ---
}
_HAIKU_USE_POSITIVE_POSIX_ERRORS :: #config(HAIKU_USE_POSITIVE_POSIX_ERRORS, false)
_POSIX_ERROR_FACTOR :: -1 when _HAIKU_USE_POSITIVE_POSIX_ERRORS else 1
@(private="file") _GENERAL_ERROR_BASE :: min(int)
@(private="file") _POSIX_ERROR_BASE :: _GENERAL_ERROR_BASE + 0x7000
EDOM :: _POSIX_ERROR_FACTOR * (_POSIX_ERROR_BASE + 16)
EILSEQ :: _POSIX_ERROR_FACTOR * (_POSIX_ERROR_BASE + 38)
ERANGE :: _POSIX_ERROR_FACTOR * (_POSIX_ERROR_BASE + 17)
}
when ODIN_OS == .JS {
_ :: libc
_get_errno :: proc "c" () -> ^int {

View File

@@ -110,7 +110,7 @@ when ODIN_OS == .Windows {
}
}
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku || ODIN_OS == .Windows {
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Windows {
LC_ALL :: 0
LC_COLLATE :: 1

View File

@@ -34,7 +34,7 @@ when ODIN_OS == .Windows {
SIGTERM :: 15
}
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
SIG_ERR :: rawptr(~uintptr(0))
SIG_DFL :: rawptr(uintptr(0))
SIG_IGN :: rawptr(uintptr(1))

View File

@@ -4,29 +4,12 @@ package libc
import "base:intrinsics"
import "core:c"
va_list :: intrinsics.c_va_list
@(private="file")
@(default_calling_convention="none")
foreign _ {
@(link_name="llvm.va_start") _va_start :: proc(arglist: ^i8) ---
@(link_name="llvm.va_end") _va_end :: proc(arglist: ^i8) ---
@(link_name="llvm.va_copy") _va_copy :: proc(dst, src: ^i8) ---
}
va_start :: intrinsics.c_va_start
va_end :: intrinsics.c_va_end
va_copy :: intrinsics.c_va_copy
va_list :: c.va_list
va_start :: #force_inline proc(ap: ^va_list, _: any) {
_va_start(cast(^i8)ap)
}
va_end :: #force_inline proc(ap: ^va_list) {
_va_end(cast(^i8)ap)
}
va_copy :: #force_inline proc(dst, src: ^va_list) {
_va_copy(cast(^i8)dst, cast(^i8)src)
}
// We cannot provide va_arg as there is no way to create "C" style procedures
// in Odin which take variable arguments the C way. The #c_vararg attribute only

View File

@@ -200,36 +200,6 @@ when ODIN_OS == .Darwin {
}
}
when ODIN_OS == .Haiku {
fpos_t :: distinct i64
_IOFBF :: 0
_IOLBF :: 1
_IONBF :: 2
BUFSIZ :: 8192
EOF :: int(-1)
FOPEN_MAX :: 128
FILENAME_MAX :: 256
L_tmpnam :: 512
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
TMP_MAX :: 32768
foreign libc {
stderr: ^FILE
stdin: ^FILE
stdout: ^FILE
}
}
when ODIN_OS == .NetBSD {
@(private) LRENAME :: "__posix_rename"
@(private) LFGETPOS :: "__fgetpos50"

View File

@@ -42,22 +42,6 @@ when ODIN_OS == .Linux {
}
}
when ODIN_OS == .Haiku {
RAND_MAX :: 0x7fffffff
// GLIBC and MUSL only
@(private="file")
@(default_calling_convention="c")
foreign libc {
__ctype_get_mb_cur_max :: proc() -> ushort ---
}
MB_CUR_MAX :: #force_inline proc() -> size_t {
return size_t(__ctype_get_mb_cur_max())
}
}
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD {
RAND_MAX :: 0x7fffffff

View File

@@ -45,7 +45,7 @@ when ODIN_OS == .Windows {
}
}
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku || ODIN_OS == .JS {
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .JS {
@(default_calling_convention="c")
foreign libc {
// 7.27.2 Time manipulation functions
@@ -95,7 +95,7 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS =
time_t :: distinct i64
when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku {
when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD {
clock_t :: distinct int32_t
} else {
clock_t :: distinct long

View File

@@ -17,12 +17,12 @@ foreign libc {
fwscanf :: proc(stream: ^FILE, format: [^]wchar_t, #c_vararg arg: ..any) -> int ---
swprintf :: proc(stream: ^FILE, n: size_t, format: [^]wchar_t, #c_vararg arg: ..any) -> int ---
swscanf :: proc(s, format: [^]wchar_t, #c_vararg arg: ..any) -> int ---
vfwprintf :: proc(stream: ^FILE, format: [^]wchar_t, arg: va_list) -> int ---
vfwscanf :: proc(stream: ^FILE, format: [^]wchar_t, arg: va_list) -> int ---
vswprintf :: proc(s: [^]wchar_t, n: size_t, format: [^]wchar_t, arg: va_list) -> int ---
vswscanf :: proc(s, format: [^]wchar_t, arg: va_list) -> int ---
vwprintf :: proc(format: [^]wchar_t, arg: va_list) -> int ---
vwscanf :: proc(format: [^]wchar_t, arg: va_list) -> int ---
vfwprintf :: proc(stream: ^FILE, format: [^]wchar_t, arg: ^va_list) -> int ---
vfwscanf :: proc(stream: ^FILE, format: [^]wchar_t, arg: ^va_list) -> int ---
vswprintf :: proc(s: [^]wchar_t, n: size_t, format: [^]wchar_t, arg: ^va_list) -> int ---
vswscanf :: proc(s, format: [^]wchar_t, arg: ^va_list) -> int ---
vwprintf :: proc(format: [^]wchar_t, arg: ^va_list) -> int ---
vwscanf :: proc(format: [^]wchar_t, arg: ^va_list) -> int ---
wprintf :: proc(format: [^]wchar_t, #c_vararg arg: ..any) -> int ---
wscanf :: proc(format: [^]wchar_t, #c_vararg arg: ..any) -> int ---

View File

@@ -30,10 +30,6 @@ when ODIN_OS == .Windows {
wctrans_t :: distinct int
wctype_t :: distinct ulong
} else when ODIN_OS == .Haiku {
wctrans_t :: distinct i32
wctype_t :: distinct i32
}
@(default_calling_convention="c")

View File

@@ -360,21 +360,26 @@ refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width :=
refill := u64(width)
b := u64(0)
if z.num_bits > refill {
return
}
for {
if len(z.input_data) != 0 {
b = u64(z.input_data[0])
z.input_data = z.input_data[1:]
}
z.code_buffer |= b << u8(z.num_bits)
z.num_bits += 8
if z.num_bits > refill {
break
}
if z.code_buffer == 0 && z.num_bits > 63 {
z.num_bits = 0
}
if z.code_buffer >= 1 << uint(z.num_bits) {
// Code buffer is malformed.
z.num_bits = max(u64)
return
}
if len(z.input_data) != 0 {
b = u64(z.input_data[0])
z.input_data = z.input_data[1:]
} else {
return
}
z.code_buffer |= b << u8(z.num_bits)
z.num_bits += 8
}
}

View File

@@ -322,11 +322,8 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro
@(optimization_mode="favor_size")
decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
if z.num_bits < 16 {
if z.num_bits > 63 {
return 0, .Code_Buffer_Malformed
}
compress.refill_lsb(z)
if z.code_buffer == 0 {
if z.num_bits > 63 {
return 0, .Stream_Too_Short
}
}
@@ -491,7 +488,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
*/
expected_output_size = max(max(expected_output_size, compress.COMPRESS_OUTPUT_ALLOCATE_MIN), 512)
// fmt.printfln("ZLIB: Expected Payload Size: %v", expected_output_size)
// fmt.printf("\nZLIB: Expected Payload Size: %v\n\n", expected_output_size);
if expected_output_size > 0 && expected_output_size <= compress.COMPRESS_OUTPUT_ALLOCATE_MAX {
/*
@@ -522,16 +519,11 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
final := u32(0)
type := u32(0)
defer if int(z.bytes_written) != len(z.output.buf) {
resize(&z.output.buf, int(z.bytes_written))
}
for {
final = compress.read_bits_lsb(z, 1)
type = compress.read_bits_lsb(z, 2)
// fmt.printfln("len(z): %v", len(z.input_data))
// fmt.printfln("Final: %v | Type: %v", final, type)
// fmt.printf("Final: %v | Type: %v\n", final, type)
switch type {
case 0:
@@ -566,6 +558,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
case 3:
return .BType_3
case:
// fmt.printf("Err: %v | Final: %v | Type: %v\n", err, final, type)
if type == 1 {
// Use fixed code lengths.
build_huffman(z_repeat, Z_FIXED_LENGTH[:]) or_return
@@ -594,6 +587,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
for n < ntot {
c = decode_huffman(z, codelength_ht) or_return
if c < 0 || c >= 19 {
return .Huffman_Bad_Code_Lengths
}
@@ -638,12 +632,15 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all
}
parse_huffman_block(z, z_repeat, z_offset) or_return
}
if final == 1 {
break
}
}
if int(z.bytes_written) != len(z.output.buf) {
resize(&z.output.buf, int(z.bytes_written)) or_return
}
return nil
}

View File

@@ -60,13 +60,13 @@ Iterator :: struct($Value: typeid) {
_called_next: bool,
}
// init initializes a tree.
// `init` initializes a tree.
init :: proc {
init_ordered,
init_cmp,
}
// init_cmp initializes a tree.
// `init_cmp` initializes a tree.
init_cmp :: proc(
t: ^$T/Tree($Value),
cmp_fn: proc(a, b: Value) -> Ordering,
@@ -78,7 +78,7 @@ init_cmp :: proc(
t._size = 0
}
// init_ordered initializes a tree containing ordered items, with
// `init_ordered` initializes a tree containing ordered items, with
// a comparison function that results in an ascending order sort.
init_ordered :: proc(
t: ^$T/Tree($Value),
@@ -87,7 +87,7 @@ init_ordered :: proc(
init_cmp(t, slice.cmp_proc(Value), node_allocator)
}
// destroy de-initializes a tree.
// `destroy` de-initializes a tree.
destroy :: proc(t: ^$T/Tree($Value), call_on_remove: bool = true) {
iter := iterator(t, Direction.Forward)
for _ in iterator_next(&iter) {
@@ -95,24 +95,24 @@ destroy :: proc(t: ^$T/Tree($Value), call_on_remove: bool = true) {
}
}
// len returns the number of elements in the tree.
// `len` returns the number of elements in the tree.
len :: proc "contextless" (t: ^$T/Tree($Value)) -> int {
return t._size
}
// first returns the first node in the tree (in-order) or nil if and only if (⟺)
// `first` returns the first node in the tree (in-order) or nil if and only if (⟺)
// the tree is empty.
first :: proc "contextless" (t: ^$T/Tree($Value)) -> ^Node(Value) {
return tree_first_or_last_in_order(t, Direction.Backward)
}
// last returns the last element in the tree (in-order) or nil if and only if (⟺)
// `last` returns the last element in the tree (in-order) or nil if and only if (⟺)
// the tree is empty.
last :: proc "contextless" (t: ^$T/Tree($Value)) -> ^Node(Value) {
return tree_first_or_last_in_order(t, Direction.Forward)
}
// find finds the value in the tree, and returns the corresponding
// `find` finds the value in the tree, and returns the corresponding
// node or nil if and only if (⟺) the value is not present.
find :: proc(t: ^$T/Tree($Value), value: Value) -> ^Node(Value) {
cur := t._root
@@ -130,7 +130,7 @@ find :: proc(t: ^$T/Tree($Value), value: Value) -> ^Node(Value) {
return cur
}
// find_or_insert attempts to insert the value into the tree, and returns
// `find_or_insert` attempts to insert the value into the tree, and returns
// the node, a boolean indicating if the value was inserted, and the
// node allocator error if relevant. If the value is already
// present, the existing node is returned un-altered.
@@ -168,7 +168,7 @@ find_or_insert :: proc(
return
}
// remove removes a node or value from the tree, and returns true if and only if (⟺) the
// `remove` removes a node or value from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove :: proc {
@@ -176,7 +176,7 @@ remove :: proc {
remove_node,
}
// remove_value removes a value from the tree, and returns true if and only if (⟺) the
// `remove_value` removes a value from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove_value :: proc(t: ^$T/Tree($Value), value: Value, call_on_remove: bool = true) -> bool {
@@ -187,7 +187,7 @@ remove_value :: proc(t: ^$T/Tree($Value), value: Value, call_on_remove: bool = t
return remove_node(t, n, call_on_remove)
}
// remove_node removes a node from the tree, and returns true if and only if (⟺) the
// `remove_node` removes a node from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove_node :: proc(t: ^$T/Tree($Value), node: ^Node(Value), call_on_remove: bool = true) -> bool {
@@ -249,7 +249,7 @@ remove_node :: proc(t: ^$T/Tree($Value), node: ^Node(Value), call_on_remove: boo
return true
}
// iterator returns a tree iterator in the specified direction.
// `iterator` returns a tree iterator in the specified direction.
iterator :: proc "contextless" (t: ^$T/Tree($Value), direction: Direction) -> Iterator(Value) {
it: Iterator(Value)
it._tree = transmute(^Tree(Value))t
@@ -260,7 +260,7 @@ iterator :: proc "contextless" (t: ^$T/Tree($Value), direction: Direction) -> It
return it
}
// iterator_from_pos returns a tree iterator in the specified direction,
// `iterator_from_pos` returns a tree iterator in the specified direction,
// spanning the range [pos, last] (inclusive).
iterator_from_pos :: proc "contextless" (
t: ^$T/Tree($Value),
@@ -280,14 +280,14 @@ iterator_from_pos :: proc "contextless" (
return it
}
// iterator_get returns the node currently pointed to by the iterator,
// `iterator_get` returns the node currently pointed to by the iterator,
// or nil if and only if (⟺) the node has been removed, the tree is empty, or the end
// of the tree has been reached.
iterator_get :: proc "contextless" (it: ^$I/Iterator($Value)) -> ^Node(Value) {
return it._cur
}
// iterator_remove removes the node currently pointed to by the iterator,
// `iterator_remove` removes the node currently pointed to by the iterator,
// and returns true if and only if (⟺) the removal was successful. Semantics are the
// same as the Tree remove.
iterator_remove :: proc(it: ^$I/Iterator($Value), call_on_remove: bool = true) -> bool {
@@ -303,7 +303,7 @@ iterator_remove :: proc(it: ^$I/Iterator($Value), call_on_remove: bool = true) -
return ok
}
// iterator_next advances the iterator and returns the (node, true) or
// `iterator_next` advances the iterator and returns the (node, true) or
// or (nil, false) if and only if (⟺) the end of the tree has been reached.
//
// Note: The first call to iterator_next will return the first node instead

View File

@@ -1,7 +1,6 @@
package container_handle_map
import "base:runtime"
import "base:builtin"
import "base:intrinsics"
@(require) import "core:container/xar"
@@ -139,4 +138,4 @@ dynamic_iterate :: proc "contextless" (it: ^$DHI/Dynamic_Handle_Map_Iterator($D/
}
it.index = 0
return
}
}

View File

@@ -29,6 +29,13 @@ Pool :: struct($T: typeid) {
num_outstanding: int,
num_ready: int,
link_off: uintptr,
// Guards free_list. An untagged Treiber stack is vulnerable to ABA:
// between get's head load and its CAS, another thread can pop the head,
// reuse it, and push it back — the CAS then succeeds and installs a
// stale next pointer, handing an in-flight element to two owners.
// A tagged head would keep this lock-free but requires a double-width
// CAS, which is not portably available.
mu: sync.Mutex,
free_list: ^T,
}
@@ -61,20 +68,20 @@ destroy :: proc(p: ^Pool($T)) {
get :: proc(p: ^Pool($T)) -> (elem: ^T, err: runtime.Allocator_Error) #optional_allocator_error {
defer sync.atomic_add_explicit(&p.num_outstanding, 1, .Relaxed)
for {
elem = sync.atomic_load_explicit(&p.free_list, .Acquire)
if elem == nil {
// NOTE: pool arena has an internal lock.
return new(T, _pool_arena_allocator(&p.arena))
}
if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, elem, _get_next(p, elem), .Acquire, .Relaxed); ok {
_set_next(p, elem, nil)
_unpoison_elem(p, elem)
sync.atomic_sub_explicit(&p.num_ready, 1, .Relaxed)
return
}
sync.mutex_lock(&p.mu)
elem = p.free_list
if elem == nil {
sync.mutex_unlock(&p.mu)
// NOTE: pool arena has an internal lock.
return new(T, _pool_arena_allocator(&p.arena))
}
p.free_list = _get_next(p, elem)
sync.mutex_unlock(&p.mu)
_set_next(p, elem, nil)
_unpoison_elem(p, elem)
sync.atomic_sub_explicit(&p.num_ready, 1, .Relaxed)
return
}
put :: proc(p: ^Pool($T), elem: ^T) {
@@ -84,13 +91,10 @@ put :: proc(p: ^Pool($T), elem: ^T) {
defer sync.atomic_sub_explicit(&p.num_outstanding, 1, .Relaxed)
defer sync.atomic_add_explicit(&p.num_ready, 1, .Relaxed)
for {
head := sync.atomic_load_explicit(&p.free_list, .Relaxed)
_set_next(p, elem, head)
if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, head, elem, .Release, .Relaxed); ok {
return
}
}
sync.mutex_lock(&p.mu)
_set_next(p, elem, p.free_list)
p.free_list = elem
sync.mutex_unlock(&p.mu)
}
num_outstanding :: proc(p: ^Pool($T)) -> int {

View File

@@ -236,16 +236,6 @@ back_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
}
@(deprecated="Use `front_ptr` instead")
peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
return front_ptr(q, loc)
}
@(deprecated="Use `back_ptr` instead")
peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
return back_ptr(q, loc)
}
/*
Push an element to the back of the queue.
@@ -416,7 +406,7 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T, loc := #caller_location) -
}
/*
Consume `n` elements from the back of the queue.
Consume `n` elements from the front of the queue.
This will raise a bounds checking error if the queue does not have enough elements.
*/

View File

@@ -63,13 +63,13 @@ Iterator :: struct($Key: typeid, $Value: typeid) {
_called_next: bool,
}
// init initializes a tree.
// `init` initializes a tree.
init :: proc {
init_ordered,
init_cmp,
}
// init_cmp initializes a tree.
// `init_cmp` initializes a tree.
init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering, node_allocator := context.allocator) {
t._root = nil
t._node_allocator = node_allocator
@@ -77,13 +77,13 @@ init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering,
t._size = 0
}
// init_ordered initializes a tree containing ordered keys, with
// `init_ordered` initializes a tree containing ordered keys, with
// a comparison function that results in an ascending order sort.
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered(Key) {
init_cmp(t, slice.cmp_proc(Key), node_allocator)
}
// destroy de-initializes a tree.
// `destroy` de-initializes a tree.
destroy :: proc(t: ^$T/Tree($Key, $Value), call_on_remove: bool = true) {
iter := iterator(t, .Forward)
for _ in iterator_next(&iter) {
@@ -95,19 +95,19 @@ len :: proc "contextless" (t: $T/Tree($Key, $Value)) -> (node_count: int) {
return t._size
}
// first returns the first node in the tree (in-order) or nil if and only if (⟺)
// `first` returns the first node in the tree (in-order) or nil if and only if (⟺)
// the tree is empty.
first :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
return tree_first_or_last_in_order(t, Direction.Backward)
}
// last returns the last element in the tree (in-order) or nil if and only if (⟺)
// `last` returns the last element in the tree (in-order) or nil if and only if (⟺)
// the tree is empty.
last :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
return tree_first_or_last_in_order(t, Direction.Forward)
}
// find finds the key in the tree, and returns the corresponding node, or nil if and only if (⟺) the value is not present.
// `find` finds the key in the tree, and returns the corresponding node, or nil if and only if (⟺) the value is not present.
find :: proc(t: $T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
node = t._root
for node != nil {
@@ -120,7 +120,7 @@ find :: proc(t: $T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
return node
}
// find_value finds the key in the tree, and returns the corresponding value, or nil if and only if (⟺) the value is not present.
// `find_value` finds the key in the tree, and returns the corresponding value, or nil if and only if (⟺) the value is not present.
find_value :: proc(t: $T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
if n := find(t, key); n != nil {
return n.value, true
@@ -128,10 +128,36 @@ find_value :: proc(t: $T/Tree($Key, $Value), key: Key) -> (value: Value, ok: boo
return
}
// find_or_insert attempts to insert the key-value pair into the tree, and returns
// `find_or_insert` attempts to insert the key-value pair into the tree, and returns
// the node, a boolean indicating if a new node was inserted, and the
// node allocator error if relevant. If the key is already present, the existing node is updated and returned.
// node allocator error if relevant. If the key is already present, the existing node is returned un-altered.
find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n: ^Node(Key, Value), inserted: bool, err: runtime.Allocator_Error) {
n_ptr := &t._root
for n_ptr^ != nil {
n = n_ptr^
switch t._cmp_fn(key, n.key) {
case .Less:
n_ptr = &n._left
case .Greater:
n_ptr = &n._right
case .Equal:
return
}
}
_parent := n
n = new_clone(Node(Key, Value){key=key, value=value, _parent=_parent, _color=.Red}, t._node_allocator) or_return
n_ptr^ = n
insert_case1(t, n)
t._size += 1
return n, true, nil
}
// `upsert` attempts to insert the key-value pair into the tree, and returns
// the node, a boolean indicating if a new node was inserted, and the
// node allocator error if relevant. If the key is already present, the existing node's value is updated.
upsert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n: ^Node(Key, Value), inserted: bool, err: runtime.Allocator_Error) {
n_ptr := &t._root
for n_ptr^ != nil {
n = n_ptr^
@@ -154,7 +180,7 @@ find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n:
return n, true, nil
}
// remove removes a node or value from the tree, and returns true if and only if (⟺) the
// `remove` removes a node or value from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove :: proc {
@@ -162,7 +188,7 @@ remove :: proc {
remove_node,
}
// remove_value removes a value from the tree, and returns true if and only if (⟺) the
// `remove_value` removes a value from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's key + value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true) -> bool {
@@ -173,7 +199,7 @@ remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true)
return remove_node(t, n, call_on_remove)
}
// remove_node removes a node from the tree, and returns true if and only if (⟺) the
// `remove_node` removes a node from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's key + value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove_node :: proc(t: ^$T/Tree($Key, $Value), node: ^$N/Node(Key, Value), call_on_remove := true) -> (found: bool) {
@@ -207,7 +233,7 @@ remove_node :: proc(t: ^$T/Tree($Key, $Value), node: ^$N/Node(Key, Value), call_
return true
}
// iterator returns a tree iterator in the specified direction.
// `iterator` returns a tree iterator in the specified direction.
iterator :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> Iterator(Key, Value) {
it: Iterator(Key, Value)
it._tree = cast(^Tree(Key, Value))t
@@ -218,7 +244,7 @@ iterator :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction)
return it
}
// iterator_from_pos returns a tree iterator in the specified direction,
// `iterator_from_pos` returns a tree iterator in the specified direction,
// spanning the range [pos, last] (inclusive).
iterator_from_pos :: proc "contextless" (t: ^$T/Tree($Key, $Value), pos: ^Node(Key, Value), direction: Direction) -> Iterator(Key, Value) {
it: Iterator(Key, Value)
@@ -234,14 +260,14 @@ iterator_from_pos :: proc "contextless" (t: ^$T/Tree($Key, $Value), pos: ^Node(K
return it
}
// iterator_get returns the node currently pointed to by the iterator,
// `iterator_get` returns the node currently pointed to by the iterator,
// or nil if and only if (⟺) the node has been removed, the tree is empty, or the end
// of the tree has been reached.
iterator_get :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> ^Node(Key, Value) {
return it._cur
}
// iterator_remove removes the node currently pointed to by the iterator,
// `iterator_remove` removes the node currently pointed to by the iterator,
// and returns true if and only if (⟺) the removal was successful. Semantics are the
// same as the Tree remove.
iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = true) -> bool {
@@ -257,7 +283,7 @@ iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = t
return ok
}
// iterator_next advances the iterator and returns the (node, true) or
// `iterator_next` advances the iterator and returns the (node, true) or
// or (nil, false) if and only if (⟺) the end of the tree has been reached.
//
// Note: The first call to iterator_next will return the first node instead

View File

@@ -265,7 +265,7 @@ array_push_back_elem :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_
chunk_idx, elem_idx, chunk_cap := _meta_get(SHIFT, uint(x.len))
if x.chunks[chunk_idx] == nil {
x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator) or_return
x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator, loc) or_return
}
x.chunks[chunk_idx][elem_idx] = value
x.len += 1
@@ -328,7 +328,7 @@ array_push_back_elem_and_get_ptr :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc
chunk_idx, elem_idx, chunk_cap := _meta_get(SHIFT, uint(x.len))
if x.chunks[chunk_idx] == nil {
x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator) or_return
x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator, loc) or_return
}
x.chunks[chunk_idx][elem_idx] = value
x.len += 1
@@ -375,7 +375,7 @@ array_pop_safe :: proc(x: ^$X/Array($T, $SHIFT)) -> (val: T, ok: bool) {
Example:
import "core:encoding/xar"
import "core:container/xar"
unordered_remove_example :: proc() {
x: xar.Array(int, 4)

View File

@@ -13,10 +13,11 @@ constant-time byte comparison.
- The crypto packages are not thread-safe.
- Best-effort is make to mitigate timing side-channels on reasonable
architectures. Architectures that are known to be unreasonable include
but are not limited to i386, i486, and WebAssembly.
but are not limited to i386, i486, VIA Nano 2000, ARM7T/ARM9T/Cortex-M3,
and WASM.
- Implementations assume a 64-bit architecture (64-bit integer arithmetic
is fast, and includes add-with-carry, sub-with-borrow, and full-result
multiply).
is fast, and includes contant-time add-with-carry, sub-with-borrow, and
full-result multiply).
- Hardware sidechannels are explicitly out of scope for this package.
Notable examples include but are not limited to:
- Power/RF side-channels etc.
@@ -29,4 +30,4 @@ constant-time byte comparison.
## License
This library is made available under the zlib license.
This library is made available under the zlib license.

View File

@@ -0,0 +1,904 @@
// Constant time Big Integers
package _bigint
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
import "base:intrinsics"
import "core:crypto"
import subtle "core:crypto/_subtle"
import "core:slice"
// Integers 'i31'
// --------------
//
// The 'i31' functions implement computations on big integers using
// an internal representation as an array of 32-bit integers. For
// an array `x`:
// -- x[0] encodes the array length and the "announced bit length"
// of the integer: namely, if the announced bit length is k,
// then x[0] = ((k / 31) << 5) + (k % 31).
// -- x[1], x[2]... contain the value in little-endian order, 31
// bits per word (x[1] contains the least significant 31 bits).
// The upper bit of each word is 0.
//
// Multiplications rely on the elementary 32x32->64 multiplication.
//
// The announced bit length specifies the number of bits that are
// significant in the subsequent 32-bit words. Unused bits in the
// last (most significant) word are set to 0; subsequent words are
// uninitialized and need not exist at all.
//
// The execution time and memory access patterns of all computations
// depend on the announced bit length, but not on the actual word
// values. For modular integers, the announced bit length of any integer
// modulo `n` is equal to the actual bit length of `n`; thus, computations
// on modular integers are "constant-time" (only the modulus length may leak).
I31_MASK :: 0x7fff_ffff
// Compute the bit length of a 32-bit integer.
// Returned value is between 0 and 32 (inclusive).
@(require_results)
_u32_bit_length :: proc "contextless" (x: u32) -> (length: u32) {
x := x
k := subtle.neq(x, 0)
c := subtle.gt(x, 0xFFFF); x = subtle.csel(x, x >> 16, c); k += c << 4
c = subtle.gt(x, 0x00FF); x = subtle.csel(x, x >> 8, c); k += c << 3
c = subtle.gt(x, 0x000F); x = subtle.csel(x, x >> 4, c); k += c << 2
c = subtle.gt(x, 0x0003); x = subtle.csel(x, x >> 2, c); k += c << 1
k += subtle.gt(x, 0x0001)
return k
}
// Multiply two 31-bit integers, with a 62-bit result. This default
// implementation assumes that the basic multiplication operator
// yields constant-time code.
//
// The mul31_lo() returns only the low 31 bits of the product.
//
// Note/Odin:
// The original BearSSL code provides alternative implemenetations
// of these routines gated behind `BR_CT_MUL31`, however that macro
// is only useful on Intel 80386/80486, VIA Nano 2000, and ARM7T/ARM9T.
@(require_results)
_mul31 :: #force_inline proc "contextless" (x, y: u32) -> (res: u64) {
return u64(x) * u64(y)
}
@(private="file", require_results)
_mul31_lo :: #force_inline proc "contextless" (x, y: u32) -> (res: u32) {
return (x * y) & I31_MASK
}
// Wrapper for `div_rem`; the remainder is returned, and the quotient is
// discarded.
@(private, require_results)
_rem_u32 :: #force_inline proc "contextless" (hi: u32, lo: u32, d: u32) -> (res: u32) {
_, rem := div_rem_u32(hi, lo, d)
return rem
}
// Wrapper for `div_rem`; the quotient is returned, and the remainder is
// discarded.
@(private="file", require_results)
_div_u32 :: #force_inline proc "contextless" (hi: u32, lo: u32, d: u32) -> (quo: u32) {
q, _ := div_rem_u32(hi, lo, d)
return q
}
// Constant-time division. The dividend `hi:lo` is divided by the divisor `d`;
// the quotient and remainder are returned.
//
// If `hi == d`, then the quotient does not fit on 32 bits; returned value is thus truncated.
// If `hi > d`, returned values are indeterminate.
@(require_results)
div_rem_u32 :: proc "contextless" (hi: u32, lo: u32, d: u32) -> (quo: u32, rem: u32) {
// TODO: optimize this
hi := hi
lo := lo
ch := subtle.eq(hi, d)
hi = subtle.csel(hi, 0, ch)
for k := uint(31); k > 0; k -= 1 {
j := 32 - k
w := (hi << j) | (lo >> k)
ctl := subtle.ge(w, d) | (hi >> k)
hi2 := (w - d) >> j
lo2 := lo - (d << k)
hi = subtle.csel(hi, hi2, ctl)
lo = subtle.csel(lo, lo2, ctl)
quo |= ctl << k
}
cf := subtle.ge(lo, d) | hi
quo |= cf
rem = subtle.csel(lo, lo - d, cf)
return
}
// i31_rem computes x / y and returns the remainder.
@(require_results)
i31_rem :: proc "contextless" (x: []u32, y: u32) -> u32 {
words := uint(x[0] + 31) >> 5
x_ := x[1:]
r: u32
for i := int(words-1); i >= 0; i -= 1 {
r = _rem_u32(r, x_[i], y)
}
return r
}
// Test whether an integer `x` is zero.
@(optimization_mode="none", require_results)
i31_is_zero :: proc "contextless" (x: []u32) -> (res: u32) {
z: u32
for u := (x[0] + 31) >> 5; u > 0; u -= 1 {
z |= x[u]
}
return ~(z | -z) >> 31
}
// Add `b` to `a` and return the `carry` (`0` or `1`). if `ctl` is `1`.
// If `ctl` is `0`, `a` is left alone but the `carry` will still be computed.
//
// The slices `a` and `b` MUST have the same announced bit length (in subscript `0`)
//
// `a` and `b` MAY be the same array, but partial overlap is not allowed.
@(require_results)
i31_add :: proc "contextless" (a: []u32, b: []u32, ctl: u32) -> (carry: u32) {
words := uint(a[0] + 63) >> 5
for u in 1..<words {
aw := a[u]
bw := b[u]
naw := aw + bw + carry
carry = naw >> 31
a[u] = subtle.csel(aw, naw & I31_MASK, ctl)
}
return
}
// Subtract `b` from `a` and return the `carry` (`0` or `1`), if `ctl` is `1`.
// If `ctl` is `0`, then `a` is unmodified, but the carry is still computed
// and returned.
//
// The slices `a` and `b` MUST have the same announced bit length (in subscript `0`)
//
// `a` and `b` MAY be the same array, but partial overlap is not allowed.
@(require_results)
i31_sub :: proc "contextless" (a: []u32, b: []u32, ctl: u32) -> (carry: u32) {
words := uint(a[0] + 63) >> 5
for u in 1..<words {
aw := a[u]
bw := b[u]
naw := aw - bw - carry
carry = naw >> 31
a[u] = subtle.csel(aw, naw & I31_MASK, ctl)
}
return
}
// Compute the ENCODED actual bit length of an integer `x`.
// The argument `x` should point to the first (least significant)
// value word of the integer.
//
// The upper bit of each value word MUST be `0`.
//
// Returned value is `((k / 31) << 5) + (k % 31)` if the bit length is `k`.
//
// CT: value or length of `x` does not leak.
@(require_results)
i31_bit_length :: proc "contextless" (x: []u32) -> (res: u32) {
tw, twk: u32
xlen := len(x)
for xlen > 0 {
xlen -= 1
c := subtle.eq(tw, 0)
w := x[xlen]
tw = subtle.csel(tw, w, c)
twk = subtle.csel(twk, u32(xlen), c)
}
return (twk << 5) + _u32_bit_length(tw)
}
// Decode an integer from its big-endian unsigned representation. The
// "true" bit length of the integer is computed and set in the encoded
// announced bit length (`x[0]`), but all words of `x` corresponding to
// the full slice of source bytes.
//
// `x` needs to have a minimum length of: `1 + ((len(src) * 8) + 31) / 31`
//
// CT: value or length of `x` does not leak.
i31_decode :: proc "contextless" (x: []u32, src: []byte) {
u := len(src) - 1
v := 1
acc := u32(0)
acc_len := uint(0)
for u >= 0 {
b := u32(src[u])
acc |= b << acc_len
acc_len += 8
if acc_len >= 31 {
x[v] = acc & I31_MASK
acc_len -= 31
acc = b >> (8 - acc_len)
v += 1
}
u -= 1
}
if acc_len != 0 {
x[v] = acc
v += 1
}
x[0] = i31_bit_length(x[1:])
}
// Decode an integer from its big-endian unsigned representation.
// The integer MUST be lower than `m`; the (encoded) announced bit length
// written in `x` will be equal to that of `m`. All bytes from the
// `src` slice are read.
//
// Returned value is `1` if the decode value fits within the modulus, `0`
// otherwise. In the latter case, the `x` buffer will be set to `0` (but
// still with the announced bit length of `m`).
//
// CT: value or length of `x` does not leak. Memory access pattern depends
// only `src`'s length and the announced bit length of `m`. Whether `x` fits or
// not does not leak either.
@(require_results)
i31_decode_mod :: proc "contextless" (x: []u32, src: []byte, m: []u32) -> (res: u32) {
// Two-pass algorithm: in the first pass, we determine whether the
// value fits; in the second pass, we do the actual write.
//
// During the first pass, `res` contains the comparison result so far:
// 0x00000000 value is equal to the modulus
// 0x00000001 value is greater than the modulus
// 0xFFFFFFFF value is lower than the modulus
//
// Since we iterate starting with the least significant bytes (at
// the end of `src`), each new comparison overrides the previous
// except when the comparison yields 0 (equal).
//
// During the second pass, `res` is either 0xFFFFFFFF (value fits) 0x00000000 (value does not fit).
// We must iterate over all bytes of the source, _and_ possibly
// some extra virtual bytes (with value 0) so as to cover the
// complete modulus as well. We also add 4 such extra bytes beyond
// the modulus length because it then guarantees that no accumulated
// partial word remains to be processed.
_len := uint(len(src))
mlen := uint((m[0] + 31) >> 5)
tlen := uint(mlen << 2)
if tlen < _len {
tlen = _len
}
tlen += 4
for pass in 0..<2 {
v := uint(1)
acc := u32(0)
acc_len := u32(0)
for u in uint(0)..<tlen {
b: u32 = ---
if u < _len {
b = u32(src[_len - 1 - u])
} else {
b = 0
}
acc |= (b << acc_len)
acc_len += 8
if acc_len >= 31 {
xw := acc & I31_MASK
acc_len -= 31
acc = b >> (8 - acc_len)
if v <= mlen {
if pass == 1 {
x[v] = res & xw
} else {
cc := u32(subtle.cmp(xw, m[v]))
res = subtle.csel(cc, res, subtle.eq(cc, 0))
}
} else {
if pass == 0 {
res = subtle.csel(1, res, subtle.eq(xw, 0))
}
}
v += 1
}
}
// When we reach this point at the end of the first pass:
// r is either 0, 1 or -1; we want to set r to 0 if it
// is equal to 0 or 1, and leave it to -1 otherwise.
//
// When we reach this point at the end of the second pass:
// r is either 0 or -1; we want to leave that value
// untouched. This is a subcase of the previous.
res >>= 1
res |= (res << 1)
}
x[0] = m[0]
return res & 1
}
// Zeroize integer `x`. The announced bit length is set to the provided value,
// and the corresponding words are set to 0. The ENCODED bit length is expected
//here.
i31_zero :: proc "contextless" (x: []u32, bit_len: u32) {
x[0] = bit_len
intrinsics.mem_zero(raw_data(x[1:]), ((bit_len + 31) >> 5) * size_of(u32))
}
// Make a random integer of the provided size. The size is encoded.
// The header word is untouched.
i31_mkrand :: proc(x: []u32, esize: u32) {
_len := (esize + 31) >> 5
x_ := slice.reinterpret([]byte, x)
crypto.rand_bytes(x_[4:4 + _len * size_of(u32)])
for u in 1..<_len {
x[u] &= I31_MASK
}
m := _len & 31
if m == 0 {
x[_len] &= I31_MASK
} else {
x[_len] &= I31_MASK >> (31 - m)
}
}
// Right-shift an integer. The shift amount must be lower than 31 bits.
i31_rshift :: proc "contextless" (x: []u32, shift_amount: i32) {
_len := uint(x[0] + 31) >> 5
if _len == 0 {
return
}
count := uint(shift_amount)
r := x[1] >> count
for u in 2..= _len {
w := u32(x[u])
x[u - 1] = ((w << (31 - count)) | r) & I31_MASK
r = w >> count
}
x[_len] = r
}
// Reduce integer `a` modulo `m`. The result is written to `x`,
// and its announced bit length is set to be equal to that of `m`.
//
// `x` MUST be distinct from `a` and `m`.
//
// CT: only announced bit lengths leak, not values of `x`, `a` or `m`.
i31_reduce :: proc "contextless" (x: []u32, a: []u32, m: []u32) {
m_bitlen := m[0]
mlen := uint(m_bitlen + 31) >> 5
x[0] = m_bitlen
if m_bitlen == 0 {
return
}
// If the source is shorter, then simply copy all words from a[]
// and zero out the upper words.
a_bitlen := a[0]
alen := uint(a_bitlen + 31) >> 5
if a_bitlen < m_bitlen {
copy(x[1:], a[1:][:alen])
for u in alen..<mlen {
x[u + 1] = 0
}
return
}
// The source length is at least equal to that of the modulus.
// We must thus copy N-1 words, and input the remaining words one
// by one.
copy(x[1:], a[2 + (alen - mlen):][:mlen - 1])
x[mlen] = 0
for u := 1 + alen - mlen; u > 0; u -= 1 {
i31_muladd_small(x, a[u], m)
}
}
// Decode an integer from its big-endian unsigned representation, and
// reduce it modulo the provided modulus `m`. The announced bit length
// of the result is set to be equal to that of the modulus.
//
// `x` MUST be distinct from `m`.
i31_decode_reduce :: proc "contextless" (x: []u32, src: []byte, m: []u32) {
// Get the encoded bit length.
m_ebitlen := m[0]
// Special case for an invalid (null) modulus.
if m_ebitlen == 0 {
x[0] = 0
return
}
// Clear the destination.
i31_zero(x, m_ebitlen)
// First decode directly as many bytes as possible.
// This requires computing the actual bit length.
m_rbitlen := m_ebitlen >> 5
m_rbitlen = (m_ebitlen & 31) + (m_rbitlen << 5) - m_rbitlen
mblen := uint(m_rbitlen + 7) >> 3
k := mblen - 1
_len := uint(len(src))
if k >= _len {
i31_decode(x, src)
x[0] = m_ebitlen
return
}
i31_decode(x, src[:k])
x[0] = m_ebitlen
// Input remaining bytes, using 31-bit words.
acc := u32(0)
acc_len := uint(0)
for {
v := u32(src[k])
if acc_len >= 23 {
acc_len -= 23
acc <<= (8 - acc_len)
acc |= v >> acc_len
i31_muladd_small(x, acc, m)
acc = v & (0xFF >> (8 - acc_len))
} else {
acc = (acc << 8) | v
acc_len += 8
}
if k += 1; k >= _len {
break
}
}
// We may have some bits accumulated. We then perform a shift to
// be able to inject these bits as a full 31-bit word.
if acc_len != 0 {
acc = (acc | (x[1] << acc_len)) & I31_MASK
i31_rshift(x, i32(31 - acc_len))
i31_muladd_small(x, acc, m)
}
}
// Multiply `x` by 2^31 and then add integer `z`, modulo `m`.
// This function assumes that `x` and `m` have the same announced bit
// length, the announced bit length of `m` matches its true bit length.
//
// `x` and `m` MUST be distinct arrays.
// `z` MUST fit in 31 bits (upper bit set to 0).
//
// CT: only the common announced bit length of `x` and `m` leaks, not
// the values of `x`, `z` or `m`.
i31_muladd_small :: proc "contextless" (x: []u32, z: u32, m: []u32) {
// We can test on the modulus bit length since we accept to leak
// that length.
m_bitlen := m[0]
if m_bitlen == 0 {
return
}
hi: u32
if m_bitlen <= 31 {
hi = x[1] >> 1
lo := (x[1] << 31) | z
x[1] = _rem_u32(hi, lo, m[1])
return
}
mlen := uint(m_bitlen + 31) >> 5
mblr := uint(m_bitlen) & 31
// Principle: we estimate the quotient (x*2^31+z)/m by
// doing a 64/32 division with the high words.
//
// Let:
// w = 2^31
// a = (w*a0 + a1) * w^N + a2
// b = b0 * w^N + b2
// such that:
// 0 <= a0 < w
// 0 <= a1 < w
// 0 <= a2 < w^N
// w/2 <= b0 < w
// 0 <= b2 < w^N
// a < w*b
// I.e. the two top words of a are a0:a1, the top word of b is
// b0, we ensured that b0 is "full" (high bit set), and a is
// such that the quotient q = a/b fits on one word (0 <= q < w).
//
// If a = b*q + r (with 0 <= r < q), we can estimate q by
// doing an Euclidean division on the top words:
// a0*w+a1 = b0*u + v (with 0 <= v < b0)
// Then the following holds:
// 0 <= u <= w
// u-2 <= q <= u
hi = x[mlen]
a0, a1, b0: u32
if mblr == 0 {
a0 = x[mlen]
intrinsics.mem_copy(raw_data(x[2:]), raw_data(x[1:]), (mlen - 1) * size_of(u32))
x[1] = z
a1 = x[mlen]
b0 = m[mlen]
} else {
a0 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & I31_MASK
intrinsics.mem_copy(raw_data(x[2:]), raw_data(x[1:]), (mlen - 1) * size_of(u32))
x[1] = z
a1 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & I31_MASK
b0 = ((m[mlen] << (31 - mblr)) | (m[mlen - 1] >> mblr)) & I31_MASK
}
// We estimate a divisor q. If the quotient returned by div()
// is g:
// -- If a0 == b0 then g == 0; we want q = 0x7FFFFFFF.
// -- Otherwise:
// -- if g == 0 then we set q = 0;
// -- otherwise, we set q = g - 1.
// The properties described above then ensure that the true
// quotient is q-1, q or q+1.
//
// Take care that a0, a1 and b0 are 31-bit words, not 32-bit. We
// must adjust the parameters to br_div() accordingly.
g := _div_u32(a0 >> 1, a1 | (a0 << 31), b0)
q := subtle.csel(subtle.csel(g - 1, 0, subtle.eq(g, 0)), I31_MASK, subtle.eq(a0, b0))
// We subtract q*m from x (with the extra high word of value 'hi').
// Since q may be off by 1 (in either direction), we may have to
// add or subtract m afterwards.
//
// The 'tb' flag will be true (1) at the end of the loop if the
// result is greater than or equal to the modulus (not counting
// 'hi' or the carry).
cc := u32(0)
tb := u32(1)
for u in 1..= mlen {
mw := m[u]
zl := _mul31(mw, q) + u64(cc)
cc = u32(zl >> 31)
zw := u32(zl) & I31_MASK
xw := x[u]
nxw := xw - zw
cc += nxw >> 31
nxw &= I31_MASK
x[u] = nxw
tb = subtle.csel(subtle.gt(nxw, mw), tb, subtle.eq(nxw, mw))
}
// If we underestimated q, then either cc < hi (one extra bit
// beyond the top array word), or cc == hi and tb is true (no
// extra bit, but the result is not lower than the modulus). In
// these cases we must subtract m once.
//
// Otherwise, we may have overestimated, which will show as
// cc > hi (thus a negative result). Correction is adding m once.
over := subtle.gt(cc, hi)
under := ~over & (tb | subtle.lt(cc, hi))
_ = i31_add(x, m, over)
_ = i31_sub(x, m, under)
}
// Encode an integer into its big-endian unsigned representation. The
// output length in bytes is provided (parameter 'len'); if the length
// is too short then the integer is appropriately truncated; if it is
// too long then the extra bytes are set to 0.
i31_encode :: proc "contextless" (dst: []byte, x: []u32) {
xlen := uint(x[0] + 31) >> 5
if xlen == 0 {
intrinsics.mem_zero(raw_data(dst[:]), len(dst) * size_of(u32))
return
}
_len := uint(len(dst))
k := uint(1)
acc := u32(0)
acc_len := uint(0)
for _len != 0 {
w := (k <= xlen) ? x[k] : 0
k += 1
if (acc_len == 0) {
acc = w
acc_len = 31
} else {
z := acc | (w << acc_len)
acc_len -= 1
acc = w >> (31 - acc_len)
if _len >= 4 {
_len -= 4
ptr := (^u32be)(raw_data(dst[_len:]))
intrinsics.unaligned_store(ptr, u32be(z))
} else {
switch _len {
case 3:
dst[_len - 3] = byte(z >> 16)
fallthrough
case 2:
dst[_len - 2] = byte(z >> 8)
fallthrough
case 1:
dst[_len - 1] = byte(z)
}
return
}
}
}
}
// Compute `-(1/x) % 2^31`. If `x` is even, then this function returns `0`.
i31_ninv31 :: proc "contextless" (x: u32) -> (y: u32) {
y = 2 - x
y *= 2 - y * x
y *= 2 - y * x
y *= 2 - y * x
y *= 2 - y * x
return subtle.csel(0, -y, x & 1) & I31_MASK
}
// Compute a modular Montgomery multiplication. `d` is filled with the
// value of `x*y/R % m` (where `R` is the Montgomery factor).
//
// The array `d` MUST be distinct from `x`, `y` and `m`[].
// `x` and `y` MUST be numerically lower than `m`.
//
// `x` and `y` MAY be the same array.
//
// The `m0i` parameter is equal to `-(1/m0) mod 2^31`, where `m0` is the least
// significant value word of `m` (this works only if `m` is an odd integer).
i31_montymul :: proc "contextless" (d: []u32, x: []u32, y: []u32, m: []u32, m0i: u32) {
// Each outer loop iteration computes:
// `d <- (d + xu*y + f*m) / 2^31`
// We have `xu <= 2^31-1` and `f <= 2^31-1`.
// Thus, if `d <= 2*m-1` on input, then:
// `2*m-1 + 2*(2^31-1)*m <= (2^32)*m-1`
// and the new `d` value is less than `2*m`.
//
// We represent `d` over 31-bit words, with an extra word `dh`,
// which can thus be only 0 or 1.
_len := uint((m[0] + 31) >> 5)
len4 := _len & ~uint(3)
i31_zero(d, m[0])
dh := u32(0)
for u in 0..<_len {
// The carry for each operation fits on 32 bits:
// `d[v+1] <= 2^31-1`
// `xu*y[v+1] <= (2^31-1)*(2^31-1)`
// `f*m[v+1] <= (2^31-1)*(2^31-1)`
// `r <= 2^32-1`
// `(2^31-1) + 2*(2^31-1)*(2^31-1) + (2^32-1) = 2^63 - 2^31`
//
// After division by `2^31`, the new `r` is then at most `2^32-1`
//
// Using a 32-bit carry has performance benefits on 32-bit
// systems; however, on 64-bit architectures, we prefer to
// keep the carry (r) in a 64-bit register, thus avoiding some
// "clear high bits" operations.
xu := x[u + 1]
f := _mul31_lo((d[1] + _mul31_lo(xu, y[1])), m0i)
r := u64(0)
v := uint(0)
for ; v < len4; v += 4 {
z := u64(d[v + 1]) + _mul31(xu, y[v + 1]) + _mul31(f, m[v + 1]) + r
r = z >> 31
d[v + 0] = u32(z) & I31_MASK
z = u64(d[v + 2]) + _mul31(xu, y[v + 2]) + _mul31(f, m[v + 2]) + r
r = z >> 31
d[v + 1] = u32(z) & I31_MASK
z = u64(d[v + 3]) + _mul31(xu, y[v + 3]) + _mul31(f, m[v + 3]) + r
r = z >> 31
d[v + 2] = u32(z) & I31_MASK
z = u64(d[v + 4]) + _mul31(xu, y[v + 4]) + _mul31(f, m[v + 4]) + r
r = z >> 31
d[v + 3] = u32(z) & I31_MASK
}
for ; v < _len; v += 1 {
z := u64(d[v + 1]) + _mul31(xu, y[v + 1]) + _mul31(f, m[v + 1]) + r
r = z >> 31
d[v] = u32(z) & I31_MASK
}
// Since the new `dh` can only be `0` or `1`, the addition of
// the old dh with the carry MUST fit on 32 bits, and
// thus can be done into dh itself.
dh += u32(r)
d[_len] = dh & I31_MASK
dh >>= 31
}
// We must write back the bit length because it was overwritten in
// the loop (not overwriting it would require a test in the loop,
// which would yield bigger and slower code).
d[0] = m[0]
// `d` may still be greater than `m` at that point; notably, the `dh`
// word may be non-zero.
_ = i31_sub(d, m, subtle.neq(dh, 0) | subtle.not(i31_sub(d, m, 0)))
}
// Convert a modular integer to Montgomery representation.
//
// The integer `x` MUST be lower than `m`, but with the same announced bit length.
i31_to_monty :: proc "contextless" (x: []u32, m: []u32) {
// uint32_t k;
for k := (m[0] + 31) >> 5; k > 0; k -= 1 {
i31_muladd_small(x, 0, m)
}
}
// Convert a modular integer back from Montgomery representation.
//
// The integer `x` MUST be lower than `m`[], but with the same announced bit
// length.
//
// The `m0i` parameter is equal to `-(1/m0) mod 2^32`, where `m0` is the least
// significant value word of `m` (this works only if `m` is an odd integer).
i31_from_monty :: proc "contextless" (x: []u32, m: []u32, m0i: u32) {
_len := uint(m[0] + 31) >> 5
for _ in 0..<_len {
f := _mul31_lo(x[1], m0i)
cc := u64(0)
for v in 0..<_len {
z := u64(x[v + 1]) + _mul31(f, m[v + 1]) + cc
cc = z >> 31
if v != 0 {
x[v] = u32(z & I31_MASK)
}
}
x[_len] = u32(cc)
}
// We may have to do an extra subtraction, but only if the value in `x`
// is indeed greater than or equal to that of `m`, which is why we must
// do two calls:
// - First call computes the carry
// - Second call performs the subtraction only if the carry is 0).
_ = i31_sub(x, m, subtle.not(i31_sub(x, m, 0)))
}
// Compute a modular exponentiation.
//
// `x` MUST be an integer modulo `m` (same announced bit length, lower value).
// `m` MUST be odd.
//
// The exponent `e` is in big-endian unsigned notation.
//
// The `m0i` parameter is equal to `-(1/m0) mod 2^31`, where `m0` is the least
// significant value word of `m` (this works only if `m` is an odd integer).
//
// The `t1` and `t2` parameters must be temporary arrays, each large enough to
// accommodate an integer with the same size as `m`.
i31_modpow :: proc "contextless" (x: []u32, e: []byte, m: []u32, m0i: u32, t1: []u32, t2: []u32) {
// `mlen` is the length of `m` expressed in `u32`'s (including the
// "bit length" first field).
mlen := uint((m[0] + 63) >> 5)
elen := u32(len(e))
// Throughout the algorithm:
// -- `t1` is in Montgomery representation; it contains x, x^2, x^4, x^8...
// -- The result is accumulated, in normal representation, in the `x` array.
// -- `t2` is used as destination buffer for each multiplication.
//
// Note that there is no need to call `i32_from_monty()`.
copy(t1[:mlen], x[:mlen])
i31_to_monty(t1, m)
i31_zero(x, m[0])
x[1] = 1
for k := u32(0); k < (elen << 3); k += 1 {
ctl := (e[elen - 1 - (k >> 3)] >> (k & 7)) & 1
i31_montymul(t2, x, t1, m, m0i)
for &d, i in x[:mlen] {
d = subtle.csel(d, t2[i], ctl)
}
i31_montymul(t2, t1, t1, m, m0i)
copy(t1[:mlen], t2[:mlen])
}
}
// Compute a modular exponentiation.
//
// `x` MUST be an integer modulo `m` (same announced bit length, lower value).
// `m` MUST be odd.
//
// The exponent `e` is in big-endian unsigned notation.
//
// The `m0i` parameter is equal to `-(1/m0) mod 2^31`, where `m0` is the least
// significant value word of `m`[] (this works only if m[] is an odd integer).
//
// The `tmp` array is used for temporaries; it must be large enough to accommodate
// at least two temporary values with the same size as `m` (including the leading
// "bit length" word).
//
// If there is room for more temporaries, then this function may use the extra
// room for window-based optimisation, resulting in faster computations.
//
// Returned value is `true` on success, `false` on error. An error is reported if
// the provided `tmp`array is too short.
i31_modpow_opt :: proc "contextless" (x: []u32, e: []byte, m: []u32, m0i: u32, tmp: []u32) -> u32 {
// NOTE/yawning: This is only used by the rsa_i31 code, with the key
// generation taking a function pointer to either this routine,
// or the i62 variant.
//
// If we ever need to support the i32 version, it is used extensively,
// but non e-waste architecutures will all do the right thing with
// the i62 version, albeit with a perforance hit on 32-bit CPUs.
unimplemented_contextless()
// i31_mod_pow(x, e, m, m0i, tmp[:len(m)], tmp[len(m):])
// return 1
}
// Compute `d+a*b`, result in `d`.
//
// The initial announced bit length of `d` MUST match that of `a`[].
//
// The `d` array MUST be large enough to accommodate the full result,
// plus (possibly) an extra word. The resulting announced bit length
// of `d` will be the sum of the announced bit lengths of `a` and `b`
// (therefore, it may be larger than the actual bit length of the numerical result).
//
// `a` and `b` may be the same array. `d` must be disjoint from both `a` and `b`.
i31_mulacc :: proc "contextless" (d: []u32, a: []u32, b: []u32) {
a_len := uint((a[0] + 31) >> 5)
b_len := uint((b[0] + 31) >> 5)
// We want to add the two bit lengths, but these are encoded,
// which requires some extra care.
d_l := (a[0] & 31) + (b[0] & 31)
d_h := (a[0] >> 5) + (b[0] >> 5)
d[0] = (d_h << 5) + d_l + (~u32(d_l - 31) >> 31)
for u in 0..<b_len {
// Carry always fits on 31 bits; we want to keep it in a
// 32-bit register on 32-bit architectures (on a 64-bit
// architecture, cast down from 64 to 32 bits means
// clearing the high bits, which is not free; on a 32-bit
// architecture, the same operation really means ignoring
// the top register, which has negative or zero cost).
f := b[1 + u]
cc := u64(0)
for v in 0..<a_len {
z := u64(d[1 + u + v]) + _mul31(f, a[1 + v]) + cc
cc = z >> 31
d[1 + u + v] = u32(z) & I31_MASK
}
d[1 + u + a_len] = u32(cc)
}
}

View File

@@ -0,0 +1,361 @@
package _bigint
// Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
import "base:intrinsics"
import "core:math/bits"
import subtle "core:crypto/_subtle"
import "core:slice"
@(private="file")
I62_MASK :: 0x3fff_ffff_ffff_ffff
// Compute x*y+v1+v2. Operands are 64-bit, and result is 128-bit, with
// high word in "hi" and low word in "lo".
@(private="file", require_results)
_fma1 :: #force_inline proc "contextless" (x, y, v1, v2: u64) -> (hi, lo: u64) {
hi, lo = bits.mul_u64(x, y)
carry: u64
lo, carry = bits.add_u64(lo, v1, 0)
hi += carry
lo, carry = bits.add_u64(lo, v2, 0)
hi += carry
return
}
// Compute x1*y1+x2*y2+v1+v2. Operands are 64-bit, and result is 128-bit,
// with high word in "hi" and low word in "lo".
//
// Callers should ensure that the two inner products, and the v1 and v2
// operands, are multiple of 4 (this is not used by this specific definition
// but may help other implementations).
@(private="file", require_results)
_fma2 :: #force_inline proc "contextless" (x1, y1, x2, y2, v1, v2: u64) -> (hi, lo: u64) {
hi_1, lo_1 := bits.mul_u64(x1, y1)
hi_2, lo_2 := bits.mul_u64(x2, y2)
carry: u64
lo, carry = bits.add_u64(lo_1, lo_2, 0)
hi, _ = bits.add_u64(hi_1, hi_2, carry)
lo, carry = bits.add_u64(lo, v1, 0)
hi += carry
lo, carry = bits.add_u64(lo, v2, 0)
hi += carry
return
}
@(private="file", require_results)
_mul62_lo :: #force_inline proc "contextless" (x, y: u64) -> u64 {
return (x * y) & I62_MASK
}
// Subtract b from a, and return the final carry. If 'ctl32' is 0, then
// a[] is kept unmodified, but the final carry is still computed and
// returned.
@(private="file", require_results)
_i62_sub :: proc "contextless" (a, b: []u64, num: int, ctl32: u32) -> u32 {
cc: u64
ctl := -ctl32
mask := u64(ctl) | (u64(ctl) << 32)
for u in 0..<num {
aw := a[u]
bw := b[u]
dw := aw - bw - cc
cc = dw >> 63
dw &= I62_MASK
a[u] = aw ~ (mask & (dw ~ aw))
}
return u32(cc)
}
// Montgomery multiplication, over arrays of 62-bit values. The
// destination array (d) must be distinct from the other operands
// (x, y and m). All arrays are in little-endian format (least
// significant word comes first) over 'num' words.
@(private="file")
_i62_montymul :: proc "contextless" (d, x, y, m: []u64, num: int, m0i: u64) {
dh: u64
num4 := 1 + u64((num - 1) & ~int(3))
intrinsics.mem_zero(raw_data(d), num * size_of(u64))
for u in 0..<num {
xu := x[u] << 2
f := _mul62_lo(d[0] + _mul62_lo(x[u], y[0]), m0i) << 2
hi, lo := _fma2(xu, y[0], f, m[0], d[0] << 2, 0)
r := hi
v: int
for v = 1; v < int(num4); v += 4 {
hi, lo = _fma2(xu, y[v + 0], f, m[v + 0], d[v + 0] << 2, r << 2)
r = hi + (r >> 62)
d[v - 1] = lo >> 2
hi, lo = _fma2(xu, y[v + 1], f, m[v + 1], d[v + 1] << 2, r << 2)
r = hi + (r >> 62)
d[v + 0] = lo >> 2
hi, lo = _fma2(xu, y[v + 2], f, m[v + 2], d[v + 2] << 2, r << 2)
r = hi + (r >> 62)
d[v + 1] = lo >> 2
hi, lo = _fma2(xu, y[v + 3], f, m[v + 3], d[v + 3] << 2, r << 2)
r = hi + (r >> 62)
d[v + 2] = lo >> 2
}
for ; v < num; v += 1 {
hi, lo = _fma2(xu, y[v], f, m[v], d[v] << 2, r << 2)
r = hi + (r >> 62)
d[v - 1] = lo >> 2
}
zh := dh + r
d[num - 1] = zh & I62_MASK
dh = zh >> 62
}
_ = _i62_sub(d, m, num, u32(dh) | subtle.not(_i62_sub(d, m, num, 0)))
}
// Conversion back from Montgomery representation.
@(private="file")
_i62_frommonty :: proc "contextless" (x, m: []u64, num: int, m0i: u64) {
for _ in 0..<num {
cc: u64
f := _mul62_lo(x[0], m0i) << 2
for v in 0..<num {
hi, lo := _fma1(f, m[v], x[v] << 2, cc)
cc = hi << 2
if (v != 0) {
x[v - 1] = lo >> 2
}
}
x[num - 1] = cc >> 2
}
_ = _i62_sub(x, m, num, subtle.not(_i62_sub(x, m, num, 0)))
}
// Variant of i31_modpow_opt() that internally uses 64x64->128
// multiplications. It expects the same parameters as i31_modpow_opt(),
// except that the temporaries should be 64-bit integers, not 32-bit
// integers.
i62_modpow_opt :: proc "contextless" (x31: []u32, e: []byte, m31: []u32, m0i31: u32, tmp: []u64) -> u32 {
twlen := len(tmp)
// Get modulus size, in words.
mw31num := int((m31[0] + 31) >> 5)
mw62num := int((mw31num + 1) >> 1)
// In order to apply this function, we must have enough room to
// copy the operand and modulus into the temporary array, along
// with at least two temporaries. If there is not enough room,
// switch to br_i31_modpow(). We also use br_i31_modpow() if the
// modulus length is not at least four words (94 bits or more).
if mw31num < 4 || mw62num << 2 > twlen {
// We assume here that we can split an aligned uint64_t
// into two properly aligned uint32_t. Since both types
// are supposed to have an exact width with no padding,
// then this property must hold.
txlen := mw31num + 1
if twlen < txlen {
return 0
}
tmp_as_u32s := slice.reinterpret([]u32, tmp)
t1, t2 := tmp_as_u32s[:txlen], tmp_as_u32s[txlen:]
i31_modpow(x31, e, m31, m0i31, t1, t2)
return 1
}
// Convert x to Montgomery representation: this means that
// we replace x with x*2^z mod m, where z is the smallest multiple
// of the word size such that 2^z >= m. We want to reuse the 31-bit
// functions here (for constant-time operation), but we need z
// for a 62-bit word size.
for _ in 0..<mw62num {
i31_muladd_small(x31, 0, m31)
i31_muladd_small(x31, 0, m31)
}
// Assemble operands into arrays of 62-bit words. Note that
// all the arrays of 62-bit words that we will handle here
// are without any leading size word.
//
// We also adjust tmp and twlen to account for the words used
// for these extra arrays.
m := tmp[:mw62num]
x := tmp[mw62num:mw62num*2]
tmp_ := tmp[mw62num << 1:]
twlen -= mw62num << 1
for u := 0; u < mw31num; u += 2 {
v := u >> 1
if u + 1 == mw31num {
m[v] = u64(m31[u + 1])
x[v] = u64(x31[u + 1])
} else {
m[v] = u64(m31[u + 1]) + (u64(m31[u + 2]) << 31)
x[v] = u64(x31[u + 1]) + (u64(x31[u + 2]) << 31)
}
}
// Compute window size. We support windows up to 5 bits; for a
// window of size k bits, we need 2^k+1 temporaries (for k = 1,
// we use special code that uses only 2 temporaries).
win_len: int
for win_len = 5; win_len > 1; win_len -= 1 {
if (1 << uint(win_len) + 1) * mw62num <= twlen {
break
}
}
t1 := tmp_[:mw62num]
t2 := tmp_[mw62num:]
// Compute m0i, which is equal to -(1/m0) mod 2^62. We were
// provided with m0i31, which already fulfills this property
// modulo 2^31; the single expression below is then sufficient.
m0i := u64(m0i31)
m0i = _mul62_lo(m0i, 2 + _mul62_lo(m0i, m[0]))
// Compute window contents. If the window has size one bit only,
// then t2 is set to x; otherwise, t2[0] is left untouched, and
// t2[k] is set to x^k (for k >= 1).
if win_len == 1 {
copy(t2, x)
} else {
copy(t2[mw62num:], x)
base := t2[mw62num:]
for u := 2; u < 1 << uint(win_len); u += 1 {
_i62_montymul(base[mw62num:], base, x, m, mw62num, m0i)
base = base[mw62num:]
}
}
// Set x to 1, in Montgomery representation. We again use the
// 31-bit code.
i31_zero(x31, m31[0])
x31[(m31[0] + 31) >> 5] = 1
i31_muladd_small(x31, 0, m31)
if mw31num & 1 != 0 {
i31_muladd_small(x31, 0, m31)
}
for u := 0; u < mw31num; u+= 2 {
v := u >> 1
if u + 1 == mw31num {
x[v] = u64(x31[u + 1])
} else {
x[v] = u64(x31[u + 1]) + (u64(x31[u + 2]) << 31)
}
}
e_, e_len := e, len(e)
// We process bits from most to least significant. At each
// loop iteration, we have acc_len bits in acc.
acc: u32
acc_len: uint
for acc_len > 0 || e_len > 0 {
// Get the next bits.
k := uint(win_len)
if acc_len < uint(win_len) {
if e_len > 0 {
acc = (acc << 8) | u32(e_[0])
e_ = e_[1:]
e_len -= 1
acc_len += 8
} else {
k = acc_len
}
}
bits := (acc >> (acc_len - k)) & ((u32(1) << k) - 1)
acc_len -= k
// We could get exactly k bits. Compute k squarings.
for _ in 0..<k {
_i62_montymul(t1, x, x, m, mw62num, m0i)
copy(x, t1)
}
// Window lookup: we want to set t2 to the window
// lookup value, assuming the bits are non-zero. If
// the window length is 1 bit only, then t2 is
// already set; otherwise, we do a constant-time lookup.
if win_len > 1 {
intrinsics.mem_zero(raw_data(t2), mw62num * size_of(u64))
base := t2[mw62num:]
for u := u32(1); u < u32(1) << k; u += 1 {
mask := -u64(subtle.eq(u, bits))
for v in 0..<mw62num {
t2[v] |= mask & base[v]
}
base = base[mw62num:]
}
}
// Multiply with the looked-up value. We keep the product
// only if the exponent bits are not all-zero.
_i62_montymul(t1, x, t2, m, mw62num, m0i)
mask1 := -u64(subtle.eq(bits, 0))
mask2 := ~mask1
for u in 0..<mw62num {
x[u] = (mask1 & x[u]) | (mask2 & t1[u])
}
}
// Convert back from Montgomery representation.
_i62_frommonty(x, m, mw62num, m0i)
// Convert result into 31-bit words.
for u := 0; u < mw31num; u += 2 {
zw := u64(x[u >> 1])
x31[u + 1] = u32(zw) & I31_MASK
if u + 1 < mw31num {
x31[u + 2] = u32(zw >> 31)
}
}
return 1
}
// Wrapper for i62_modpow_opt() that uses the same type as
// i31_modpow_opt(); however, it requires its 'tmp' argument to the
// 64-bit aligned.
i62_modpow_opt_as_i31 :: proc "contextless" (x31: []u32, e: []byte, m31: []u32, m0i31: u32, tmp: []u32) -> u32 {
// As documented, this function expects the 'tmp' argument to be
// 64-bit aligned. This is OK since this function is internal (it
// is not part of BearSSL's public API).
ensure_contextless(uintptr(raw_data(tmp)) & 7 == 0)
ensure_contextless(len(tmp) & 1 == 0) // Length MUST be even.
tmp_as_u64s := slice.reinterpret([]u64, tmp)
return i62_modpow_opt(x31, e, m31, m0i31, tmp_as_u64s)
}

View File

@@ -0,0 +1,265 @@
package _bigint
// Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
import subtle "core:crypto/_subtle"
import "core:math/big"
import "core:slice"
// Perform trial divisions on a candidate prime. We opt for the simple
// route and "just" compute a series of trial divisions.
//
// Returned value is 1 on success (none of the small primes
// divides x), 0 on error (a non-trivial GCD is obtained).
@(private="file", require_results)
trial_divisions :: proc "contextless" (x: []u32) -> u32 {
for factor in big._private_prime_table {
if factor <= 11 {
continue
}
if i31_rem(x, u32(factor)) == 0 {
return 0
}
}
return 1
}
// Perform n rounds of Miller-Rabin on the candidate prime x. This
// function assumes that x = 3 mod 4.
//
// WARNING: t MUST be 64-bit aligned, and be large enough such that
// it can hold 4 encoded integers that have the same number of limbs
// as x.
//
// Returned value is 1 on success (all rounds completed successfully),
// 0 otherwise.
@(private="file", require_results)
i62_miller_rabin :: proc(x: []u32, n: int, t: []u32) -> u32 {
// Since x = 3 mod 4, the Miller-Rabin test is simple:
// - get a random base a (such that 1 < a < x-1)
// - compute z = a^((x-1)/2) mod x
// - if z != 1 and z != x-1, the number x is composite
//
// We generate bases 'a' randomly with a size which is
// one bit less than x, which ensures that a < x-1. It
// is not useful to verify that a > 1 because the probability
// that we get a value a equal to 0 or 1 is much smaller
// than the probability of our Miller-Rabin tests not to
// detect a composite, which is already quite smaller than the
// probability of the hardware misbehaving and return a
// composite integer because of some glitch (e.g. bad RAM
// or ill-timed cosmic ray).
// Compute (x-1)/2 (encoded).
xm1d2 := slice.reinterpret([]byte, t)
xm1d2_len := ((x[0] - (x[0] >> 5)) + 7) >> 3
i31_encode(xm1d2[:xm1d2_len], x)
cc: u32
for u in 0..<xm1d2_len {
w := u32(xm1d2[u])
xm1d2[u] = byte((w >> 1) | cc)
cc = w << 7
}
// We used some words of the provided buffer for (x-1)/2.
xm1d2_len_u32 := (xm1d2_len + 3) >> 2
t_ := t[xm1d2_len_u32:]
tlen := len(t_)
xlen := (x[0] + 31) >> 5
asize := x[0] - 1 - subtle.eq0(x[0] & 31)
x0i := i31_ninv31(x[1])
for _ in 0..<n {
// Generate a random base. We don't need the base to be
// really uniform modulo x, so we just get a random
// number which is one bit shorter than x.
a := t_
a[0] = x[0]
a[xlen] = 0
i31_mkrand(a, asize)
// Compute a^((x-1)/2) mod x. We assume here that the
// function will not fail (the temporary array is large
// enough).
t2 := t_[1 + xlen:]
t2len := tlen - 1 - int(xlen)
if (t2len & 1) != 0 {
// Since the source array is 64-bit aligned and
// has an even number of elements (TEMPS), we
// can use the parity of the remaining length to
// detect and adjust alignment.
t2 = t2[1:]
t2len -= 1
}
i62_modpow_opt_as_i31(a, xm1d2[:xm1d2_len], x, x0i, t2[:t2len])
// We must obtain either 1 or x-1. Note that x is odd,
// hence x-1 differs from x only in its low word (no
// carry).
eq1 := a[1] ~ 1
eqm1 := a[1] ~ (x[1] - 1)
for u in 2..=xlen {
eq1 |= a[u]
eqm1 |= a[u] ~ x[u]
}
if ((subtle.eq0(eq1) | subtle.eq0(eqm1)) == 0) {
return 0
}
}
return 1
}
// Create a random prime of the provided size. 'esize' is the _encoded_
// bit length. The two top bits and the two bottom bits are set to 1.
i62_mkprime :: proc(x: []u32, esize: u32, pubexp: u32, t: []u32) {
x[0] = esize
_len := (esize + 31) >> 5
for {
// Generate random bits. We force the two top bits and the
// two bottom bits to 1.
i31_mkrand(x, esize)
if (esize & 31) == 0 {
x[_len] |= 0x60000000
} else if (esize & 31) == 1 {
x[_len] |= 0x00000001
x[_len - 1] |= 0x40000000
} else {
x[_len] |= 0x00000003 << ((esize & 31) - 2)
}
x[1] |= 0x00000003
// Trial division with low primes (3, 5, 7 and 11). We
// use the following properties:
//
// 2^2 = 1 mod 3
// 2^4 = 1 mod 5
// 2^3 = 1 mod 7
// 2^10 = 1 mod 11
m3, m5, m7, m11: u32
s7, s11: uint
for u in 0..<_len {
w := x[1 + u]
w3 := (w & 0xFFFF) + (w >> 16) // max: 98302
w5 := (w & 0xFFFF) + (w >> 16) // max: 98302
w7 := (w & 0x7FFF) + (w >> 15) // max: 98302
w11 := (w & 0xFFFFF) + (w >> 20) // max: 1050622
m3 += w3 << (u & 1)
m3 = (m3 & 0xFF) + (m3 >> 8) // max: 1025
m5 += w5 << ((4 - u) & 3)
m5 = (m5 & 0xFFF) + (m5 >> 12) // max: 4479
m7 += w7 << s7
m7 = (m7 & 0x1FF) + (m7 >> 9) // max: 1280
s7 += 1
if s7 == 3 {
s7 = 0
}
m11 += w11 << s11
s11 += 1
if s11 == 10 {
s11 = 0
}
m11 = (m11 & 0x3FF) + (m11 >> 10) // max: 526847
}
m3 = (m3 & 0x3F) + (m3 >> 6) // max: 78
m3 = (m3 & 0x0F) + (m3 >> 4) // max: 18
m3 = ((m3 * 43) >> 5) & 3
m5 = (m5 & 0xFF) + (m5 >> 8) // max: 271
m5 = (m5 & 0x0F) + (m5 >> 4) // max: 31
m5 -= 20 & -subtle.gt(m5, 19)
m5 -= 10 & -subtle.gt(m5, 9)
m5 -= 5 & -subtle.gt(m5, 4)
m7 = (m7 & 0x3F) + (m7 >> 6) // max: 82
m7 = (m7 & 0x07) + (m7 >> 3) // max: 16
m7 = ((m7 * 147) >> 7) & 7
// 2^5 = 32 = -1 mod 11.
m11 = (m11 & 0x3FF) + (m11 >> 10) // max: 1536
m11 = (m11 & 0x3FF) + (m11 >> 10) // max: 1023
m11 = (m11 & 0x1F) + 33 - (m11 >> 5) // max: 64
m11 -= 44 & -subtle.gt(m11, 43)
m11 -= 22 & -subtle.gt(m11, 21)
m11 -= 11 & -subtle.gt(m11, 10)
// If any of these modulo is 0, then the candidate is
// not prime. Also, if pubexp is 3, 5, 7 or 11, and the
// corresponding modulus is 1, then the candidate must
// be rejected, because we need e to be invertible
// modulo p-1. We can use simple comparisons here
// because they won't leak information on a candidate
// that we keep, only on one that we reject (and is thus
// not secret).
if m3 == 0 || m5 == 0 || m7 == 0 || m11 == 0 {
continue
}
if (pubexp == 3 && m3 == 1) || (pubexp == 5 && m5 == 1) || (pubexp == 7 && m7 == 1) || (pubexp == 11 && m11 == 1) {
continue
}
// More trial divisions.
if trial_divisions(x) == 0 {
continue
}
// Miller-Rabin algorithm. Since we selected a random
// integer, not a maliciously crafted integer, we can use
// relatively few rounds to lower the risk of a false
// positive (i.e. declaring prime a non-prime) under
// 2^(-80). It is not useful to lower the probability much
// below that, since that would be substantially below
// the probability of the hardware misbehaving. Sufficient
// numbers of rounds are extracted from the Handbook of
// Applied Cryptography, note 4.49 (page 149).
//
// Since we work on the encoded size (esize), we need to
// compare with encoded thresholds.
rounds: int
switch {
case esize < 309:
rounds = 12
case esize < 464:
rounds = 9
case esize < 670:
rounds = 6
case esize < 877:
rounds = 4
case esize < 1341:
rounds = 3
case:
rounds = 2
}
if i62_miller_rabin(x, rounds, t) == 1 {
return
}
}
}

View File

@@ -71,7 +71,7 @@ fe_equal :: proc "contextless" (arg1, arg2: ^Montgomery_Domain_Field_Element) ->
// This will only underflow if and only if (⟺) arg1 == arg2, and we return the borrow,
// which will be 1.
is_eq := subtle.u64_is_zero(fe_non_zero(&tmp))
is_eq := subtle.eq0(fe_non_zero(&tmp))
fe_clear(&tmp)

View File

@@ -77,7 +77,7 @@ fe_equal :: proc "contextless" (arg1, arg2: ^Montgomery_Domain_Field_Element) ->
// This will only underflow if and only if (⟺) arg1 == arg2, and we return the borrow,
// which will be 1.
is_eq := subtle.u64_is_zero(fe_non_zero(&tmp))
is_eq := subtle.eq0(fe_non_zero(&tmp))
fe_clear(&tmp)

View File

@@ -60,7 +60,7 @@ fe_from_bytes :: proc "contextless" (
reduced[3], borrow = bits.sub_u64(tmp[3], ELL[3], borrow)
reduced[4], borrow = bits.sub_u64(tmp[4], ELL[4], borrow)
reduced[5], borrow = bits.sub_u64(tmp[5], ELL[5], borrow)
need_reduced := subtle.u64_is_zero(borrow)
need_reduced := subtle.eq0(borrow)
fe_cond_select(&tmp, &tmp, &reduced, int(need_reduced))
fe_to_montgomery(out1, &tmp)

View File

@@ -0,0 +1,72 @@
#+private
package _mldsa
CRHBYTES :: 64
TRBYTES :: 64
N :: 256
Q :: 8380417
D :: 13
K_MAX :: 8
L_MAX :: 7
POLYZ_PACKEDBYTES_MAX :: 640
POLYT1_PACKEDBYTES :: 320
POLYT0_PACKEDBYTES :: 416
POLYVECT1_PACKEDBYTES_MAX :: K_MAX * POLYT1_PACKEDBYTES
POLYW1_PACKEDBYTES_MAX :: 192
CTILDBYTES_MAX :: 64
@(require_results)
polyeta_packedbytes :: #force_inline proc "contextless" (params: ^Params) -> int {
POLYETA_PACKEDBYTES_2 :: 96
POLYETA_PACKEDBYTES_4 :: 128
switch params.eta {
case 2:
return POLYETA_PACKEDBYTES_2
case 4:
return POLYETA_PACKEDBYTES_4
case:
unreachable()
}
}
@(require_results)
polyz_packedbytes :: #force_inline proc "contextless" (params: ^Params) -> int {
POLYZ_PACKEDBYTES_GAMMA1_17 :: 576
POLYZ_PACKEDBYTES_GAMMA1_19 :: 640
switch params.gamma1 {
case 1 << 17:
return POLYZ_PACKEDBYTES_GAMMA1_17
case 1 << 19:
return POLYZ_PACKEDBYTES_GAMMA1_19
case:
unreachable()
}
}
@(require_results)
polyw1_packedbytes :: #force_inline proc "contextless" (params: ^Params) -> int {
POLYW1_PACKEDBYTES_GAMMA2_95232 :: 192
POLYW1_PACKEDBYTES_GAMMA2_261888 :: 128
switch params.gamma2 {
case (Q-1)/88:
return POLYW1_PACKEDBYTES_GAMMA2_95232
case (Q-1)/32:
return POLYW1_PACKEDBYTES_GAMMA2_261888
case:
unreachable()
}
}
@(require_results)
polyvech_packedbytes :: #force_inline proc "contextless" (params: ^Params) -> int {
return params.omega + params.k
}

View File

@@ -0,0 +1,394 @@
package _mldsa
import "core:crypto"
import "core:crypto/shake"
// This implementation is derived from the PQ-CRYSTALS reference
// implementation [[ https://github.com/pq-crystals/dilithium ]],
// primarily for licensing reasons. Arguably mldsa-native is
// a more "up to date" codebase, but the changes to the
// ref code is minor and they slapped an attribution-required
// license on something that was originally CC-0/Apache 2.0.
SEEDBYTES :: 32
RNDBYTES :: 32
CTXBYTES_MAX :: 255
Params :: struct {
k: int,
l: int,
eta: i32,
tau: int,
beta: i32,
gamma1: i32,
gamma2: i32,
omega: int,
ctild_bytes: int,
}
@(rodata)
Params_44 := Params{
k = 4,
l = 4,
eta = 2,
tau = 39,
beta = 78,
gamma1 = 1 << 17,
gamma2 = (Q-1)/88,
omega = 80,
ctild_bytes = 32,
}
@(rodata)
Params_65 := Params{
k = 6,
l = 5,
eta = 4,
tau = 49,
beta = 196,
gamma1 = 1 << 19,
gamma2 = (Q-1)/32,
omega = 55,
ctild_bytes = 48,
}
@(rodata)
Params_87 := Params{
k = 8,
l = 7,
eta = 2,
tau = 60,
beta = 120,
gamma1 = 1 << 19,
gamma2 = (Q-1)/32,
omega = 75,
ctild_bytes = 64,
}
Private_Key :: struct {
params: ^Params,
rho: [SEEDBYTES]byte,
tr: [TRBYTES]byte,
key: [SEEDBYTES]byte,
t0: Polyvec_K,
s1: Polyvec_L,
s2: Polyvec_K,
pub_key: Public_Key,
seed: [SEEDBYTES]byte,
}
Public_Key :: struct {
params: ^Params,
t1: Polyvec_K,
rho: [SEEDBYTES]byte,
mu: [TRBYTES]byte,
}
@(private)
Signature :: struct {
params: ^Params,
c: [CTILDBYTES_MAX]byte,
z: Polyvec_L,
h: Polyvec_K,
}
dsa_keygen_internal :: proc(
priv_key: ^Private_Key,
seed: []byte,
params: ^Params,
) {
ensure(len(seed) == SEEDBYTES, "crypto/mldsa: invalid seed")
pub_key := &priv_key.pub_key
pub_key.params = params
priv_key.params = params
copy(priv_key.seed[:], seed)
seedbuf: [2*SEEDBYTES + CRHBYTES]byte = ---
mat_: [K_MAX]Polyvec_L = ---
defer crypto.zero_explicit(&seedbuf, size_of(seedbuf))
defer crypto.zero_explicit(&mat_, size_of(mat_))
// Expand randomness for rho, rhoprime and key
copy(seedbuf[:], seed)
seedbuf[SEEDBYTES] = byte(params.k)
seedbuf[SEEDBYTES+1] = byte(params.l)
shake256(seedbuf[:], seedbuf[:SEEDBYTES+2])
copy(priv_key.rho[:], seedbuf[:SEEDBYTES])
rhoprime := seedbuf[SEEDBYTES:SEEDBYTES+CRHBYTES]
copy(priv_key.key[:], seedbuf[SEEDBYTES+CRHBYTES:])
// Expand matrix
mat := mat_[:params.k]
polyvec_matrix_expand(mat, priv_key.rho[:], params)
// Sample short vectors s1 and s2
polyvec_l_uniform_eta(&priv_key.s1, rhoprime, 0, params)
polyvec_k_uniform_eta(&priv_key.s2, rhoprime, u16(params.l), params)
// Matrix-vector multiplication
s1hat: Polyvec_L = ---
defer crypto.zero_explicit(&s1hat, size_of(Polyvec_L))
polyvec_copy(&s1hat, &priv_key.s1, params)
polyvec_l_ntt(&s1hat, params)
polyvec_matrix_pointwise_montgomery(&pub_key.t1, mat, &s1hat, params)
polyvec_k_reduce(&pub_key.t1, params)
polyvec_k_invntt_tomont(&pub_key.t1, params)
// Add error vector s2
polyvec_k_add(&pub_key.t1, &pub_key.t1, &priv_key.s2, params)
// Extract t1 and write public key
pk_bytes_: [SEEDBYTES+POLYVECT1_PACKEDBYTES_MAX]byte = ---
pk_bytes := pk_bytes_[:public_key_size(params)]
polyvec_k_caddq(&pub_key.t1, params)
polyvec_k_power2round(&pub_key.t1, &priv_key.t0, &pub_key.t1, params)
copy(pub_key.rho[:], priv_key.rho[:])
_ = pack_pk(pk_bytes, pub_key)
// Compute H(rho, t1) and write secret key
shake256(pub_key.mu[:], pk_bytes)
copy(priv_key.tr[:], pub_key.mu[:])
}
dsa_sign_internal :: proc(
sig_bytes: []byte,
m: []byte,
ctx: []byte,
rnd: []byte,
priv_key: ^Private_Key,
external_mu: []byte = nil
) -> bool {
params := priv_key.params
switch params {
case &Params_44, &Params_65, &Params_87:
case:
return false
}
if len(sig_bytes) != signature_size(params) {
return false
}
ensure(len(ctx) <= CTXBYTES_MAX, "crypto/mlkem: invalid contxt size")
ensure(len(rnd) == RNDBYTES, "crypto/mlkem: invalid rnd size")
mu, rhoprime: [CRHBYTES]byte = ---, ---
mat_: [K_MAX]Polyvec_L
w1_bytes_: [SEEDBYTES+POLYVECT1_PACKEDBYTES_MAX]byte = ---
s1, y: Polyvec_L = ---, ---
t0, s2, w1, w0: Polyvec_K = ---, ---, ---, ---
cp: Poly
polyvec_copy(&s1, &priv_key.s1, params)
polyvec_copy(&s2, &priv_key.s2, params)
polyvec_copy(&t0, &priv_key.t0, params)
defer crypto.zero_explicit(&mu, size_of(mu))
defer crypto.zero_explicit(&rhoprime, size_of(rhoprime))
defer crypto.zero_explicit(&mat_, size_of(mat_))
defer crypto.zero_explicit(&w1_bytes_, size_of(w1_bytes_))
defer polyvec_clear([]^Polyvec_L{&s1, &y})
defer polyvec_clear([]^Polyvec_K{&t0, &s2, &w1, &w0})
defer crypto.zero_explicit(&cp, size_of(cp))
sig: Signature = ---
sig.params = params
h := &sig.h
z := &sig.z
c := sig.c[:params.ctild_bytes]
w1_bytes := w1_bytes_[:params.k*polyw1_packedbytes(params)]
// Compute mu = CRH(tr, pre, msg)
if len(external_mu) == 0 {
// The FIPS publication handles the shake prefix
// in the public sign operation, but doing it
// here makes more sense.
ctx_buf: [2]byte
shake_ctx: shake.Context = ---
defer shake.reset(&shake_ctx)
ctx_len := len(ctx)
shake.init_256(&shake_ctx)
shake.write(&shake_ctx, priv_key.tr[:])
if ctx_len > 0 {
ctx_buf[1] = byte(ctx_len)
}
shake.write(&shake_ctx, ctx_buf[:])
if ctx_len > 0 {
shake.write(&shake_ctx, ctx)
}
shake.write(&shake_ctx, m)
shake.read(&shake_ctx, mu[:])
} else {
ensure(len(external_mu) == CRHBYTES, "crypto/mlkem: invalid external mu")
copy(mu[:], external_mu)
}
// Compute rhoprime = CRH(key, rnd, mu)
shake256(rhoprime[:], priv_key.key[:], rnd, mu[:])
// Expand matrix and transform vectors
mat := mat_[:params.k]
polyvec_matrix_expand(mat, priv_key.rho[:], params)
polyvec_l_ntt(&s1, params)
polyvec_k_ntt(&s2, params)
polyvec_k_ntt(&t0, params)
// Rejection-sampling loop
iv: u32 // ref uses u16, but ML-DSA-87 will reuse the IV at p = ~2^{-23400}
for {
// Sample intermediate vector y
polyvec_l_uniform_gamma1(&y, rhoprime[:], iv, params)
iv += 1
// Matrix-vector multiplication
polyvec_copy(z, &y, params)
polyvec_l_ntt(z, params)
polyvec_matrix_pointwise_montgomery(&w1, mat, z, params)
polyvec_k_reduce(&w1, params)
polyvec_k_invntt_tomont(&w1, params)
// Decompose w and call the random oracle
polyvec_k_caddq(&w1, params)
polyvec_k_decompose(&w1, &w0, &w1, params)
polyvec_k_pack_w1(w1_bytes, &w1, params)
shake256(c, mu[:], w1_bytes)
poly_challenge(&cp, c, params)
poly_ntt(&cp)
// Compute z, reject if it reveals secret
polyvec_l_pointwise_poly_montgomery(z, &cp, &s1, params)
polyvec_l_invntt_tomont(z, params)
polyvec_l_add(z, z, &y, params)
polyvec_l_reduce(z, params)
if polyvec_l_chknorm(z, params.gamma1 - params.beta, params) {
continue
}
// Check that subtracting cs2 does not change high bits of w
// and low bits do not reveal secret information
polyvec_k_pointwise_poly_montgomery(h, &cp, &s2, params)
polyvec_k_invntt_tomont(h, params)
polyvec_k_sub(&w0, &w0, h, params)
polyvec_k_reduce(&w0, params)
if polyvec_k_chknorm(&w0, params.gamma2 - params.beta, params) {
continue
}
// Compute hints for w1
polyvec_k_pointwise_poly_montgomery(h, &cp, &t0, params)
polyvec_k_invntt_tomont(h, params)
polyvec_k_reduce(h, params)
if polyvec_k_chknorm(h, params.gamma2, params) {
continue
}
polyvec_k_add(&w0, &w0, h, params)
n := polyvec_k_make_hint(h, &w0, &w1, params)
if n <= uint(params.omega) {
break
}
}
// Write signature
return pack_sig(sig_bytes, &sig)
}
dsa_verify_internal :: proc(
sig_bytes: []byte,
m: []byte,
ctx: []byte,
pub_key: ^Public_Key,
) -> bool {
ensure(len(ctx) <= CTXBYTES_MAX, "crypto/mlkem: invalid contxt size")
params := pub_key.params
switch params {
case &Params_44, &Params_65, &Params_87:
case:
return false
}
sig: Signature = ---
if !unpack_sig(&sig, sig_bytes, params) {
return false
}
if polyvec_l_chknorm(&sig.z, params.gamma1 - params.beta, params) {
return false
}
c := sig.c[:params.ctild_bytes]
z := &sig.z
h := &sig.h
t1: Polyvec_K = ---
polyvec_copy(&t1, &pub_key.t1, params)
rho := pub_key.rho[:]
// Compute CRH(H(rho, t1), pre, msg)
mu: [CRHBYTES]byte
{
// The FIPS publication handles the shake prefix
// in the public sign operation, but doing it
// here makes more sense.
ctx_buf: [2]byte
shake_ctx: shake.Context = ---
defer shake.reset(&shake_ctx)
ctx_len := len(ctx)
shake.init_256(&shake_ctx)
shake.write(&shake_ctx, pub_key.mu[:])
if ctx_len > 0 {
ctx_buf[1] = byte(ctx_len)
}
shake.write(&shake_ctx, ctx_buf[:])
if ctx_len > 0 {
shake.write(&shake_ctx, ctx)
}
shake.write(&shake_ctx, m)
shake.read(&shake_ctx, mu[:])
}
// Matrix-vector multiplication; compute Az - c2^dt1
mat_: [K_MAX]Polyvec_L
w1: Polyvec_K = ---
cp: Poly = ---
mat := mat_[:params.l]
poly_challenge(&cp, c, params)
polyvec_matrix_expand(mat, rho, params)
polyvec_l_ntt(z, params)
polyvec_matrix_pointwise_montgomery(&w1, mat, z, params)
poly_ntt(&cp)
polyvec_k_shiftl(&t1, params)
polyvec_k_ntt(&t1, params)
polyvec_k_pointwise_poly_montgomery(&t1, &cp, &t1, params)
polyvec_k_sub(&w1, &w1, &t1, params)
polyvec_k_reduce(&w1, params)
polyvec_k_invntt_tomont(&w1, params)
// Reconstruct w1
buf_: [K_MAX*POLYW1_PACKEDBYTES_MAX]byte = ---
buf := buf_[:params.k*polyw1_packedbytes(params)]
polyvec_k_caddq(&w1, params)
polyvec_k_use_hint(&w1, &w1, h, params)
polyvec_k_pack_w1(buf, &w1, params)
// Call random oracle and verify challenge
c2_: [CTILDBYTES_MAX]byte
c2 := c2_[:params.ctild_bytes]
shake256(c2, mu[:], buf)
// Note/perf: Can be vartime
return crypto.compare_constant_time(c, c2) == 1
}

View File

@@ -0,0 +1,75 @@
#+private
package _mldsa
@(rodata)
ZETAS := [N]i32 {
0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468,
1826347, 2353451, -359251, -2091905, 3119733, -2884855, 3111497, 2680103,
2725464, 1024112, -1079900, 3585928, -549488, -1119584, 2619752, -2108549,
-2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497, 280005,
2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439,
-3861115, -3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299,
-1699267, -1643818, 3505694, -3821735, 3507263, -2140649, -1600420, 3699596,
811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, -2797779,
-3930395, -1528703, -3677745, -3041255, -1452451, 3475950, 2176455, -1585221,
-1257611, 1939314, -4083598, -1000202, -3190144, -3157330, -3632928, 126922,
3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047,
-671102, -1228525, -22981, -1308169, -381987, 1349076, 1852771, -1430430,
-3343383, 264944, 508951, 3097992, 44288, -1100098, 904516, 3958618,
-3724342, -8578, 1653064, -3249728, 2389356, -210977, 759969, -1316856,
189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330,
1285669, -1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961,
2091667, 3407706, 2316500, 3817976, -3342478, 2244091, -2446433, -3562462,
266997, 2434439, -1235728, 3513181, -3520352, -3759364, -1197226, -3193378,
900702, 1859098, 909542, 819034, 495491, -1613174, -43260, -522500,
-655327, -3122442, 2031748, 3207046, -3556995, -525098, -768622, -3595838,
342297, 286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044,
2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, 1595974,
-3767016, 1250494, 2635921, -3548272, -2994039, 1869119, 1903435, -1050970,
-1333058, 1237275, -3318210, -1430225, -451100, 1312455, 3306115, -1962642,
-1279661, 1917081, -2546312, -1374803, 1500165, 777191, 2235880, 3406031,
-542412, -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993,
-2013608, 2432395, 2454455, -164721, 1957272, 3369112, 185531, -1207385,
-3183426, 162844, 1616392, 3014001, 810149, 1652634, -3694233, -1799107,
-3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, 472078,
-426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893,
-2939036, -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687,
-554416, 3919660, -48306, -1362209, 3937738, 1400424, -846154, 1976782,
}
ntt :: proc "contextless" (a: ^[N]i32) #no_bounds_check {
j, k := 0, 1
for l := 128; l > 0; l >>= 1 {
for start := 0; start < N; start = j + l {
zeta := ZETAS[k]
k += 1
for j = start; j < start + l; j += 1 {
t := montgomery_reduce(i64(zeta) * i64(a[j + l]))
a[j + l] = a[j] - t
a[j] = a[j] + t
}
}
}
}
invntt_tomont :: proc "contextless" (a: ^[N]i32) #no_bounds_check {
F :: 41978 // mont^2/256
j, k := 0, 255
for l := 1; l < N; l <<= 1 {
for start := 0; start < N; start = j + l {
zeta := -ZETAS[k]
k -= 1
for j = start; j < start + l; j += 1 {
t := a[j]
a[j] = t + a[j + l]
a[j + l] = t - a[j + l]
a[j + l] = montgomery_reduce(i64(zeta) * i64(a[j + l]))
}
}
}
for i in 0..<N {
a[i] = montgomery_reduce(F * i64(a[i]))
}
}

View File

@@ -0,0 +1,208 @@
package _mldsa
import "base:intrinsics"
import "core:crypto"
@(require_results)
pack_pk :: proc "contextless" (pk_bytes: []byte, pub_key: ^Public_Key) -> bool {
if len(pk_bytes) != public_key_size(pub_key.params) {
return false
}
seed_bytes, t1_bytes := pk_bytes[:SEEDBYTES], pk_bytes[SEEDBYTES:]
copy(seed_bytes, pub_key.rho[:])
for i in 0..<pub_key.params.k {
polyt1_pack(t1_bytes[i*POLYT1_PACKEDBYTES:], &pub_key.t1.vec[i])
}
return true
}
@(require_results)
unpack_pk :: proc(pub_key: ^Public_Key, pk_bytes: []byte, params: ^Params) -> bool {
if len(pk_bytes) != public_key_size(params) {
return false
}
seed_bytes, t1_bytes := pk_bytes[:SEEDBYTES], pk_bytes[SEEDBYTES:]
pub_key.params = params
copy(pub_key.rho[:], seed_bytes)
for i in 0..<params.k {
polyt1_unpack(&pub_key.t1.vec[i], t1_bytes[i*POLYT1_PACKEDBYTES:])
}
shake256(pub_key.mu[:], pk_bytes)
return true
}
set_pk :: proc(dst, src: ^Public_Key) {
dst.params = src.params
polyvec_copy(&dst.t1, &src.t1, src.params)
copy(dst.rho[:], src.rho[:])
copy(dst.mu[:], src.mu[:])
}
clear_pk :: proc "contextless" (pub_key: ^Public_Key) {
crypto.zero_explicit(pub_key, size_of(Public_Key))
}
@(require_results)
pack_sk :: proc "contextless" (sk_bytes: []byte, priv_key: ^Private_Key) -> bool {
params := priv_key.params
if len(sk_bytes) != private_key_size(params) {
return false
}
sk_bytes := sk_bytes
polyeta_len := polyeta_packedbytes(params)
copy(sk_bytes, priv_key.rho[:])
sk_bytes = sk_bytes[SEEDBYTES:]
copy(sk_bytes, priv_key.key[:])
sk_bytes = sk_bytes[SEEDBYTES:]
copy(sk_bytes, priv_key.tr[:])
sk_bytes = sk_bytes[TRBYTES:]
for i in 0..<params.l {
polyeta_pack(sk_bytes[i*polyeta_len:], &priv_key.s1.vec[i], params)
}
sk_bytes = sk_bytes[polyeta_len*params.l:]
for i in 0..<params.k {
polyeta_pack(sk_bytes[i*polyeta_len:], &priv_key.s2.vec[i], params)
}
sk_bytes = sk_bytes[polyeta_len*params.k:]
for i in 0..<params.k {
polyt1_pack(sk_bytes[i*POLYT1_PACKEDBYTES:], &priv_key.t0.vec[i])
}
return true
}
set_sk :: proc(dst, src: ^Private_Key) {
dst.params = src.params
copy(dst.rho[:], src.rho[:])
copy(dst.tr[:], src.tr[:])
copy(dst.key[:], src.key[:])
polyvec_copy(&dst.t0, &src.t0, src.params)
polyvec_copy(&dst.s1, &src.s1, src.params)
polyvec_copy(&dst.s2, &src.s2, src.params)
set_pk(&dst.pub_key, &src.pub_key)
copy(dst.seed[:], src.seed[:])
}
clear_sk :: proc "contextless" (priv_key: ^Private_Key) {
crypto.zero_explicit(priv_key, size_of(Private_Key))
}
@(private,require_results)
pack_sig :: proc "contextless" (sig_bytes: []byte, sig: ^Signature) -> bool {
if len(sig_bytes) != signature_size(sig.params) {
return false
}
sig_bytes := sig_bytes
polyz_len := polyz_packedbytes(sig.params)
copy(sig_bytes, sig.c[:sig.params.ctild_bytes])
sig_bytes = sig_bytes[sig.params.ctild_bytes:]
for i in 0..<sig.params.l {
polyz_pack(sig_bytes[i*polyz_len:], &sig.z.vec[i], sig.params)
}
sig_bytes = sig_bytes[sig.params.l*polyz_len:]
intrinsics.mem_zero(raw_data(sig_bytes), len(sig_bytes))
k: int
for i in 0..<sig.params.k {
for j in 0..<N {
if sig.h.vec[i].coeffs[j] != 0 {
sig_bytes[k] = byte(j)
k += 1
}
}
sig_bytes[sig.params.omega + i] = byte(k)
}
return true
}
@(private,require_results)
unpack_sig :: proc "contextless" (sig: ^Signature, sig_bytes: []byte, params: ^Params) -> bool {
if len(sig_bytes) != signature_size(params) {
return false
}
intrinsics.mem_zero(sig, size_of(Signature))
sig_bytes := sig_bytes
polyz_len := polyz_packedbytes(params)
omega := params.omega
copy(sig.c[:], sig_bytes[:params.ctild_bytes])
sig_bytes = sig_bytes[params.ctild_bytes:]
for i in 0..<params.l {
polyz_unpack(&sig.z.vec[i], sig_bytes[i*polyz_len:], params)
}
sig_bytes = sig_bytes[params.l*polyz_len:]
// Decode h
k: int
for i in 0..<params.k {
if sig_bytes[omega + i] < byte(k) || sig_bytes[omega + i] > byte(omega) {
return false
}
for j := k; j < int(sig_bytes[omega + i]); j += 1 {
// Coefficients are ordered for strong unforgeability
if j > k && sig_bytes[j] <= sig_bytes[j-1] {
return false
}
sig.h.vec[i].coeffs[sig_bytes[j]] = 1
}
k = int(sig_bytes[omega + i])
}
// Extra indices are zero for strong unforgeability
for j := k; j < omega; j += 1 {
if sig_bytes[j] != 0 {
return false
}
}
sig.params = params
return true
}
@(private,require_results)
public_key_size :: #force_inline proc "contextless" (params: ^Params) -> int {
return SEEDBYTES + params.k * POLYT1_PACKEDBYTES
}
@(private,require_results)
private_key_size :: #force_inline proc "contextless" (params: ^Params) -> int {
return 2*SEEDBYTES + TRBYTES + (params.l + params.k) * polyeta_packedbytes(params) + params.k * POLYT0_PACKEDBYTES
}
@(private,require_results)
signature_size :: #force_inline proc "contextless" (params: ^Params) -> int {
return params.ctild_bytes + params.l * polyz_packedbytes(params) + polyvech_packedbytes(params)
}

View File

@@ -0,0 +1,564 @@
#+private
package _mldsa
import "base:intrinsics"
import "core:crypto"
import "core:crypto/shake"
Poly :: struct {
coeffs: [N]i32,
}
poly_reduce :: proc "contextless" (a: ^Poly) {
for v, i in a.coeffs {
a.coeffs[i] = reduce32(v)
}
}
poly_caddq :: proc "contextless" (a: ^Poly) {
for v, i in a.coeffs {
a.coeffs[i] = caddq(v)
}
}
poly_add :: proc "contextless" (c, a, b: ^Poly) #no_bounds_check {
for i in 0..<N {
c.coeffs[i] = a.coeffs[i] + b.coeffs[i]
}
}
poly_sub :: proc "contextless" (c, a, b: ^Poly) #no_bounds_check {
for i in 0..<N {
c.coeffs[i] = a.coeffs[i] - b.coeffs[i]
}
}
poly_shiftl :: proc "contextless" (a: ^Poly) {
for i in 0..<N {
a.coeffs[i] <<= D
}
}
poly_ntt :: proc "contextless" (a: ^Poly) {
ntt(&a.coeffs)
}
poly_invntt_tomont :: proc "contextless" (a: ^Poly) {
invntt_tomont(&a.coeffs)
}
poly_pointwise_montgomery :: proc "contextless" (c, a, b: ^Poly) #no_bounds_check {
for i in 0..<N {
c.coeffs[i] = montgomery_reduce(i64(a.coeffs[i]) * i64(b.coeffs[i]))
}
}
poly_power2round :: proc "contextless" (a1, a0, a: ^Poly) #no_bounds_check {
for i in 0..<N {
a0.coeffs[i], a1.coeffs[i] = power2round(a.coeffs[i])
}
}
poly_decompose :: proc "contextless" (a1, a0, a: ^Poly, params: ^Params) #no_bounds_check {
for i in 0..<N {
a0.coeffs[i], a1.coeffs[i] = decompose(a.coeffs[i], params.gamma2)
}
}
poly_make_hint :: proc "contextless" (h, a0, a1: ^Poly, params: ^Params) -> uint #no_bounds_check {
s: uint
for i in 0..<N {
h.coeffs[i] = i32(make_hint(a0.coeffs[i], a1.coeffs[i], params.gamma2))
s += uint(h.coeffs[i])
}
return s
}
poly_use_hint :: proc "contextless" (b, a, h: ^Poly, params: ^Params) {
for i in 0..<N {
b.coeffs[i] = use_hint(a.coeffs[i], uint(h.coeffs[i]), params.gamma2)
}
}
poly_chknorm :: proc "contextless" (a: ^Poly, bound: i32) -> bool #no_bounds_check {
// It is ok to leak which coefficient violates the bound since
// the probability for each coefficient is independent of secret
// data but we must not leak the sign of the centralized
// representative.
for i in 0..<N {
// Absolute value
t := a.coeffs[i] >> 31
t = a.coeffs[i] - (t & 2 * a.coeffs[i])
if t >= bound {
return true
}
}
return false
}
unchecked_get_u24le :: #force_inline proc "contextless" (b: []byte) -> u32 #no_bounds_check {
r := u32(b[0])
r |= u32(b[1]) << 8
r |= u32(b[2]) << 16
return r
}
rej_uniform :: proc "contextless" (a: []i32, buf: []byte) -> int #no_bounds_check {
ctr, pos: int
a_len, b_len := len(a), len(buf)
for ctr < a_len && pos + 3 <= b_len {
t := unchecked_get_u24le(buf[pos:])
t &= 0x7FFFFF
pos += 3
if t < Q {
a[ctr] = i32(t)
ctr += 1
}
}
return ctr
}
poly_uniform :: proc(a: ^Poly, seed: []byte, iv: u16) #no_bounds_check {
// Note/yawning: The dilithium reference code does something
// inexplicably more complicated, but this is identical in
// behavior, and simpler.
#assert(STREAM128_BLOCKBYTES % 3 == 0)
POLY_UNIFORM_NBLOCKS :: ((768 + STREAM128_BLOCKBYTES - 1)/STREAM128_BLOCKBYTES)
buf: [POLY_UNIFORM_NBLOCKS*STREAM128_BLOCKBYTES]byte = ---
defer crypto.zero_explicit(&buf, size_of(buf))
ctx: shake.Context = ---
defer shake.reset(&ctx)
stream128_init(&ctx, seed, iv)
shake.read(&ctx, buf[:])
ctr := rej_uniform(a.coeffs[:], buf[:])
b := buf[:STREAM128_BLOCKBYTES]
for ctr < N {
shake.read(&ctx, b)
ctr += rej_uniform(a.coeffs[ctr:], b)
}
}
rej_eta :: proc "contextless" (a: []i32, buf: []byte, params: ^Params) -> int {
ctr, pos: int
a_len, b_len := len(a), len(buf)
switch params.eta {
case 2:
for ctr < a_len && pos < b_len {
t0 := u32(buf[pos] & 0x0F)
t1 := u32(buf[pos] >> 4)
pos += 1
if t0 < 15 {
t0 = t0 - (205 * t0 >> 10) * 5
a[ctr] = i32(2 - t0)
ctr += 1
}
if t1 < 15 && ctr < a_len {
t1 = t1 - (205 * t1 >> 10) * 5
a[ctr] = i32(2 - t1)
ctr += 1
}
}
case 4:
for ctr < a_len && pos < b_len {
t0 := u32(buf[pos] & 0x0F)
t1 := u32(buf[pos] >> 4)
pos += 1
if t0 < 9 {
a[ctr] = i32(4 - t0)
ctr += 1
}
if t1 < 9 && ctr < a_len {
a[ctr] = i32(4 - t1)
ctr += 1
}
}
case:
unreachable()
}
return ctr
}
poly_uniform_eta :: proc(a: ^Poly, seed: []byte, iv: u16, params: ^Params) {
POLY_UNIFORM_ETA2_NBLOCKS :: ((136 + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES)
POLY_UNIFORM_ETA4_NBLOCKS :: ((227 + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES)
buf_: [POLY_UNIFORM_ETA4_NBLOCKS*STREAM256_BLOCKBYTES]byte = ---
buf: []byte
switch params.eta {
case 2:
buf = buf_[:POLY_UNIFORM_ETA2_NBLOCKS*STREAM256_BLOCKBYTES]
case 4:
buf = buf_[:POLY_UNIFORM_ETA4_NBLOCKS*STREAM256_BLOCKBYTES]
case:
unreachable()
}
defer crypto.zero_explicit(&buf_, size_of(buf_))
ctx: shake.Context = ---
defer shake.reset(&ctx)
stream256_init(&ctx, seed, iv)
shake.read(&ctx, buf)
ctr := rej_eta(a.coeffs[:], buf, params)
b := buf[:STREAM256_BLOCKBYTES]
for ctr < N {
shake.read(&ctx, b)
ctr += rej_eta(a.coeffs[ctr:], b, params)
}
}
poly_uniform_gamma1 :: proc(a: ^Poly, seed: []byte, iv: u16, params: ^Params) {
POLY_UNIFORM_GAMMA1_NBLOCKS_MAX :: ((POLYZ_PACKEDBYTES_MAX + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES)
n_blocks := (polyz_packedbytes(params) + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES
buf_: [POLY_UNIFORM_GAMMA1_NBLOCKS_MAX*STREAM256_BLOCKBYTES]byte = ---
buf := buf_[:n_blocks*STREAM256_BLOCKBYTES]
defer crypto.zero_explicit(&buf_, size_of(buf_))
ctx: shake.Context = ---
defer shake.reset(&ctx)
stream256_init(&ctx, seed, iv)
shake.read(&ctx, buf)
polyz_unpack(a, buf, params)
}
poly_challenge :: proc(c: ^Poly, seed: []byte, params: ^Params) #no_bounds_check {
buf: [STREAM256_BLOCKBYTES]byte = ---
defer crypto.zero_explicit(&buf, size_of(buf))
ctx: shake.Context = ---
defer shake.reset(&ctx)
shake.init_256(&ctx)
shake.write(&ctx, seed)
shake.read(&ctx, buf[:])
signs: u64
for i in uint(0)..<8 {
signs |= u64(buf[i]) << (8*i)
}
pos := 8
b: int
intrinsics.mem_zero(c, size_of(Poly))
for i := N - params.tau; i < N; i+= 1 {
for {
if pos >= STREAM256_BLOCKBYTES {
shake.read(&ctx, buf[:])
pos = 0
}
b = int(buf[pos])
pos += 1
if b <= i {
break
}
}
c.coeffs[i] = c.coeffs[b]
c.coeffs[b] = i32(1 - 2 * (signs & 1))
signs >>= 1
}
}
polyeta_pack :: proc "contextless" (r: []byte, a: ^Poly, params: ^Params) #no_bounds_check {
t: [8]byte = ---
defer crypto.zero_explicit(&t, size_of(t))
eta := params.eta
switch eta {
case 2:
for i in 0..<N/8 {
t[0] = byte(eta - a.coeffs[8*i+0])
t[1] = byte(eta - a.coeffs[8*i+1])
t[2] = byte(eta - a.coeffs[8*i+2])
t[3] = byte(eta - a.coeffs[8*i+3])
t[4] = byte(eta - a.coeffs[8*i+4])
t[5] = byte(eta - a.coeffs[8*i+5])
t[6] = byte(eta - a.coeffs[8*i+6])
t[7] = byte(eta - a.coeffs[8*i+7])
r[3*i+0] = (t[0] >> 0) | (t[1] << 3) | (t[2] << 6)
r[3*i+1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7)
r[3*i+2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5)
}
case 4:
for i in 0..<N/2 {
t[0] = byte(eta - a.coeffs[2*i+0])
t[1] = byte(eta - a.coeffs[2*i+1])
r[i] = t[0] | (t[1] << 4)
}
case:
unreachable()
}
}
polyeta_unpack :: proc "contextless" (r: ^Poly, a: []byte, params: ^Params) #no_bounds_check {
eta := params.eta
switch eta {
case 2:
for i in 0..<N/8 {
r.coeffs[8*i+0] = i32((a[3*i+0] >> 0) & 7)
r.coeffs[8*i+1] = i32((a[3*i+0] >> 3) & 7)
r.coeffs[8*i+2] = i32(((a[3*i+0] >> 6) | (a[3*i+1] << 2)) & 7)
r.coeffs[8*i+3] = i32((a[3*i+1] >> 1) & 7)
r.coeffs[8*i+4] = i32((a[3*i+1] >> 4) & 7)
r.coeffs[8*i+5] = i32(((a[3*i+1] >> 7) | (a[3*i+2] << 1)) & 7)
r.coeffs[8*i+6] = i32((a[3*i+2] >> 2) & 7)
r.coeffs[8*i+7] = i32((a[3*i+2] >> 5) & 7)
r.coeffs[8*i+0] = eta - r.coeffs[8*i+0]
r.coeffs[8*i+1] = eta - r.coeffs[8*i+1]
r.coeffs[8*i+2] = eta - r.coeffs[8*i+2]
r.coeffs[8*i+3] = eta - r.coeffs[8*i+3]
r.coeffs[8*i+4] = eta - r.coeffs[8*i+4]
r.coeffs[8*i+5] = eta - r.coeffs[8*i+5]
r.coeffs[8*i+6] = eta - r.coeffs[8*i+6]
r.coeffs[8*i+7] = eta - r.coeffs[8*i+7]
}
case 4:
for i in 0..<N/2 {
r.coeffs[2*i+0] = i32(a[i] & 0x0F)
r.coeffs[2*i+1] = i32(a[i] >> 4)
r.coeffs[2*i+0] = eta - r.coeffs[2*i+0]
r.coeffs[2*i+1] = eta - r.coeffs[2*i+1]
}
case:
unreachable()
}
}
polyt1_pack :: proc "contextless" (r: []byte, a: ^Poly) #no_bounds_check {
for i in 0..<N/4 {
r[5*i+0] = byte(a.coeffs[4*i+0] >> 0)
r[5*i+1] = byte((a.coeffs[4*i+0] >> 8) | (a.coeffs[4*i+1] << 2))
r[5*i+2] = byte((a.coeffs[4*i+1] >> 6) | (a.coeffs[4*i+2] << 4))
r[5*i+3] = byte((a.coeffs[4*i+2] >> 4) | (a.coeffs[4*i+3] << 6))
r[5*i+4] = byte(a.coeffs[4*i+3] >> 2)
}
}
polyt1_unpack :: proc "contextless" (r: ^Poly, a: []byte) #no_bounds_check {
for i in 0..<N/4 {
r.coeffs[4*i+0] = i32((u32(a[5*i+0] >> 0) | (u32(a[5*i+1]) << 8)) & 0x3FF)
r.coeffs[4*i+1] = i32((u32(a[5*i+1] >> 2) | (u32(a[5*i+2]) << 6)) & 0x3FF)
r.coeffs[4*i+2] = i32((u32(a[5*i+2] >> 4) | (u32(a[5*i+3]) << 4)) & 0x3FF)
r.coeffs[4*i+3] = i32((u32(a[5*i+3] >> 6) | (u32(a[5*i+4]) << 2)) & 0x3FF)
}
}
polyt0_pack :: proc "contextless" (r: []byte, a: ^Poly) #no_bounds_check {
t: [8]byte = ---
defer crypto.zero_explicit(&t, size_of(t))
for i in 0..<N/8 {
t[0] = byte((1 << (D-1)) - a.coeffs[8*i+0])
t[1] = byte((1 << (D-1)) - a.coeffs[8*i+1])
t[2] = byte((1 << (D-1)) - a.coeffs[8*i+2])
t[3] = byte((1 << (D-1)) - a.coeffs[8*i+3])
t[4] = byte((1 << (D-1)) - a.coeffs[8*i+4])
t[5] = byte((1 << (D-1)) - a.coeffs[8*i+5])
t[6] = byte((1 << (D-1)) - a.coeffs[8*i+6])
t[7] = byte((1 << (D-1)) - a.coeffs[8*i+7])
r[13*i+ 0] = t[0]
r[13*i+ 1] = t[0] >> 8
r[13*i+ 1] |= t[1] << 5
r[13*i+ 2] = t[1] >> 3
r[13*i+ 3] = t[1] >> 11
r[13*i+ 3] |= t[2] << 2
r[13*i+ 4] = t[2] >> 6
r[13*i+ 4] |= t[3] << 7
r[13*i+ 5] = t[3] >> 1
r[13*i+ 6] = t[3] >> 9
r[13*i+ 6] |= t[4] << 4
r[13*i+ 7] = t[4] >> 4
r[13*i+ 8] = t[4] >> 12
r[13*i+ 8] |= t[5] << 1
r[13*i+ 9] = t[5] >> 7
r[13*i+ 9] |= t[6] << 6
r[13*i+10] = t[6] >> 2
r[13*i+11] = t[6] >> 10
r[13*i+11] |= t[7] << 3
r[13*i+12] = t[7] >> 5
}
}
polyt0_unpack :: proc "contextless" (r: ^Poly, a: []byte) #no_bounds_check {
for i in 0..<N/8 {
r.coeffs[8*i+0] = i32(a[13*i+0])
r.coeffs[8*i+0] |= i32(u32(a[13*i+1]) << 8)
r.coeffs[8*i+0] &= 0x1FFF
r.coeffs[8*i+1] = i32(a[13*i+1] >> 5)
r.coeffs[8*i+1] |= i32(u32(a[13*i+2]) << 3)
r.coeffs[8*i+1] |= i32(u32(a[13*i+3]) << 11)
r.coeffs[8*i+1] &= 0x1FFF
r.coeffs[8*i+2] = i32(a[13*i+3] >> 2)
r.coeffs[8*i+2] |= i32(u32(a[13*i+4]) << 6)
r.coeffs[8*i+2] &= 0x1FFF
r.coeffs[8*i+3] = i32(a[13*i+4] >> 7)
r.coeffs[8*i+3] |= i32(u32(a[13*i+5]) << 1)
r.coeffs[8*i+3] |= i32(u32(a[13*i+6]) << 9)
r.coeffs[8*i+3] &= 0x1FFF
r.coeffs[8*i+4] = i32(a[13*i+6] >> 4)
r.coeffs[8*i+4] |= i32(u32(a[13*i+7]) << 4)
r.coeffs[8*i+4] |= i32(u32(a[13*i+8]) << 12)
r.coeffs[8*i+4] &= 0x1FFF
r.coeffs[8*i+5] = i32(a[13*i+8] >> 1)
r.coeffs[8*i+5] |= i32(u32(a[13*i+9]) << 7)
r.coeffs[8*i+5] &= 0x1FFF
r.coeffs[8*i+6] = i32(a[13*i+9] >> 6)
r.coeffs[8*i+6] |= i32(u32(a[13*i+10]) << 2)
r.coeffs[8*i+6] |= i32(u32(a[13*i+11]) << 10)
r.coeffs[8*i+6] &= 0x1FFF
r.coeffs[8*i+7] = i32(a[13*i+11] >> 3)
r.coeffs[8*i+7] |= i32(u32(a[13*i+12]) << 5)
r.coeffs[8*i+7] &= 0x1FFF
r.coeffs[8*i+0] = (1 << (D-1)) - r.coeffs[8*i+0]
r.coeffs[8*i+1] = (1 << (D-1)) - r.coeffs[8*i+1]
r.coeffs[8*i+2] = (1 << (D-1)) - r.coeffs[8*i+2]
r.coeffs[8*i+3] = (1 << (D-1)) - r.coeffs[8*i+3]
r.coeffs[8*i+4] = (1 << (D-1)) - r.coeffs[8*i+4]
r.coeffs[8*i+5] = (1 << (D-1)) - r.coeffs[8*i+5]
r.coeffs[8*i+6] = (1 << (D-1)) - r.coeffs[8*i+6]
r.coeffs[8*i+7] = (1 << (D-1)) - r.coeffs[8*i+7]
}
}
polyz_pack :: proc "contextless" (r: []byte, a: ^Poly, params: ^Params) #no_bounds_check {
t: [4]u32 = ---
defer crypto.zero_explicit(&t, size_of(t))
gamma1 := params.gamma1
switch gamma1 {
case 1 << 17:
for i in 0..<N/4 {
t[0] = u32(gamma1 - a.coeffs[4*i+0])
t[1] = u32(gamma1 - a.coeffs[4*i+1])
t[2] = u32(gamma1 - a.coeffs[4*i+2])
t[3] = u32(gamma1 - a.coeffs[4*i+3])
r[9*i+0] = byte(t[0])
r[9*i+1] = byte(t[0] >> 8)
r[9*i+2] = byte(t[0] >> 16)
r[9*i+2] |= byte(t[1] << 2)
r[9*i+3] = byte(t[1] >> 6)
r[9*i+4] = byte(t[1] >> 14)
r[9*i+4] |= byte(t[2] << 4)
r[9*i+5] = byte(t[2] >> 4)
r[9*i+6] = byte(t[2] >> 12)
r[9*i+6] |= byte(t[3] << 6)
r[9*i+7] = byte(t[3] >> 2)
r[9*i+8] = byte(t[3] >> 10)
}
case 1 << 19:
for i in 0..<N/2 {
t[0] = u32(gamma1 - a.coeffs[2*i+0])
t[1] = u32(gamma1 - a.coeffs[2*i+1])
r[5*i+0] = byte(t[0])
r[5*i+1] = byte(t[0] >> 8)
r[5*i+2] = byte(t[0] >> 16)
r[5*i+2] |= byte(t[1] << 4)
r[5*i+3] = byte(t[1] >> 4)
r[5*i+4] = byte(t[1] >> 12)
}
case:
unreachable()
}
}
polyz_unpack :: proc "contextless" (r: ^Poly, a: []byte, params: ^Params) #no_bounds_check {
gamma1 := params.gamma1
switch gamma1 {
case 1 << 17:
for i in 0..<N/4 {
r.coeffs[4*i+0] = i32(a[9*i+0])
r.coeffs[4*i+0] |= i32(u32(a[9*i+1]) << 8)
r.coeffs[4*i+0] |= i32(u32(a[9*i+2]) << 16)
r.coeffs[4*i+0] &= 0x3FFFF
r.coeffs[4*i+1] = i32(a[9*i+2] >> 2)
r.coeffs[4*i+1] |= i32(u32(a[9*i+3]) << 6)
r.coeffs[4*i+1] |= i32(u32(a[9*i+4]) << 14)
r.coeffs[4*i+1] &= 0x3FFFF
r.coeffs[4*i+2] = i32(a[9*i+4] >> 4)
r.coeffs[4*i+2] |= i32(u32(a[9*i+5]) << 4)
r.coeffs[4*i+2] |= i32(u32(a[9*i+6]) << 12)
r.coeffs[4*i+2] &= 0x3FFFF
r.coeffs[4*i+3] = i32(a[9*i+6] >> 6)
r.coeffs[4*i+3] |= i32(u32(a[9*i+7]) << 2)
r.coeffs[4*i+3] |= i32(u32(a[9*i+8]) << 10)
r.coeffs[4*i+3] &= 0x3FFFF
r.coeffs[4*i+0] = gamma1 - r.coeffs[4*i+0]
r.coeffs[4*i+1] = gamma1 - r.coeffs[4*i+1]
r.coeffs[4*i+2] = gamma1 - r.coeffs[4*i+2]
r.coeffs[4*i+3] = gamma1 - r.coeffs[4*i+3]
}
case 1 << 19:
for i in 0..<N/2 {
r.coeffs[2*i+0] = i32(a[5*i+0])
r.coeffs[2*i+0] |= i32(u32(a[5*i+1]) << 8)
r.coeffs[2*i+0] |= i32(u32(a[5*i+2]) << 16)
r.coeffs[2*i+0] &= 0xFFFFF
r.coeffs[2*i+1] = i32(a[5*i+2] >> 4)
r.coeffs[2*i+1] |= i32(u32(a[5*i+3]) << 4)
r.coeffs[2*i+1] |= i32(u32(a[5*i+4]) << 12)
/* r.coeffs[2*i+1] &= 0xFFFFF */ /* No effect, since we're anyway at 20 bits */
r.coeffs[2*i+0] = gamma1 - r.coeffs[2*i+0]
r.coeffs[2*i+1] = gamma1 - r.coeffs[2*i+1]
}
case:
unreachable()
}
}
polyw1_pack :: proc "contextless" (r: []byte, a: ^Poly, params: ^Params) #no_bounds_check {
switch params.gamma2 {
case (Q-1)/88:
for i in 0..<N/4 {
r[3*i+0] = byte(a.coeffs[4*i+0])
r[3*i+0] |= byte(a.coeffs[4*i+1] << 6)
r[3*i+1] = byte(a.coeffs[4*i+1] >> 2)
r[3*i+1] |= byte(a.coeffs[4*i+2] << 4)
r[3*i+2] = byte(a.coeffs[4*i+2] >> 4)
r[3*i+2] |= byte(a.coeffs[4*i+3] << 2)
}
case (Q-1)/32:
for i in 0..<N/2 {
r[i] = byte(a.coeffs[2*i+0] | (a.coeffs[2*i+1] << 4))
}
case:
unreachable()
}
}

View File

@@ -0,0 +1,209 @@
#+private
package _mldsa
import "core:crypto"
Polyvec_L :: struct {
vec: [L_MAX]Poly,
}
Polyvec_K :: struct {
vec: [K_MAX]Poly,
}
polyvec_copy :: proc "contextless" (dst, src: ^$T, params: ^Params) where T == Polyvec_L || T == Polyvec_K {
when T == Polyvec_L {
n := params.l
} else {
n := params.k
}
for i in 0..<n {
copy(dst.vec[i].coeffs[:], src.vec[i].coeffs[:])
}
}
polyvec_clear :: proc "contextless" (vecs: []^$T) where T == Polyvec_L || T == Polyvec_K {
for _, i in vecs {
crypto.zero_explicit(vecs[i], size_of(T))
}
}
polyvec_matrix_expand :: proc(mat: []Polyvec_L, rho: []byte, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
for j in 0..<params.l {
poly_uniform(&mat[i].vec[j], rho, u16((i << 8) + j))
}
}
}
polyvec_matrix_pointwise_montgomery :: proc "contextless" (t: ^Polyvec_K, mat: []Polyvec_L, v: ^Polyvec_L, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
polyvec_l_pointwise_acc_montgomery(&t.vec[i], &mat[i], v, params)
}
}
polyvec_l_uniform_eta :: proc(v: ^Polyvec_L, seed: []byte, iv: u16, params: ^Params) #no_bounds_check {
iv := iv
for i in 0..<params.l {
poly_uniform_eta(&v.vec[i], seed, iv, params)
iv += 1
}
}
polyvec_l_uniform_gamma1 :: proc(v: ^Polyvec_L, seed: []byte, iv: u32, params: ^Params) #no_bounds_check {
for i in 0..<params.l {
poly_uniform_gamma1(&v.vec[i], seed, u16(u32(params.l) * iv + u32(i)), params)
}
}
polyvec_l_reduce :: proc "contextless" (v: ^Polyvec_L, params: ^Params) #no_bounds_check {
for i in 0..<params.l {
poly_reduce(&v.vec[i])
}
}
polyvec_l_add :: proc "contextless" (w, u, v: ^Polyvec_L, params: ^Params) #no_bounds_check {
for i in 0..<params.l {
poly_add(&w.vec[i], &u.vec[i], &v.vec[i])
}
}
polyvec_l_ntt :: proc "contextless" (v: ^Polyvec_L, params: ^Params) {
for i in 0..<params.l {
poly_ntt(&v.vec[i])
}
}
polyvec_l_invntt_tomont :: proc "contextless" (v: ^Polyvec_L, params: ^Params) {
for i in 0..<params.l {
poly_invntt_tomont(&v.vec[i])
}
}
polyvec_l_pointwise_poly_montgomery :: proc "contextless" (r: ^Polyvec_L, a: ^Poly, v: ^Polyvec_L, params: ^Params) #no_bounds_check {
for i in 0..<params.l {
poly_pointwise_montgomery(&r.vec[i], a, &v.vec[i])
}
}
polyvec_l_pointwise_acc_montgomery :: proc "contextless" (w: ^Poly, u, v: ^Polyvec_L, params: ^Params) #no_bounds_check {
t: Poly
poly_pointwise_montgomery(w, &u.vec[0], &v.vec[0])
for i in 1..<params.l {
poly_pointwise_montgomery(&t, &u.vec[i], &v.vec[i])
poly_add(w, w, &t)
}
}
polyvec_l_chknorm :: proc "contextless" (v: ^Polyvec_L, bound: i32, params: ^Params) -> bool #no_bounds_check {
for i in 0..<params.l {
if poly_chknorm(&v.vec[i],bound) {
return true
}
}
return false
}
polyvec_k_uniform_eta :: proc (v: ^Polyvec_K, seed: []byte, iv: u16, params: ^Params) #no_bounds_check {
iv := iv
for i in 0..<params.k {
poly_uniform_eta(&v.vec[i], seed, iv, params)
iv += 1
}
}
polyvec_k_reduce :: proc "contextless" (v: ^Polyvec_K, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
poly_reduce(&v.vec[i])
}
}
polyvec_k_caddq :: proc "contextless" (v: ^Polyvec_K, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
poly_caddq(&v.vec[i])
}
}
polyvec_k_add :: proc "contextless" (w, u, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
poly_add(&w.vec[i], &u.vec[i], &v.vec[i])
}
}
polyvec_k_sub :: proc "contextless" (w, u, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
poly_sub(&w.vec[i], &u.vec[i], &v.vec[i])
}
}
polyvec_k_shiftl :: proc "contextless" (v: ^Polyvec_K, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
poly_shiftl(&v.vec[i])
}
}
polyvec_k_ntt :: proc "contextless" (v: ^Polyvec_K, params: ^Params) {
for i in 0..<params.k {
poly_ntt(&v.vec[i])
}
}
polyvec_k_invntt_tomont :: proc "contextless" (v: ^Polyvec_K, params: ^Params) {
for i in 0..<params.k {
poly_invntt_tomont(&v.vec[i])
}
}
polyvec_k_pointwise_poly_montgomery :: proc "contextless" (r: ^Polyvec_K, a: ^Poly, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
poly_pointwise_montgomery(&r.vec[i], a, &v.vec[i])
}
}
polyvec_k_chknorm :: proc "contextless" (v: ^Polyvec_K, bound: i32, params: ^Params) -> bool #no_bounds_check {
for i in 0..<params.k {
if poly_chknorm(&v.vec[i],bound) {
return true
}
}
return false
}
polyvec_k_power2round :: proc "contextless" (v1, v0, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
poly_power2round(&v1.vec[i], &v0.vec[i], &v.vec[i])
}
}
polyvec_k_decompose :: proc "contextless" (v1, v0, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
poly_decompose(&v1.vec[i], &v0.vec[i], &v.vec[i], params)
}
}
polyvec_k_make_hint :: proc "contextless" (h, v0, v1: ^Polyvec_K, params: ^Params) -> uint #no_bounds_check {
s: uint
for i in 0..<params.k {
s += poly_make_hint(&h.vec[i], &v0.vec[i], &v1.vec[i], params)
}
return s
}
polyvec_k_use_hint :: proc "contextless" (w, u, h: ^Polyvec_K, params: ^Params) #no_bounds_check {
for i in 0..<params.k {
poly_use_hint(&w.vec[i],&u.vec[i], &h.vec[i], params)
}
}
polyvec_k_pack_w1 :: proc "contextless" (r: []byte, w1: ^Polyvec_K, params: ^Params) #no_bounds_check {
packed_len := polyw1_packedbytes(params)
for i in 0..<params.k {
polyw1_pack(r[i*packed_len:], &w1.vec[i], params)
}
}

View File

@@ -0,0 +1,35 @@
#+private
package _mldsa
// MONT :: -4186625 // 2^32 % Q
@(require_results)
montgomery_reduce :: proc "contextless" (a: i64) -> i32 {
QINV :: 58728449 // q^(-1) mod 2^32
t := i32(i64(i32(a)) * QINV)
t = i32((a - i64(t) * Q) >> 32)
return t
}
@(require_results)
reduce32 :: #force_inline proc "contextless" (a: i32) -> i32 {
t := (a + (1 << 22)) >> 23
t = a - t * Q
return t
}
@(require_results)
caddq :: #force_inline proc "contextless" (a: i32) -> i32 {
a := a
a += (a >> 31) & Q
return a
}
// @(require_results)
// freeze :: #force_inline proc "contextless" (a: i32) -> i32 {
// a := a
// a = reduce32(a)
// a = caddq(a)
// return a
// }

View File

@@ -0,0 +1,56 @@
#+private
package _mldsa
power2round :: proc "contextless" (a: i32) -> (i32, i32) {
a1 := (a + (1 << (D-1)) - 1) >> D
a0 := a - (a1 << D)
return a0, a1
}
decompose :: proc "contextless" (a: i32, gamma2: i32) -> (i32, i32) {
a1 := (a + 127) >> 7
switch gamma2 {
case (Q - 1)/32:
a1 = (a1 * 1025 + (1 << 21)) >> 22
a1 &= 15
case (Q - 1)/88:
a1 = (a1 * 11275 + (1 << 23)) >> 24
a1 ~= ((43 - a1) >> 31) & a1
}
a0 := a - a1 * 2 * gamma2
a0 -= (((Q - 1)/2 - a0) >> 31) & Q
return a0, a1
}
make_hint :: proc "contextless" (a0, a1: i32, gamma2: i32) -> uint {
if (a0 > gamma2 || a0 < -gamma2 || (a0 == -gamma2 && a1 != 0)) {
return 1
}
return 0
}
use_hint :: proc "contextless" (a: i32, hint: uint, gamma2: i32) -> i32 {
a0, a1 := decompose(a, gamma2)
if hint == 0 {
return a1
}
switch gamma2 {
case (Q - 1)/32:
if (a0 > 0) {
return (a1 + 1) & 15
} else {
return (a1 -1) & 15
}
case (Q - 1)/88:
if (a0 > 0) {
return (a1 == 43) ? 0 : a1 + 1
} else {
return (a1 == 0) ? 43 : a1 - 1
}
}
unreachable()
}

View File

@@ -0,0 +1,39 @@
#+private
package _mldsa
import "core:crypto/_sha3"
import "core:crypto/shake"
STREAM128_BLOCKBYTES :: _sha3.RATE_128
STREAM256_BLOCKBYTES :: _sha3.RATE_256
stream128_init :: proc(ctx: ^shake.Context, seed: []byte, iv: u16) {
t: [2]byte = ---
t[0] = byte(iv)
t[1] = byte(iv >> 8)
shake.init_128(ctx)
shake.write(ctx, seed)
shake.write(ctx, t[:])
}
stream256_init :: proc(ctx: ^shake.Context, seed: []byte, iv: u16) {
t: [2]byte = ---
t[0] = byte(iv)
t[1] = byte(iv >> 8)
shake.init_256(ctx)
shake.write(ctx, seed)
shake.write(ctx, t[:])
}
shake256 :: proc(dst: []byte, srcs: ..[]byte) {
ctx: shake.Context = ---
defer shake.reset(&ctx)
shake.init_256(&ctx)
for src in srcs {
shake.write(&ctx, src)
}
shake.read(&ctx, dst)
}

View File

@@ -0,0 +1,52 @@
#+private
package _mlkem
import "core:encoding/endian"
unchecked_get_u24le :: #force_inline proc "contextless" (b: []byte) -> u32 #no_bounds_check {
r := u32(b[0])
r |= u32(b[1]) << 8
r |= u32(b[2]) << 16
return r
}
cbd3 :: proc "contextless" (r: ^Poly, buf: ^[3*N/4]byte) #no_bounds_check {
for i in 0..<N/4 {
t := unchecked_get_u24le(buf[3*i:])
d := t & 0x00249249
d += (t>>1) & 0x00249249
d += (t>>2) & 0x00249249
for j in uint(0)..<4 {
a := i16((d >> (6*j+0)) & 0x7)
b := i16((d >> (6*j+3)) & 0x7)
r.coeffs[4*i+int(j)] = a - b
}
}
}
cbd2 :: proc "contextless" (r: ^Poly, buf: ^[2*N/4]byte) #no_bounds_check {
for i in 0..<N/8 {
t := endian.unchecked_get_u32le(buf[4*i:])
d := t & 0x55555555
d += (t>>1) & 0x55555555
for j in uint(0)..<8 {
a := i16((d >> (4*j+0)) & 0x3)
b := i16((d >> (4*j+2)) & 0x3)
r.coeffs[8*i+int(j)] = a - b
}
}
}
poly_cbd_eta1_512 :: proc "contextless" (r: ^Poly, buf: ^[ETA1_512*N/4]byte) {
cbd3(r, buf)
}
poly_cbd_eta1 :: proc "contextless" (r: ^Poly, buf: ^[ETA1*N/4]byte) {
cbd2(r, buf)
}
poly_cbd_eta2 :: proc "contextless" (r: ^Poly, buf: ^[ETA2*N/4]byte) {
cbd2(r, buf)
}

View File

@@ -0,0 +1,53 @@
package _mlkem
K_512 :: 2
K_768 :: 3
K_1024 :: 4
K_MAX :: K_1024
N :: 256
Q :: 3329
ETA1_512 :: 3
ETA1 :: 2
ETA2 :: 2
POLYBYTES :: 384
SYMBYTES :: 32
POLYCOMPRESSEDBYTES_512 :: 128
POLYCOMPRESSEDBYTES_768 :: 128
POLYCOMPRESSEDBYTES_1024 :: 160
POLYVECBYTES_512 :: K_512 * POLYBYTES
POLYVECBYTES_768 :: K_768 * POLYBYTES
POLYVECBYTES_1024 :: K_1024 * POLYBYTES
POLYVECCOMPRESSEDBYTES_512 :: K_512 * 320
POLYVECCOMPRESSEDBYTES_768 :: K_768 * 320
POLYVECCOMPRESSEDBYTES_1024 :: K_1024 * 352
INDCPA_MSGBYTES :: SYMBYTES
INDCPA_PUBLICKEYBYTES_512 :: POLYVECBYTES_512 + SYMBYTES
INDCPA_SECRETKEYBYTES_512 :: POLYVECBYTES_512
INDCPA_PUBLICKEYBYTES_768 :: POLYVECBYTES_768 + SYMBYTES
INDCPA_SECRETKEYBYTES_768 :: POLYVECBYTES_768
INDCPA_PUBLICKEYBYTES_1024 :: POLYVECBYTES_1024 + SYMBYTES
INDCPA_SECRETKEYBYTES_1024 :: POLYVECBYTES_1024
INDCPA_PUBLICKEYBYTES_MAX :: INDCPA_PUBLICKEYBYTES_1024
INDCPA_BYTES_512 :: POLYVECCOMPRESSEDBYTES_512 + POLYCOMPRESSEDBYTES_512
INDCPA_BYTES_768 :: POLYVECCOMPRESSEDBYTES_768 + POLYCOMPRESSEDBYTES_768
INDCPA_BYTES_1024 :: POLYVECCOMPRESSEDBYTES_1024 + POLYCOMPRESSEDBYTES_1024
ENCAPSKEYBYTES_512 :: INDCPA_PUBLICKEYBYTES_512
ENCAPSKEYBYTES_768 :: INDCPA_PUBLICKEYBYTES_768
ENCAPSKEYBYTES_1024 :: INDCPA_PUBLICKEYBYTES_1024
DECAPSKEYBYTES_512 :: INDCPA_SECRETKEYBYTES_512 + INDCPA_PUBLICKEYBYTES_512 + 2 * SYMBYTES
DECAPSKEYBYTES_768 :: INDCPA_SECRETKEYBYTES_768 + INDCPA_PUBLICKEYBYTES_768 + 2 * SYMBYTES
DECAPSKEYBYTES_1024 :: INDCPA_SECRETKEYBYTES_1024 + INDCPA_PUBLICKEYBYTES_1024 + 2 * SYMBYTES
CIPHERTEXTBYTES_512 :: INDCPA_BYTES_512
CIPHERTEXTBYTES_768 :: INDCPA_BYTES_768
CIPHERTEXTBYTES_1024 :: INDCPA_BYTES_1024
CIPHERTEXTBYTES_MAX :: INDCPA_BYTES_1024

View File

@@ -0,0 +1,349 @@
#+private
package _mlkem
import "core:crypto"
import "core:crypto/shake"
@(require_results)
pack_pk :: proc "contextless" (r: []byte, pk: ^Polyvec, seed: []byte, k: int) -> bool {
pk_len := polyvec_byte_size(k)
switch {
case pk_len == 0:
return false
case len(seed) != SYMBYTES || len(r) != pk_len + SYMBYTES:
return false
}
polyvec_tobytes(r[:pk_len], pk, k)
copy(r[pk_len:], seed)
return true
}
@(require_results)
unpack_pk :: proc "contextless" (pk: ^Polyvec, seed, packedpk: []byte) -> bool {
pk_len := len(packedpk) - SYMBYTES
k: int
switch {
case pk_len == POLYVECBYTES_512:
k = K_512
case pk_len == POLYVECBYTES_768:
k = K_768
case pk_len == POLYVECBYTES_1024:
k = K_1024
case len(packedpk) - pk_len != SYMBYTES:
return false
case len(seed) != SYMBYTES:
return false
}
if k == 0 {
return false
}
ok := polyvec_frombytes(pk, packedpk[:pk_len], k)
copy(seed, packedpk[pk_len:])
return ok
}
@(require_results)
pack_sk :: proc "contextless" (r: []byte, sk: ^Polyvec, k: int) -> bool {
r_len := len(r)
if r_len == 0 || r_len != polyvec_byte_size(k) {
return false
}
polyvec_tobytes(r, sk, k)
return true
}
@(require_results)
unpack_sk :: proc "contextless" (sk: ^Polyvec, packedsk: []byte) -> bool {
k: int
switch len(packedsk) {
case POLYVECBYTES_512:
k = K_512
case POLYVECBYTES_768:
k = K_768
case POLYVECBYTES_1024:
k = K_1024
case:
return false
}
if k == 0 {
return false
}
return polyvec_frombytes(sk, packedsk, k)
}
@(require_results)
pack_ciphertext :: proc "contextless" (r: []byte, b: ^Polyvec, v: ^Poly, k: int) -> bool {
b_len := polyvec_compressed_byte_size(k)
if len(r) != b_len + poly_compressed_bytes(k) {
return false
}
polyvec_compress(r[:b_len], b, k)
poly_compress(r[b_len:], v)
return true
}
@(require_results)
unpack_ciphertext :: proc "contextless" (b: ^Polyvec, v: ^Poly, c: []byte) -> int {
b_len: int
k: int
switch len(c) {
case INDCPA_BYTES_512:
b_len = POLYVECCOMPRESSEDBYTES_512
k = K_512
case INDCPA_BYTES_768:
b_len = POLYVECCOMPRESSEDBYTES_768
k = K_768
case INDCPA_BYTES_1024:
b_len = POLYVECCOMPRESSEDBYTES_1024
k = K_1024
case:
return 0
}
polyvec_decompress(b, c[:b_len], k)
poly_decompress(v, c[b_len:])
return k
}
@(require_results)
rej_uniform :: proc "contextless" (r: []i16, buf: []byte) -> int {
r_len, b_len := len(r), len(buf)
ctr, pos: int
for ctr < r_len && pos + 3 <= b_len {
val0 := (u16(buf[pos+0] >> 0) | (u16(buf[pos+1]) << 8)) & 0xFFF
val1 := (u16(buf[pos+1] >> 4) | (u16(buf[pos+2]) << 4)) & 0xFFF
pos += 3
if val0 < Q {
r[ctr] = i16(val0)
ctr += 1
}
if(ctr < r_len && val1 < Q) {
r[ctr] = i16(val1)
ctr += 1
}
}
return ctr
}
gen_matrix :: proc(a: []Polyvec, seed: []byte, transposed: bool, k: int) {
GEN_MATRIX_NBLOCKS :: ((12*N/8*(1 << 12)/Q + XOF_BLOCKBYTES)/XOF_BLOCKBYTES)
buf: [GEN_MATRIX_NBLOCKS*XOF_BLOCKBYTES]byte = ---
ctx: shake.Context = ---
ctr: int
defer shake.reset(&ctx)
defer crypto.zero_explicit(&buf, size_of(buf))
for i in 0..<k {
for j in 0..<k {
switch transposed {
case true:
xof_absorb(&ctx, seed, byte(i), byte(j))
case false:
xof_absorb(&ctx, seed, byte(j), byte(i))
}
shake.read(&ctx, buf[:])
ctr = rej_uniform(a[i].vec[j].coeffs[:], buf[:])
b := buf[:XOF_BLOCKBYTES]
for ctr < N {
shake.read(&ctx, b)
ctr += rej_uniform(a[i].vec[j].coeffs[ctr:], b)
}
}
}
}
K_PKE_Decryption_Key :: struct {
pv: Polyvec,
k: int,
}
K_PKE_Encryption_Key :: struct {
pv: Polyvec,
p: [SYMBYTES]byte,
k: int,
}
k_pke_encryption_key_set :: proc(dst, src: ^K_PKE_Encryption_Key) {
k_pke_key_clear(dst)
for i in 0..<src.k {
copy(dst.pv.vec[i].coeffs[:], src.pv.vec[i].coeffs[:])
}
copy(dst.p[:], src.p[:])
dst.k = src.k
}
k_pke_key_clear :: proc(k: $T) where T == ^K_PKE_Encryption_Key || T == ^K_PKE_Decryption_Key {
crypto.zero_explicit(k, size_of(k^))
}
k_pke_keygen :: proc(
ek: ^K_PKE_Encryption_Key,
dk: ^K_PKE_Decryption_Key,
d: []byte,
k: int,
) {
assert(len(d) == SYMBYTES, "crypto/mlkem: invalid K-PKE d")
ensure(k == K_512 || k == K_768 || k == K_1024, "crypto/mlkem: invalid k")
buf: [2*SYMBYTES]byte = ---
defer crypto.zero_explicit(&buf, size_of(buf))
a_: [K_MAX]Polyvec = ---
e: Polyvec = ---
defer crypto.zero_explicit(&a_, size_of(Polyvec) * k)
defer polyvec_clear(&e)
a := a_[:k]
copy(buf[:], d)
buf[SYMBYTES] = byte(k)
hash_g(buf[:], buf[:SYMBYTES+1])
p, sigma := buf[:SYMBYTES], buf[SYMBYTES:]
gen_matrix(a, p, false, k)
n := byte(0)
for i in 0..<k {
if k != K_512 {
poly_getnoise_eta1(&dk.pv.vec[i], sigma, n)
} else {
poly_getnoise_eta1_512(&dk.pv.vec[i], sigma, n)
}
n += 1
}
for i in 0..<k {
if k != K_512 {
poly_getnoise_eta1(&e.vec[i], sigma, n)
} else {
poly_getnoise_eta1_512(&e.vec[i], sigma, n)
}
n += 1
}
polyvec_ntt(&dk.pv, k)
polyvec_ntt(&e, k)
for i in 0..<k {
polyvec_basemul_acc_montgomery(&ek.pv.vec[i], &a[i], &dk.pv, k)
poly_tomont(&ek.pv.vec[i])
}
polyvec_add(&ek.pv, &ek.pv, &e, k)
polyvec_reduce(&ek.pv, k)
copy(ek.p[:], p)
dk.k = k
ek.k = k
}
@(require_results)
k_pke_encrypt :: proc(
ciphertext: []byte,
ek: ^K_PKE_Encryption_Key,
m: []byte,
r: []byte,
) -> bool {
ensure(len(m) == INDCPA_MSGBYTES, "crypto/mlkem: invalid K-PKE m")
ensure(len(r) == SYMBYTES, "crypto/mlkem: invalid K-PKE r")
k := ek.k
at_: [K_MAX]Polyvec = ---
sp, ep, b: Polyvec = ---, ---, ---
kay, epp, v: Poly = ---, ---, ---
defer crypto.zero_explicit(&at_, size_of(Polyvec) * k)
defer polyvec_clear(&sp, &ep, &b)
defer poly_clear(&kay, &epp, &v)
poly_frommsg(&kay, m)
at := at_[:k]
gen_matrix(at, ek.p[:], true, k)
n := byte(0)
for i in 0..<k {
if k != K_512 {
poly_getnoise_eta1(&sp.vec[i], r, n)
} else {
poly_getnoise_eta1_512(&sp.vec[i], r, n)
}
n += 1
}
for i in 0..<k {
poly_getnoise_eta2(&ep.vec[i], r, n)
n += 1
}
poly_getnoise_eta2(&epp, r, n)
polyvec_ntt(&sp, k)
for i in 0..<k {
polyvec_basemul_acc_montgomery(&b.vec[i], &at[i], &sp, k)
}
polyvec_basemul_acc_montgomery(&v, &ek.pv, &sp, k)
polyvec_invntt_tomont(&b, k)
poly_invntt_tomont(&v)
polyvec_add(&b, &b, &ep, k)
poly_add(&v, &v, &epp)
poly_add(&v, &v, &kay)
polyvec_reduce(&b, k)
poly_reduce(&v)
return pack_ciphertext(ciphertext, &b, &v, k)
}
@(require_results)
k_pke_decrypt :: proc(
plaintext: []byte,
dk: ^K_PKE_Decryption_Key,
c: []byte,
) -> bool {
if len(plaintext) != INDCPA_MSGBYTES {
return false
}
k := dk.k
b: Polyvec = ---
v, mp: Poly = ---, ---
defer poly_clear(&v, &mp)
if unpack_ciphertext(&b, &v, c) != k {
return false
}
polyvec_ntt(&b, k)
polyvec_basemul_acc_montgomery(&mp, &dk.pv, &b, k)
poly_invntt_tomont(&mp)
poly_sub(&mp, &v, &mp)
poly_reduce(&mp)
poly_tomsg(plaintext, &mp)
return true
}

View File

@@ -0,0 +1,195 @@
package _mlkem
import "core:crypto"
import subtle "core:crypto/_subtle"
// This implementation is derived from the PQ-CRYSTALS reference
// implementation [[ https://github.com/pq-crystals/kyber ]],
// primarily for licensing reasons. Arguably mlkem-native is
// a more "up to date" codebase, but the changes to the
// ref code is minor and they slapped an attribution-required
// license on something that was originally CC-0/Apache 2.0.
// "Private Key"
Decapsulation_Key :: struct {
pke_dk: K_PKE_Decryption_Key,
ek: Encapsulation_Key,
seed: [SYMBYTES*2]byte, // (d, z)
}
// "Public Key"
Encapsulation_Key :: struct {
pke_ek: K_PKE_Encryption_Key,
raw_bytes: [INDCPA_PUBLICKEYBYTES_MAX]byte,
h: [SYMBYTES]byte,
}
decapsulation_key_expanded_bytes :: proc(
dk: ^Decapsulation_Key,
dst: []byte,
) {
sk := &dk.pke_dk
pv_len := polyvec_byte_size(sk.k)
ek_len := pv_len + SYMBYTES
ek_bytes := dk.ek.raw_bytes[:ek_len]
dst := dst
_ = pack_sk(dst[:pv_len], &sk.pv, sk.k)
dst = dst[pv_len:]
copy(dst, ek_bytes)
dst = dst[ek_len:]
hash_h(dst[:SYMBYTES], ek_bytes)
dst = dst[SYMBYTES:]
copy(dst, dk.seed[SYMBYTES:])
}
@(require_results)
encapsulation_key_set_bytes :: proc(
ek: ^Encapsulation_Key,
k: int,
b: []byte,
) -> bool {
k_len: int
switch k {
case K_512:
k_len = ENCAPSKEYBYTES_512
case K_768:
k_len = ENCAPSKEYBYTES_768
case K_1024:
k_len = ENCAPSKEYBYTES_1024
case:
return false
}
if len(b) != k_len {
return false
}
pke_ek := &ek.pke_ek
ok := unpack_pk(&pke_ek.pv, pke_ek.p[:], b)
pke_ek.k = k
copy(ek.raw_bytes[:k_len], b)
hash_h(ek.h[:], b)
// FIPS 203 unlike Kyber requires canonical encoding of
// encapsulation keys (Section 7,2), which is checked in
// unpack_pk.
if !ok {
crypto.zero_explicit(ek, size_of(Encapsulation_Key))
}
return ok
}
encapsulation_key_set_decaps :: proc(ek: ^Encapsulation_Key, dk: ^Decapsulation_Key) {
dk_ek := &dk.ek.pke_ek
ensure(dk_ek.k == K_512 || dk_ek.k == K_768 || dk_ek.k == K_1024, "crypto/mlkem: invalid decaps k")
k_pke_encryption_key_set(&ek.pke_ek, dk_ek)
copy(ek.raw_bytes[:], dk.ek.raw_bytes[:])
copy(ek.h[:], dk.ek.h[:])
}
// NIST's version of this also returns an encapsulation key, but our
// internal representation includes it as part of the decapsulation key
// in a more traditional "keypair" approach.
kem_keygen_internal :: proc(
dk: ^Decapsulation_Key,
seed: []byte, // (d, z)
k: int,
) {
ensure(len(seed) == 2 * SYMBYTES, "crypto/mlkem: invalid seed")
dk_ek := &dk.ek
d, z := seed[:SYMBYTES], seed[SYMBYTES:]
k_pke_keygen(&dk_ek.pke_ek, &dk.pke_dk, d, k)
ek_len := polyvec_byte_size(k) + SYMBYTES
ek_bytes := dk_ek.raw_bytes[:ek_len]
ensure(
pack_pk(ek_bytes, &dk_ek.pke_ek.pv, dk_ek.pke_ek.p[:], k),
"crypto/mlkem: failed to pack K-PKE ek",
)
hash_h(dk_ek.h[:], ek_bytes)
copy(dk.seed[:SYMBYTES], d)
copy(dk.seed[SYMBYTES:], z)
}
// The `_internal` "de-randomized" versions of ML-KEM.Encaps and
// ML-KEM.Decaps are only ever to be called by the actual non-interal
// implementation or test cases.
kem_encaps_internal :: proc(
shared_secret: []byte,
ciphertext: []byte,
ek: ^Encapsulation_Key,
randomness: []byte,
) {
ensure(len(shared_secret) == SYMBYTES, "crypto/mlkem: invalid K")
ensure(len(randomness) == SYMBYTES, "crypto/mlkem: invalid m")
ensure(
len(ciphertext) == ct_len_for_k(ek.pke_ek.k),
"crypto/mlkem: invalid ciphertext length",
)
buf: [2*SYMBYTES]byte = ---
defer crypto.zero_explicit(&buf, size_of(buf))
hash_g(buf[:], randomness, ek.h[:])
// Can't fail, ciphertext length is valid.
_ = k_pke_encrypt(ciphertext, &ek.pke_ek, randomness, buf[SYMBYTES:])
copy(shared_secret, buf[:SYMBYTES])
}
kem_decaps_internal :: proc(
shared_secret: []byte,
dk: ^Decapsulation_Key,
ciphertext: []byte,
) {
ct_len := ct_len_for_k(dk.pke_dk.k)
ensure(
len(ciphertext) == ct_len,
"crypto/mlkem: invalid ciphertext length",
)
m_: [SYMBYTES]byte
defer crypto.zero_explicit(&m_, size_of(m_))
// Can't fail, ciphertext length is valid.
_ = k_pke_decrypt(m_[:], &dk.pke_dk, ciphertext)
buf: [2*SYMBYTES]byte = ---
defer crypto.zero_explicit(&buf, size_of(buf))
ek := &dk.ek
hash_g(buf[:], m_[:], ek.h[:])
rkprf(shared_secret, dk.seed[SYMBYTES:], ciphertext)
ct_buf: [CIPHERTEXTBYTES_MAX]byte = ---
defer crypto.zero_explicit(&ct_buf, size_of(ct_buf))
ct_ := ct_buf[:ct_len]
_ = k_pke_encrypt(ct_, &ek.pke_ek, m_[:], buf[SYMBYTES:])
ok := crypto.compare_constant_time(ciphertext, ct_)
subtle.cmov_bytes(shared_secret, buf[:SYMBYTES], ok)
}
@(private="file")
ct_len_for_k :: proc(k: int) -> int {
switch k {
case K_512:
return CIPHERTEXTBYTES_512
case K_768:
return CIPHERTEXTBYTES_768
case K_1024:
return CIPHERTEXTBYTES_1024
case:
panic("crypto/mlkem: invalid k for ciphertext length")
}
}

View File

@@ -0,0 +1,75 @@
#+private
package _mlkem
@(rodata)
ZETAS := [128]i16 {
-1044, -758, -359, -1517, 1493, 1422, 287, 202,
-171, 622, 1577, 182, 962, -1202, -1474, 1468,
573, -1325, 264, 383, -829, 1458, -1602, -130,
-681, 1017, 732, 608, -1542, 411, -205, -1571,
1223, 652, -552, 1015, -1293, 1491, -282, -1544,
516, -8, -320, -666, -1618, -1162, 126, 1469,
-853, -90, -271, 830, 107, -1421, -247, -951,
-398, 961, -1508, -725, 448, -1065, 677, -1275,
-1103, 430, 555, 843, -1251, 871, 1550, 105,
422, 587, 177, -235, -291, -460, 1574, 1653,
-246, 778, 1159, -147, -777, 1483, -602, 1119,
-1590, 644, -872, 349, 418, 329, -156, -75,
817, 1097, 603, 610, 1322, -1285, -1465, 384,
-1215, -136, 1218, -1335, -874, 220, -1187, -1659,
-1185, -1530, -1278, 794, -1510, -854, -870, 478,
-108, -308, 996, 991, 958, -1460, 1522, 1628,
}
@(require_results)
fqmul :: #force_inline proc "contextless" (a, b: i16) -> i16 {
return montgomery_reduce(i32(a) * i32(b))
}
ntt :: proc "contextless" (r: ^[N]i16) #no_bounds_check {
j, k := 0, 1
for l := 128; l >= 2; l >>= 1 {
for start := 0; start < N; start = j + l {
zeta := ZETAS[k]
k += 1
for j = start; j < start + l; j += 1 {
t := fqmul(zeta, r[j+l])
r[j+l] = r[j] - t
r[j] = r[j] + t
}
}
}
}
invntt :: proc "contextless" (r: ^[N]i16) #no_bounds_check {
F : i16 : 1441 // mont^2/128
j, k := 0, 127
for l := 2; l <= 128; l <<= 1 {
for start := 0; start < 256; start = j+l {
zeta := ZETAS[k]
k -= 1
for j = start; j < start + l; j += 1 {
t := r[j]
r[j] = barrett_reduce(t + r[j+l])
r[j+l] = r[j+l] - t
r[j+l] = fqmul(zeta, r[j+l])
}
}
}
for v, i in r {
r[i] = fqmul(v, F)
}
}
@(require_results)
base_case_multiply :: proc "contextless" (a_0, a_1, b_0, b_1, zeta: i16) -> (i16, i16) {
r_0 := fqmul(a_1, b_1)
r_0 = fqmul(r_0, zeta)
r_0 += fqmul(a_0, b_0)
r_1 := fqmul(a_0, b_1)
r_1 += fqmul(a_1, b_0)
return r_0, r_1
}

View File

@@ -0,0 +1,241 @@
#+private
package _mlkem
import "core:crypto"
import subtle "core:crypto/_subtle"
// Elements of R_q = Z_q[X]/(X^n + 1). Represents polynomial
// coeffs[0] + X*coeffs[1] + X^2*coeffs[2] + ... + X^{n-1}*coeffs[n-1]
Poly :: struct {
coeffs: [N]i16,
}
poly_compress :: proc "contextless" (r: []byte, a: ^Poly) #no_bounds_check {
t: [8]byte = ---
defer crypto.zero_explicit(&t, size_of(t))
r := r
switch len(r) {
case POLYCOMPRESSEDBYTES_768: // Also covers _512
for i in 0..<N/8 {
for j in 0..<8 {
// map to positive standard representatives
u := a.coeffs[8*i+j]
u += (u >> 15) & Q
// t[j] = ((((uint16_t)u << 4) + Q/2)/Q) & 15
d0 := u32(u) << 4
d0 += 1665
d0 *= 80635
d0 >>= 28
t[j] = byte(d0) & 0xf
}
r[0] = t[0] | (t[1] << 4)
r[1] = t[2] | (t[3] << 4)
r[2] = t[4] | (t[5] << 4)
r[3] = t[6] | (t[7] << 4)
r = r[4:]
}
case POLYCOMPRESSEDBYTES_1024:
for i in 0..<N/8 {
for j in 0..<8 {
// map to positive standard representatives
u := a.coeffs[8*i+j]
u += (u >> 15) & Q
// t[j] = ((((uint16_t)u << 5) + Q/2)/Q) & 31
d0 := u32(u) << 5
d0 += 1664
d0 *= 40318
d0 >>= 27
t[j] = byte(d0) & 0x1f
}
r[0] = (t[0] >> 0) | (t[1] << 5)
r[1] = (t[1] >> 3) | (t[2] << 2) | (t[3] << 7)
r[2] = (t[3] >> 1) | (t[4] << 4)
r[3] = (t[4] >> 4) | (t[5] << 1) | (t[6] << 6)
r[4] = (t[6] >> 2) | (t[7] << 3)
r = r[5:]
}
case:
unreachable()
}
}
poly_decompress :: proc "contextless" (r: ^Poly, a: []byte) {
a := a
switch len(a) {
case POLYCOMPRESSEDBYTES_768: // Also covers _512
for i in 0..<N/2 {
r.coeffs[2*i+0] = i16(((u16(a[0] & 15) * Q) + 8) >> 4)
r.coeffs[2*i+1] = i16(((u16(a[0] >> 4) * Q) + 8) >> 4)
a = a[1:]
}
case POLYCOMPRESSEDBYTES_1024:
t: [8]byte = ---
defer crypto.zero_explicit(&t, size_of(t))
for i in 0..<N/8 {
t[0] = (a[0] >> 0)
t[1] = (a[0] >> 5) | (a[1] << 3)
t[2] = (a[1] >> 2)
t[3] = (a[1] >> 7) | (a[2] << 1)
t[4] = (a[2] >> 4) | (a[3] << 4)
t[5] = (a[3] >> 1)
t[6] = (a[3] >> 6) | (a[4] << 2)
t[7] = (a[4] >> 3)
a = a[5:]
for j in 0..<8 {
r.coeffs[8*i+j] = i16((u32(t[j] & 31) * Q + 16) >> 5)
}
}
case:
unreachable()
}
}
poly_tobytes :: proc "contextless" (r: []byte, a: ^Poly) #no_bounds_check {
ensure_contextless(len(r) >= POLYBYTES)
for i in 0..<N/2 {
// map to positive standard representatives
t0 := u16(a.coeffs[2*i])
t0 += u16((i16(t0) >> 15) & Q)
t1 := u16(a.coeffs[2*i+1])
t1 += u16((i16(t1) >> 15) & Q)
r[3*i+0] = byte(t0 >> 0)
r[3*i+1] = byte(t0 >> 8) | byte(t1 << 4)
r[3*i+2] = byte(t1 >> 4)
}
}
@(require_results)
poly_frombytes :: proc "contextless" (r: ^Poly, a: []byte) -> bool #no_bounds_check {
ensure_contextless(len(a) >= POLYBYTES)
ok := true
for i in 0..<N/2 {
r.coeffs[2*i] = i16(((u16(a[3*i+0]) >> 0) | (u16(a[3*i+1]) << 8)) & 0xFFF)
r.coeffs[2*i+1] = i16(((u16(a[3*i+1]) >> 4) | (u16(a[3*i+2]) << 4)) & 0xFFF)
ok &= r.coeffs[2*i] < Q && r.coeffs[2*i+1] < Q
}
return ok
}
poly_frommsg :: proc "contextless" (r: ^Poly, msg: []byte) #no_bounds_check {
#assert(INDCPA_MSGBYTES == N/8)
ensure_contextless(len(msg) == INDCPA_MSGBYTES)
for i in 0..<N/8 {
for j in 0..<8 {
r.coeffs[8*i+j] = subtle.csel_i16(0, (Q+1)/2, (msg[i] >> uint(j))&1)
}
}
}
poly_tomsg :: proc "contextless" (msg: []byte, a: ^Poly) #no_bounds_check {
ensure_contextless(len(msg) == INDCPA_MSGBYTES)
for i in 0..<N/8 {
msg[i] = 0
for j in uint(0)..<8 {
t := u32(a.coeffs[8*i+int(j)])
// t += ((int16_t)t >> 15) & Q
// t = (((t << 1) + Q/2)/Q) & 1
t <<= 1
t += 1665
t *= 80635
t >>= 28
t &= 1
msg[i] |= byte(t << j)
}
}
}
poly_getnoise_eta1_512 :: proc(r: ^Poly, seed: []byte, iv: byte) {
buf: [ETA1_512*N/4]byte = ---
defer crypto.zero_explicit(&buf, size_of(buf))
prf(buf[:], seed, iv)
poly_cbd_eta1_512(r, &buf)
}
poly_getnoise_eta1 :: proc(r: ^Poly, seed: []byte, iv: byte) {
buf: [ETA1*N/4]byte = ---
defer crypto.zero_explicit(&buf, size_of(buf))
prf(buf[:], seed, iv)
poly_cbd_eta1(r, &buf)
}
poly_getnoise_eta2 :: proc(r: ^Poly, seed: []byte, iv: byte) {
buf: [ETA2*N/4]byte = ---
defer crypto.zero_explicit(&buf, size_of(buf))
prf(buf[:], seed, iv)
poly_cbd_eta2(r, &buf)
}
poly_ntt :: proc "contextless" (r: ^Poly) {
ntt(&r.coeffs)
poly_reduce(r)
}
poly_invntt_tomont :: proc "contextless" (r: ^Poly) {
invntt(&r.coeffs)
}
poly_basemul_montgomery :: proc "contextless" (r, a, b: ^Poly) #no_bounds_check {
for i in 0..<N/4 {
j := 4 * i
r.coeffs[j], r.coeffs[j+1] = base_case_multiply(a.coeffs[j], a.coeffs[j+1], b.coeffs[j], b.coeffs[j+1], ZETAS[64+i])
r.coeffs[j+2], r.coeffs[j+3] = base_case_multiply(a.coeffs[j+2], a.coeffs[j+3], b.coeffs[j+2], b.coeffs[j+3], -ZETAS[64+i])
}
}
poly_tomont :: proc "contextless" (r: ^Poly) {
F : i16 : (1 << 32) % Q
for v, i in r.coeffs {
r.coeffs[i] = montgomery_reduce(i32(v)*i32(F))
}
}
poly_reduce :: proc "contextless" (r: ^Poly) {
for v, i in r.coeffs {
r.coeffs[i] = barrett_reduce(v)
}
}
poly_add :: proc "contextless" (r, a, b: ^Poly) {
for i in 0..<N {
r.coeffs[i] = a.coeffs[i] + b.coeffs[i]
}
}
poly_sub :: proc "contextless" (r, a, b: ^Poly) {
for i in 0..<N {
r.coeffs[i] = a.coeffs[i] - b.coeffs[i]
}
}
poly_clear :: proc "contextless" (a: ..^Poly) {
for j in 0..<len(a) {
p := a[j]
crypto.zero_explicit(p, size_of(Poly))
}
}
poly_compressed_bytes :: #force_inline proc "contextless" (k: int) -> int {
switch k {
case K_512:
return POLYCOMPRESSEDBYTES_512
case K_768:
return POLYCOMPRESSEDBYTES_768
case K_1024:
return POLYCOMPRESSEDBYTES_1024
case:
unreachable()
}
}

View File

@@ -0,0 +1,224 @@
#+private
package _mlkem
import "core:crypto"
Polyvec :: struct {
vec: [K_MAX]Poly,
}
polyvec_compress :: proc "contextless" (r: []byte, a: ^Polyvec, kay: int) #no_bounds_check {
d0: u64
r := r
switch len(r) {
case POLYVECCOMPRESSEDBYTES_512, POLYVECCOMPRESSEDBYTES_768:
ensure_contextless(kay == K_512 || kay == K_768)
t: [4]u16 = ---
defer crypto.zero_explicit(&t, size_of(t))
for i in 0..<kay {
for j in 0..<N/4 {
for k in 0..<4 {
t[k] = u16(a.vec[i].coeffs[4*j+k])
t[k] += u16((i16(t[k]) >> 15) & Q)
// t[k] = ((((uint32_t)t[k] << 10) + Q/2)/Q) & 0x3ff
d0 = u64(t[k])
d0 <<= 10
d0 += 1665
d0 *= 1290167
d0 >>= 32
t[k] = u16(d0 & 0x3ff)
}
r[0] = byte(t[0] >> 0)
r[1] = byte((t[0] >> 8) | (t[1] << 2))
r[2] = byte((t[1] >> 6) | (t[2] << 4))
r[3] = byte((t[2] >> 4) | (t[3] << 6))
r[4] = byte(t[3] >> 2)
r = r[5:]
}
}
case POLYVECCOMPRESSEDBYTES_1024:
ensure_contextless(kay == K_1024)
t: [8]u16 = ---
defer crypto.zero_explicit(&t, size_of(t))
for i in 0..<K_1024 {
for j in 0..<N/8 {
for k in 0..<8 {
t[k] = u16(a.vec[i].coeffs[8*j+k])
t[k] += u16((i16(t[k]) >> 15) & Q)
// t[k] = ((((uint32_t)t[k] << 11) + Q/2)/Q) & 0x7ff
d0 = u64(t[k])
d0 <<= 11
d0 += 1664
d0 *= 645084
d0 >>= 31
t[k] = u16(d0 & 0x7ff)
}
r[0] = byte(t[0] >> 0)
r[1] = byte((t[0] >> 8) | (t[1] << 3))
r[2] = byte((t[1] >> 5) | (t[2] << 6))
r[3] = byte(t[2] >> 2)
r[4] = byte((t[2] >> 10) | (t[3] << 1))
r[5] = byte((t[3] >> 7) | (t[4] << 4))
r[6] = byte((t[4] >> 4) | (t[5] << 7))
r[7] = byte(t[5] >> 1)
r[8] = byte((t[5] >> 9) | (t[6] << 2))
r[9] = byte((t[6] >> 6) | (t[7] << 5))
r[10] = byte(t[7] >> 3)
r = r[11:]
}
}
case:
unreachable()
}
}
polyvec_decompress :: proc "contextless" (r: ^Polyvec, a: []byte, kay: int) #no_bounds_check {
a := a
switch len(a) {
case POLYVECCOMPRESSEDBYTES_512, POLYVECCOMPRESSEDBYTES_768:
ensure_contextless(kay == K_512 || kay == K_768)
t: [4]u16 = ---
defer crypto.zero_explicit(&t, size_of(t))
for i in 0..<kay {
for j in 0..<N/4 {
t[0] = u16(a[0] >> 0) | (u16(a[1]) << 8)
t[1] = u16(a[1] >> 2) | (u16(a[2]) << 6)
t[2] = u16(a[2] >> 4) | (u16(a[3]) << 4)
t[3] = u16(a[3] >> 6) | (u16(a[4]) << 2)
a = a[5:]
for k in 0..<4 {
r.vec[i].coeffs[4*j+k] = i16((u32(t[k] & 0x3FF) * Q + 512) >> 10)
}
}
}
case POLYVECCOMPRESSEDBYTES_1024:
t: [8]u16 = ---
defer crypto.zero_explicit(&t, size_of(t))
for i in 0..<K_1024 {
for j in 0..<N/8 {
t[0] = u16(a[0] >> 0) | (u16(a[1]) << 8)
t[1] = u16(a[1] >> 3) | (u16(a[2]) << 5)
t[2] = u16(a[2] >> 6) | (u16(a[3]) << 2) | (u16(a[4]) << 10)
t[3] = u16(a[4] >> 1) | (u16(a[5]) << 7)
t[4] = u16(a[5] >> 4) | (u16(a[6]) << 4)
t[5] = u16(a[6] >> 7) | (u16(a[7]) << 1) | (u16(a[8]) << 9)
t[6] = u16(a[8] >> 2) | (u16(a[9]) << 6)
t[7] = u16(a[9] >> 5) | (u16(a[10]) << 3)
a = a[11:]
for k in 0..<8 {
r.vec[i].coeffs[8*j+k] = i16((u32(t[k] & 0x7FF) * Q + 1024) >> 11)
}
}
}
case:
unreachable()
}
}
polyvec_tobytes :: proc "contextless" (r: []byte, a: ^Polyvec, k: int) #no_bounds_check {
ensure_contextless(len(r) == k * POLYBYTES, "crypto/mlkem: invalid buffer")
r := r
for i in 0..<k {
poly_tobytes(r, &a.vec[i])
r = r[POLYBYTES:]
}
}
@(require_results)
polyvec_frombytes :: proc "contextless" (r: ^Polyvec, a: []byte, k: int) -> bool #no_bounds_check {
switch k {
case K_512, K_768, K_1024:
case:
panic_contextless("crypto/mlkem: invalid POLYVECBYTES")
}
ensure_contextless(len(a) == k * POLYBYTES, "crypto/mlkem: invalid buffer")
a := a
ok := true
for i in 0..<k {
ok &= poly_frombytes(&r.vec[i], a)
a = a[POLYBYTES:]
}
return ok
}
@(require_results)
polyvec_byte_size :: #force_inline proc "contextless" (k: int) -> int {
switch k {
case K_512, K_768, K_1024:
return k * POLYBYTES
case:
return 0
}
}
@(require_results)
polyvec_compressed_byte_size :: #force_inline proc "contextless" (k: int) -> int {
switch k {
case K_512:
return POLYVECCOMPRESSEDBYTES_512
case K_768:
return POLYVECCOMPRESSEDBYTES_768
case K_1024:
return POLYVECCOMPRESSEDBYTES_1024
case:
return 0
}
}
polyvec_ntt :: proc "contextless" (r: ^Polyvec, k: int) {
for i in 0..<k {
poly_ntt(&r.vec[i])
}
}
polyvec_invntt_tomont :: proc "contextless" (r: ^Polyvec, k: int) {
for i in 0..<k {
poly_invntt_tomont(&r.vec[i])
}
}
polyvec_basemul_acc_montgomery :: proc "contextless" (r: ^Poly, a, b: ^Polyvec, k: int) {
t: Poly = ---
defer crypto.zero_explicit(&t, size_of(t))
poly_basemul_montgomery(r, &a.vec[0], &b.vec[0])
for i in 1..<k {
poly_basemul_montgomery(&t, &a.vec[i], &b.vec[i])
poly_add(r, r, &t)
}
poly_reduce(r)
}
polyvec_reduce :: proc "contextless" (r: ^Polyvec, k: int) {
for i in 0..<k {
poly_reduce(&r.vec[i])
}
}
polyvec_add :: proc "contextless" (r, a, b: ^Polyvec, k: int) {
for i in 0..<k {
poly_add(&r.vec[i], &a.vec[i], &b.vec[i])
}
}
polyvec_clear :: proc "contextless" (rs: ..^Polyvec) {
for j in 0..<len(rs) {
r := rs[j]
crypto.zero_explicit(r, size_of(Polyvec))
}
}

View File

@@ -0,0 +1,19 @@
#+private
package _mlkem
@(require_results)
montgomery_reduce :: #force_inline proc "contextless" (a: i32) -> i16 {
QINV :: -3327 // q^-1 mod 2^16
t := i16(a) * QINV
return i16((a - i32(t) * Q) >> 16)
}
@(require_results)
barrett_reduce :: #force_inline proc "contextless" (a: i16) -> i16 {
V : i16 : ((1<<26) + Q / 2) / Q
t := i16((i32(V)*i32(a) + (1<<25)) >> 26)
t *= Q
return a - t
}

View File

@@ -0,0 +1,61 @@
#+private
package _mlkem
import "core:crypto"
import "core:crypto/_sha3"
import "core:crypto/sha3"
import "core:crypto/shake"
XOF_BLOCKBYTES :: _sha3.RATE_128
#assert(XOF_BLOCKBYTES % 3 == 0)
prf :: proc(out, key: []byte, iv: byte) {
ctx: shake.Context = ---
defer shake.reset(&ctx)
shake.init_256(&ctx)
shake.write(&ctx, key)
shake.write(&ctx, []byte{iv})
shake.read(&ctx, out)
}
rkprf :: proc(out, key, input: []byte) {
ctx: shake.Context = ---
defer shake.reset(&ctx)
shake.init_256(&ctx)
shake.write(&ctx, key)
shake.write(&ctx, input)
shake.read(&ctx, out)
}
xof_absorb :: proc(ctx: ^shake.Context, seed: []byte, x, y: byte) {
shake.init_128(ctx)
extseed: [SYMBYTES+2]byte = ---
defer crypto.zero_explicit(&extseed, size_of(extseed))
copy(extseed[:], seed)
extseed[SYMBYTES+0] = x
extseed[SYMBYTES+1] = y
shake.write(ctx, extseed[:])
}
hash_h :: proc(dst, src: []byte) {
ctx: sha3.Context = ---
sha3.init_256(&ctx)
sha3.update(&ctx, src)
sha3.final(&ctx, dst)
}
hash_g :: proc(dst: []byte, srcs: ..[]byte) {
ctx: sha3.Context = ---
sha3.init_512(&ctx)
for src in srcs {
sha3.update(&ctx, src)
}
sha3.final(&ctx, dst)
}

View File

@@ -31,21 +31,24 @@ DS_SHA3 :: 0x06
DS_SHAKE :: 0x1f
DS_CSHAKE :: 0x04
DS_TURBOSHAKE_DEFAULT :: 0x1f
Context :: struct {
st: struct #raw_union {
st: struct #raw_union {
b: [200]u8,
q: [25]u64,
},
pt: int,
rsiz: int,
mdlen: int,
dsbyte: byte,
is_initialized: bool,
is_finalized: bool, // For SHAKE (unlimited squeeze is allowed)
pt: int,
rsiz: int,
mdlen: int,
keccak_round_start: i32, // Round at which keccak_permute starts
dsbyte: byte,
is_initialized: bool,
is_finalized: bool, // For SHAKE (unlimited squeeze is allowed)
}
@(private, rodata)
keccakf_rndc := [?]u64 {
keccak_rndc := [?]u64 {
0x0000000000000001, 0x0000000000008082, 0x800000000000808a,
0x8000000080008000, 0x000000000000808b, 0x0000000080000001,
0x8000000080008081, 0x8000000000008009, 0x000000000000008a,
@@ -57,20 +60,23 @@ keccakf_rndc := [?]u64 {
}
@(private, rodata)
keccakf_rotc := [?]int {
keccak_rotc := [?]int {
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
}
@(private, rodata)
keccakf_piln := [?]i32 {
keccak_piln := [?]i32 {
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4,
15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
}
// Covers the Keccak permutation rounds, allows for a starting round parameter to support for example Keccak-p[1600,12]
@(private)
keccakf :: proc "contextless" (st: ^[25]u64) {
i, j, r: i32 = ---, ---, ---
keccak_permute :: proc "contextless" (st: ^[25]u64, starting_round: i32 = 0) {
ensure_contextless((starting_round >= 0 && starting_round <= 23), "crypto/sha3: invalid Keccak permutation starting round")
i, j, r: i32 = ---, ---, starting_round
t: u64 = ---
bc: [5]u64 = ---
@@ -80,7 +86,7 @@ keccakf :: proc "contextless" (st: ^[25]u64) {
}
}
for r = 0; r < ROUNDS; r += 1 {
for ; r < ROUNDS; r += 1 {
// theta
for i = 0; i < 5; i += 1 {
bc[i] = st[i] ~ st[i + 5] ~ st[i + 10] ~ st[i + 15] ~ st[i + 20]
@@ -96,9 +102,9 @@ keccakf :: proc "contextless" (st: ^[25]u64) {
// rho pi
t = st[1]
for i = 0; i < 24; i += 1 {
j = keccakf_piln[i]
j = keccak_piln[i]
bc[0] = st[j]
st[j] = bits.rotate_left64(t, keccakf_rotc[i])
st[j] = bits.rotate_left64(t, keccak_rotc[i])
t = bc[0]
}
@@ -112,7 +118,7 @@ keccakf :: proc "contextless" (st: ^[25]u64) {
}
}
st[0] ~= keccakf_rndc[r]
st[0] ~= keccak_rndc[r]
}
when ODIN_ENDIAN != .Little {
@@ -142,7 +148,7 @@ update :: proc "contextless" (ctx: ^Context, data: []byte) {
ctx.st.b[j] ~= data[i]
j += 1
if j >= ctx.rsiz {
keccakf(&ctx.st.q)
keccak_permute(&ctx.st.q, ctx.keccak_round_start)
j = 0
}
}
@@ -164,7 +170,7 @@ final :: proc "contextless" (ctx: ^Context, hash: []byte, finalize_clone: bool =
ctx.st.b[ctx.pt] ~= ctx.dsbyte
ctx.st.b[ctx.rsiz - 1] ~= 0x80
keccakf(&ctx.st.q)
keccak_permute(&ctx.st.q, ctx.keccak_round_start)
for i := 0; i < ctx.mdlen; i += 1 {
hash[i] = ctx.st.b[i]
}
@@ -188,7 +194,7 @@ shake_xof :: proc "contextless" (ctx: ^Context) {
ctx.st.b[ctx.pt] ~= ctx.dsbyte
ctx.st.b[ctx.rsiz - 1] ~= 0x80
keccakf(&ctx.st.q)
keccak_permute(&ctx.st.q, ctx.keccak_round_start)
ctx.pt = 0
ctx.is_finalized = true // No more absorb, unlimited squeeze.
@@ -201,11 +207,11 @@ shake_out :: proc "contextless" (ctx: ^Context, hash: []byte) {
j := ctx.pt
for i := 0; i < len(hash); i += 1 {
if j >= ctx.rsiz {
keccakf(&ctx.st.q)
keccak_permute(&ctx.st.q, ctx.keccak_round_start)
j = 0
}
hash[i] = ctx.st.b[j]
j += 1
}
ctx.pt = j
}
}

View File

@@ -0,0 +1,12 @@
package _sha3
init_turboshake :: proc "contextless" (ctx: ^Context, d: byte, sec_strength: int) {
ensure_contextless((d >= 0x01 && d <= 0x7f), "crypto/sha3: invalid TurboSHAKE domain separation byte, allowed: >= 0x01 && <= 0x7f")
ctx.mdlen = sec_strength / 8
ctx.dsbyte = d
init(ctx)
ctx.keccak_round_start = 12
}

View File

@@ -3,40 +3,248 @@ Various useful bit operations in constant time.
*/
package _subtle
import "core:math/bits"
import "base:intrinsics"
// byte_eq returns 1 if and only if (⟺) a == b, 0 otherwise.
@(optimization_mode="none")
byte_eq :: proc "contextless" (a, b: byte) -> int {
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
// Constant-time primitives. These functions manipulate integer values in
// order to provide constant-time comparisons and multiplexers.
//
// Boolean values (the "ctl" bits) MUST have value 0 or 1.
//
// Implementation notes:
// =====================
//
// The uintN_t types are unsigned and with width exactly N bits; the C
// standard guarantees that computations are performed modulo 2^N, and
// there can be no overflow. Negation (unary '-') works on unsigned types
// as well.
//
// The intN_t types are guaranteed to have width exactly N bits, with no
// padding bit, and using two's complement representation. Casting
// intN_t to uintN_t really is conversion modulo 2^N. Beware that intN_t
// types, being signed, trigger implementation-defined behaviour on
// overflow (including raising some signal): with GCC, while modular
// arithmetics are usually applied, the optimizer may assume that
// overflows don't occur (unless the -fwrapv command-line option is
// added); Clang has the additional -ftrapv option to explicitly trap on
// integer overflow or underflow.
// This code only works on a two's complement system.
#assert((-1 & 3) == 3)
// not negates a boolean which MUST be `0` or `1`
@(optimization_mode="none", require_results)
not :: proc "contextless" (ctrl: $T) -> T where intrinsics.type_is_unsigned(T) {
return ctrl ~ 1
}
@(optimization_mode="none", require_results)
byte_eq :: proc "contextless" (a, b: byte) -> byte {
v := a ~ b
// v == 0 if and only if (⟺) a == b. The subtraction will underflow, setting the
// sign bit, which will get returned.
return int((u32(v)-1) >> 31)
return byte((u32(v)-1) >> 31)
}
// u64_eq returns 1 if and only if (⟺) a == b, 0 otherwise.
@(optimization_mode="none")
@(optimization_mode="none", require_results)
u32_eq :: proc "contextless" (a, b: u32) -> u32 {
q := a ~ b
return ((q | -q) >> 31) ~ 1
}
@(optimization_mode="none", require_results)
u64_eq :: proc "contextless" (a, b: u64) -> u64 {
_, borrow := bits.sub_u64(0, a ~ b, 0)
return (~borrow) & 1
q := a ~ b
return ((q | -q) >> 63) ~ 1
}
// eq returns 1 if and only if (⟺) a == b, 0 otherwise.
eq :: proc {
byte_eq,
u32_eq,
u64_eq,
}
// u64_is_zero returns 1 if and only if (⟺) a == 0, 0 otherwise.
@(optimization_mode="none")
u64_is_zero :: proc "contextless" (a: u64) -> u64 {
_, borrow := bits.sub_u64(a, 1, 0)
return borrow
@(require_results)
byte_neq :: proc "contextless" (a, b: byte) -> byte {
return #force_inline byte_eq(a, b) ~ 1
}
// u64_is_non_zero returns 1 if and only if (⟺) a != 0, 0 otherwise.
@(optimization_mode="none")
u64_is_non_zero :: proc "contextless" (a: u64) -> u64 {
is_zero := u64_is_zero(a)
return (~is_zero) & 1
@(optimization_mode="none", require_results)
u32_neq :: proc "contextless" (a, b: u32) -> u32 {
q := a ~ b
return (q | -q) >> 31
}
@(optimization_mode="none", require_results)
u64_neq :: proc "contextless" (a, b: u64) -> u64 {
q := a ~ b
return (q | -q) >> 63
}
// neq returns 1 if and only if (⟺) a != b, 0 otherwise.
neq :: proc {
byte_neq,
u32_neq,
u64_neq,
}
@(optimization_mode="none", require_results)
u32_gt :: proc "contextless" (x, y: u32) -> u32 {
/*
* If both x < 2^31 and y < 2^31, then y-x will have its high
* bit set if x > y, cleared otherwise.
*
* If either x >= 2^31 or y >= 2^31 (but not both), then the
* result is the high bit of x.
*
* If both x >= 2^31 and y >= 2^31, then we can virtually
* subtract 2^31 from both, and we are back to the first case.
* Since (y-2^31)-(x-2^31) = y-x, the subtraction is already
* fine.
*/
z := y - x
return (z ~ ((x ~ y) & (x ~ z))) >> 31
}
@(optimization_mode="none", require_results)
u64_gt :: proc "contextless" (x, y: u64) -> u64 {
z := y - x
return (z ~ ((x ~ y) & (x ~ z))) >> 63
}
// gt returns 1 if x > y, 0 otherwise.
gt :: proc {
u32_gt,
u64_gt,
}
// gt returns 1 if x >= y, 0 otherwise.
@(require_results)
ge :: proc "contextless" (x, y: $T) -> T where T == u32 || T == u64 {
return #force_inline(gt(y, x)) ~ 1
}
// lt returns 1 if x < y, 0 otherwise.
@(require_results)
lt :: proc "contextless" (x, y: $T) -> T where T == u32 || T == u64 {
return #force_inline(gt(y, x))
}
// le returns 1 if x <= y, 0 otherwise.
@(require_results)
le :: proc "contextless" (x, y: $T) -> T where T == u32 || T == u64 {
return #force_inline(gt(x, y)) ~ 1
}
@(require_results)
u32_cmp :: proc "contextless" (x, y: u32) -> i32 {
return i32(#force_inline gt(x, y)) | -i32(#force_inline gt(y, x))
}
@(require_results)
u64_cmp :: proc "contextless" (x, y: u64) -> i64 {
return i64(#force_inline gt(x, y)) | -i64(#force_inline gt(y, x))
}
// cmp returns -1, 0, or 1, depending on wheter x is lower than, equal
// to, or greater than y.
cmp :: proc {
u32_cmp,
u64_cmp,
}
// eq0 returns 1 if and only if (⟺) a == 0, 0 otherwise.
@(require_results)
eq0 :: proc "contextless" (a: $T) -> T where T == u32 || T == u64 {
return #force_inline eq(a, 0)
}
// neq0 returns 1 if and only if (⟺) a != 0, 0 otherwise.
@(require_results)
neq0 :: proc "contextless" (a: $T) -> T where T == u32 || T == u64 {
return #force_inline eq(a, 0) ~ 1
}
cmov_bytes :: proc "contextless" (dst, src: []byte, #any_int ctrl: int) {
ensure_contextless(len(src) == len(dst), "crypto: cmov length mismatch")
cmov_impl(dst, src, ctrl)
}
cmov_u32s :: proc "contextless" (dst, src: []u32, #any_int ctrl: int) {
ensure_contextless(len(src) == len(dst), "crypto: cmov length mismatch")
cmov_impl(dst, src, ctrl)
}
@(private="file", optimization_mode="none")
cmov_impl :: proc "contextless"(dst, src: []$T, ctrl: int) {
s_len := len(src)
c := -(T)(ctrl)
for i in 0..<s_len {
dst[i] ~= c & (dst[i] ~ src[i])
}
}
// cmov copies `src` into `dst` if and only if (⟺) ctrl == 1. `dst` and
// `src` may overlap completely (but not partially).
cmov :: proc {
cmov_bytes,
cmov_u32s,
}
@(optimization_mode="none", require_results)
csel_i16 :: proc "contextless" (a, b: i16, #any_int ctrl: u16) -> i16 {
c := -ctrl
return a ~ i16(c & u16(a ~ b))
}
@(optimization_mode="none", require_results)
csel_u16 :: proc "contextless" (a, b: u16, #any_int ctrl: u16) -> u16 {
c := -ctrl
return a ~ (c & (a ~ b))
}
@(optimization_mode="none", require_results)
csel_u32 :: proc "contextless" (a, b: u32, #any_int ctrl: u32) -> u32 {
c := -ctrl
return a ~ (c & (a ~ b))
}
@(optimization_mode="none", require_results)
csel_u64 :: proc "contextless" (a, b: u64, #any_int ctrl: u64) -> u64 {
c := -ctrl
return a ~ (c & (a ~ b))
}
// csel returns `a` if ctl == `0`, `b` if ctl == `1`.
csel :: proc {
csel_i16,
csel_u16,
csel_u32,
csel_u64,
}

View File

@@ -196,10 +196,10 @@ fe_gen_y_p384r1 :: proc "contextless" (fe: ^Field_Element_p384r1) {
@(require_results)
fe_is_zero_p256r1 :: proc "contextless" (fe: ^Field_Element_p256r1) -> int {
return int(subtle.u64_is_zero(p256r1.fe_non_zero(fe)))
return int(subtle.eq0(p256r1.fe_non_zero(fe)))
}
@(require_results)
fe_is_zero_p384r1 :: proc "contextless" (fe: ^Field_Element_p384r1) -> int {
return int(subtle.u64_is_zero(p384r1.fe_non_zero(fe)))
return int(subtle.eq0(p384r1.fe_non_zero(fe)))
}

View File

@@ -133,10 +133,10 @@ sc_is_zero :: proc {
@(require_results)
sc_is_zero_p256r1 :: proc "contextless" (fe: ^Scalar_p256r1) -> int {
return int(subtle.u64_is_zero(p256r1.fe_non_zero(fe)))
return int(subtle.eq0(p256r1.fe_non_zero(fe)))
}
@(require_results)
sc_is_zero_p384r1 :: proc "contextless" (fe: ^Scalar_p384r1) -> int {
return int(subtle.u64_is_zero(p384r1.fe_non_zero(fe)))
return int(subtle.eq0(p384r1.fe_non_zero(fe)))
}

View File

@@ -293,7 +293,7 @@ when crypto.COMPACT_IMPLS == false {
// conditionally select the right result.
pt_add_mixed(tmp, point, &tmp.x, &tmp.y)
ctrl := subtle.u64_is_non_zero(idx)
ctrl := subtle.neq0(idx)
pt_cond_select(point, point, tmp, int(ctrl))
}
}

View File

@@ -21,7 +21,7 @@ init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := DEFAULT_IMPLEMENTATION)
encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) {
ensure(ctx._is_initialized)
ensure(len(dst) == BLOCK_SIZE, "crypto/aes: invalid dst size")
ensure(len(dst) == BLOCK_SIZE, "crypto/aes: invalid src size")
ensure(len(src) == BLOCK_SIZE, "crypto/aes: invalid src size")
switch &impl in ctx._impl {
case ct64.Context:
@@ -35,7 +35,7 @@ encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) {
decrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) {
ensure(ctx._is_initialized)
ensure(len(dst) == BLOCK_SIZE, "crypto/aes: invalid dst size")
ensure(len(dst) == BLOCK_SIZE, "crypto/aes: invalid src size")
ensure(len(src) == BLOCK_SIZE, "crypto/aes: invalid src size")
switch &impl in ctx._impl {
case ct64.Context:

View File

@@ -143,7 +143,7 @@ derive :: proc(
m_ := 4 * u64(p) * (m / u64(4 * p))
b := mem.alloc_bytes_non_zeroed(
int(m_) * BLOCK_SIZE_BYTES,
alignment = mem.DEFAULT_PAGE_SIZE,
alignment = mem.PAGE_SIZE,
allocator = allocator,
) or_return
defer delete(b, allocator)

View File

@@ -16,7 +16,8 @@ HAS_RAND_BYTES :: runtime.HAS_RAND_BYTES
//
// The execution time of this routine is constant regardless of the contents
// of the slices being compared, as long as the length of the slices is equal.
// If the length of the two slices is dif and only if (⟺)erent, it will early-return 0.
// If and only if (⟺) the length of the two slices is diferent, it will
// early-return 0.
compare_constant_time :: proc "contextless" (a, b: []byte) -> int {
// If the length of the slices is different, early return.
//
@@ -48,7 +49,7 @@ compare_byte_ptrs_constant_time :: proc "contextless" (a, b: ^byte, n: int) -> i
// After the loop, v == 0 if and only if (⟺) a == b. The subtraction will underflow
// if and only if (⟺) v == 0, setting the sign-bit, which gets returned.
return subtle.eq(0, v)
return int(subtle.eq(0, v))
}
// is_zero_constant_time returns 1 if and only if (⟺) b is all 0s, 0 otherwise.
@@ -58,7 +59,7 @@ is_zero_constant_time :: proc "contextless" (b: []byte) -> int {
v |= b_
}
return subtle.byte_eq(0, v)
return int(subtle.byte_eq(0, v))
}
/*
@@ -84,18 +85,6 @@ zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr {
return data
}
/*
Set each byte of a memory range to a specific value.
This procedure copies value specified by the `value` parameter into each of the
`len` bytes of a memory range, located at address `data`.
This procedure returns the pointer to `data`.
*/
set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr {
return runtime.memset(data, i32(value), len)
}
// rand_bytes fills the dst buffer with cryptographic entropy taken from
// the system entropy source. This routine will block if the system entropy
// source is not ready yet. All system entropy source failures are treated

View File

@@ -106,6 +106,7 @@ Public_Key :: struct {
// private_key_generate uses the system entropy source to generate a new
// Private_Key. This will only fail if and only if (⟺) the system entropy source is
// missing or broken.
@(require_results)
private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
private_key_clear(priv_key)
@@ -143,6 +144,7 @@ private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
// private_key_set_bytes decodes a byte-encoded private key, and returns
// true if and only if (⟺) the operation was successful.
@(require_results)
private_key_set_bytes :: proc(priv_key: ^Private_Key, curve: Curve, b: []byte) -> bool {
private_key_clear(priv_key)
@@ -222,6 +224,40 @@ private_key_generate_public :: proc(priv_key: ^Private_Key) {
priv_key._pub_key._curve = priv_key._curve
}
// private_key_set sets priv_key to src.
private_key_set :: proc(priv_key, src: ^Private_Key) {
if src == nil || src._curve == .Invalid {
private_key_clear(priv_key)
return
}
priv_key._curve = src._curve
reflect.set_union_variant_typeid(
priv_key._impl,
_PRIV_IMPL_IDS[priv_key._curve],
)
#partial switch priv_key._curve {
case .SECP256R1:
secec.sc_set(&priv_key._impl.(secec.Scalar_p256r1), &src._impl.(secec.Scalar_p256r1))
case .SECP384R1:
secec.sc_set(&priv_key._impl.(secec.Scalar_p384r1), &src._impl.(secec.Scalar_p384r1))
case .X25519:
priv_buf := &(priv_key._impl.(X25519_Buf))
src_buf := &(src._impl.(X25519_Buf))
copy(priv_buf[:], src_buf[:])
case .X448:
priv_buf := &(priv_key._impl.(X448_Buf))
src_buf := &(src._impl.(X448_Buf))
copy(priv_buf[:], src_buf[:])
case:
panic("crypto/ecdh: invalid curve")
}
public_key_set(&priv_key._pub_key, &src._pub_key)
}
// private_key_bytes sets dst to byte-encoding of priv_key.
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
ensure(priv_key._curve != .Invalid, "crypto/ecdh: uninitialized private key")
@@ -245,8 +281,15 @@ private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
}
}
// private_key_public_bytes sets dst to the byte-encoding of the public
// key corresponding to priv_key.
private_key_public_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
public_key_bytes(&priv_key._pub_key, dst)
}
// private_key_equal returns true if and only if (⟺) the private keys are equal,
// in constant time.
@(require_results)
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
if p._curve != q._curve {
return false
@@ -277,6 +320,7 @@ private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
// public_key_set_bytes decodes a byte-encoded public key, and returns
// true if and only if (⟺) the operation was successful.
@(require_results)
public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> bool {
public_key_clear(pub_key)
@@ -325,6 +369,16 @@ public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> b
return true
}
// public_key_set sets pub_key to src.
public_key_set :: proc(pub_key, src: ^Public_Key) {
if src == nil || src._curve == .Invalid {
public_key_clear(pub_key)
return
}
pub_key^ = src^
}
// public_key_set_priv sets pub_key to the public component of priv_key.
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
ensure(priv_key._curve != .Invalid, "crypto/ecdh: uninitialized private key")
@@ -367,6 +421,7 @@ public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
// public_key_equal returns true if and only if (⟺) the public keys are equal,
// in constant time.
@(require_results)
public_key_equal :: proc(p, q: ^Public_Key) -> bool {
if p._curve != q._curve {
return false
@@ -435,12 +490,14 @@ ecdh :: proc(priv_key: ^Private_Key, pub_key: ^Public_Key, dst: []byte) -> bool
}
// curve returns the Curve used by a Private_Key or Public_Key instance.
curve :: proc(k: ^$T) -> Curve where(T == Private_Key || T == Public_Key) {
@(require_results)
curve :: proc(k: ^$T) -> Curve where (T == Private_Key || T == Public_Key) {
return k._curve
}
// key_size returns the key size of a Private_Key or Public_Key in bytes.
key_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
@(require_results)
key_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
when T == Private_Key {
return PRIVATE_KEY_SIZES[k._curve]
} else {
@@ -450,6 +507,7 @@ key_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
// shared_secret_size returns the shared secret size of a key exchange
// in bytes.
shared_secret_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
@(require_results)
shared_secret_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
return SHARED_SECRET_SIZES[k._curve]
}

View File

@@ -2,7 +2,6 @@ package ecdsa
import "core:crypto"
import secec "core:crypto/_weierstrass"
import "core:mem"
import "core:reflect"
// Curve the curve identifier associated with a given Private_Key
@@ -81,6 +80,7 @@ Public_Key :: struct {
// private_key_generate uses the system entropy source to generate a new
// Private_Key. This will only fail if and only if (⟺) the system entropy source is
// missing or broken.
@(require_results)
private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
private_key_clear(priv_key)
@@ -112,6 +112,7 @@ private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
// private_key_set_bytes decodes a byte-encoded private key, and returns
// true if and only if (⟺) the operation was successful.
@(require_results)
private_key_set_bytes :: proc(priv_key: ^Private_Key, curve: Curve, b: []byte) -> bool {
private_key_clear(priv_key)
@@ -194,8 +195,41 @@ private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
}
}
// private_key_public_bytes sets dst to the byte-encoding of the public
// key corresponding to priv_key.
private_key_public_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
public_key_bytes(&priv_key._pub_key, dst)
}
// private_key_set sets priv_key to src.
private_key_set :: proc(priv_key, src: ^Private_Key) {
if src == nil || src._curve == .Invalid {
private_key_clear(priv_key)
return
}
priv_key._curve = src._curve
reflect.set_union_variant_typeid(
priv_key._impl,
_PRIV_IMPL_IDS[priv_key._curve],
)
#partial switch priv_key._curve {
case .SECP256R1:
secec.sc_set(&priv_key._impl.(secec.Scalar_p256r1), &src._impl.(secec.Scalar_p256r1))
case .SECP384R1:
secec.sc_set(&priv_key._impl.(secec.Scalar_p384r1), &src._impl.(secec.Scalar_p384r1))
case:
panic("crypto/ecdh: invalid curve")
}
public_key_set(&priv_key._pub_key, &src._pub_key)
}
// private_key_equal returns true if and only if (⟺) the private keys are equal,
// in constant time.
@(require_results)
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
if p._curve != q._curve {
return false
@@ -215,11 +249,12 @@ private_key_equal :: proc(p, q: ^Private_Key) -> bool {
// private_key_clear clears priv_key to the uninitialized state.
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
mem.zero_explicit(priv_key, size_of(Private_Key))
crypto.zero_explicit(priv_key, size_of(Private_Key))
}
// public_key_set_bytes decodes a byte-encoded public key, and returns
// true if and only if (⟺) the operation was successful.
@(require_results)
public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> bool {
public_key_clear(pub_key)
@@ -262,6 +297,16 @@ public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> b
return true
}
// public_key_set sets pub_key to src.
public_key_set :: proc(pub_key, src: ^Public_Key) {
if src == nil || src._curve == .Invalid {
public_key_clear(pub_key)
return
}
pub_key^ = src^
}
// public_key_set_priv sets pub_key to the public component of priv_key.
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
ensure(priv_key._curve != .Invalid, "crypto/ecdsa: uninitialized private key")
@@ -298,6 +343,7 @@ public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
// public_key_equal returns true if and only if (⟺) the public keys are equal,
// in constant time.
@(require_results)
public_key_equal :: proc(p, q: ^Public_Key) -> bool {
if p._curve != q._curve {
return false
@@ -317,5 +363,21 @@ public_key_equal :: proc(p, q: ^Public_Key) -> bool {
// public_key_clear clears pub_key to the uninitialized state.
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
mem.zero_explicit(pub_key, size_of(Public_Key))
crypto.zero_explicit(pub_key, size_of(Public_Key))
}
// curve returns the Curve used by a Private_Key or Public_Key instance.
@(require_results)
curve :: proc(k: ^$T) -> Curve where (T == Private_Key || T == Public_Key) {
return k._curve
}
// key_size returns the key size of a Private_Key or Public_Key in bytes.
@(require_results)
key_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
when T == Private_Key {
return PRIVATE_KEY_SIZES[k._curve]
} else {
return PUBLIC_KEY_SIZES[k._curve]
}
}

View File

@@ -1,17 +1,13 @@
package ecdsa
import "core:encoding/asn1"
import secec "core:crypto/_weierstrass"
// ASN.1 format ECDSA signatures are`SEQUENCE { r INTEGER, s INTEGER }`
// this implements enough to generate/parse signatures. Eventually when
// we have a full ASN.1 DER library, these routines will be removed.
// ASN.1 ECDSA signatures are `SEQUENCE { r INTEGER, s INTEGER }`. These thin
// wrappers over core:encoding/asn1 generate/parse that structure the DER
// minimal-encoding rules.
@(private="file")
TAG_SEQUENCE :: 0x30
@(private="file")
TAG_INTEGER :: 0x02
@(private,require_results)
@(private, require_results)
generate_asn1_sig :: proc(r, s: ^$T, allocator := context.allocator) -> []byte {
when T == secec.Scalar_p256r1 {
SC_SZ :: secec.SC_SIZE_P256R1
@@ -21,163 +17,35 @@ generate_asn1_sig :: proc(r, s: ^$T, allocator := context.allocator) -> []byte {
#panic("crypto/ecdsa: invalid curve")
}
INT_TLP :: 3 // tag, tength, (optional) leading zero-byte
encode_uint :: proc(b: []byte) -> []byte {
b := b
r_buf, s_buf: [SC_SZ]byte = ---, ---
secec.sc_bytes(r_buf[:], r)
secec.sc_bytes(s_buf[:], s)
// DER requires minimal encoding.
off := INT_TLP
for v in b[off:] {
if v != 0 {
break
}
off += 1
}
// If the sign big is set, add a leading zero.
if b[off] & 0x80 == 0x80 {
off -= 1
b[off] = 0
}
// Encode the length (up to 127 octets, adequate for ECDSA).
l := len(b[off:])
off -= 1
b[off] = byte(l)
// Encode the tag
off -= 1
b[off] = TAG_INTEGER
return b[off:]
sig, err := asn1.marshal(
asn1.sequence({asn1.integer_unsigned(r_buf[:]), asn1.integer_unsigned(s_buf[:])}),
allocator,
)
if err != .None {
return nil
}
r_buf, s_buf: [INT_TLP+SC_SZ]byte = ---, ---
secec.sc_bytes(r_buf[INT_TLP:], r)
secec.sc_bytes(s_buf[INT_TLP:], s)
r_bytes, s_bytes := encode_uint(r_buf[:]), encode_uint(s_buf[:])
seq_len := len(r_bytes) + len(s_bytes)
// WARNING: If secp521r1 support is added, this needs to support
// long-form length encoding.
ensure(seq_len <= 127, "BUG: crypto/ecdsa: signature length too large")
b := make([]byte, seq_len + 2, allocator)
b[0] = TAG_SEQUENCE
b[1] = byte(seq_len)
copy(b[2:], r_bytes)
copy(b[2+len(r_bytes):], s_bytes)
return b
return sig
}
@(private,require_results)
@(private, require_results)
parse_asn1_sig :: proc(sig: []byte) -> (r, s: []byte, ok: bool) {
read_seq :: proc(b: []byte) -> (v: []byte, rest: []byte, ok: bool) {
b_len := len(b)
if b_len < 3 {
return nil, nil, false
}
if b[0] != TAG_SEQUENCE {
return nil, nil, false
}
seq_len, off: int
if b[1] & 0x80 == 0x80 {
if b[1] != 0x81 || b_len < 4 { // 2-length octets is sufficient for ecdsa.
return nil, nil, false
}
if b[2] & 0x80 == 0x80 || b[3] & 0x80 == 80 {
return nil, nil, false
}
seq_len = int(b[2]) * 127 + int(b[3])
off = 4
} else {
seq_len = int(b[1])
off = 2
}
if b_len - off < seq_len {
return nil, nil, false
}
return b[off:off+seq_len], b[off+seq_len:], true
}
read_int :: proc(b: []byte) -> (v: []byte, rest: []byte, ok: bool) {
b_len := len(b)
if b_len < 3 {
return nil, nil, false
}
if b[0] != TAG_INTEGER {
return nil, nil, false
}
v_len := int(b[1])
if v_len > 0x80 || b_len - 2 < v_len { // 127-bytes max.
return nil, nil, false
}
return b[2:2+v_len], b[2+v_len:], true
}
// SEQUENCE
seq_bytes, rest: []byte
seq_bytes, rest, ok = read_seq(sig)
if !ok {
return nil, nil, false
}
if len(rest) != 0 {
cur: asn1.Cursor
asn1.cursor_init(&cur, sig)
seq, e0 := asn1.read_sequence(&cur)
if e0 != .None || asn1.done(&cur) != .None {
return nil, nil, false
}
// INTEGER (r)
r, rest, ok = read_int(seq_bytes)
if !ok {
// r and s are unsigned; read_unsigned_integer_bytes validates the INTEGER
// and strips the DER sign octet, returning the magnitude as a view of sig.
rb, e1 := asn1.read_unsigned_integer_bytes(&seq)
sb, e2 := asn1.read_unsigned_integer_bytes(&seq)
if e1 != .None || e2 != .None || asn1.done(&seq) != .None {
return nil, nil, false
}
// INTEGER (s)
s, rest, ok = read_int(rest)
if !ok {
return nil, nil, false
}
if len(rest) != 0 {
return nil, nil, false
}
// DER requires a leading 0 if and only if (⟺) the sign bit of the leading byte
// is set to distinguish between positive and negative integers,
// and the minimal length representation. `r` and `s` are always
// going to be unsigned, so we validate malformed DER and strip
// the leading 0 as needed.
fixup_der_uint :: proc(b: []byte) -> ([]byte, bool) {
switch len(b) {
case 0:
// 0 length is invalid
return nil, false
case 1:
// Missing leading zero
if b[0] & 0x80 == 0x80 {
return nil, false
}
case:
if b[0] == 0 {
// Sign bit not set
if b[1] & 0x80 != 0x80 {
return nil, false
}
return b[1:], true
} else if b[0] & 0x80 == 0x80 {
// Missing leading zero
return nil, false
}
}
return b, true
}
if r, ok = fixup_der_uint(r); !ok {
return nil, nil, false
}
if s, ok = fixup_der_uint(s); !ok {
return nil, nil, false
}
return r, s, true
return rb, sb, true
}

View File

@@ -13,8 +13,8 @@ import secec "core:crypto/_weierstrass"
// The signature format is ASN1. `SEQUECE `{ r INTEGER, s INTEGER }`.
@(require_results)
sign_asn1 :: proc(priv_key: ^Private_Key, hash_algo: hash.Algorithm, msg: []byte, allocator: runtime.Allocator, deterministic := !crypto.HAS_RAND_BYTES) -> ([]byte, bool) {
ensure(hash_algo != .Invalid, "crypto/edsa: invalid hash algorithm")
ensure(priv_key._curve != .Invalid, "crypto/edsa: invalid curve")
ensure(hash_algo != .Invalid, "crypto/ecdsa: invalid hash algorithm")
ensure(priv_key._curve != .Invalid, "crypto/ecdsa: invalid curve")
if !deterministic && !crypto.HAS_RAND_BYTES {
return nil, false
@@ -49,8 +49,8 @@ sign_asn1 :: proc(priv_key: ^Private_Key, hash_algo: hash.Algorithm, msg: []byte
// The signature format is `r | s`.
@(require_results)
sign_raw :: proc(priv_key: ^Private_Key, hash_algo: hash.Algorithm, msg, sig: []byte, deterministic := !crypto.HAS_RAND_BYTES) -> bool {
ensure(hash_algo != .Invalid, "crypto/edsa: invalid hash algorithm")
ensure(priv_key._curve != .Invalid, "crypto/edsa: invalid curve")
ensure(hash_algo != .Invalid, "crypto/ecdsa: invalid hash algorithm")
ensure(priv_key._curve != .Invalid, "crypto/ecdsa: invalid curve")
ensure(len(sig) == RAW_SIGNATURE_SIZES[priv_key._curve], "crypto/ecdsa: invalid destination size")
if !deterministic && !crypto.HAS_RAND_BYTES {

View File

@@ -10,8 +10,8 @@ import secec "core:crypto/_weierstrass"
// The signature format is `r | s`.
@(require_results)
verify_raw :: proc(pub_key: ^Public_Key, hash_algo: hash.Algorithm, msg, sig: []byte) -> bool {
ensure(hash_algo != .Invalid, "crypto/edsa: invalid hash algorithm")
ensure(pub_key._curve != .Invalid, "crypto/edsa: invalid curve")
ensure(hash_algo != .Invalid, "crypto/ecdsa: invalid hash algorithm")
ensure(pub_key._curve != .Invalid, "crypto/ecdsa: invalid curve")
if len(sig) != RAW_SIGNATURE_SIZES[pub_key._curve] {
return false
@@ -40,8 +40,8 @@ verify_raw :: proc(pub_key: ^Public_Key, hash_algo: hash.Algorithm, msg, sig: []
// The signature format is ASN.1 `SEQUENCE { r INTEGER, s INTEGER }`.
@(require_results)
verify_asn1 :: proc(pub_key: ^Public_Key, hash_algo: hash.Algorithm, msg, sig: []byte) -> bool {
ensure(hash_algo != .Invalid, "crypto/edsa: invalid hash algorithm")
ensure(pub_key._curve != .Invalid, "crypto/edsa: invalid curve")
ensure(hash_algo != .Invalid, "crypto/ecdsa: invalid hash algorithm")
ensure(pub_key._curve != .Invalid, "crypto/ecdsa: invalid curve")
r_bytes, s_bytes, ok := parse_asn1_sig(sig)
if !ok {

View File

@@ -50,6 +50,7 @@ Public_Key :: struct {
// private_key_generate uses the system entropy source to generate a new
// Private_Key. This will only fail if and only if (⟺) the system entropy source is
// missing or broken.
@(require_results)
private_key_generate :: proc(priv_key: ^Private_Key) -> bool {
private_key_clear(priv_key)
@@ -61,13 +62,12 @@ private_key_generate :: proc(priv_key: ^Private_Key) -> bool {
defer crypto.zero_explicit(&b, size_of(b))
crypto.rand_bytes(b[:])
private_key_set_bytes(priv_key, b[:])
return true
return private_key_set_bytes(priv_key, b[:])
}
// private_key_set_bytes decodes a byte-encoded private key, and returns
// true if and only if (⟺) the operation was successful.
@(require_results)
private_key_set_bytes :: proc(priv_key: ^Private_Key, b: []byte) -> bool {
if len(b) != PRIVATE_KEY_SIZE {
return false
@@ -97,6 +97,21 @@ private_key_set_bytes :: proc(priv_key: ^Private_Key, b: []byte) -> bool {
return true
}
// private_key_set sets priv_key to src.
private_key_set :: proc(priv_key, src: ^Private_Key) {
if src == nil || !src._is_initialized {
private_key_clear(priv_key)
return
}
copy(priv_key._b[:], src._b[:])
grp.sc_set(&priv_key._s, &src._s)
copy(priv_key._hdigest2[:], src._hdigest2[:])
public_key_set(&priv_key._pub_key, &src._pub_key)
priv_key._is_initialized = true
}
// private_key_bytes sets dst to byte-encoding of priv_key.
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
ensure(priv_key._is_initialized, "crypto/ed25519: uninitialized private key")
@@ -105,6 +120,12 @@ private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
copy(dst, priv_key._b[:])
}
// private_key_public_bytes sets dst to the byte-encoding of the public
// key corresponding to priv_key.
private_key_public_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
public_key_bytes(&priv_key._pub_key, dst)
}
// private_key_clear clears priv_key to the uninitialized state.
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
crypto.zero_explicit(priv_key, size_of(Private_Key))
@@ -168,6 +189,7 @@ sign :: proc(priv_key: ^Private_Key, msg, sig: []byte) {
// public_key_set_bytes decodes a byte-encoded public key, and returns
// true if and only if (⟺) the operation was successful.
@(require_results)
public_key_set_bytes :: proc "contextless" (pub_key: ^Public_Key, b: []byte) -> bool {
if len(b) != PUBLIC_KEY_SIZE {
return false
@@ -186,6 +208,16 @@ public_key_set_bytes :: proc "contextless" (pub_key: ^Public_Key, b: []byte) ->
return true
}
// public_key_set sets pub_key to src.
public_key_set :: proc(pub_key, src: ^Public_Key) {
if src == nil || !src._is_initialized {
public_key_clear(pub_key)
return
}
pub_key^ = src^
}
// public_key_set_priv sets pub_key to the public component of priv_key.
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
ensure(priv_key._is_initialized, "crypto/ed25519: uninitialized private key")
@@ -206,18 +238,25 @@ public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
}
// public_key_equal returns true if and only if (⟺) pub_key is equal to other.
@(require_results)
public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool {
ensure(pub_key._is_initialized && other._is_initialized, "crypto/ed25519: uninitialized public key")
return crypto.compare_constant_time(pub_key._b[:], other._b[:]) == 1
}
// public_key_clear clears pub_key to the uninitialized state.
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
crypto.zero_explicit(pub_key, size_of(Public_Key))
}
// verify returns true if and only if (⟺) sig is a valid signature by pub_key over msg.
//
// The optional `allow_small_order_A` parameter will make this
// implementation strictly compatible with FIPS 186-5, at the expense of
// SBS-security. Doing so is NOT recommended, and the disallowed
// public keys all have a known discrete-log.
@(require_results)
verify :: proc(pub_key: ^Public_Key, msg, sig: []byte, allow_small_order_A := false) -> bool {
switch {
case !pub_key._is_initialized:

View File

@@ -18,6 +18,7 @@ package md5
zhibog, dotbmp: Initial implementation.
*/
import "base:intrinsics"
import "core:crypto"
import "core:encoding/endian"
import "core:math/bits"
@@ -100,7 +101,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
i += 1
}
transform(ctx, ctx.data[:])
crypto.set(&ctx.data, 0, 56)
intrinsics.mem_zero(&ctx.data, 56)
}
ctx.bitlen += u64(ctx.datalen * 8)

View File

@@ -19,6 +19,7 @@ package sha1
zhibog, dotbmp: Initial implementation.
*/
import "base:intrinsics"
import "core:crypto"
import "core:encoding/endian"
import "core:math/bits"
@@ -107,7 +108,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
i += 1
}
transform(ctx, ctx.data[:])
crypto.set(&ctx.data, 0, 56)
intrinsics.mem_zero(&ctx.data, 56)
}
ctx.bitlen += u64(ctx.datalen * 8)

290
core/crypto/mldsa/api.odin Normal file
View File

@@ -0,0 +1,290 @@
package mldsa
import "core:crypto"
import "core:crypto/_mldsa"
// Parameters are the supported ML-DSA parameter sets.
Parameters :: enum {
Invalid,
ML_DSA_44,
ML_DSA_65,
ML_DSA_87,
}
// PRIVATE_KEY_SEED_SIZE is the size of a private key in bytes.
PRIVATE_KEY_SEED_SIZE :: _mldsa.SEEDBYTES // 32-bytes
// MAX_CTX_SIZE is the maximum size of the signature context
// (domain separation tag) in bytes.
MAX_CTX_SIZE :: _mldsa.CTXBYTES_MAX // 255-bytes
// PUBLIC_KEY_SIZES are the per-parameter sizes of a public
// key in bytes.
PUBLIC_KEY_SIZES := [Parameters]int {
.Invalid = 0,
.ML_DSA_44 = 1312,
.ML_DSA_65 = 1952,
.ML_DSA_87 = 2592,
}
// SIGNATURE_SIZES are the per-parameter sizes of a signature
// in byte.
SIGNATURE_SIZES := [Parameters]int {
.Invalid = 0,
.ML_DSA_44 = 2420,
.ML_DSA_65 = 3309,
.ML_DSA_87 = 4627,
}
@(private="file")
_PARAMS_TO_INTERNAL := [Parameters]^_mldsa.Params {
.Invalid = nil,
.ML_DSA_44 = &_mldsa.Params_44,
.ML_DSA_65 = &_mldsa.Params_65,
.ML_DSA_87 = &_mldsa.Params_87,
}
// Private_Key is a ML-DSA private key.
Private_Key :: _mldsa.Private_Key
// Public_Key is a ML-DSA public key.
Public_Key :: _mldsa.Public_Key
// private_key_generate uses the system entropy source to generate a new
// Private_Key. This will only fail if and only if (⟺) the system entropy
// source is missing or broken.
@(require_results)
private_key_generate :: proc(priv_key: ^Private_Key, params: Parameters) -> bool {
private_key_clear(priv_key)
if !crypto.HAS_RAND_BYTES {
return false
}
params_ := _PARAMS_TO_INTERNAL[params]
if params_ == nil {
return false
}
seed: [PRIVATE_KEY_SEED_SIZE]byte = ---
defer crypto.zero_explicit(&seed, size_of(seed))
crypto.rand_bytes(seed[:])
_mldsa.dsa_keygen_internal(priv_key, seed[:], params_)
return true
}
// private_key_set_bytes decodes a byte-encoded private key in "seed" format,
// and returns true if and only if (⟺) the operation was successful.
@(require_results)
private_key_set_bytes :: proc(priv_key: ^Private_Key, params: Parameters, b: []byte) -> bool {
private_key_clear(priv_key)
params_ := _PARAMS_TO_INTERNAL[params]
if params_ == nil {
return false
}
if len(b) != PRIVATE_KEY_SEED_SIZE {
return false
}
_mldsa.dsa_keygen_internal(priv_key, b, params_)
return true
}
// private_key_bytes sets dst to byte-encoding of priv_key in the "seed"
// format.
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
ensure(priv_key.params != nil, "crypto/mldsa: uninitialized private key")
ensure(len(dst) == PRIVATE_KEY_SEED_SIZE, "crypto/mldsa: invalid destination size")
copy(dst, priv_key.seed[:])
}
// private_key_public_bytes sets dst to the byte-encoding of the public
// key corresponding to priv_key.
private_key_public_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
public_key_bytes(&priv_key.pub_key, dst)
}
// private_key_set sets priv_key to src.
private_key_set :: proc(priv_key, src: ^Private_Key) {
if src == nil || internal_to_params(src.params) == .Invalid {
private_key_clear(priv_key)
return
}
_mldsa.set_sk(priv_key, src)
}
// private_key_equal returns true if and only if (⟺) the private keys are
// equal, in constant time.
@(require_results)
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
if p.params != q.params {
return false
}
if p.params == nil {
return true
}
// Just compare the seed that was passed to dsa_keygen_internal,
// since the process is completely deterministic.
return crypto.compare_constant_time(p.seed[:], q.seed[:]) == 1
}
// private_key_clear clears priv_key to the uninitialized state.
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
_mldsa.clear_sk(priv_key)
}
// public_key_set_bytes decodes a byte-encoded public key, and returns
// true if and only if (⟺) the operation was successful.
@(require_results)
public_key_set_bytes :: proc(pub_key: ^Public_Key, params: Parameters, b: []byte) -> bool {
params_ := _PARAMS_TO_INTERNAL[params]
if params_ == nil {
return false
}
return _mldsa.unpack_pk(pub_key, b, params_)
}
// public_key_set sets pub_key to src.
public_key_set :: proc(pub_key, src: ^Public_Key) {
if src == nil || internal_to_params(src.params) == .Invalid {
public_key_clear(pub_key)
return
}
_mldsa.set_pk(pub_key, src)
}
// public_key_set_priv sets pub_key to the public component of priv_key.
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
ensure(priv_key.params != nil, "crypto/mldsa: uninitialized private key")
public_key_set(pub_key, &priv_key.pub_key)
}
// public_key_bytes sets dst to byte-encoding of pub_key.
public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
ensure(pub_key.params != nil, "crypto/mldsa: uninitialized public key")
params := internal_to_params(pub_key.params)
ensure(len(dst) == PUBLIC_KEY_SIZES[params], "crypto/mldsa: invalid destination size")
_ = _mldsa.pack_pk(dst, pub_key)
}
// public_key_equal returns true if and only if (⟺) the public keys are equal,
// in constant time.
@(require_results)
public_key_equal :: proc(p, q: ^Public_Key) -> bool {
if p.params != q.params {
return false
}
if p.params == nil {
return true
}
// Comparing the pre-computed hash should be enough, but pack
// both public keys and do the comparisons.
PUBLIC_KEY_SIZE_MAX :: 2592
l := PUBLIC_KEY_SIZES[internal_to_params(p.params)]
p_buf_, q_buf_: [PUBLIC_KEY_SIZE_MAX]byte = ---, ---
p_buf, q_buf := p_buf_[:l], q_buf_[:l]
_ = _mldsa.pack_pk(p_buf, p)
_ = _mldsa.pack_pk(q_buf, q)
return crypto.compare_constant_time(p_buf, q_buf) == 1
}
// public_key_clear clears pub_key to the uninitialized state.
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
_mldsa.clear_pk(pub_key)
}
// sign writes the signature by priv_key over (ctx, msg) to sig and
// returns true if and only if (⟺) the signing succeeded.
//
// ctx is an optional domain separation tag and may be omitted (nil).
@(require_results)
sign :: proc(priv_key: ^Private_Key, ctx, msg, sig: []byte, deterministic := !crypto.HAS_RAND_BYTES) -> bool {
params := internal_to_params(priv_key.params)
ensure(params != .Invalid, "crypto/mldsa: invalid private key")
ensure(len(sig) == SIGNATURE_SIZES[params], "crypto/mldsa: invalid destination size")
if !deterministic && !crypto.HAS_RAND_BYTES {
return false
}
if len(ctx) > MAX_CTX_SIZE {
return false
}
rnd: [_mldsa.RNDBYTES]byte
defer crypto.zero_explicit(&rnd, size_of(rnd))
if !deterministic {
crypto.rand_bytes(rnd[:])
}
return _mldsa.dsa_sign_internal(sig, msg, ctx, rnd[:], priv_key)
}
// verify returns true if and only if (⟺) sig is a valid signature by pub_key
// over (ctx, msg).
@(require_results)
verify :: proc(pub_key: ^Public_Key, ctx, msg, sig: []byte) -> bool {
params := internal_to_params(pub_key.params)
ensure(params != .Invalid, "crypto/mldsa: invalid public key")
if len(sig) != SIGNATURE_SIZES[params] {
return false
}
if len(ctx) > MAX_CTX_SIZE {
return false
}
return _mldsa.dsa_verify_internal(sig, msg, ctx, pub_key)
}
// params returns the Parameters used by a Private_Key or Public_Key
// instance.
@(require_results)
params :: proc(k: ^$T) -> Parameters where (T == Private_Key || T == Public_Key) {
return internal_to_params(k.params)
}
// key_size returns the key size of a Private_Key or Public_Key in bytes.
@(require_results)
key_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
when T == Private_Key {
return PRIVATE_KEY_SEED_SIZE
} else {
return PUBLIC_KEY_SIZES[internal_to_params(k.params)]
}
}
// signature_size returns the key size of a signature in bytes.
@(require_results)
signature_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
return SIGNATURE_SIZES[internal_to_params(k.params)]
}
@(private="file",require_results)
internal_to_params :: proc "contextless" (params: ^_mldsa.Params) -> Parameters {
switch params {
case &_mldsa.Params_44:
return .ML_DSA_44
case &_mldsa.Params_65:
return .ML_DSA_65
case &_mldsa.Params_87:
return .ML_DSA_87
case:
return .Invalid
}
}

View File

@@ -0,0 +1,7 @@
/*
Module-Lattice-Based Digital Signature Algorithm.
See:
- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf ]]
*/
package mldsa

304
core/crypto/mlkem/api.odin Normal file
View File

@@ -0,0 +1,304 @@
package mlkem
import "core:crypto"
import "core:crypto/_mlkem"
// Parameters are the supported ML-KEM parameter sets.
Parameters :: enum {
Invalid,
ML_KEM_512,
ML_KEM_768,
ML_KEM_1024,
}
// DECAPSULATION_KEY_SEED_SIZE is the size of a Decapsulation key in bytes.
DECAPSULATION_KEY_SEED_SIZE :: 64 // (d, z) in NIST terms.
// DECAPSULATION_KEY_EXPANDED_SIZES are the per-parameter sizes of the
// decapsulation key in bytes.
DECAPSULATION_KEY_EXPANDED_SIZES := [Parameters]int {
.Invalid = 0,
.ML_KEM_512 = _mlkem.DECAPSKEYBYTES_512, // 1632-bytes
.ML_KEM_768 = _mlkem.DECAPSKEYBYTES_768, // 2400-bytes
.ML_KEM_1024 = _mlkem.DECAPSKEYBYTES_1024, // 3168-bytes
}
// ENCAPSULATION_KEY_SIZES are the per-parameter sizes of the encapsulation
// key in bytes.
ENCAPSULATION_KEY_SIZES := [Parameters]int {
.Invalid = 0,
.ML_KEM_512 = _mlkem.ENCAPSKEYBYTES_512, // 800-bytes
.ML_KEM_768 = _mlkem.ENCAPSKEYBYTES_768, // 1184-bytes
.ML_KEM_1024 = _mlkem.ENCAPSKEYBYTES_1024, // 1568-bytes
}
// CIPHERTEXT_SIZES are the per-parameter set sizes of the ciphertext
// in bytes.
CIPHERTEXT_SIZES := [Parameters]int {
.Invalid = 0,
.ML_KEM_512 = _mlkem.CIPHERTEXTBYTES_512, // 768-bytes
.ML_KEM_768 = _mlkem.CIPHERTEXTBYTES_768, // 1088-bytes
.ML_KEM_1024 = _mlkem.CIPHERTEXTBYTES_1024, // 1568-bytes
}
// SHARED_SECRET_SIZE is the size of the final shared secret in bytes.
SHARED_SECRET_SIZE :: 32
// Decapsulation_Key is a ML-KEM decapsulation (aka "private") key.
// This implementation opts to include the encapsulation (aka "public")
// key as well for cases where the decapsulation key is reused (eg: HPKE
// with X-Wing).
Decapsulation_Key :: _mlkem.Decapsulation_Key
// Encapsulation_Key is a ML-KEM encapsulation (aka "public") key.
Encapsulation_Key :: _mlkem.Encapsulation_Key
// decapsulation_key_generate uses the system entropy source to generate
// a decapsulation key. This will only fail if and only if (⟺) the system
// entropy source is missing or broken.
@(require_results)
decapsulation_key_generate :: proc(dk: ^Decapsulation_Key, params: Parameters) -> bool {
decapsulation_key_clear(dk)
if !crypto.HAS_RAND_BYTES {
return false
}
k := params_to_k(params)
if k == 0 {
panic("crypto/mlkem: invalid parameter set")
}
seed: [DECAPSULATION_KEY_SEED_SIZE]byte = ---
defer crypto.zero_explicit(&seed, size_of(seed))
crypto.rand_bytes(seed[:])
_mlkem.kem_keygen_internal(dk, seed[:], k)
return true
}
// decapsulation_key_set_bytes decodes a byte-encoded decapsulation key
// in (d, z) "seed" format, and returns true if and only if (⟺) the
// operation was successful.
@(require_results)
decapsulation_key_set_bytes :: proc(dk: ^Decapsulation_Key, params: Parameters, seed: []byte) -> bool {
k := params_to_k(params)
if k == 0 {
return false
}
if len(seed) != DECAPSULATION_KEY_SEED_SIZE {
return false
}
_mlkem.kem_keygen_internal(dk, seed, k)
return true
}
// decapsulation_key_bytes sets dst to byte-encoding of dk in the (d, z)
// "seed" format.
decapsulation_key_bytes :: proc(dk: ^Decapsulation_Key, dst: []byte) {
ensure(dk.pke_dk.k != 0, "crypto/mlkem: uninitialized Decapsulation_Key")
ensure(len(dst) == DECAPSULATION_KEY_SEED_SIZE, "crypto/mlkem: invalid destination size")
copy(dst, dk.seed[:])
}
// decapsulation_key_expanded_bytes sets dst to the byte-encoding of dk.
// in the expanded FIPS 203 format. This primarily exists for export
// purposes.
decapsulation_key_expanded_bytes :: proc(dk: ^Decapsulation_Key, dst: []byte) {
dk_len: int
switch dk.pke_dk.k {
case _mlkem.K_512:
dk_len = DECAPSULATION_KEY_EXPANDED_SIZES[.ML_KEM_512]
case _mlkem.K_768:
dk_len = DECAPSULATION_KEY_EXPANDED_SIZES[.ML_KEM_768]
case _mlkem.K_1024:
dk_len = DECAPSULATION_KEY_EXPANDED_SIZES[.ML_KEM_1024]
case:
panic("crypto/mlkem: uninitialized Decapsulation_Key")
}
ensure(len(dst) == dk_len, "crypto/mlkem: invalid destination size")
_mlkem.decapsulation_key_expanded_bytes(dk, dst)
}
// decapsulation_key_encaps_bytes sets dst to the byte-encoding of the
// encasulation key corresponding to dk.
decapsulation_key_encaps_bytes :: proc(dk: ^Decapsulation_Key, dst: []byte) {
encapsulation_key_bytes(&dk.ek, dst)
}
// decapsulation_key_clear clears dk to the uninitialized state.
decapsulation_key_clear :: proc(dk: ^Decapsulation_Key) {
crypto.zero_explicit(dk, size_of(Decapsulation_Key))
}
// encapsulation_key_set_bytes decodes a byte-encoded encapsulation key,
// and returns true if and only if (⟺) the operation was successful.
@(require_results)
encapsulation_key_set_bytes :: proc(ek: ^Encapsulation_Key, params: Parameters, b: []byte) -> bool {
k := params_to_k(params)
if k == 0 {
return false
}
if len(b) != ENCAPSULATION_KEY_SIZES[params] {
return false
}
return _mlkem.encapsulation_key_set_bytes(ek, k, b)
}
// encapsulation_key_set_decaps sets ek to the encapsulation key corresponding
// to dk.
encapsulation_key_set_decaps :: proc(ek: ^Encapsulation_Key, dk: ^Decapsulation_Key) {
ensure(dk.pke_dk.k != 0, "crypto/mlkem: uninitialized Decapsulation_Key")
_mlkem.encapsulation_key_set_decaps(ek, dk)
}
// encapsulation_key_encaps_bytes sets dst to the byte-encoding of ek.
encapsulation_key_bytes :: proc(ek: ^Encapsulation_Key, dst: []byte) {
ensure(ek.pke_ek.k != 0, "crypto/mlkem: uninitialized Encapsulation_Key")
k_len: int
switch ek.pke_ek.k {
case _mlkem.K_512:
k_len = ENCAPSULATION_KEY_SIZES[.ML_KEM_512]
case _mlkem.K_768:
k_len = ENCAPSULATION_KEY_SIZES[.ML_KEM_768]
case _mlkem.K_1024:
k_len = ENCAPSULATION_KEY_SIZES[.ML_KEM_1024]
case:
panic("crypto/mlkem: invalid destination size")
}
copy(dst, ek.raw_bytes[:k_len])
}
// encapsulation_key_clear clears ek to the uninitialized state.
encapsulation_key_clear :: proc(ek: ^Encapsulation_Key) {
crypto.zero_explicit(ek, size_of(Encapsulation_Key))
}
// encaps_raw_ek_bytes uses the byte encoded encapsulation key to generate
// a shared secret and an associated ciphertext. This routine will fail
// if the system entropy source is unavailable, or of the encapsulation key
// is invalid.
@(require_results)
encaps_ek_raw_bytes :: proc(params: Parameters, raw_ek, shared_secret, ciphertext: []byte) -> bool {
ek: Encapsulation_Key = ---
if !encapsulation_key_set_bytes(&ek, params, raw_ek) {
return false
}
defer encapsulation_key_clear(&ek)
return encaps_ek(&ek, shared_secret, ciphertext)
}
// encaps_ek uses the encapsulation key to generate a shared secret and an
// associated ciphertext. This routine will fail if the system entropy source
// is unavailable.
@(require_results)
encaps_ek :: proc(ek: ^Encapsulation_Key, shared_secret, ciphertext: []byte) -> bool {
ensure(len(shared_secret) == SHARED_SECRET_SIZE, "crypto/mlkem: invalid shared_seret size")
if !crypto.HAS_RAND_BYTES {
return false
}
m: [_mlkem.SYMBYTES]byte = ---
defer crypto.zero_explicit(&m, size_of(m))
crypto.rand_bytes(m[:])
_mlkem.kem_encaps_internal(shared_secret, ciphertext, ek, m[:])
return true
}
encaps :: proc {
encaps_ek,
encaps_ek_raw_bytes,
}
// decaps uses the decapsulation key to generate a shared secret from a
// ciphertext. Due to ML-KEM's implicit rejection mechanism, this function
// will only return false if and only if (⟺) the lengths of the inputs
// are invalid or the decapsulation key is uninitialized.
//
// This routine returning true does not guarantee that the shared secret
// matches that generated by the peer.
@(require_results)
decaps :: proc(dk: ^Decapsulation_Key, ciphertext, shared_secret: []byte) -> bool {
ensure(len(shared_secret) == SHARED_SECRET_SIZE, "crypto/mlkem: invalid shared_seret size")
ct_len: int
switch dk.pke_dk.k {
case _mlkem.K_512:
ct_len = CIPHERTEXT_SIZES[.ML_KEM_512]
case _mlkem.K_768:
ct_len = CIPHERTEXT_SIZES[.ML_KEM_768]
case _mlkem.K_1024:
ct_len = CIPHERTEXT_SIZES[.ML_KEM_1024]
case:
return false
}
if len(ciphertext) != ct_len {
return false
}
_mlkem.kem_decaps_internal(shared_secret, dk, ciphertext)
return true
}
// params returns the Parameters used by a Decapsulation_Key or
// Encapsulation_Key instance.
@(require_results)
params :: proc(k: ^$T) -> Parameters where (T == Encapsulation_Key || T == Decapsulation_Key) {
when T == Encapsulation_Key {
return k_to_params(k.pke_ek.k)
} else {
return k_to_params(k.pke_dk.k)
}
}
// key_size returns the key size of a Decapsulation_Key or Encapsulation_Key
// in bytes.
@(require_results)
key_size :: proc(k: ^$T) -> int where (T == Encapsulation_Key || T == Decapsulation_Key) {
when T == Encapsulation_Key {
return ENCAPSULATION_KEY_SIZES[k.pke_ek.k]
} else {
return DECAPSULATION_KEY_SEED_SIZE
}
}
@(private="file")
params_to_k :: #force_inline proc "contextless" (params: Parameters) -> int {
#partial switch params {
case .ML_KEM_512:
return _mlkem.K_512
case .ML_KEM_768:
return _mlkem.K_768
case .ML_KEM_1024:
return _mlkem.K_1024
}
return 0
}
@(private="file")
k_to_params :: #force_inline proc "contextless" (k: int) -> Parameters {
switch k {
case _mlkem.K_512:
return .ML_KEM_512
case _mlkem.K_768:
return .ML_KEM_768
case _mlkem.K_1024:
return .ML_KEM_1024
}
return .Invalid
}

View File

@@ -0,0 +1,7 @@
/*
ML-KEM Module-Lattice-Based Key-Encapsulation Mechanism.
See:
- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.pdf ]]
*/
package mlkem

590
core/crypto/noise/api.odin Normal file
View File

@@ -0,0 +1,590 @@
package noise
import "base:runtime"
import "core:crypto/aead"
import "core:crypto/ecdh"
import "core:crypto/hash"
import "core:strings"
// MAX_PACKET_SIZE is the maximum Noise message size, including TAG_SIZE
// if relevant (`seal_message`, `open_message`).
MAX_PACKET_SIZE :: 65535
// PSK_SIZE is the size of an optional handshake pre-shared symmetric key.
PSK_SIZE :: 32
// TAG_SIZE is the size of the AEAD authentication tag.
TAG_SIZE :: 16
// MAX_STEP_MSG_SIZE is the maximum per-handshake step message size,
// excluding the optional payload.
//
// `e` is DH_LEN, `s` is either DH_LEN or DH_LEN + TAG_SIZE, and there
// is a maximum of one per each message, and a possible mandatory tag.
MAX_STEP_MSG_SIZE :: (MAX_DH_SIZE*2)+TAG_SIZE+TAG_SIZE
// Status is the status of Noise protocol operation.
Status :: enum {
Ok,
// States
Handshake_Pending,
Handshake_Complete,
Handshake_Split,
Handshake_Failed,
// Errors
Invalid_Protocol_String,
Invalid_Pre_Shared_Key,
Invalid_DH_Key,
No_Self_Identity,
No_Peer_Identity,
Unexpected_Peer_Identity,
Unexpected_Pre_Shared_Key,
DH_Failure,
Invalid_Handshake_Message,
Decryption_Failure,
IV_Exhausted,
Invalid_Cipher_State,
Invalid_Destination_Buffer,
Invalid_Payload_Message,
Max_Packet_Size,
Out_Of_Memory,
}
// Handshake_State is the per-handshake state.
Handshake_State :: struct {
s: ecdh.Private_Key,
e: ecdh.Private_Key,
rs: ecdh.Public_Key,
re: ecdh.Public_Key,
psk: [PSK_SIZE]byte,
symmetric_state: Symmetric_State,
message_pattern: ^Message_Pattern,
current_message: int,
status: Status,
initiator: bool,
pre_set_e: bool,
}
// Cipher_States are the keyed AEAD instances and associated state,
// derived from a successful handshake.
Cipher_States :: struct {
c1_i_to_r: Cipher_State,
c2_r_to_i: Cipher_State,
initiator: bool,
}
// handshake_init initializes a Handshake_State with the provided parameters.
// The relevant values are copied into the Handshake_State instance, and
// can be discarded/sanitized right after handshake_init returns (eg: psk).
//
// Note: While this implementation supports setting `e`, this is primarily
// intended for testing, or cases where the runtime cryptographic entropy
// source is unavailable. Use of this functionality is STRONGLY
// discouraged.
@(require_results)
handshake_init :: proc(
self: ^Handshake_State,
initiator: bool,
prologue: []byte,
s: ^ecdh.Private_Key, // Our static key
rs: ^ecdh.Public_Key, // Peer static key
protocol_name: string,
psk: []byte = nil,
_e: ^ecdh.Private_Key = nil, // Our ephemeral key (for testing/RNG-less systems)
) -> Status {
return handshakestate_initialize(
self,
initiator,
prologue,
s,
_e,
rs,
nil,
protocol_name,
psk,
)
}
// handshake_initiator_step takes an input_message received from the responder
// if any and an optional payload to be sent to the responder, and performs
// one step of the Noise handshake process, returning the message to be sent
// to the responder if any, the payload received from the responder if any,
// and the status of the handshake.
//
// The output message MUST be sent to the responder even if the status code
// returned is .Handshake_Complete.
//
// If the dst parameter is provided, the message and payload will be written
// to dst, otherwise new buffers will be allocated.
@(require_results)
handshake_initiator_step :: proc(
self: ^Handshake_State,
input_message: []byte,
payload: []byte = nil,
dst: []byte = nil,
allocator := context.allocator,
) -> ([]byte, []byte, Status) {
output_message: []byte
payload_buffer: []byte
status: Status
dst := dst
if input_message == nil {
output_message, status = handshakestate_write_message(self, payload, dst, allocator)
} else {
payload_buffer, status = handshakestate_read_message(self, input_message, dst, allocator)
if status == .Handshake_Pending {
if dst != nil {
dst = dst[len(payload_buffer):]
}
output_message, status = handshakestate_write_message(self, payload, dst, allocator)
}
}
return output_message, payload_buffer, status
}
// handshake_responder_step takes a input_message received from the initiator,
// and and an optional payload to be sent to the initiator, and performs
// one step of the Noise handshake process, returning the message to be sent
// to the initiator if any, the payload received from the initiator if any,
// and the status of the handshake.
//
// The output message MUST be sent to the initiator even if the status code
// returned is .Handshake_Complete.
//
// If the dst parameter is provided, the message and payload will be written
// to dst, otherwise new buffers will be allocated.
@(require_results)
handshake_responder_step :: proc(
self: ^Handshake_State,
input_message: []byte,
payload: []byte = nil,
dst: []byte = nil,
allocator := context.allocator,
) -> ([]byte, []byte, Status) {
output_message: []byte
if input_message == nil {
return nil, nil, .Invalid_Handshake_Message
}
dst := dst
payload_buffer, status := handshakestate_read_message(self, input_message, dst, allocator)
if status == .Handshake_Pending {
if dst != nil {
dst = dst[len(payload_buffer):]
}
output_message, status = handshakestate_write_message(self, payload, dst, allocator)
}
return output_message, payload_buffer, status
}
// handshake_write_message calls the Noise HandshakeState's WriteMessage
// function directly. In most cases you are better off using
// handshake_initiator_step or handshake_responder_step.
//
// If the dst parameter is provided, the message and payload will be written
// to dst, otherwise new buffers will be allocated.
@(require_results)
handshake_write_message :: proc(
self: ^Handshake_State,
payload: []byte,
dst: []byte = nil,
allocator := context.allocator,
) -> ([]byte, Status) {
return handshakestate_write_message(self, payload, dst, allocator)
}
// handshake_read_message calls the Noise HandshakeState's ReadMessage
// function directly. In most cases you are better off using
// handshake_initiator_step or handshake_responder_step.
//
// If the dst parameter is provided, the message and payload will be written
// to dst, otherwise new buffers will be allocated.
@(require_results)
handshake_read_message :: proc(
self: ^Handshake_State,
message: []byte,
dst: []byte = nil,
allocator := context.allocator,
) -> ([]byte, Status) {
return handshakestate_read_message(self, message, dst, allocator)
}
// handshake_split initializes a Cipher_States instance from a completed
// handshake. This can be called once and only once per Handshake_State
// instance.
@(require_results)
handshake_split :: proc(self: ^Handshake_State, cipher_states: ^Cipher_States) -> Status {
if self.status != .Handshake_Complete {
return self.status
}
symmetricstate_split(&self.symmetric_state, cipher_states)
if self.message_pattern.is_one_way {
cipherstate_reset(&cipher_states.c2_r_to_i)
cipher_states.c2_r_to_i.is_invalid = true
}
cipher_states.initiator = self.initiator
self.status = .Handshake_Split
return .Ok
}
// handshake_peer_identity returns the peer's static DH key used by
// a completed handshake.
//
// This returns a pointer to the Handshake_State's copy of the peer's
// public key, that will get wiped by handshake_reset. If the key is
// needed after a call to handshake_reset, it must be copied.
@(require_results)
handshake_peer_identity :: proc(self: ^Handshake_State) -> (^ecdh.Public_Key, Status) {
#partial switch self.status {
case .Handshake_Complete, .Handshake_Split:
case:
return nil, self.status
}
if ecdh.curve(&self.rs) == .Invalid {
return nil, .No_Peer_Identity
}
return &self.rs, .Ok
}
// handshake_hash returns the handshake transcript hash of a completed
// handshake, for the purposes of channel binding. See 11.2 of the
// specification for details on usage.
//
// This returns a slice to an internal buffer that will get wiped by
// handshake_reset. If the hash is needed after a call to handshake_reset,
// the slice must be copied.
@(require_results)
handshake_hash :: proc(self: ^Handshake_State) -> ([]byte, Status) {
#partial switch self.status {
case .Handshake_Complete, .Handshake_Split:
case:
return nil, self.status
}
return symmetricstate_get_handshake_hash(&self.symmetric_state), .Ok
}
// handshake_reset sanitizes the Handshake_State. It is both safe and
// recommended to call this as soon as practical (after any calls to
// handshake_peer_identity, handshake_hash, and handshake_split are
// complete).
handshake_reset :: proc(self: ^Handshake_State) {
handshakestate_reset(self)
}
// seal_message encrypts the provided data, authenticates the aad and
// ciphertext, and returns the resulting ciphertext. The ciphertext
// will ALWAYS be `len(plaintext) + TAG_SIZE` bytes in length.
//
// If the dst parameter is provided, the ciphertext will be written
// to dst, otherwise a new buffer will be allocated.
@(require_results)
seal_message :: proc(self: ^Cipher_States, aad, plaintext: []byte, dst: []byte = nil, allocator := context.allocator) -> ([]byte, Status) {
data_len := len(plaintext)
dst := dst
did_alloc: bool
switch {
case dst == nil:
err: runtime.Allocator_Error
dst, err = make([]byte, data_len + TAG_SIZE, allocator)
if err != nil {
return nil, .Out_Of_Memory
}
did_alloc = true
case:
if len(dst) != data_len + TAG_SIZE {
return nil, .Invalid_Destination_Buffer
}
}
status: Status
switch self.initiator {
case true:
dst, status = cipherstate_encrypt_with_ad(&self.c1_i_to_r, aad, plaintext, dst)
case false:
dst, status = cipherstate_encrypt_with_ad(&self.c2_r_to_i, aad, plaintext, dst)
}
if status != .Ok && did_alloc {
delete(dst, allocator)
dst = nil
}
return dst, status
}
// open_message authenticates the aad and ciphertext, decrypts the
// ciphertext and returns the resulting plaintext. The plaintext will
// ALWAYS be `len(ciphertext) - TAG_SIZE` bytes in length.
//
// If the dst parameter is provided, the plaintext will be written to
// dst, otherwise a new buffer will be allocated.
@(require_results)
open_message :: proc(self: ^Cipher_States, aad, ciphertext: []byte, dst: []byte = nil, allocator := context.allocator) -> ([]byte, Status) {
if len(ciphertext) < TAG_SIZE {
return nil, .Invalid_Payload_Message
}
data_len := len(ciphertext) - TAG_SIZE
dst := dst
did_alloc: bool
switch {
case dst == nil:
if data_len > 0 {
err: runtime.Allocator_Error
dst, err = make([]byte, data_len, allocator)
if err != nil {
return nil, .Out_Of_Memory
}
did_alloc = true
}
case:
if len(dst) != data_len {
return nil, .Invalid_Destination_Buffer
}
}
status: Status
switch self.initiator {
case true:
dst, status = cipherstate_decrypt_with_ad(&self.c2_r_to_i, aad, ciphertext, dst)
case false:
dst, status = cipherstate_decrypt_with_ad(&self.c1_i_to_r, aad, ciphertext, dst)
}
if status != .Ok && did_alloc {
delete(dst, allocator)
dst = nil
}
return dst, status
}
// cipherstates_rekey updates the selected AEAD key, using a one way function.
// See 11.3 of the specification for examples of usage.
//
// Note: If one side updates the seal_key, the other side must update
// the non-seal_key and vice versa.
@(require_results)
cipherstates_rekey :: proc(self: ^Cipher_States, seal_key: bool) -> Status {
cs := cipherstates_cs(self, seal_key)
if cs.is_invalid {
return .Invalid_Cipher_State
}
if !cipherstate_has_key(cs) {
return .Handshake_Pending
}
cipherstate_rekey(cs)
return .Ok
}
// cipherstates_set_n sets the interal counter used to generate the AEAD
// IV to an explicit value. This can be used to deal with out-of-order
// transport messages. See 11.4 of the specification.
//
// WARNING: Reusing n across different aad/messages with the same Cipher_States
// will result in catastrophic loss of security.
@(require_results)
cipherstates_set_n :: proc(self: ^Cipher_States, seal_key: bool, n: u64) -> Status {
cs := cipherstates_cs(self, seal_key)
if cs.is_invalid {
return .Invalid_Cipher_State
}
if !cipherstate_has_key(cs) {
return .Handshake_Pending
}
cs.n = n
return .Ok
}
// cipherstates_n returns the interal counter used to generate the AEAD
// IV. This can be used to deal with out-of-order transport messages.
// See 11.4 of the specification.
//
// WARNING: Reusing n across different aad/messages with the same Cipher_States
// will result in catastrophic loss of security.
@(require_results)
cipherstates_n :: proc(self: ^Cipher_States, seal_key: bool, n: u64) -> (u64, Status) {
cs := cipherstates_cs(self, seal_key)
if cs.is_invalid {
return 0, .Invalid_Cipher_State
}
if !cipherstate_has_key(cs) {
return 0, .Handshake_Pending
}
return cs.n, .Ok
}
// cipherstates_reset sanitizes the Cipher_States.
cipherstates_reset :: proc(self: ^Cipher_States) {
self.initiator = false
cipherstate_reset(&self.c1_i_to_r)
cipherstate_reset(&self.c2_r_to_i)
}
@(private = "file")
cipherstates_cs :: proc(self: ^Cipher_States, seal_key: bool) -> ^Cipher_State {
switch self.initiator {
case true:
switch seal_key {
case true:
return &self.c1_i_to_r
case false:
return &self.c2_r_to_i
}
case false:
switch seal_key {
case true:
return &self.c2_r_to_i
case false:
return &self.c1_i_to_r
}
}
unreachable()
}
// split_protocol_string splits a protocol string into individual components.
@(require_results)
split_protocol_string :: proc(protocol_name: string) -> (Handshake_Pattern, ecdh.Curve, aead.Algorithm, hash.Algorithm, Status) {
str := protocol_name
if len(str) > 255 {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
s, ok := strings.split_by_byte_iterator(&str, '_')
if !ok || s != "Noise" {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
if s, ok = strings.split_by_byte_iterator(&str, '_'); !ok {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
pattern: Handshake_Pattern
switch s {
case "N" : pattern = .N
case "K" : pattern = .K
case "X" : pattern = .X
case "XX": pattern = .XX
case "NK": pattern = .NK
case "NN": pattern = .NN
case "KN": pattern = .KN
case "KK": pattern = .KK
case "NX": pattern = .NX
case "KX": pattern = .KX
case "XN": pattern = .XN
case "IN": pattern = .IN
case "XK": pattern = .XK
case "IK": pattern = .IK
case "IX": pattern = .IX
case "NK1": pattern = .NK1
case "NX1": pattern = .NX1
case "X1N": pattern = .X1N
case "X1K": pattern = .X1K
case "XK1": pattern = .XK1
case "X1K1": pattern = .X1K1
case "X1X": pattern = .X1X
case "XX1": pattern = .XX1
case "X1X1": pattern = .X1X1
case "K1N": pattern = .K1N
case "K1K": pattern = .K1K
case "KK1": pattern = .KK1
case "K1K1": pattern = .K1K1
case "K1X": pattern = .K1X
case "KX1": pattern = .KX1
case "K1X1": pattern = .K1X1
case "I1N": pattern = .I1N
case "I1K": pattern = .I1K
case "IK1": pattern = .IK1
case "I1K1": pattern = .I1K1
case "I1X": pattern = .I1X
case "IX1": pattern = .IX1
case "I1X1": pattern = .I1X1
case "Npsk0": pattern = .Npsk0
case "Kpsk0": pattern = .Kpsk0
case "Xpsk1": pattern = .Xpsk1
case "NNpsk0": pattern = .NNpsk0
case "NNpsk2": pattern = .NNpsk2
case "NKpsk0": pattern = .NKpsk0
case "NKpsk2": pattern = .NKpsk2
case "NXpsk2": pattern = .NXpsk2
case "XNpsk3": pattern = .XNpsk3
case "XKpsk3": pattern = .XKpsk3
case "XXpsk3": pattern = .XXpsk3
case "KNpsk0": pattern = .KNpsk0
case "KNpsk2": pattern = .KNpsk2
case "KKpsk0": pattern = .KKpsk0
case "KKpsk2": pattern = .KKpsk2
case "KXpsk2": pattern = .KXpsk2
case "INpsk1": pattern = .INpsk1
case "INpsk2": pattern = .INpsk2
case "IKpsk1": pattern = .IKpsk1
case "IKpsk2": pattern = .IKpsk2
case "IXpsk2": pattern = .IXpsk2
case: pattern = .Invalid
}
if s, ok = strings.split_by_byte_iterator(&str, '_'); !ok {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
dh: ecdh.Curve
switch s {
case "25519": dh = .X25519
case "448": dh = .X448
case: dh = .Invalid
}
if s, ok = strings.split_by_byte_iterator(&str, '_'); !ok {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
cipher: aead.Algorithm
switch s {
case "AESGCM": cipher = .AES_GCM_256
case "ChaChaPoly": cipher = .CHACHA20POLY1305
case: cipher = .Invalid
}
if s, ok = strings.split_by_byte_iterator(&str, '_'); !ok {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
hash: hash.Algorithm
switch s {
case "SHA512": hash = .SHA512
case "SHA256": hash = .SHA256
case "BLAKE2s": hash = .BLAKE2S
case "BLAKE2b": hash = .BLAKE2B
case: hash = .Invalid
}
status: Status
if len(str) != 0 {
status = .Invalid_Protocol_String
}
if pattern == .Invalid || dh == .Invalid || cipher == .Invalid || hash == .Invalid {
status = .Invalid_Protocol_String
}
return pattern, dh, cipher, hash, status
}

View File

@@ -0,0 +1,35 @@
/*
An implementation of the Noise Protocol Framework (Revision 34).
The `fallback` modifier and multi-PSK patterns are not supported
for the sake of simplicity.
See:
- [[ https://noiseprotocol.org/ ]]
*/
package noise
// In general, to complete a noise handshake you must:
//
// - If you are initiating the connection, call `handshake_initiator_step`
// passing `nil` as the `input_message` parameter.
//
// - Send the resulting `[]byte` to the responder (generally a server) via
// the method of your choice. This MUST be done even if the status code
// returned is `.Handshake_Complete`.
//
// - If the status code returned by `handshake_initiator_step` was
// `.Handshake_Complete`, the handshake completed successfully,
// and it is now possible to validate the peer identity, obtain the
// handshake transcript hash, and most usefully call `handshake_split`
// to populate the `Cipher_States` struct that will be used to
// encrypt/decrypt data.
//
// Otherwise, read the response from the responder and feed the response
// data as the `input_message` to the next `handshake_initiator_step`
// until it returns `.Handshake_Complete`.
//
// - If you are the responder, the method is much the same, except you
// must pass a valid `input_message` received from an initiator to the
// first call to `handshake_responder_step`. Repeat until the returned
// status is `.Handshake_Complete`.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,989 @@
#+private
package noise
import "base:runtime"
import "core:crypto"
import "core:crypto/aead"
import "core:crypto/ecdh"
import "core:crypto/hash"
import "core:crypto/hkdf"
import "core:encoding/endian"
import "core:slice"
AEAD_KEY_SIZE :: 32
MIN_DH_SIZE :: 32
MAX_DH_SIZE :: 56
MAX_HASH_SIZE :: 64
Protocol :: struct {
handshake_pattern: Handshake_Pattern,
dh: ecdh.Curve,
cipher: aead.Algorithm,
hash: hash.Algorithm,
}
Symmetric_State :: struct {
protocol: Protocol,
cipher_state: Cipher_State,
_ck: [MAX_HASH_SIZE]byte,
_h: [MAX_HASH_SIZE]byte,
}
Cipher_State :: struct {
ctx: aead.Context,
n: u64,
n_exhausted: bool,
is_invalid: bool,
}
@(require_results)
dh_len :: proc(protocol: ^Protocol) -> int {
return ecdh.PUBLIC_KEY_SIZES[protocol.dh]
}
@(require_results)
hash_len :: proc(protocol: ^Protocol) -> int {
return hash.DIGEST_SIZES[protocol.hash]
}
// Generates a new Diffie-Hellman key pair. A DH key pair consists of
// public_key and private_key elements. public_key represents an encoding
// of a DH public key into a byte sequence of length DHLEN. The public_key
// encoding details are specific to each set of DH functions.
generate_keypair :: proc(protocol: ^Protocol, private_key: ^ecdh.Private_Key) {
#partial switch protocol.dh {
case .X25519, .X448:
case: panic("crypto/noise: unsupported DH curve in protocol")
}
if !ecdh.private_key_generate(private_key, protocol.dh) {
panic("crypto/noise: entropy source unavailable")
}
}
// Performs a Diffie-Hellman calculation between the private key in key_pair
// and the public_key and returns an output sequence of bytes of length DHLEN.
// For security, the Gap-DH problem based on this function must be unsolvable
// by any practical cryptanalytic adversary [2].
//
// The public_key either encodes some value which is a generator in a
// large prime-order group (which value may have multiple equivalent
// encodings), or is an invalid value. Implementations must handle invalid
// public keys either by returning some output which is purely a function
// of the public key and does not depend on the private key, or by signaling
// an error to the caller.
//
// The DH function may define more specific rules for handling invalid values.
@(require_results)
_dh :: proc(our_private_key: ^ecdh.Private_Key, their_public_key: ^ecdh.Public_Key, dst: []byte) -> Status {
if ok := ecdh.ecdh(our_private_key, their_public_key, dst); !ok {
return .DH_Failure
}
return .Ok
}
// Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte
// unsigned integer nonce n which must be unique for the key k.
// Returns the ciphertext. Encryption must be done with an "AEAD"
// encryption mode with the associated data(AD) (using the terminology
// from [1]) and returns a ciphertext that is the same size as the plaintext
// plus 16 bytes for authentication data. The entire ciphertext must be
// indistinguishable from random if the key is secret (note that this is
// an additional requirement that isn't necessarily met by all AEAD schemes).
_encrypt :: proc(ctx: ^aead.Context, n: u64, ad, plaintext, dst: []byte) {
pt_len := len(plaintext)
ensure(len(dst) == pt_len + TAG_SIZE, "crypto/noise: invalid AEAD encrypt destination")
iv: [12]byte
#partial switch aead.algorithm(ctx) {
case .AES_GCM_256: endian.unchecked_put_u64be(iv[4:], n)
case .CHACHA20POLY1305: endian.unchecked_put_u64le(iv[4:], n)
}
ciphertext, tag := dst[:pt_len], dst[pt_len:]
aead.seal_ctx(ctx, ciphertext, tag, iv[:], ad, plaintext)
}
// Decrypts ciphertext using a cipher key k of 32 bytes, an 8-byte unsigned
// integer nonce n, and associated data ad. Returns the plaintext, unless
// authentication fails, in which case an error is signaled to the caller.
@(require_results)
_decrypt :: proc(ctx: ^aead.Context, n: u64, ad, ciphertext, dst: []byte) -> Status {
if len(ciphertext) < TAG_SIZE {
return .Decryption_Failure
}
iv: [12]byte
#partial switch aead.algorithm(ctx) {
case .AES_GCM_256: endian.unchecked_put_u64be(iv[4:], n)
case .CHACHA20POLY1305: endian.unchecked_put_u64le(iv[4:], n)
}
ct_len := len(ciphertext) - TAG_SIZE
ct, tag := ciphertext[:ct_len], ciphertext[ct_len:]
if ok := aead.open_ctx(ctx, dst, iv[:], ad, ct, tag); !ok {
return .Decryption_Failure
}
return .Ok
}
// Hashes some arbitrary-length data with a collision-resistant cryptographic
// hash function and returns an output of HASHLEN bytes.
_hash :: proc(dst: []byte, protocol: ^Protocol, data: ..[]byte) {
ctx: hash.Context
hash.init(&ctx, protocol.hash)
for datum in data {
hash.update(&ctx, datum)
}
hash.final(&ctx, dst)
}
// Takes a chaining_key byte sequence of length HASHLEN, and an
// input_key_material byte sequence with length either zero bytes,
// 32 bytes, or DHLEN bytes. Returns a pair or triple of byte sequences
// each of length HASHLEN, depending on whether num_outputs is two or three:
// - Sets temp_key = HMAC-HASH(chaining_key, input_key_material).
// - Sets output1 = HMAC-HASH(temp_key, byte(0x01)).
// - Sets output2 = HMAC-HASH(temp_key, output1 || byte(0x02)).
// - If num_outputs == 2 then returns the pair (output1, output2).
// - Sets output3 = HMAC-HASH(temp_key, output2 || byte(0x03)).
// - Returns the triple (output1, output2, output3).
//
// Note that temp_key, output1, output2, and output3 are all HASHLEN
// bytes in length. Also note that the HKDF() function is simply HKDF
// from [4] with the chaining_key as HKDF salt, and zero-length HKDF info.
@(require_results)
_hkdf :: proc(dst, chaining_key, input_key_material: []byte, protocol: ^Protocol) -> ([]byte, []byte, []byte) {
assert(len(input_key_material) == 0 || len(input_key_material) == 32 || len(input_key_material) == dh_len(protocol))
hkdf.extract_and_expand(protocol.hash, chaining_key, input_key_material, nil, dst)
h_len := hash_len(protocol)
assert(len(dst) == h_len * 2 || len(dst) == h_len * 3)
r1, r2 := dst[:h_len], dst[h_len:h_len*2]
if len(dst) == h_len * 2 {
return r1, r2, nil
}
return r1, r2, dst[h_len*2:]
}
// Sets k = key. Sets n = 0.
cipherstate_initialize_key :: proc(self: ^Cipher_State, key: []byte, protocol: ^Protocol) {
k_len := len(key)
switch {
case k_len == 0:
// k = empty
aead.reset(&self.ctx)
self.n = 0
case k_len < AEAD_KEY_SIZE:
panic("crypto/noise: invalid AEAD key size")
case:
aead.init(&self.ctx, protocol.cipher, key[:AEAD_KEY_SIZE])
self.n = 0
}
}
// Returns true if k is non-empty, false otherwise.
@(require_results)
cipherstate_has_key :: proc(self: ^Cipher_State) -> bool {
return aead.algorithm(&self.ctx) != .Invalid
}
// If k is non-empty returns ENCRYPT(k, n++, ad, plaintext). Otherwise
// returns plaintext.
@(require_results)
cipherstate_encrypt_with_ad :: proc(self: ^Cipher_State, ad, plaintext, dst: []byte) -> ([]byte, Status) {
if self.is_invalid {
return nil, .Invalid_Cipher_State
}
if self.n_exhausted {
return nil, .IV_Exhausted
}
pt_len := len(plaintext)
if pt_len > MAX_PACKET_SIZE - 16 {
return nil, .Max_Packet_Size
}
if cipherstate_has_key(self) {
if len(dst) != pt_len + TAG_SIZE {
return nil, .Invalid_Destination_Buffer
}
_encrypt(&self.ctx, self.n, ad, plaintext, dst)
self.n += 1
if self.n == 0 {
self.n_exhausted = true
}
} else {
if len(dst) != pt_len {
return nil, .Invalid_Destination_Buffer
}
if raw_data(dst) != raw_data(plaintext) {
copy(dst, plaintext)
}
}
return dst, .Ok
}
// If k is non-empty returns DECRYPT(k, n++, ad, ciphertext). Otherwise
// returns ciphertext. If an authentication failure occurs in DECRYPT()
// then n is not incremented and an error is signaled to the caller.
@(require_results)
cipherstate_decrypt_with_ad :: proc(self: ^Cipher_State, ad, ciphertext, dst: []byte) -> ([]byte, Status) {
if self.is_invalid {
return nil, .Invalid_Cipher_State
}
if self.n_exhausted {
return nil, .IV_Exhausted
}
if cipherstate_has_key(self) {
if status := _decrypt(&self.ctx, self.n, ad, ciphertext, dst); status != .Ok {
return nil, status
}
self.n += 1
if self.n == 0 {
self.n_exhausted = true
}
} else {
if len(dst) != len(ciphertext) {
return nil, .Invalid_Destination_Buffer
}
if raw_data(dst) != raw_data(ciphertext) {
copy(dst, ciphertext)
}
}
return dst, .Ok
}
// Sets k = REKEY(k).
cipherstate_rekey :: proc(self: ^Cipher_State) {
if cipherstate_has_key(self) {
algorithm := aead.algorithm(&self.ctx)
// The "sensible" way to implement this is to inlike REKEY(k),
// so we do.
//
// Returns a new 32-byte cipher key as a pseudorandom function
// of k. If this function is not specifically defined for some
// set of cipher functions, then it defaults to returning the
// first 32 bytes from `ENCRYPT(k, maxnonce, zerolen, zeros)`,
// where maxnonce equals (2^64)-1, zerolen is a zero-length
// byte sequence, and zeros is a sequence of 32 bytes filled
// with zeros.
zeroes: [AEAD_KEY_SIZE + TAG_SIZE]byte
defer crypto.zero_explicit(&zeroes, size_of(zeroes))
// 1 2 3 4 5 6 7 8
n: u64 = 0xFF_FF_FF_FF_FF_FF_FF_FF
_encrypt(&self.ctx, n, nil, zeroes[:AEAD_KEY_SIZE], zeroes[:])
aead.init(&self.ctx, algorithm, zeroes[:AEAD_KEY_SIZE])
}
}
cipherstate_reset :: proc(self: ^Cipher_State) {
aead.reset(&self.ctx)
crypto.zero_explicit(self, size_of(Cipher_State))
}
// Takes an arbitrary-length protocol_name byte sequence (see Section 8).
// Executes the following steps:
// - If protocol_name is less than or equal to HASHLEN bytes in length,
// sets h equal to protocol_name with zero bytes appended to make
// HASHLEN bytes.
// - Otherwise sets h = HASH(protocol_name).
// - Sets ck = h.
// - Calls InitializeKey(empty).
@(require_results)
symmetricstate_initialize :: proc(ss: ^Symmetric_State, protocol_name: string) -> Status {
if status := protocol_from_string(&ss.protocol, protocol_name); status != .Ok {
return status
}
cipherstate_initialize_key(&ss.cipher_state, nil, &ss.protocol)
h_len := hash_len(&ss.protocol)
h := ss._h[:h_len]
if len(protocol_name) <= h_len {
copy(h, protocol_name)
} else {
_hash(h, &ss.protocol, transmute([]byte)protocol_name)
}
copy(ss._ck[:h_len], h)
return .Ok
}
// Sets h = HASH(h || data).
symmetricstate_mix_hash :: proc(self: ^Symmetric_State, data: ..[]byte) {
h := self._h[:hash_len(&self.protocol)]
if len(data) == 1 {
_hash(h, &self.protocol, h, data[0])
} else if len(data) == 2 {
_hash(h, &self.protocol, h, data[0], data[1])
} else if len(data) == 3 {
_hash(h, &self.protocol, h, data[0], data[1], data[2])
} else {
panic("crypto/noise: invalid MixHash inputs")
}
}
// Executes the following steps:
// - Sets ck, temp_k = HKDF(ck, input_key_material, 2).
// - If HASHLEN is 64, then truncates temp_k to 32 bytes.
// - Calls InitializeKey(temp_k).
symmetricstate_mix_key :: proc(self: ^Symmetric_State, input_key_material: []byte) {
h_len := hash_len(&self.protocol)
dst_len := h_len * 2
dst: [2*MAX_HASH_SIZE]byte = ---
defer crypto.zero_explicit(&dst, dst_len)
ck, temp_k, _ := _hkdf(dst[:dst_len], self._ck[:h_len], input_key_material, &self.protocol)
copy(self._ck[:], ck)
cipherstate_initialize_key(&self.cipher_state, temp_k, &self.protocol)
}
// This function is used for handling pre-shared symmetric keys, as described
// in Section 9. It executes the following steps:
// - Sets ck, temp_h, temp_k = HKDF(ck, input_key_material, 3).
// - Calls MixHash(temp_h).
// - If HASHLEN is 64, then truncates temp_k to 32 bytes.
// - Calls InitializeKey(temp_k).
symmetricstate_mix_key_and_hash :: proc(self: ^Symmetric_State, input_key_material: []byte) {
h_len := hash_len(&self.protocol)
dst_len := h_len * 3
dst: [3*MAX_HASH_SIZE]byte = ---
defer crypto.zero_explicit(&dst, dst_len)
ck, temp_h, temp_k := _hkdf(dst[:dst_len], self._ck[:h_len], input_key_material, &self.protocol)
copy(self._ck[:], ck)
symmetricstate_mix_hash(self, temp_h)
cipherstate_initialize_key(&self.cipher_state, temp_k, &self.protocol)
}
// Returns h. This function should only be called at the end of a handshake,
// i.e. after the Split() function has been called.
//
// This function is used for channel binding, as described in Section 11.2
@(require_results)
symmetricstate_get_handshake_hash :: proc(self: ^Symmetric_State) -> []byte {
return self._h[:hash_len(&self.protocol)]
}
// Sets ciphertext = EncryptWithAd(h, plaintext), calls MixHash(ciphertext),
// and returns ciphertext.
//
// Note that if k is empty, the EncryptWithAd() call will set ciphertext
// equal to plaintext.
@(require_results)
symmetricstate_encrypt_and_hash :: proc(self: ^Symmetric_State, plaintext, dst: []byte) -> ([]byte, Status) {
ciphertext, status := cipherstate_encrypt_with_ad(&self.cipher_state, self._h[:hash_len(&self.protocol)], plaintext, dst)
if status != .Ok {
return nil, status
}
symmetricstate_mix_hash(self, ciphertext)
return ciphertext, status
}
// Sets plaintext = DecryptWithAd(h, ciphertext), calls MixHash(ciphertext),
// and returns plaintext.
//
// Note that if k is empty, the DecryptWithAd() call will set plaintext
// equal to ciphertext.
@(require_results)
symmetricstate_decrypt_and_hash :: proc(self: ^Symmetric_State, ciphertext, dst: []byte) -> ([]byte, Status) {
h_len := hash_len(&self.protocol)
h: [MAX_HASH_SIZE]byte = ---
copy(h[:], self._h[:h_len])
defer crypto.zero_explicit(&h, size_of(h))
// We reverse the order to save having to copy the ciphertext, in
// the case that ciphertext and dst alias.
symmetricstate_mix_hash(self, ciphertext)
return cipherstate_decrypt_with_ad(&self.cipher_state, h[:h_len], ciphertext, dst)
}
// Returns a pair of CipherState objects for encrypting transport messages.
// Executes the following steps, where zerolen is a zero-length byte sequence:
// - Sets temp_k1, temp_k2 = HKDF(ck, zerolen, 2).
// - If HASHLEN is 64, then truncates temp_k1 and temp_k2 to 32 bytes.
// - Creates two new CipherState objects c1 and c2.
// - Calls c1.InitializeKey(temp_k1) and c2.InitializeKey(temp_k2).
// - Returns the pair (c1, c2).
symmetricstate_split :: proc(self: ^Symmetric_State, cipher_states: ^Cipher_States) {
h_len := hash_len(&self.protocol)
dst_len := h_len * 2
dst: [2*MAX_HASH_SIZE]byte = ---
defer crypto.zero_explicit(&dst, dst_len)
temp_k1, temp_k2, _ := _hkdf(dst[:dst_len], self._ck[:h_len], nil, &self.protocol)
cipherstate_initialize_key(&cipher_states.c1_i_to_r, temp_k1, &self.protocol)
cipherstate_initialize_key(&cipher_states.c2_r_to_i, temp_k2, &self.protocol)
}
symmetricstate_reset :: proc(self: ^Symmetric_State) {
cipherstate_reset(&self.cipher_state)
crypto.zero_explicit(self, size_of(Symmetric_State))
}
// Takes a valid handshake_pattern (see Section 7) and an initiator boolean
// specifying this party's role as either initiator or responder.
// Takes a prologue byte sequence which may be zero-length, or which may
// contain context information that both parties want to confirm is identical
// (see Section 6).
//
// Takes a set of DH key pairs (s, e) and public keys (rs, re) for
// initializing local variables, any of which may be empty. Public keys
// are only passed in if the handshake_pattern uses pre-messages
// (see Section 7). The ephemeral values (e, re) are typically left empty,
// since they are created and exchanged during the handshake; but there
// are exceptions (see Section 10).
//
// Performs the following steps:
// - Derives a protocol_name byte sequence by combining the names for
// the handshake pattern and crypto functions, as specified in Section 8.
// - Calls InitializeSymmetric(protocol_name).
// - Calls MixHash(prologue).
// - Sets the initiator, s, e, rs, and re variables to the corresponding
// arguments.
// - Calls MixHash() once for each public key listed in the pre-messages
// from handshake_pattern, with the specified public key as input
// (see Section 7 for an explanation of pre-messages).
// - If both initiator and responder have pre-messages, the initiator's
// public keys are hashed first.
// - If multiple public keys are listed in either party's pre-message,
// the public keys are hashed in the order that they are listed.
// - Sets message_pattern to the message patterns from handshake_pattern.
@(require_results)
handshakestate_initialize :: proc(
handshake_state: ^Handshake_State,
initiator: bool,
prologue: []byte,
s: ^ecdh.Private_Key,
e: ^ecdh.Private_Key, // Only set for testing.
rs: ^ecdh.Public_Key,
re: ^ecdh.Public_Key, // Only set for testing.
protocol_name: string,
psk: []byte = nil,
) -> Status {
crypto.zero_explicit(handshake_state, size_of(Handshake_State))
symmetric_state := &handshake_state.symmetric_state
status: Status
do_init: {
if status = symmetricstate_initialize(symmetric_state, protocol_name); status != .Ok {
break do_init
}
curve := symmetric_state.protocol.dh
if s != nil && ecdh.curve(s) != curve {
status = .Invalid_DH_Key
break do_init
}
if e != nil && ecdh.curve(e) != curve {
status = .Invalid_DH_Key
break do_init
}
if rs != nil && ecdh.curve(rs) != curve {
status = .Invalid_DH_Key
break do_init
}
if re != nil && ecdh.curve(re) != curve {
status = .Invalid_DH_Key
break do_init
}
// Check if we will require s later down the line.
s_pre, s_hs: bool
if initiator {
s_pre, s_hs = pattern_requires_initiator_s(symmetric_state.protocol.handshake_pattern)
} else {
s_pre, s_hs = pattern_requires_responder_s(symmetric_state.protocol.handshake_pattern)
}
if (s_pre || s_hs) && s == nil {
status = .No_Self_Identity
break do_init
}
message_pattern := HANDSHAKE_PATTERNS[symmetric_state.protocol.handshake_pattern]
if message_pattern.pre_messages != nil {
if initiator {
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
if rs == nil {
status = .No_Peer_Identity
break do_init
}
}
} else {
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
if rs == nil {
status = .No_Peer_Identity
break do_init
}
}
}
} else {
if rs != nil {
status = .Unexpected_Peer_Identity
break do_init
}
}
symmetricstate_mix_hash(symmetric_state, prologue)
// In all supported patterns, `ini_s` will always precede `res_s`.
if message_pattern.pre_messages != nil {
tmp: [MAX_DH_SIZE]byte = ---
d_len := dh_len(&symmetric_state.protocol)
dst := tmp[:d_len]
if initiator {
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
ecdh.private_key_public_bytes(s, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
ecdh.public_key_bytes(rs, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
} else {
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
ecdh.public_key_bytes(rs, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
ecdh.private_key_public_bytes(s, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
}
}
if message_pattern.is_psk {
if len(psk) != PSK_SIZE {
status = .Invalid_Pre_Shared_Key
break do_init
}
} else if len(psk) != 0 {
status = .Unexpected_Pre_Shared_Key
break do_init
}
}
if status != .Ok {
symmetricstate_reset(symmetric_state)
return status
}
if s != nil {
ecdh.private_key_set(&handshake_state.s, s)
}
if e != nil {
ecdh.private_key_set(&handshake_state.e, e)
handshake_state.pre_set_e = true
}
if rs != nil {
ecdh.public_key_set(&handshake_state.rs, rs)
}
if re != nil {
ecdh.public_key_set(&handshake_state.re, re)
}
copy(handshake_state.psk[:], psk)
handshake_state.message_pattern = HANDSHAKE_PATTERNS[symmetric_state.protocol.handshake_pattern]
handshake_state.current_message = 0
handshake_state.status = .Handshake_Pending
handshake_state.initiator = initiator
return .Ok
}
handshakestate_reset :: proc(self: ^Handshake_State) {
symmetricstate_reset(&self.symmetric_state)
ecdh.private_key_clear(&self.s)
ecdh.private_key_clear(&self.e)
crypto.zero_explicit(self, size_of(Handshake_State))
}
// Takes a payload byte sequence which may be zero-length, and a
// message_buffer to write the output into.
// Performs the following steps, aborting if any EncryptAndHash() call
// returns an error:
// - Fetches and deletes the next message pattern from message_pattern,
// then sequentially processes each token from the message pattern:
// - For "e": Sets e (which must be empty) to GENERATE_KEYPAIR().
// Appends e.public_key to the buffer. Calls MixHash(e.public_key).
// - For "s": Appends EncryptAndHash(s.public_key) to the buffer.
// - For "ee": Calls MixKey(DH(e, re)).
// - For "es": Calls MixKey(DH(e, rs)) if initiator, MixKey(DH(s, re))
// if responder.
// - For "se": Calls MixKey(DH(s, re)) if initiator, MixKey(DH(e, rs))
// if responder.
// - For "ss": Calls MixKey(DH(s, rs)).
// - Appends EncryptAndHash(payload) to the buffer.
// (SKIPPED) If there are no more message patterns returns two new
// CipherState objects by calling Split().
//
// Calling Split() is left to a separate function, although it is technically
// part of the specification.
@(require_results)
handshakestate_write_message :: proc(self: ^Handshake_State, payload, dst: []byte, allocator := context.allocator) -> ([]byte, Status) {
ensure(self.status == .Handshake_Pending, "crypto/noise: invalid state for WriteMessage")
protocol := &self.symmetric_state.protocol
d_len := dh_len(protocol)
pattern_buf: [dynamic; MAX_STEP_MSG_SIZE]byte
dh_buf: [MAX_DH_SIZE]byte = ---
defer crypto.zero_explicit(&dh_buf, size_of(dh_buf))
pattern := self.message_pattern.messages[self.current_message]
for token in pattern {
switch token {
case .e:
switch self.pre_set_e {
case true:
// Note: "which must be empty", but we allow pre-generated `e`
// for testing/rng-less systems.
self.pre_set_e = false
case false:
if ecdh.curve(&self.e) != .Invalid {
panic("crypto/noise: e was not empty when processing token 'e' during WriteMessage")
}
generate_keypair(protocol, &self.e)
}
e_public := dh_buf[:d_len]
ecdh.private_key_public_bytes(&self.e, e_public)
n := append(&pattern_buf, ..e_public)
ensure(n == d_len, "crypto/noise: truncated append `e`")
symmetricstate_mix_hash(&self.symmetric_state, e_public)
if self.message_pattern.is_psk {
symmetricstate_mix_key(&self.symmetric_state, e_public)
}
case .s:
s_public := dh_buf[:d_len]
ecdh.private_key_public_bytes(&self.s, s_public)
tmp: [MAX_DH_SIZE+TAG_SIZE]byte = ---
dh_buf := tmp[:d_len+TAG_SIZE]
if !cipherstate_has_key(&self.symmetric_state.cipher_state) {
dh_buf = tmp[:d_len]
}
ct, status := symmetricstate_encrypt_and_hash(&self.symmetric_state, s_public, dh_buf)
if status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
n := append(&pattern_buf, ..ct)
ensure(n == len(ct), "crypto/noise: truncated append `s`")
case .ee:
dh := dh_buf[:d_len]
if status := _dh(&self.e, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .es:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .se:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .ss:
dh := dh_buf[:d_len]
if status := _dh(&self.s, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .psk:
symmetricstate_mix_key_and_hash(&self.symmetric_state, self.psk[:])
}
}
self.current_message += 1 // Advance after the current message is successful.
pattern_len := len(pattern_buf)
payload_len := len(payload)
msg_len := pattern_len + payload_len
if cipherstate_has_key(&self.symmetric_state.cipher_state) {
msg_len += TAG_SIZE
}
msg: []byte
if msg_len != 0 {
did_alloc: bool
if dst != nil {
if len(dst) < msg_len {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
msg = dst[:msg_len]
} else {
err: runtime.Allocator_Error
msg, err = make([]byte, msg_len, allocator)
if err != nil {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
did_alloc = true
}
copy(msg, pattern_buf[:])
ciphertext := msg[pattern_len:]
if _, status := symmetricstate_encrypt_and_hash(&self.symmetric_state, payload, ciphertext); status != .Ok {
if did_alloc {
delete(msg)
}
self.status = .Handshake_Failed
return nil, status
}
}
if self.current_message == len(self.message_pattern.messages) {
self.current_message = -1
self.status = .Handshake_Complete
}
return msg, self.status
}
// Takes a byte sequence containing a Noise handshake message, and a
// payload_buffer to write the message's plaintext payload into.
// Performs the following steps, aborting if any DecryptAndHash()
// call returns an error:
// - Fetches and deletes the next message pattern from message_pattern,
// then sequentially processes each token from the message pattern:
// - For "e": Sets re (which must be empty) to the next DHLEN bytes
// from the message. Calls MixHash(re.public_key).
// - For "s": Sets temp to the next DHLEN + 16 bytes of the message
// if HasKey() == True, or to the next DHLEN bytes otherwise.
// Sets rs (which must be empty) to DecryptAndHash(temp).
// - For "ee": Calls MixKey(DH(e, re)).
// - For "es": Calls MixKey(DH(e, rs)) if initiator, MixKey(DH(s, re))
// if responder.
// - For "se": Calls MixKey(DH(s, re)) if initiator, MixKey(DH(e, rs))
// if responder.
// -For "ss": Calls MixKey(DH(s, rs)).
// - Calls DecryptAndHash() on the remaining bytes of the message and stores
// the output into payload_buffer.
// (SKIPPED) If there are no more message patterns returns two new
// CipherState objects by calling Split().
//
// Calling Split() is left to a separate function, although it is technically
// part of the specification.
@(require_results)
handshakestate_read_message :: proc(self: ^Handshake_State, message, dst: []byte, allocator := context.allocator) -> ([]byte, Status) {
ensure(self.status == .Handshake_Pending, "crypto/noise: invalid state for ReadMessage")
protocol := &self.symmetric_state.protocol
d_len := dh_len(&self.symmetric_state.protocol)
dh_buf: [MAX_DH_SIZE]byte = ---
defer crypto.zero_explicit(&dh_buf, size_of(dh_buf))
msg := message
pattern := self.message_pattern.messages[self.current_message]
for token in pattern {
switch token {
case .e:
if len(msg) < d_len {
return nil, .Invalid_Handshake_Message
}
re := msg[:d_len]
if ecdh.curve(&self.re) != .Invalid {
panic("crypto/noise: re was not empty when processing token 'e' during ReadMessage")
}
if !ecdh.public_key_set_bytes(&self.re, protocol.dh, re) {
return nil, .Invalid_Handshake_Message
}
symmetricstate_mix_hash(&self.symmetric_state, re)
if self.message_pattern.is_psk {
symmetricstate_mix_key(&self.symmetric_state, re)
}
msg = msg[d_len:]
case .s:
rs_len := d_len
if cipherstate_has_key(&self.symmetric_state.cipher_state) {
rs_len += TAG_SIZE
}
if len(msg) < rs_len {
self.status = .Handshake_Failed
return nil, .Invalid_Handshake_Message
}
rs := dh_buf[:d_len]
if _, status := symmetricstate_decrypt_and_hash(&self.symmetric_state, msg[:rs_len], rs); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
if ecdh.curve(&self.rs) != .Invalid {
panic("crypto/noise: rs was not empty when processing token 's' during ReadMessage")
}
if !ecdh.public_key_set_bytes(&self.rs, protocol.dh, rs) {
self.status = .Handshake_Failed
return nil, .Invalid_Handshake_Message
}
msg = msg[rs_len:]
case .ee:
dh := dh_buf[:d_len]
if status := _dh(&self.e, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .es:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .se:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .ss:
dh := dh_buf[:d_len]
if status := _dh(&self.s, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .psk:
symmetricstate_mix_key_and_hash(&self.symmetric_state, self.psk[:])
}
}
self.current_message += 1 // Advance after the current message is successful.
payload: []byte
payload_len := len(msg)
if cipherstate_has_key(&self.symmetric_state.cipher_state) {
if payload_len < TAG_SIZE {
self.status = .Handshake_Failed
return nil, self.status
}
payload_len -= TAG_SIZE
}
did_alloc: bool
if dst != nil {
if len(dst) < payload_len {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
payload = dst[:payload_len]
} else if payload_len > 0 {
err: runtime.Allocator_Error
payload, err = make([]byte, payload_len, allocator)
if err != nil {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
did_alloc = true
}
if _, status := symmetricstate_decrypt_and_hash(&self.symmetric_state, msg, payload); status != .Ok {
if did_alloc {
delete(payload)
}
self.status = .Handshake_Failed
return nil, self.status
}
if self.current_message == len(self.message_pattern.messages) {
self.current_message = -1
self.status = .Handshake_Complete
}
return payload, self.status
}
@(require_results)
protocol_from_string :: proc(self: ^Protocol, protocol_name: string) -> Status {
self^ = Protocol{}
pattern, dh, cipher, hash, status := split_protocol_string(protocol_name)
if status != .Ok {
return status
}
self.handshake_pattern = pattern
self.dh = dh
self.cipher = cipher
self.hash = hash
return .Ok
}

7
core/crypto/rsa/doc.odin Normal file
View File

@@ -0,0 +1,7 @@
/*
RSA (RivestShamirAdleman) cryptosystem.
See:
- [[ https://www.rfc-editor.org/info/rfc8017/ ]]
*/
package rsa

444
core/crypto/rsa/rsa.odin Normal file
View File

@@ -0,0 +1,444 @@
package rsa
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
import "core:bytes"
import "core:crypto"
import subtle "core:crypto/_subtle"
import "core:encoding/endian"
// Minimum size for a RSA modulus (in bits).
//
// Note: 1024-bits is arguably insufficient as of this writing, with
// 2048-bits being a more sensible value, however 1024-bits is likely
// still in frequent enough use.
//
// Note: CA signed TLS certificates have a strict requirement of a modulus
// size that is at least 2048-bits [[ https://cabforum.org/working-groups/server/baseline-requirements/documents/]].
MODULUS_MIN_SIZE :: 1024
// Maximum size for a RSA modulus (in bits).
//
// This value MUST be a multiple of 64. This value MUST NOT exceed 47666
// (some computations in RSA key generation rely on the factor size being
// no more than 23833 bits). RSA key sizes beyond 3072 bits don't make a
// lot of sense anyway.
MODULUS_MAX_SIZE :: 4096
// Maxmimum size for a RSA public exponent (in bits).
//
// Note: This implementation supports arbitrary size exponents, however
// limit it to something sensible (some implementations are known to
// choke on exponents >= 2^32), with the most common choice being
// `65537`.
EXPONENT_MAX_SIZE :: 32
// Maximum size for a RSA factor (in bits). This is for RSA private-key
// operations. Default is to support factors up to a bit more than half
// the maximum modulus size.
//
// This value MUST be a multiple of 32.
FACTOR_MAX_SIZE :: (MODULUS_MAX_SIZE + 64) >> 1
// Default size for a RSA key (in bits).
DEFAULT_MODULUS_SIZE :: 2048
// RSA public exponent used for key generation. This MUST be a prime
// number greater than 2.
@(private)
PUBLIC_EXPONENT :: 65537
#assert(EXPONENT_MAX_SIZE <= 32)
// Private_Key is a RSA private key.
Private_Key :: struct {
_pub_key: Public_Key,
_d: Modulus, // Private exponent has the same size as n.
_p: Factor,
_q: Factor,
// CRT coefficients.
_dp: Factor, // d % (p - 1)
_dq: Factor, // d % (q - 1)
_iq: Factor, // q^(-1) mod p
_is_initialized: bool,
}
// Public_Key is a RSA public key.
Public_Key :: struct {
_n: Modulus,
_e: u32,
_is_initialized: bool,
}
// private_key_generate uses the system entropy source to generate a new
// Private_Key. The key size is specified in bits, and must be a multiple
// of 8.
@(require_results)
private_key_generate :: proc(priv_key: ^Private_Key, key_size := DEFAULT_MODULUS_SIZE) -> bool {
if !crypto.HAS_RAND_BYTES {
return false
}
if key_size < MODULUS_MIN_SIZE || key_size > MODULUS_MAX_SIZE {
return false
}
if key_size % 8 != 0 {
return false
}
private_key_clear(priv_key)
defer if !priv_key._is_initialized {
private_key_clear(priv_key)
}
for {
// The only way this can fail is if we get extremely unlucky
// and we fail to derive `iq` (1/d mod p).
if keygen_inner(priv_key, key_size) == 1 {
break
}
}
priv_key._is_initialized = true
priv_key._pub_key._is_initialized = true
// Self-test the key.
priv_key._is_initialized = pkcs1_sig_selftest(priv_key)
return priv_key._is_initialized
}
// private_key_n copies the private key's public modulus to dst if dst is
// non-nil and of sufficient size, and returns the number of bytes
// copied/would be copied (ie: calling with `dst = nil` gets the required
// size).
@(require_results)
private_key_n :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
return public_key_n(&priv_key._pub_key, dst)
}
// private_key_e returns the private key's public exponent as a u32.
@(require_results)
private_key_e :: proc(priv_key: ^Private_Key) -> u32 {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
return public_key_e(&priv_key._pub_key)
}
// private_key_d copies the private key's private exponent `d` to dst if
// dst is non-nil and of sufficient size, and returns the number of bytes
// copied/would be copied (ie: calling with `dst = nil` gets the required
// size).
//
// Note: The data returned MUST be kept confidential.
@(require_results)
private_key_d :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
return modulus_copyout(&priv_key._d, dst)
}
// private_key_p copies the private key's first prime factor `p` to dst
// if dst is non-nil and of sufficient size, and returns the number of
// bytes copied/would be copied (ie: calling with `dst = nil` gets the
// required size).
//
// Note: The data returned MUST be kept confidential.
@(require_results)
private_key_p :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
return factor_copyout(&priv_key._p, dst)
}
// private_key_q copies the private key's second prime factor `q` to dst
// if dst is non-nil and of sufficient size, and returns the number of
// bytes copied/would be copied (ie: calling with `dst = nil` gets the
// required size).
//
// Note: The data returned MUST be kept confidential.
@(require_results)
private_key_q :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
return factor_copyout(&priv_key._q, dst)
}
// private_key_dp copies the private key's first reduced exponent
// `d % (p-1)` to dst if dst is non-nil and of sufficient size, and
// returns the number of bytes copied/would be copied (ie: calling with
//`dst = nil` gets the required size).
//
// Note: The data returned MUST be kept confidential.
@(require_results)
private_key_dp :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
return factor_copyout(&priv_key._dp, dst)
}
// private_key_dq copies the private key's second reduced exponent
// `d % (q-1)` to dst if dst is non-nil and of sufficient size, and
// returns the number of bytes copied/would be copied (ie: calling with
//`dst = nil` gets the required size).
//
// Note: The data returned MUST be kept confidential.
@(require_results)
private_key_dq :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
return factor_copyout(&priv_key._dq, dst)
}
// private_key_iq copies the private key's CRT coefficient `iq` to dst if
// dst is non-nil and of sufficient size, and returns the number of bytes
// copied/would be copied (ie: calling with`dst = nil` gets the required
// size).
//
// Note: The data returned MUST be kept confidential.
@(require_results)
private_key_iq :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
return factor_copyout(&priv_key._iq, dst)
}
// private_key_size returns the size of the private key's public modulus
// in bytes. All ciphertexts and signatures will also be this size.
@(require_results)
private_key_size :: proc(priv_key: ^Private_Key) -> int {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
return priv_key._pub_key._n.v_len
}
// private_key_set_bytes sets a private key from byte-encoded components,
// and returns true if and only if (⟺) the operation was successful.
//
// Note: All values are mandatory, and match the values included in the
// PKCS private key format.
//
// WARNING: This routine validates that it is possible to sign/verify with
// the deserialized values, however d is not checked at all, nor is the
// primality of p and q.
@(require_results)
private_key_set_bytes :: proc(
priv_key: ^Private_Key,
n: []byte,
e: []byte,
d: []byte,
p: []byte,
q: []byte,
dp: []byte,
dq: []byte,
iq: []byte,
) -> bool {
private_key_clear(priv_key)
defer if !priv_key._is_initialized {
private_key_clear(priv_key)
}
if !public_key_set_bytes(&priv_key._pub_key, n, e) {
return false
}
if !modulus_set_bytes(&priv_key._d, d) {
return false
}
if !factor_set_bytes(&priv_key._p, p) {
return false
}
if !factor_set_bytes(&priv_key._q, q) {
return false
}
if !factor_set_bytes(&priv_key._dp, dp) {
return false
}
if !factor_set_bytes(&priv_key._dq, dq) {
return false
}
if !factor_set_bytes(&priv_key._iq, iq) {
return false
}
priv_key._is_initialized = true
// Test the key.
//
// Note: This DOES NOT check that p/q are prime and if d is
// consistent (as it is not used by our implementation).
priv_key._is_initialized = pkcs1_sig_selftest(priv_key)
return priv_key._is_initialized
}
// private_key_set sets priv_key to src.
private_key_set :: proc(priv_key, src: ^Private_Key) {
if src == nil || !src._is_initialized {
private_key_clear(priv_key)
return
}
public_key_set(&priv_key._pub_key, &src._pub_key)
modulus_set(&priv_key._d, &src._d)
factor_set(&priv_key._p, &src._p)
factor_set(&priv_key._q, &src._q)
factor_set(&priv_key._dp, &src._dp)
factor_set(&priv_key._dq, &src._dq)
factor_set(&priv_key._iq, &src._iq)
priv_key._is_initialized = true
}
// private_key_equal returns true if and only if (⟺) priv_key is equal to other.
@(require_results)
private_key_equal :: proc(priv_key, other: ^Private_Key) -> bool {
ensure(priv_key._is_initialized && other._is_initialized, "crypto/rsa: uninitialized private key")
pk_eq := public_key_equal(&priv_key._pub_key, &other._pub_key)
eq := crypto.compare_constant_time(modulus_bytes(&priv_key._d), modulus_bytes(&other._d))
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._p), factor_bytes(&other._p))
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._q), factor_bytes(&other._q))
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._dp), factor_bytes(&other._dp))
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._dq), factor_bytes(&other._dq))
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._iq), factor_bytes(&other._iq))
return pk_eq & (eq == 1)
}
// private_key_clear clears priv_key to the uninitialized state.
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
crypto.zero_explicit(priv_key, size_of(Private_Key))
}
// public_key_n copies the public key's modulus `n` to dst if dst is
// non-nil and of sufficient size, and returns the number of bytes
// copied/would be copied (ie: calling with `dst = nil` gets the
// required size).
@(require_results)
public_key_n :: proc(pub_key: ^Public_Key, dst: []byte) -> (n_len: int) {
ensure(pub_key._is_initialized, "crypto/rsa: uninitialized public key")
return modulus_copyout(&pub_key._n, dst)
}
// public_key_e returns the public key's exponent `e` as a u32.
@(require_results)
public_key_e :: proc(pub_key: ^Public_Key) -> u32 {
ensure(pub_key._is_initialized, "crypto/rsa: uninitialized public key")
return pub_key._e
}
// public_key_size returns the size of the public key's modulus in bytes.
// All ciphertexts and signatures will also be this size.
@(require_results)
public_key_size :: proc(pub_key: ^Public_Key) -> int {
ensure(pub_key._is_initialized, "crypto/rsa: uninitialized public key")
return pub_key._n.v_len
}
// public_key_set_bytes sets a public key from byte-encoded components,
// and returns true if and only if (⟺) the operation was successful.
@(require_results)
public_key_set_bytes :: proc(pub_key: ^Public_Key, n, e: []byte) -> bool {
public_key_clear(pub_key)
defer if !pub_key._is_initialized {
public_key_clear(pub_key)
}
ok := modulus_set_bytes(&pub_key._n, n)
if !ok {
return false
}
if modulus_len(&pub_key._n) < MODULUS_MIN_SIZE >> 3 {
return false
}
if !modulus_is_odd(&pub_key._n) {
return false
}
e_ := bytes.trim_left(e, []byte{0x00})
e_len := len(e_)
if e_len > EXPONENT_MAX_SIZE >> 3 {
return false
}
e_buf: [4]byte
copy(e_buf[4 - e_len:], e)
e_u32 := endian.unchecked_get_u32be(e_buf[:])
if e_u32 < 3 || e_u32 & 1 == 0 {
return false
}
pub_key._e = e_u32
pub_key._is_initialized = true
return true
}
// public_key_set sets pub_key to src.
public_key_set :: proc(pub_key, src: ^Public_Key) {
if src == nil || !src._is_initialized {
public_key_clear(pub_key)
return
}
modulus_set(&pub_key._n, &src._n)
pub_key._e = src._e
pub_key._is_initialized = true
}
// public_key_set_priv sets pub_key to the public component of priv_key.
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
pub_key^ = priv_key._pub_key
}
// public_key_equal returns true if and only if (⟺) pub_key is equal to other.
public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool {
ensure(pub_key._is_initialized && other._is_initialized, "crypto/rsa: uninitialized public key")
eq := crypto.compare_constant_time(modulus_bytes(&pub_key._n), modulus_bytes(&other._n))
eq &= int(subtle.eq(pub_key._e, other._e))
return eq == 1
}
// public_key_clear clears pub_key to the uninitialized state.
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
crypto.zero_explicit(pub_key, size_of(Public_Key))
}
// size returns the size of the key's public modulus in bytes.
// All ciphertexts and signatures will also be this size.
size :: proc "contextless" (key: ^$T) -> int where T == Private_Key || T == Private_Key {
when T == Private_Key {
return private_key_size(key)
} else {
return public_key_size(key)
}
}

View File

@@ -0,0 +1,197 @@
package rsa
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
import "core:crypto"
import subtle "core:crypto/_subtle"
import "core:crypto/hash"
// decrypt_oaep returns the plaintext and true if and only if (⟺) it
// successfully decrypts the ciphertext with OAEP parameterized by
// label, hash_algo, and mgf1_algo, and writes the plaintext into dst.
// If mgf1_algo is unspecified, hash_algo will be used.
//
// Note: dst MUST be large enough to contain the plaintext.
@(require_results)
decrypt_oaep :: proc(
priv_key: ^Private_Key,
hash_algo: hash.Algorithm,
ciphertext: []byte,
dst: []byte,
label: []byte = nil,
mgf1_algo := hash.Algorithm.Invalid,
) -> (plaintext: []byte, ok: bool) {
if !priv_key._is_initialized {
return
}
ct_len := len(ciphertext)
if ct_len != modulus_len(&priv_key._pub_key._n) {
return
}
if hash_algo == .Invalid {
return
}
mgf1_algo_ := mgf1_algo
if mgf1_algo == .Invalid {
mgf1_algo_ = hash_algo
}
tmp: [MODULUS_MAX_SIZE >> 3]byte
pt_buf := tmp[:ct_len]
defer crypto.zero_explicit(raw_data(pt_buf), ct_len)
copy(pt_buf, ciphertext)
r := private_modpow(pt_buf, priv_key)
r_, l := oaep_dec_unpad(hash_algo, mgf1_algo_, label, pt_buf)
// Conditional branches are ok as we are past the padding
// verification.
if ok = r & r_ == 1; ok {
if l <= len(dst) {
copy(dst, pt_buf[:l])
plaintext = dst[:l]
} else {
ok = false
}
}
return
}
// oaep_max_plaintext_size returns the maximum supported plaintext size
// for a given key, with OAEP parameterized by hash_algo and mgf1_algo.
// If mgf1_algo is unspecified, hash_algo will be used.
@(require_results)
oaep_max_plaintext_size :: proc(
k: ^$T,
hash_algo: hash.Algorithm,
mgf1_algo := hash.Algorithm.Invalid,
) -> int where T == Private_Key || T == Public_Key {
if !k._is_initialized {
return 0
}
if hash_algo == .Invalid {
return 0
}
mgf1_algo_ := mgf1_algo
if mgf1_algo == .Invalid {
mgf1_algo_ = hash_algo
}
overhead := 2 + hash.DIGEST_SIZES[hash_algo] + hash.DIGEST_SIZES[mgf1_algo_]
pub_key: ^Public_Key
when T == Private_Key {
pub_keyk = &k._pub_key
} else {
pub_key = k
}
return modulus_len(&k._n) - overhead
}
@(private="file")
xor_hash_data :: proc(hash_algo: hash.Algorithm, dst: []byte, src: []byte) {
tmp: [hash.MAX_DIGEST_SIZE]byte = ---
hash_len := hash.DIGEST_SIZES[hash_algo]
digest := tmp[:hash_len]
defer crypto.zero_explicit(raw_data(digest), hash_len)
hash.hash_bytes_to_buffer(hash_algo, src, digest)
for v, u in digest {
dst[u] ~= v
}
}
@(private="file")
oaep_dec_unpad :: proc(
hash_algo: hash.Algorithm,
mgf1_algo: hash.Algorithm,
label: []byte,
data: []byte,
) -> (u32, int) {
hash_len := hash.DIGEST_SIZES[hash_algo]
k := len(data)
buf := data
// There must be room for the padding.
if k < (hash_len << 1) + 2 {
return 0, 0
}
// Unmask the seed, then the DB value.
seed, db := buf[1:1+hash_len], buf[1+hash_len:]
mgf1_xor(seed, mgf1_algo, db)
mgf1_xor(db, mgf1_algo, seed)
// Hash the label and XOR it with the value in the array; if
// they are equal then these should yield only zeros.
xor_hash_data(hash_algo, db, label)
// At that point, if the padding was correct, when we should
// have: 0x00 || seed || 0x00 ... 0x00 0x01 || M
// Padding is valid as long as:
// - There is at least hlen+1 leading bytes of value 0x00.
// - There is at least one non-zero byte.
// - The first (leftmost) non-zero byte has value 0x01.
//
// Ultimately, we may leak the resulting message length, i.e.
// the position of the byte of value 0x01, but we must take care
// to do so only if the number of zero bytes has been verified
// to be at least hlen+1.
//
// The loop below counts the number of bytes of value 0x00, and
// checks that the next byte has value 0x01, in constant-time.
//
// - If the initial byte (before the seed) is not 0x00, then
// r and s are set to 0, and stay there.
// - Value r is 1 until the first non-zero byte is reached
// (after the seed); it switches to 0 at that point.
// - Value s is set to 1 if and only if the data encountered
// at the time of the transition of r from 1 to 0 has value
// exactly 0x01.
// - Value zlen counts the number of leading bytes of value zero
// (after the seed).
r := u32(subtle.eq(buf[0], 0))
s, zlen: u32
for u in hash_len + 1..<k {
w := u32(buf[u])
// nz == 1 only for the first non-zero byte.
nz := r & ((w + 0xFF) >> 8)
s |= nz & subtle.eq(w, 0x01)
r &= subtle.not(nz)
zlen += r
}
// Padding is correct only if s == 1, _and_ zlen >= hlen.
s &= subtle.ge(zlen, u32(hash_len))
// At that point, padding was verified, and we are now allowed
// to make conditional jumps.
if s != 0 {
plen := 2 + hash_len + int(zlen)
k -= plen
copy(buf[:k], buf[plen:])
}
return s, k
}

View File

@@ -0,0 +1,55 @@
package rsa
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
import subtle "core:crypto/_subtle"
// unsafe_decrypt_tls_pms decrypts a TLS RSA-Encrypted Premaster Secret
// Message, unconditionally moves the decrypted plaintext to `data[:48]`,
// and returns 1 if and only if (⟺) the operation was successful.
//
// WARNING: This routine MUST only be used when implementing server-side
// support for TLS 1.2's Client Key Exchange message, and extreme care
// MUST be taken when handling failures. This key exchange scheme was
// removed in TLS 1.3, and not implementing support in the first place
// is strongly RECOMMENDED even for TLS 1.2 servers.
@(require_results)
unsafe_decrypt_tls_pms :: proc(priv_key: ^Private_Key, data: []byte) -> u32 {
// A first check on length. Since this test works only on the
// buffer length, it needs not (and cannot) be constant-time.
_len := len(data)
if _len < 59 || _len != priv_key._pub_key._n.v_len {
return 0
}
x := private_modpow(data, priv_key)
x &= u32(subtle.eq(data[0], 0x00))
x &= u32(subtle.eq(data[1], 0x02))
for u in 2..<(_len-49) {
x &= u32(subtle.neq(data[u], 0))
}
x &= u32(subtle.eq(data[_len - 49], 0x00))
copy(data[:48], data[_len - 48:])
return x
}

View File

@@ -0,0 +1,105 @@
package rsa
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
import "base:intrinsics"
import "core:crypto"
import "core:crypto/hash"
// encrypt_oaep returns true if and only if (⟺) it successfully
// encrypts the plaintext with OAEP parameterized by label, hash_algo,
// and mgf1_algo, and writes the cipherttext into dst. If mgf1_algo is
// unspecified, hash_algo will be used.
//
// This routine will fail if the system entropy source is unavailable.
encrypt_oaep :: proc(
pub_key: ^Public_Key,
hash_algo: hash.Algorithm,
plaintext: []byte,
dst: []byte,
label: []byte = nil,
mgf1_algo := hash.Algorithm.Invalid,
) -> bool {
if !pub_key._is_initialized {
return false
}
if hash_algo == .Invalid {
return false
}
mgf1_algo_ := mgf1_algo
if mgf1_algo == .Invalid {
mgf1_algo_ = hash_algo
}
if len(dst) != modulus_len(&pub_key._n) {
return false
}
if len(plaintext) > oaep_max_plaintext_size(pub_key, hash_algo, mgf1_algo_) {
return false
}
if oaep_enc_pad(hash_algo, mgf1_algo_, label, dst, plaintext) != 1 {
return false
}
return public_modpow(dst, pub_key) == 1
}
@(private="file")
oaep_enc_pad :: proc(
hash_algo: hash.Algorithm,
mgf1_algo: hash.Algorithm,
label: []byte,
dst: []byte,
src: []byte,
) -> u32 {
hash_len := hash.DIGEST_SIZES[hash_algo]
src_len := len(src)
k := len(dst)
// Note: Length checks are handled by the caller.
// Apply padding. At this point, things cannot fail.
buf := dst
// Assemble: DB = lHash || PS || 0x01 || M
// We first place the source message M with copy(), so that
// overlaps between source and destination buffers are supported.
copy(buf[k - src_len:], src)
hash.hash_bytes_to_buffer(hash_algo, label, buf[1+hash_len:1+hash_len << 1])
intrinsics.mem_zero(raw_data(buf[1 + hash_len << 1:]), k - src_len - (hash_len << 1) - 2)
buf[k - src_len - 1] = 0x01
// Make the random seed.
seed, db := buf[1:1+hash_len], buf[1+hash_len:]
crypto.rand_bytes(seed)
// Mask DB with the mask generated from the seed.
mgf1_xor(db, mgf1_algo, seed)
// Mask the seed with the mask generated from the masked DB.
mgf1_xor(seed, mgf1_algo, db)
// Padding result: EM = 0x00 || maskedSeed || maskedDB.
buf[0] = 0x00
return 1
}

View File

@@ -0,0 +1,110 @@
#+private
package rsa
import "core:bytes"
Big_Int :: struct($N: int) {
v: [N]byte,
v_len: int,
}
Modulus :: Big_Int(MODULUS_MAX_SIZE >> 3)
Factor :: Big_Int(FACTOR_MAX_SIZE >> 3)
@(require_results)
modulus_set_bytes :: proc(n: ^Modulus, b: []byte) -> bool {
b_ := bytes.trim_left(b, []byte{0x00})
b_len := len(b_)
if b_len > size_of(n.v) || b_len == 0 {
return false
}
copy(n.v[:], b_)
n.v_len = b_len
return true
}
modulus_set :: proc "contextless" (n, other: ^Modulus) {
// Copy the full thing.
copy(n.v[:], other.v[:])
n.v_len = other.v_len
}
@(require_results)
modulus_bytes :: #force_inline proc "contextless" (n: ^Modulus) -> []byte {
return n.v[:n.v_len]
}
@(require_results)
modulus_len :: #force_inline proc "contextless" (n: ^Modulus) -> int {
return n.v_len
}
@(require_results)
modulus_copyout :: proc(n: ^Modulus, dst: []byte) -> (n_len: int) {
if n_len = modulus_len(n); n_len == 0 {
return
}
if len(dst) > 0 {
ensure(len(dst) >= n_len, "crypto/rsa: insufficent buffer size")
copy(dst, modulus_bytes(n))
}
return
}
@(require_results)
modulus_is_odd :: proc "contextless" (n: ^Modulus) -> bool {
if n.v_len == 0 || n.v[n.v_len-1] & 1 == 0 {
return false
}
return true
}
@(require_results)
factor_set_bytes :: proc(n: ^Factor, b: []byte) -> bool {
b_ := bytes.trim_left(b, []byte{0x00})
b_len := len(b_)
if b_len > size_of(n.v) || b_len == 0 {
return false
}
copy(n.v[:], b_)
n.v_len = b_len
return true
}
factor_set :: proc "contextless" (n, other: ^Factor) {
// Copy the full thing.
copy(n.v[:], other.v[:])
n.v_len = other.v_len
}
@(require_results)
factor_bytes :: #force_inline proc "contextless" (n: ^Factor) -> []byte {
return n.v[:n.v_len]
}
@(require_results)
factor_len :: #force_inline proc "contextless" (n: ^Factor) -> int {
return n.v_len
}
@(require_results)
factor_copyout :: proc(n: ^Factor, dst: []byte) -> (n_len: int) {
if n_len = factor_len(n); n_len == 0 {
return
}
if len(dst) > 0 {
ensure(len(dst) >= n_len, "crypto/rsa: insufficent buffer size")
copy(dst, factor_bytes(n))
}
return
}

View File

@@ -0,0 +1,367 @@
package rsa
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
import "core:crypto"
import bigint "core:crypto/_bigint"
import subtle "core:crypto/_subtle"
import "core:slice"
// Swap two buffers in RAM. They must be disjoint.
@(private="file")
bufswap_u32 :: proc "contextless" (b1, b2: []u32) {
l := len(b1)
for u in 0..<l {
b1[u], b2[u] = b2[u], b1[u]
}
}
@(private, require_results)
keygen_inner :: proc(sk: ^Private_Key, key_size: int) -> u32 {
// We need temporary values for at least 7 integers of the same size
// as a factor (including header word); more space helps with performance
// (in modular exponentiations), but we much prefer to remain under
// 2 kilobytes in total, to save stack space. The macro TEMPS below
// exceeds 512 (which is a count in 32-bit words) when MODULUS_MAX_SIZE
// is greater than 4464 (default value is 4096, so the 2-kB limit is
// maintained unless MODULUS_MAX_SIZE was modified).
TEMPS :: max(512, ((((7 * ((((MODULUS_MAX_SIZE + 1) >> 1) + 61) / 31))) + 1) >> 1) << 1)
assert(key_size >= MODULUS_MIN_SIZE && key_size <= MODULUS_MAX_SIZE)
t64: [TEMPS >> 1]u64
t32 := slice.reinterpret([]u32, t64[:])
defer crypto.zero_explicit(&t64, size_of(t64))
esize_p := u32(key_size + 1) >> 1
esize_q := u32(key_size) - esize_p
sk._p.v_len = int((esize_p + 7) >> 3)
sk._q.v_len = int((esize_q + 7) >> 3)
sk._dp.v_len = sk._p.v_len
sk._dq.v_len = sk._q.v_len
sk._iq.v_len = sk._p.v_len
pk := &sk._pub_key
pk._n.v_len = (key_size + 7) >> 3
pk._e = PUBLIC_EXPONENT
sk._d.v_len = pk._n.v_len // Private exponent length is that of the modulus.
// We now switch to encoded sizes.
//
// floor((x * 16913) / (2^19)) is equal to floor(x/31) for all
// integers x from 0 to 34966; the intermediate product fits on
// 30 bits, thus we can use MUL31().
esize_p += u32(bigint._mul31(esize_p, 16913) >> 19)
esize_q += u32(bigint._mul31(esize_q, 16913) >> 19)
plen := (esize_p + 31) >> 5
qlen := (esize_q + 31) >> 5
p := t32
q := p[1 + plen:]
t := q[1 + qlen:]
// Since we use a prime exponent, when searching for candidate primes,
// checking if `GCD(e, prime - 1) = 1` is a simple matter of euclidian
// division.
for {
bigint.i62_mkprime(p, esize_p, PUBLIC_EXPONENT, t)
p[1] -= 1
if bigint.i31_rem(p, PUBLIC_EXPONENT) != 0 {
p[1] += 1
break
}
}
for {
bigint.i62_mkprime(q, esize_q, PUBLIC_EXPONENT, t)
q[1] -= 1
if bigint.i31_rem(q, PUBLIC_EXPONENT) != 0 {
q[1] += 1
break
}
}
// If p and q have the same size, then it is possible that q > p
// (when the target modulus size is odd, we generate p with a
// greater bit length than q). If q > p, we want to swap p and q
// for two reasons:
// - The final step below (inversion of q modulo p) is easier if
// p > q.
// - While BearSSL's RSA code is perfectly happy with RSA keys such
// that p < q, some other implementations have restrictions and
// require p > q.
//
// Note that we can do a simple non-constant-time swap here,
// because the only information we leak here is that we insist on
// returning p and q such that p > q, which is not a secret.
if esize_p == esize_q && bigint.i31_sub(p, q, 0) == 1 {
bufswap_u32(p[:1+plen], q)
}
sk_p, sk_q := factor_bytes(&sk._p), factor_bytes(&sk._q)
bigint.i31_encode(sk_p, p)
bigint.i31_encode(sk_q, q)
// The odds of this happening are infinitesimally small, however
// checking for it is cheap.
if crypto.compare_constant_time(sk_p, sk_q) == 1 {
return 0
}
// Compute the public modulus too.
bigint.i31_zero(t, p[0])
bigint.i31_mulacc(t, p, q)
bigint.i31_encode(modulus_bytes(&pk._n), t)
// Compute the private exponent.
//
// Computing p - 1 and q - 1 this way is safe as p and q
// are guaranteed to be odd, thus the LSB will always be
// set.
p[1], q[1] = p[1] - 1, q[1] - 1 // p = p - 1, q = q - 1
if compute_privexp(sk, p, q, pk._e, t) != 1 {
return 0
}
// Compute `d % (p - 1)`.
d_mod := t[:1+plen]
bigint.i31_decode_reduce(d_mod, modulus_bytes(&sk._d), p)
bigint.i31_encode(factor_bytes(&sk._dp), d_mod)
// Compute `d % (q - 1)`.
bigint.i31_decode_reduce(d_mod, modulus_bytes(&sk._d), q)
bigint.i31_encode(factor_bytes(&sk._dq), d_mod)
// Compute `q^(-1) mod p`.
p[1], q[1] = p[1] + 1, q[1] + 1 // Restore p, q.
return compute_qinv(sk, p, q, plen, t)
}
@(private="file")
compute_qinv :: proc "contextless" (sk: ^Private_Key, p, q: []u32, plen: u32, t: []u32) -> u32 {
// Per Fermat's Little Theorem, `q^(-1) mod p = q^(p-2) mod p`.
//
// Note: p is guaranteed to be odd as it is a large prime.
// Compute and encode `p-2`.
p_minus_two := t[:1+plen]
copy(p_minus_two, p[:1+plen])
two := t[1+plen:] // Temporarily use this for 2.
bigint.i31_zero(two, p[0])
bigint.i31_decode(two, []byte{2})
_ = bigint.i31_sub(p_minus_two, two, 1)
iq := factor_bytes(&sk._iq) // Temporarily use this for p - 2.
bigint.i31_encode(iq, p_minus_two)
// Enforce 64-bit alignment.
t_ := t
if len(t_) & 1 != 0 {
t_ = t_[1:]
}
m0i := bigint.i31_ninv31(p[1])
ret := bigint.i62_modpow_opt_as_i31(q, iq, p, m0i, t)
if ret != 0 {
bigint.i31_encode(iq, q)
}
return ret
}
@(private="file")
compute_privexp :: proc "contextless" (sk: ^Private_Key, p_minus_one, q_minus_one: []u32, e: u32, tmp: []u32) -> u32 {
// Compute phi = (p-1)*(q-1). The mulacc function sets the announced
// bit length of t to be the sum of the announced bit lengths of
// p-1 and q-1, which is usually exact but may overshoot by one 1
// bit in some cases; we readjust it to its true length.
phi := tmp
bigint.i31_zero(phi, p_minus_one[0])
bigint.i31_mulacc(phi, p_minus_one, q_minus_one)
_len := (phi[0] + 31) >> 5
phi[0] = bigint.i31_bit_length(phi[1:1+_len])
_len = (phi[0] + 31) >> 5
// Divide phi by public exponent e. The final remainder r must be
// non-zero (otherwise, the key is invalid). The quotient is k,
// which we write over phi, since we don't need phi after that.
r: u32
for u := _len; u >= 1; u -= 1 {
// Upon entry, r < e, and phi[u] < 2^31; hence,
// hi:lo < e*2^31. Thus, the produced word k[u]
// must be lower than 2^31, and the new remainder r
// is lower than e.
hi := r >> 1
lo := (r << 31) + phi[u]
phi[u], r = bigint.div_rem_u32(hi, lo, e)
}
if r == 0 {
return 0
}
k := phi
// Compute u and v such that u*e - v*r = GCD(e,r). We use
// a binary GCD algorithm, with 6 extra integers a, b,
// u0, u1, v0 and v1. Initial values are:
// a = e u0 = 1 v0 = 0
// b = r u1 = r v1 = e-1
// The following invariants are maintained:
// a = u0*e - v0*r
// b = u1*e - v1*r
// 0 < a <= e
// 0 < b <= r
// 0 <= u0 <= r
// 0 <= v0 <= e
// 0 <= u1 <= r
// 0 <= v1 <= e
//
// At each iteration, we reduce either a or b by one bit, and
// adjust u0, u1, v0 and v1 to maintain the invariants:
// - if a is even, then a <- a/2
// - otherwise, if b is even, then b <- b/2
// - otherwise, if a > b, then a <- (a-b)/2
// - otherwise, if b > a, then b <- (b-a)/2
// Algorithm stops when a = b. At that point, the common value
// is the GCD of e and r; it must be 1 (otherwise, the private
// key or public exponent is not valid). The (u0,v0) or (u1,v1)
// pairs are the solution we are looking for.
//
// Since either a or b is reduced by at least 1 bit at each
// iteration, 62 iterations are enough to reach the end
// condition.
//
// To maintain the invariants, we must compute the same operations
// on the u* and v* values that we do on a and b:
// - When a is divided by 2, u0 and v0 must be divided by 2.
// - When b is divided by 2, u1 and v1 must be divided by 2.
// - When b is subtracted from a, u1 and v1 are subtracted from
// u0 and v0, respectively.
// - When a is subtracted from b, u0 and v0 are subtracted from
// u1 and v1, respectively.
//
// However, we want to keep the u* and v* values in their proper
// ranges. The following remarks apply:
//
// - When a is divided by 2, then a is even. Therefore:
//
// * If r is odd, then u0 and v0 must have the same parity;
// if they are both odd, then adding r to u0 and e to v0
// makes them both even, and the division by 2 brings them
// back to the proper range.
//
// * If r is even, then u0 must be even; if v0 is odd, then
// adding r to u0 and e to v0 makes them both even, and the
// division by 2 brings them back to the proper range.
//
// Thus, all we need to do is to look at the parity of v0,
// and add (r,e) to (u0,v0) when v0 is odd. In order to avoid
// a 32-bit overflow, we can add ((r+1)/2,(e/2)+1) after the
// division (r+1 does not overflow since r < e; and (e/2)+1
// is equal to (e+1)/2 since e is odd).
//
// - When we subtract b from a, three cases may occur:
//
// * u1 <= u0 and v1 <= v0: just do the subtractions
//
// * u1 > u0 and v1 > v0: compute:
// (u0, v0) <- (u0 + r - u1, v0 + e - v1)
//
// * u1 <= u0 and v1 > v0: compute:
// (u0, v0) <- (u0 + r - u1, v0 + e - v1)
//
// The fourth case (u1 > u0 and v1 <= v0) is not possible
// because it would contradict "b < a" (which is the reason
// why we subtract b from a).
//
// The tricky case is the third one: from the equations, it
// seems that u0 may go out of range. However, the invariants
// and ranges of other values imply that, in that case, the
// new u0 does not actually exceed the range.
//
// We can thus handle the subtraction by adding (r,e) based
// solely on the comparison between v0 and v1.
a, b: u32 = e, r
u0, v0: u32 = 1, 0
u1, v1: u32 = r, e - 1
hr, he := (r + 1) >> 1, (e >> 1) + 1
for _ in 0..<62 {
oa := a & 1 // 1 if a is odd
ob := b & 1 // 1 if b is odd
agtb := subtle.gt(a, b) // 1 if a > b
bgta := subtle.gt(b, a) // 1 if b > a
sab := oa & ob & agtb // 1 if a <- a-b
sba := oa & ob & bgta // 1 if b <- b-a
// a <- a-b, u0 <- u0-u1, v0 <- v0-v1
ctl := subtle.gt(v1, v0)
a -= b & -sab
u0 -= (u1 - (r & -ctl)) & -sab
v0 -= (v1 - (e & -ctl)) & -sab
// b <- b-a, u1 <- u1-u0 mod r, v1 <- v1-v0 mod e
ctl = subtle.gt(v0, v1)
b -= a & -sba
u1 -= (u0 - (r & -ctl)) & -sba
v1 -= (v0 - (e & -ctl)) & -sba
da := subtle.not(oa) | sab // 1 if a <- a/2
db := (oa & subtle.not(ob)) | sba // 1 if b <- b/2
// a <- a/2, u0 <- u0/2, v0 <- v0/2
ctl = v0 & 1
a ~= (a ~ (a >> 1)) & -da
u0 ~= (u0 ~ ((u0 >> 1) + (hr & -ctl))) & -da
v0 ~= (v0 ~ ((v0 >> 1) + (he & -ctl))) & -da
// b <- b/2, u1 <- u1/2 mod r, v1 <- v1/2 mod e
ctl = v1 & 1
b ~= (b ~ (b >> 1)) & -db
u1 ~= (u1 ~ ((u1 >> 1) + (hr & -ctl))) & -db
v1 ~= (v1 ~ ((v1 >> 1) + (he & -ctl))) & -db
}
// Check that the GCD is indeed 1. If not, then the key is invalid
// (and there's no harm in leaking that piece of information).
if (a != 1) {
return 0
}
// Now we have u0*e - v0*r = 1. Let's compute the result as:
// d = u0 + v0*k
// We still have k in the tmp[] array, and its announced bit
// length is that of phi.
m := k[1+_len:]
m[0] = (1 << 5) + 1 // bit length is 32 bits, encoded
m[1] = v0 & bigint.I31_MASK
m[2] = v0 >> 31
z := m[3:]
bigint.i31_zero(z, k[0])
z[1] = u0 & bigint.I31_MASK
z[2] = u0 >> 31
bigint.i31_mulacc(z, k, m)
// Encode the result.
bigint.i31_encode(modulus_bytes(&sk._d), z)
return 1
}

Some files were not shown because too many files have changed in this diff Show More