aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYawning Angel <yawning@schwanenlied.me>2026-01-02 23:59:38 +0900
committerYawning Angel <yawning@schwanenlied.me>2026-01-28 22:20:03 +0900
commit6bbd060352dc0aba41594362c9b82bda636fedde (patch)
treedf58032e0363da7eec872ab9ed4e7070d642887a
parent429e8a46dba047833087f22bddd8d03519929258 (diff)
core/crypto/_subtle: Refactor out common helpers
-rw-r--r--core/crypto/_edwards25519/edwards25519_scalar_mul.odin6
-rw-r--r--core/crypto/_fiat/field_scalar25519/field.odin7
-rw-r--r--core/crypto/_subtle/subtle.odin42
-rw-r--r--core/crypto/crypto.odin13
4 files changed, 60 insertions, 8 deletions
diff --git a/core/crypto/_edwards25519/edwards25519_scalar_mul.odin b/core/crypto/_edwards25519/edwards25519_scalar_mul.odin
index c24447072..6e0eb42c7 100644
--- a/core/crypto/_edwards25519/edwards25519_scalar_mul.odin
+++ b/core/crypto/_edwards25519/edwards25519_scalar_mul.odin
@@ -1,7 +1,7 @@
package _edwards25519
import field "core:crypto/_fiat/field_scalar25519"
-import "core:math/bits"
+import subtle "core:crypto/_subtle"
import "core:mem"
// GE_BASEPOINT_TABLE is 1 * G, ... 15 * G, in precomputed format.
@@ -281,8 +281,8 @@ mul_tbl_add :: proc "contextless" (
{2, 0, 0, 0, 0}, // z * 2
}
for i := u64(1); i < 16; i = i + 1 {
- _, ctrl := bits.sub_u64(0, (i ~ idx), 0)
- ge_addend_conditional_assign(tmp_addend, &tbl[i - 1], int(~ctrl) & 1)
+ ctrl := subtle.eq(i, idx)
+ ge_addend_conditional_assign(tmp_addend, &tbl[i - 1], int(ctrl))
}
ge_add_addend(ge, ge, tmp_addend, tmp_add)
}
diff --git a/core/crypto/_fiat/field_scalar25519/field.odin b/core/crypto/_fiat/field_scalar25519/field.odin
index 933637c54..96b279ce7 100644
--- a/core/crypto/_fiat/field_scalar25519/field.odin
+++ b/core/crypto/_fiat/field_scalar25519/field.odin
@@ -1,5 +1,6 @@
package field_scalar25519
+import subtle "core:crypto/_subtle"
import "core:encoding/endian"
import "core:math/bits"
import "core:mem"
@@ -121,13 +122,11 @@ fe_equal :: proc "contextless" (arg1, arg2: ^Montgomery_Domain_Field_Element) ->
tmp: Montgomery_Domain_Field_Element
fe_sub(&tmp, arg1, arg2)
- // This will only underflow iff arg1 == arg2, and we return the borrow,
- // which will be 1.
- _, borrow := bits.sub_u64(fe_non_zero(&tmp), 1, 0)
+ is_eq := subtle.eq(fe_non_zero(&tmp), 0)
fe_clear(&tmp)
- return int(borrow)
+ return int(is_eq)
}
fe_zero :: proc "contextless" (out1: ^Montgomery_Domain_Field_Element) {
diff --git a/core/crypto/_subtle/subtle.odin b/core/crypto/_subtle/subtle.odin
new file mode 100644
index 000000000..89328072c
--- /dev/null
+++ b/core/crypto/_subtle/subtle.odin
@@ -0,0 +1,42 @@
+/*
+Various useful bit operations in constant time.
+*/
+package _subtle
+
+import "core:math/bits"
+
+// byte_eq returns 1 iff a == b, 0 otherwise.
+@(optimization_mode="none")
+byte_eq :: proc "contextless" (a, b: byte) -> int {
+ v := a ~ b
+
+ // v == 0 iff a == b. The subtraction will underflow, setting the
+ // sign bit, which will get returned.
+ return int((u32(v)-1) >> 31)
+}
+
+// u64_eq returns 1 iff a == b, 0 otherwise.
+@(optimization_mode="none")
+u64_eq :: proc "contextless" (a, b: u64) -> u64 {
+ _, borrow := bits.sub_u64(0, a ~ b, 0)
+ return (~borrow) & 1
+}
+
+eq :: proc {
+ byte_eq,
+ u64_eq,
+}
+
+// u64_is_zero returns 1 iff a == 0, 0 otherwise.
+@(optimization_mode="none")
+u64_is_zero :: proc "contextless" (a: u64) -> u64 {
+ _, borrow := bits.sub_u64(a, 1, 0)
+ return borrow
+}
+
+// u64_is_non_zero returns 1 iff 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
+}
diff --git a/core/crypto/crypto.odin b/core/crypto/crypto.odin
index 7ccf126e6..435c5daaf 100644
--- a/core/crypto/crypto.odin
+++ b/core/crypto/crypto.odin
@@ -2,6 +2,7 @@
package crypto
import "base:runtime"
+import subtle "core:crypto/_subtle"
import "core:mem"
// HAS_RAND_BYTES is true iff the runtime provides a cryptographic
@@ -44,7 +45,17 @@ compare_byte_ptrs_constant_time :: proc "contextless" (a, b: ^byte, n: int) -> i
// After the loop, v == 0 iff a == b. The subtraction will underflow
// iff v == 0, setting the sign-bit, which gets returned.
- return int((u32(v)-1) >> 31)
+ return subtle.eq(0, v)
+}
+
+// is_zero_constant_time returns 1 iff b is all 0s, 0 otherwise.
+is_zero_constant_time :: proc "contextless" (b: []byte) -> int {
+ v: byte
+ for b_ in b {
+ v |= b_
+ }
+
+ return subtle.byte_eq(0, v)
}
// rand_bytes fills the dst buffer with cryptographic entropy taken from