Rebrand to Constantine. Bigints representation should stay opaque. Exporting just the word_types would make a super small library.

This commit is contained in:
mratsim 2018-12-01 20:12:05 +01:00
parent cae9f743d3
commit eb15fb33b5
9 changed files with 251 additions and 263 deletions

View File

@ -1,4 +1,4 @@
hardy is licensed under the Apache License version 2 constantine is licensed under the Apache License version 2
Copyright (c) 2018 Status Research & Development GmbH Copyright (c) 2018 Status Research & Development GmbH
----------------------------------------------------- -----------------------------------------------------

View File

@ -1,4 +1,4 @@
hardy is licensed under the MIT License constantine is licensed under the MIT License
Copyright (c) 2018 Status Research & Development GmbH Copyright (c) 2018 Status Research & Development GmbH
----------------------------------------------------- -----------------------------------------------------

View File

@ -1,19 +1,19 @@
# Hardy - Hardened big int primitives # Constantine - Constant time finitie field primitives for Elliptic Curve Cryptography
[![Build Status (Travis)](https://img.shields.io/travis/status-im/nim-hardy/master.svg?label=Linux%20/%20macOS "Linux/macOS build status (Travis)")](https://travis-ci.org/status-im/nim-hardy) [![Build Status (Travis)](https://img.shields.io/travis/status-im/nim-constantine/master.svg?label=Linux%20/%20macOS "Linux/macOS build status (Travis)")](https://travis-ci.org/status-im/nim-constantine)
[![Windows build status (Appveyor)](https://img.shields.io/appveyor/ci/nimbus/nim-hardy/master.svg?label=Windows "Windows build status (Appveyor)")](https://ci.appveyor.com/project/nimbus/nim-hardy) [![Windows build status (Appveyor)](https://img.shields.io/appveyor/ci/nimbus/nim-constantine/master.svg?label=Windows "Windows build status (Appveyor)")](https://ci.appveyor.com/project/nimbus/nim-constantine)
[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg) ![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
This library provides constant time big int primitives. This library provides constant time finite field primitives.
The main use will be for implementation of elliptic curve cryptography The main use will be for implementation of elliptic curve cryptography
## Installation ## Installation
You can install the developement version of the library through nimble with the following command You can install the developement version of the library through nimble with the following command
``` ```
nimble install https://github.com/status-im/nim-hardy@#master nimble install https://github.com/status-im/nim-constantine@#master
``` ```
## License ## License

View File

@ -1,10 +1,6 @@
# Hardy # Constantine
# Copyright (c) 2018 Status Research & Development GmbH # Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import hardy/[ct_primitives, datatypes]
export ct_primitives, datatypes

View File

@ -1,4 +1,4 @@
packageName = "hardy" packageName = "constantine"
version = "0.0.1" version = "0.0.1"
author = "Status Research & Development GmbH" author = "Status Research & Development GmbH"
description = "This library provides constant time big int primitives." description = "This library provides constant time big int primitives."

View File

@ -1,11 +1,34 @@
# Hardy # Constantine
# Copyright (c) 2018 Status Research & Development GmbH # Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./datatypes type
BaseUint* = SomeUnsignedInt or byte
Ct*[T: BaseUint] = distinct T
CTBool*[T: Ct] = distinct range[T(0)..T(1)]
## To avoid the compiler replacing bitwise boolean operations
## by conditional branches, we don't use booleans.
## We use an int to prevent compiler "optimization" and introduction of branches
func ctrue*(T: type(BaseUint)): auto {.inline.}=
(CTBool[Ct[T]])(true)
func cfalse*(T: type(BaseUint)): auto {.inline.}=
(CTBool[Ct[T]])(false)
func ct*[T: BaseUint](x: T): Ct[T] {.inline.}=
(Ct[T])(x)
func `$`*[T](x: Ct[T]): string {.inline.} =
$T(x)
func `$`*(x: CTBool): string {.inline.} =
$bool(x)
# ######################### # #########################
# #
@ -22,7 +45,7 @@ import ./datatypes
# does not guarantee a constant-time conditional move # does not guarantee a constant-time conditional move
# The compiler might introduce branching. # The compiler might introduce branching.
# These primitives are distinct type and internal to Hardy. # These primitives are distinct type and internal to Constantine.
# We don't want to pollute unsuspecting users # We don't want to pollute unsuspecting users
# with `not` and `-` on unsigned ints # with `not` and `-` on unsigned ints
@ -32,26 +55,26 @@ import ./datatypes
# - https://github.com/nim-lang/Nim/pull/8531 # - https://github.com/nim-lang/Nim/pull/8531
# - https://github.com/nim-lang/Nim/issues/4121 (can be workaround with #8531) # - https://github.com/nim-lang/Nim/issues/4121 (can be workaround with #8531)
func high*(T: typedesc[HardBase]): T {.inline.}= func high*(T: typedesc[Ct]): T {.inline.}=
not T(0) not T(0)
func `and`*[T: HardBase](x, y: T): T {.magic: "BitandI".} func `and`*[T: Ct](x, y: T): T {.magic: "BitandI".}
func `or`*[T: HardBase](x, y: T): T {.magic: "BitorI".} func `or`*[T: Ct](x, y: T): T {.magic: "BitorI".}
func `xor`*[T: HardBase](x, y: T): T {.magic: "BitxorI".} func `xor`*[T: Ct](x, y: T): T {.magic: "BitxorI".}
func `not`*[T: HardBase](x: T): T {.magic: "BitnotI".} func `not`*[T: Ct](x: T): T {.magic: "BitnotI".}
func `+`*[T: HardBase](x, y: T): T {.magic: "AddU".} func `+`*[T: Ct](x, y: T): T {.magic: "AddU".}
func `-`*[T: HardBase](x, y: T): T {.magic: "SubU".} func `-`*[T: Ct](x, y: T): T {.magic: "SubU".}
func `shr`*[T: HardBase](x: T, y: SomeInteger): T {.magic: "ShrI".} func `shr`*[T: Ct](x: T, y: SomeInteger): T {.magic: "ShrI".}
func `shl`*[T: HardBase](x: T, y: SomeInteger): T {.magic: "ShlI".} func `shl`*[T: Ct](x: T, y: SomeInteger): T {.magic: "ShlI".}
func `*`*[T: HardBase](x, y: T): T {.magic: "MulU".} func `*`*[T: Ct](x, y: T): T {.magic: "MulU".}
# Warning ⚠️ : We assume that mul hardware multiplication is constant time # Warning ⚠️ : We assume that mul hardware multiplication is constant time
# but this is not always true, especially on ARMv7 and ARMv9 # but this is not always true, especially on ARMv7 and ARMv9
# We don't implement div/mod as we can't assume the hardware implementation # We don't implement div/mod as we can't assume the hardware implementation
# is constant-time # is constant-time
func `-`*(x: HardBase): HardBase {.inline.}= func `-`*(x: Ct): Ct {.inline.}=
## Unary minus returns the two-complement representation ## Unary minus returns the two-complement representation
## of an unsigned integer ## of an unsigned integer
{.emit:"`result` = -`x`;".} {.emit:"`result` = -`x`;".}
@ -62,10 +85,10 @@ func `-`*(x: HardBase): HardBase {.inline.}=
# #
# ############################################################ # ############################################################
func isMsbSet*[T: HardBase](x: T): HardBool[T] {.inline.} = func isMsbSet*[T: Ct](x: T): CTBool[T] {.inline.} =
## Returns the most significant bit of an integer ## Returns the most significant bit of an integer
const msb_pos = T.sizeof * 8 - 1 const msb_pos = T.sizeof * 8 - 1
result = (HardBool[T])(x shr msb_pos) result = (CTBool[T])(x shr msb_pos)
# ############################################################ # ############################################################
# #
@ -73,14 +96,14 @@ func isMsbSet*[T: HardBase](x: T): HardBool[T] {.inline.} =
# #
# ############################################################ # ############################################################
template undistinct[T: HardBase](x: HardBool[T]): T = template undistinct[T: Ct](x: CTBool[T]): T =
T(x) T(x)
func `not`*(ctl: HardBool): HardBool {.inline.}= func `not`*(ctl: CTBool): CTBool {.inline.}=
## Negate a constant-time boolean ## Negate a constant-time boolean
(type result)(ctl.undistinct xor (type ctl.undistinct)(1)) (type result)(ctl.undistinct xor (type ctl.undistinct)(1))
template mux*[T: HardBase](ctl: HardBool[T], x, y: T): T = template mux*[T: Ct](ctl: CTBool[T], x, y: T): T =
## Multiplexer / selector ## Multiplexer / selector
## Returns x if ctl == 1 ## Returns x if ctl == 1
## else returns y ## else returns y
@ -92,22 +115,22 @@ template mux*[T: HardBase](ctl: HardBool[T], x, y: T): T =
# the alternative `(x and ctl) or (y and -ctl)` # the alternative `(x and ctl) or (y and -ctl)`
# is optimized into a branch by Clang :/ # is optimized into a branch by Clang :/
func noteq[T: HardBase](x, y: T): HardBool[T] {.inline.}= func noteq[T: Ct](x, y: T): CTBool[T] {.inline.}=
const msb = T.sizeof * 8 - 1 const msb = T.sizeof * 8 - 1
let z = x xor y let z = x xor y
result = (type result)((z or -z) shr msb) result = (type result)((z or -z) shr msb)
func `==`*[T: HardBase](x, y: T): HardBool[T] {.inline.}= func `==`*[T: Ct](x, y: T): CTBool[T] {.inline.}=
not(noteq(x, y)) not(noteq(x, y))
func `<`*[T: HardBase](x, y: T): HardBool[T] {.inline.}= func `<`*[T: Ct](x, y: T): CTBool[T] {.inline.}=
result = isMsbSet( result = isMsbSet(
x xor ( x xor (
(x xor y) or ((x - y) xor y) (x xor y) or ((x - y) xor y)
) )
) )
func `<=`*[T: HardBase](x, y: T): HardBool[T] {.inline.}= func `<=`*[T: Ct](x, y: T): CTBool[T] {.inline.}=
not(y < x) not(y < x)
# ############################################################ # ############################################################
@ -120,7 +143,7 @@ func `<=`*[T: HardBase](x, y: T): HardBool[T] {.inline.}=
# in terms of `==` while we define `==` in terms of `!=` # in terms of `==` while we define `==` in terms of `!=`
# So we would have not(not(noteq(x,y))) # So we would have not(not(noteq(x,y)))
template trmFixSystemNotEq*{x != y}[T: HardBase](x, y: T): HardBool[T] = template trmFixSystemNotEq*{x != y}[T: Ct](x, y: T): CTBool[T] =
noteq(x, y) noteq(x, y)
# ############################################################ # ############################################################
@ -129,10 +152,10 @@ template trmFixSystemNotEq*{x != y}[T: HardBase](x, y: T): HardBool[T] =
# #
# ############################################################ # ############################################################
func isNonZero*[T: HardBase](x: T): HardBool[T] {.inline.} = func isNonZero*[T: Ct](x: T): CTBool[T] {.inline.} =
isMsbSet(x or -x) isMsbSet(x or -x)
func isZero*[T: HardBase](x: T): HardBool[T] {.inline.} = func isZero*[T: Ct](x: T): CTBool[T] {.inline.} =
not x.isNonZero not x.isNonZero
# ############################################################ # ############################################################
@ -142,7 +165,7 @@ func isZero*[T: HardBase](x: T): HardBool[T] {.inline.} =
# #
# ############################################################ # ############################################################
template trmIsZero*{x == 0}[T: HardBase](x: T): HardBool[T] = x.isZero template trmIsZero*{x == 0}[T: Ct](x: T): CTBool[T] = x.isZero
template trmIsZero*{0 == x}[T: HardBase](x: T): HardBool[T] = x.isZero template trmIsZero*{0 == x}[T: Ct](x: T): CTBool[T] = x.isZero
template trmIsNonZero*{x != 0}[T: HardBase](x: T): HardBool[T] = x.isNonZero template trmIsNonZero*{x != 0}[T: Ct](x: T): CTBool[T] = x.isNonZero
template trmIsNonZero*{0 != x}[T: HardBase](x: T): HardBool[T] = x.isNonZero template trmIsNonZero*{0 != x}[T: Ct](x: T): CTBool[T] = x.isNonZero

View File

@ -1,40 +0,0 @@
# Hardy
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
type
BaseUint* = SomeUnsignedInt or byte
HardBase*[T: BaseUint] = distinct T
HardBool*[T: HardBase] = distinct range[T(0)..T(1)]
## To avoid the compiler replacing bitwise boolean operations
## by conditional branches, we don't use booleans.
## We use an int to prevent compiler "optimization" and introduction of branches
Hard*[T: HardBase] = distinct openarray[T]
## Hardy primitives are memory-backend agnostic.
## Hardy integers can be stored in an opaque stack array
## or a seq or even a string.
##
## Allocations is left to the client library.
## Note that constant-time allocation is very involved for
## heap-allocated types (i.e. requires a memory pool)
func htrue*(T: type(BaseUint)): auto {.inline.}=
(HardBool[HardBase[T]])(true)
func hfalse*(T: type(BaseUint)): auto {.inline.}=
(HardBool[HardBase[T]])(false)
func hard*[T: BaseUint](x: T): HardBase[T] {.inline.}=
(HardBase[T])(x)
func `$`*[T](x: HardBase[T]): string {.inline.} =
$T(x)
func `$`*(x: HardBool): string {.inline.} =
$bool(x)

View File

@ -1,186 +1,9 @@
# hardy # constantine
# Copyright (c) 2018 Status Research & Development GmbH # Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import unittest, random, math, import
../hardy test_word_types
# Random seed for reproducibility
randomize(0xDEADBEEF)
template undistinct[T](x: HardBase[T]): T =
T(x)
suite "Hardened unsigned integers":
test "High - getting the biggest representable number":
check:
high(HardBase[byte]).undistinct == 0xFF.byte
high(HardBase[uint8]).undistinct == 0xFF.uint8
high(HardBase[uint16]).undistinct == 0xFFFF.uint16
high(HardBase[uint32]).undistinct == 0xFFFFFFFF.uint32
high(HardBase[uint64]).undistinct == 0xFFFFFFFF_FFFFFFFF.uint64
test "bitwise `and`, `or`, `xor`, `not`":
let x1 = rand(high(int)).uint64
let y1 = rand(high(int)).uint64
let x2 = rand(high(int)).uint64
let y2 = rand(high(int)).uint64
let x3 = rand(high(int)).uint64
let y3 = rand(high(int)).uint64
template bitwise_check(op: untyped): untyped =
block:
check:
op(hard(0'u32), hard(0'u32)).undistinct == op(0'u32, 0'u32)
op(hard(0'u32), hard(1'u32)).undistinct == op(0'u32, 1'u32)
op(hard(1234'u64), hard(5678'u64)).undistinct == op(1234'u64, 5678'u64)
op(x1.hard, y1.hard).undistinct == op(x1, y1)
op(x2.hard, y2.hard).undistinct == op(x2, y2)
op(x3.hard, y3.hard).undistinct == op(x3, y3)
bitwise_check(`and`)
bitwise_check(`or`)
bitwise_check(`xor`)
block:
check:
not(hard(0'u32)).undistinct == not 0'u32
not(hard(1'u32)).undistinct == not 1'u32
not(hard(1234'u64)).undistinct == not 1234'u64
not(hard(5678'u64)).undistinct == not 5678'u64
not(hard(x1)).undistinct == not x1
not(hard(x2)).undistinct == not x2
not(hard(x3)).undistinct == not x3
not(hard(y1)).undistinct == not y1
not(hard(y2)).undistinct == not y2
not(hard(y3)).undistinct == not y3
test "Logical shifts":
let x1 = rand(high(int)).uint64
let y1 = rand(high(int)).uint64
let x2 = rand(high(int)).uint64
let y2 = rand(high(int32)).uint64
let x3 = rand(high(int32)).uint64
let y3 = rand(high(int32)).uint64
let s1 = rand(10)
let s2 = rand(10)
let s3 = rand(10)
template shift_check(op: untyped): untyped =
block:
check:
op(hard(0'u32), 1).undistinct == op(0'u32, 1)
op(hard(1'u32), 2).undistinct == op(1'u32, 2)
op(hard(1234'u64), 3).undistinct == op(1234'u64, 3)
op(hard(2'u64^30), 1).undistinct == op(2'u64^30, 1)
op(hard(2'u64^31 + 1), 1).undistinct == op(2'u64^31 + 1, 1)
op(hard(2'u64^32), 1).undistinct == op(2'u64^32, 1)
op(x1.hard, s1).undistinct == op(x1, s1)
op(x2.hard, s2).undistinct == op(x2, s2)
op(x3.hard, s3).undistinct == op(x3, s3)
op(y1.hard, s1).undistinct == op(y1, s1)
op(y2.hard, s2).undistinct == op(y2, s2)
op(y3.hard, s3).undistinct == op(y3, s3)
shift_check(`shl`)
shift_check(`shr`)
test "Operators `+`, `-`, `*`":
let x1 = rand(high(int)).uint64
let y1 = rand(high(int)).uint64
let x2 = rand(high(int)).uint64
let y2 = rand(high(int)).uint64
let x3 = rand(high(int)).uint64
let y3 = rand(high(int)).uint64
template operator_check(op: untyped): untyped =
block:
check:
op(hard(0'u32), hard(0'u32)).undistinct == op(0'u32, 0'u32)
op(hard(0'u32), hard(1'u32)).undistinct == op(0'u32, 1'u32)
op(hard(1234'u64), hard(5678'u64)).undistinct == op(1234'u64, 5678'u64)
op(x1.hard, y1.hard).undistinct == op(x1, y1)
op(x2.hard, y2.hard).undistinct == op(x2, y2)
op(x3.hard, y3.hard).undistinct == op(x3, y3)
operator_check(`+`)
operator_check(`-`)
operator_check(`*`)
test "Unary `-`, returning the 2-complement of an unsigned integer":
let x1 = rand(high(int)).uint64
let y1 = rand(high(int)).uint64
let x2 = rand(high(int)).uint64
let y2 = rand(high(int)).uint64
let x3 = rand(high(int)).uint64
let y3 = rand(high(int)).uint64
check:
(-hard(0'u32)).undistinct == 0
(-high(HardBase[uint32])).undistinct == 1'u32
(-hard(0x80000000'u32)).undistinct == 0x80000000'u32 # This is low(int32) == 0b10000..0000
undistinct(-x1.hard) == undistinct(not(x1.hard) + hard(1'u64))
undistinct(-x2.hard) == undistinct(not(x2.hard) + hard(1'u64))
undistinct(-x3.hard) == undistinct(not(x3.hard) + hard(1'u64))
undistinct(-y1.hard) == undistinct(not(y1.hard) + hard(1'u64))
undistinct(-y2.hard) == undistinct(not(y2.hard) + hard(1'u64))
undistinct(-y3.hard) == undistinct(not(y3.hard) + hard(1'u64))
suite "Hardened booleans":
test "Boolean not":
check:
not(htrue(uint32)).bool == false
not(hfalse(uint32)).bool == true
test "Comparison":
check:
bool(hard(0'u32) != hard(0'u32)) == false
bool(hard(0'u32) != hard(1'u32)) == true
bool(hard(10'u32) == hard(10'u32)) == true
bool(hard(10'u32) != hard(20'u32)) == true
bool(hard(10'u32) <= hard(10'u32)) == true
bool(hard(10'u32) <= hard(20'u32)) == true
bool(hard(10'u32) <= hard(5'u32)) == false
bool(hard(10'u32) <= hard(0xFFFFFFFF'u32)) == true
bool(hard(10'u32) < hard(10'u32)) == false
bool(hard(10'u32) < hard(20'u32)) == true
bool(hard(10'u32) < hard(5'u32)) == false
bool(hard(10'u32) < hard(0xFFFFFFFF'u32)) == true
bool(hard(10'u32) > hard(10'u32)) == false
bool(hard(10'u32) > hard(20'u32)) == false
bool(hard(10'u32) > hard(5'u32)) == true
bool(hard(10'u32) > hard(0xFFFFFFFF'u32)) == false
bool(hard(10'u32) >= hard(10'u32)) == true
bool(hard(10'u32) >= hard(20'u32)) == false
bool(hard(10'u32) >= hard(5'u32)) == true
bool(hard(10'u32) >= hard(0xFFFFFFFF'u32)) == false
test "Multiplexer/selector - mux(ctl, x, y) <=> ctl? x: y":
let u = 10'u32.hard
let v = 20'u32.hard
let w = 5'u32.hard
let y = htrue(uint32)
let n = hfalse(uint32)
check:
bool(mux(y, u, v) == u)
bool(mux(n, u, v) == v)
bool(mux(y, u, w) == u)
bool(mux(n, u, w) == w)
bool(mux(y, v, w) == v)
bool(mux(n, v, w) == w)

186
tests/test_word_types.nim Normal file
View File

@ -0,0 +1,186 @@
# constantine
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import unittest, random, math,
../constantine/word_types
# Random seed for reproducibility
randomize(0xDEADBEEF)
template undistinct[T](x: Ct[T]): T =
T(x)
suite "Constant-time unsigned integers":
test "High - getting the biggest representable number":
check:
high(Ct[byte]).undistinct == 0xFF.byte
high(Ct[uint8]).undistinct == 0xFF.uint8
high(Ct[uint16]).undistinct == 0xFFFF.uint16
high(Ct[uint32]).undistinct == 0xFFFFFFFF.uint32
high(Ct[uint64]).undistinct == 0xFFFFFFFF_FFFFFFFF.uint64
test "bitwise `and`, `or`, `xor`, `not`":
let x1 = rand(high(int)).uint64
let y1 = rand(high(int)).uint64
let x2 = rand(high(int)).uint64
let y2 = rand(high(int)).uint64
let x3 = rand(high(int)).uint64
let y3 = rand(high(int)).uint64
template bitwise_check(op: untyped): untyped =
block:
check:
op(ct(0'u32), ct(0'u32)).undistinct == op(0'u32, 0'u32)
op(ct(0'u32), ct(1'u32)).undistinct == op(0'u32, 1'u32)
op(ct(1234'u64), ct(5678'u64)).undistinct == op(1234'u64, 5678'u64)
op(x1.ct, y1.ct).undistinct == op(x1, y1)
op(x2.ct, y2.ct).undistinct == op(x2, y2)
op(x3.ct, y3.ct).undistinct == op(x3, y3)
bitwise_check(`and`)
bitwise_check(`or`)
bitwise_check(`xor`)
block:
check:
not(ct(0'u32)).undistinct == not 0'u32
not(ct(1'u32)).undistinct == not 1'u32
not(ct(1234'u64)).undistinct == not 1234'u64
not(ct(5678'u64)).undistinct == not 5678'u64
not(ct(x1)).undistinct == not x1
not(ct(x2)).undistinct == not x2
not(ct(x3)).undistinct == not x3
not(ct(y1)).undistinct == not y1
not(ct(y2)).undistinct == not y2
not(ct(y3)).undistinct == not y3
test "Logical shifts":
let x1 = rand(high(int)).uint64
let y1 = rand(high(int)).uint64
let x2 = rand(high(int)).uint64
let y2 = rand(high(int32)).uint64
let x3 = rand(high(int32)).uint64
let y3 = rand(high(int32)).uint64
let s1 = rand(10)
let s2 = rand(10)
let s3 = rand(10)
template shift_check(op: untyped): untyped =
block:
check:
op(ct(0'u32), 1).undistinct == op(0'u32, 1)
op(ct(1'u32), 2).undistinct == op(1'u32, 2)
op(ct(1234'u64), 3).undistinct == op(1234'u64, 3)
op(ct(2'u64^30), 1).undistinct == op(2'u64^30, 1)
op(ct(2'u64^31 + 1), 1).undistinct == op(2'u64^31 + 1, 1)
op(ct(2'u64^32), 1).undistinct == op(2'u64^32, 1)
op(x1.ct, s1).undistinct == op(x1, s1)
op(x2.ct, s2).undistinct == op(x2, s2)
op(x3.ct, s3).undistinct == op(x3, s3)
op(y1.ct, s1).undistinct == op(y1, s1)
op(y2.ct, s2).undistinct == op(y2, s2)
op(y3.ct, s3).undistinct == op(y3, s3)
shift_check(`shl`)
shift_check(`shr`)
test "Operators `+`, `-`, `*`":
let x1 = rand(high(int)).uint64
let y1 = rand(high(int)).uint64
let x2 = rand(high(int)).uint64
let y2 = rand(high(int)).uint64
let x3 = rand(high(int)).uint64
let y3 = rand(high(int)).uint64
template operator_check(op: untyped): untyped =
block:
check:
op(ct(0'u32), ct(0'u32)).undistinct == op(0'u32, 0'u32)
op(ct(0'u32), ct(1'u32)).undistinct == op(0'u32, 1'u32)
op(ct(1234'u64), ct(5678'u64)).undistinct == op(1234'u64, 5678'u64)
op(x1.ct, y1.ct).undistinct == op(x1, y1)
op(x2.ct, y2.ct).undistinct == op(x2, y2)
op(x3.ct, y3.ct).undistinct == op(x3, y3)
operator_check(`+`)
operator_check(`-`)
operator_check(`*`)
test "Unary `-`, returning the 2-complement of an unsigned integer":
let x1 = rand(high(int)).uint64
let y1 = rand(high(int)).uint64
let x2 = rand(high(int)).uint64
let y2 = rand(high(int)).uint64
let x3 = rand(high(int)).uint64
let y3 = rand(high(int)).uint64
check:
(-ct(0'u32)).undistinct == 0
(-high(Ct[uint32])).undistinct == 1'u32
(-ct(0x80000000'u32)).undistinct == 0x80000000'u32 # This is low(int32) == 0b10000..0000
undistinct(-x1.ct) == undistinct(not(x1.ct) + ct(1'u64))
undistinct(-x2.ct) == undistinct(not(x2.ct) + ct(1'u64))
undistinct(-x3.ct) == undistinct(not(x3.ct) + ct(1'u64))
undistinct(-y1.ct) == undistinct(not(y1.ct) + ct(1'u64))
undistinct(-y2.ct) == undistinct(not(y2.ct) + ct(1'u64))
undistinct(-y3.ct) == undistinct(not(y3.ct) + ct(1'u64))
suite "Constant-time booleans":
test "Boolean not":
check:
not(ctrue(uint32)).bool == false
not(cfalse(uint32)).bool == true
test "Comparison":
check:
bool(ct(0'u32) != ct(0'u32)) == false
bool(ct(0'u32) != ct(1'u32)) == true
bool(ct(10'u32) == ct(10'u32)) == true
bool(ct(10'u32) != ct(20'u32)) == true
bool(ct(10'u32) <= ct(10'u32)) == true
bool(ct(10'u32) <= ct(20'u32)) == true
bool(ct(10'u32) <= ct(5'u32)) == false
bool(ct(10'u32) <= ct(0xFFFFFFFF'u32)) == true
bool(ct(10'u32) < ct(10'u32)) == false
bool(ct(10'u32) < ct(20'u32)) == true
bool(ct(10'u32) < ct(5'u32)) == false
bool(ct(10'u32) < ct(0xFFFFFFFF'u32)) == true
bool(ct(10'u32) > ct(10'u32)) == false
bool(ct(10'u32) > ct(20'u32)) == false
bool(ct(10'u32) > ct(5'u32)) == true
bool(ct(10'u32) > ct(0xFFFFFFFF'u32)) == false
bool(ct(10'u32) >= ct(10'u32)) == true
bool(ct(10'u32) >= ct(20'u32)) == false
bool(ct(10'u32) >= ct(5'u32)) == true
bool(ct(10'u32) >= ct(0xFFFFFFFF'u32)) == false
test "Multiplexer/selector - mux(ctl, x, y) <=> ctl? x: y":
let u = 10'u32.ct
let v = 20'u32.ct
let w = 5'u32.ct
let y = ctrue(uint32)
let n = cfalse(uint32)
check:
bool(mux(y, u, v) == u)
bool(mux(n, u, v) == v)
bool(mux(y, u, w) == u)
bool(mux(n, u, w) == w)
bool(mux(y, v, w) == v)
bool(mux(n, v, w) == w)