diff --git a/core/crypto/README.md b/core/crypto/README.md index 5065a6d02..0d1a8711b 100644 --- a/core/crypto/README.md +++ b/core/crypto/README.md @@ -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. \ No newline at end of file +This library is made available under the zlib license. diff --git a/core/crypto/_bigint/i31.odin b/core/crypto/_bigint/i31.odin new file mode 100644 index 000000000..e71a95310 --- /dev/null +++ b/core/crypto/_bigint/i31.odin @@ -0,0 +1,904 @@ +// Constant time Big Integers +package _bigint + +// Copyright (c) 2016 Thomas Pornin +// 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..> 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..> 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)..= 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.. 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..> 31 + d[1 + u + v] = u32(z) & I31_MASK + } + d[1 + u + a_len] = u32(cc) + } +} diff --git a/core/crypto/_bigint/i62.odin b/core/crypto/_bigint/i62.odin new file mode 100644 index 000000000..eb6d99907 --- /dev/null +++ b/core/crypto/_bigint/i62.odin @@ -0,0 +1,361 @@ +package _bigint + +// Copyright (c) 2017 Thomas Pornin +// 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..> 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..> 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..> 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..> 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.. 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..> 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) +} diff --git a/core/crypto/_bigint/i62_primes.odin b/core/crypto/_bigint/i62_primes.odin new file mode 100644 index 000000000..a9606db05 --- /dev/null +++ b/core/crypto/_bigint/i62_primes.odin @@ -0,0 +1,265 @@ +package _bigint + +// Copyright (c) 2017 Thomas Pornin +// 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..> 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..> 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 + } + } +} diff --git a/core/crypto/_fiat/field_p256r1/field.odin b/core/crypto/_fiat/field_p256r1/field.odin index f7dd978aa..fcb7357a7 100644 --- a/core/crypto/_fiat/field_p256r1/field.odin +++ b/core/crypto/_fiat/field_p256r1/field.odin @@ -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) diff --git a/core/crypto/_fiat/field_p384r1/field.odin b/core/crypto/_fiat/field_p384r1/field.odin index 2bddff18c..af371873e 100644 --- a/core/crypto/_fiat/field_p384r1/field.odin +++ b/core/crypto/_fiat/field_p384r1/field.odin @@ -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) diff --git a/core/crypto/_fiat/field_scalarp384r1/field.odin b/core/crypto/_fiat/field_scalarp384r1/field.odin index cfc27f322..d465bae44 100644 --- a/core/crypto/_fiat/field_scalarp384r1/field.odin +++ b/core/crypto/_fiat/field_scalarp384r1/field.odin @@ -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) diff --git a/core/crypto/_mlkem/poly.odin b/core/crypto/_mlkem/poly.odin index 1982f9102..13dd54930 100644 --- a/core/crypto/_mlkem/poly.odin +++ b/core/crypto/_mlkem/poly.odin @@ -130,7 +130,7 @@ poly_frommsg :: proc "contextless" (r: ^Poly, msg: []byte) #no_bounds_check { for i in 0..> uint(j))&1) + r.coeffs[8*i+j] = subtle.csel_i16(0, (Q+1)/2, (msg[i] >> uint(j))&1) } } } diff --git a/core/crypto/_subtle/subtle.odin b/core/crypto/_subtle/subtle.odin index 01c84cf2a..e57d4ca36 100644 --- a/core/crypto/_subtle/subtle.odin +++ b/core/crypto/_subtle/subtle.odin @@ -3,76 +3,245 @@ Various useful bit operations in constant time. */ package _subtle -import "core:crypto/_fiat" -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 +// 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") -cmov_bytes :: proc "contextless" (dst, src: []byte, ctrl: int) { +@(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) - ensure_contextless(s_len == len(dst), "crypto: cmov length mismatch") - c := -(byte)(ctrl) + c := -(T)(ctrl) for i in 0.. i16 { - c := -(u16)(ctrl) +// 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") -csel_u16 :: proc "contextless" (a, b: u16, ctrl: int) -> u16 { - c := -(u16)(ctrl) +@(optimization_mode="none", require_results) +csel_u16 :: proc "contextless" (a, b: u16, #any_int ctrl: u16) -> u16 { + c := -ctrl return a ~ (c & (a ~ b)) } -csel_u32 :: proc "contextless" (a, b: u32, ctrl: int) -> u32 { - return _fiat.cmovznz_u32(_fiat.u1(ctrl), 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)) } -csel_u64 :: proc "contextless" (a, b: u64, ctrl: int) -> u64 { - return _fiat.cmovznz_u64(_fiat.u1(ctrl), 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, diff --git a/core/crypto/_weierstrass/fe.odin b/core/crypto/_weierstrass/fe.odin index 8ff6fe346..2b93d58a4 100644 --- a/core/crypto/_weierstrass/fe.odin +++ b/core/crypto/_weierstrass/fe.odin @@ -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))) } diff --git a/core/crypto/_weierstrass/sc.odin b/core/crypto/_weierstrass/sc.odin index 1ecfea6f9..6a3c7b68c 100644 --- a/core/crypto/_weierstrass/sc.odin +++ b/core/crypto/_weierstrass/sc.odin @@ -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))) } diff --git a/core/crypto/_weierstrass/scalar_mul.odin b/core/crypto/_weierstrass/scalar_mul.odin index eb3fa1459..1af3ba989 100644 --- a/core/crypto/_weierstrass/scalar_mul.odin +++ b/core/crypto/_weierstrass/scalar_mul.odin @@ -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)) } } diff --git a/core/crypto/crypto.odin b/core/crypto/crypto.odin index 3218b8670..fff66fdd0 100644 --- a/core/crypto/crypto.odin +++ b/core/crypto/crypto.odin @@ -49,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. @@ -59,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)) } /* diff --git a/core/crypto/ed25519/ed25519.odin b/core/crypto/ed25519/ed25519.odin index 9f2d2b330..cc7f93e07 100644 --- a/core/crypto/ed25519/ed25519.odin +++ b/core/crypto/ed25519/ed25519.odin @@ -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 @@ -189,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 @@ -237,6 +238,7 @@ 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") @@ -254,6 +256,7 @@ public_key_clear :: proc "contextless" (pub_key: ^Public_Key) { // 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: diff --git a/core/crypto/rsa/doc.odin b/core/crypto/rsa/doc.odin new file mode 100644 index 000000000..537333cfc --- /dev/null +++ b/core/crypto/rsa/doc.odin @@ -0,0 +1,7 @@ +/* +RSA (Rivest–Shamir–Adleman) cryptosystem. + +See: +- [[ https://www.rfc-editor.org/info/rfc8017/ ]] +*/ +package rsa diff --git a/core/crypto/rsa/rsa.odin b/core/crypto/rsa/rsa.odin new file mode 100644 index 000000000..29ef4dac8 --- /dev/null +++ b/core/crypto/rsa/rsa.odin @@ -0,0 +1,444 @@ +package rsa + +// Copyright (c) 2016 Thomas Pornin +// 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) + } +} diff --git a/core/crypto/rsa/rsa_dec_oaep.odin b/core/crypto/rsa/rsa_dec_oaep.odin new file mode 100644 index 000000000..37f48fea9 --- /dev/null +++ b/core/crypto/rsa/rsa_dec_oaep.odin @@ -0,0 +1,197 @@ +package rsa + +// Copyright (c) 2018 Thomas Pornin +// 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..> 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 +} diff --git a/core/crypto/rsa/rsa_dec_tls_pms.odin b/core/crypto/rsa/rsa_dec_tls_pms.odin new file mode 100644 index 000000000..cedd9032d --- /dev/null +++ b/core/crypto/rsa/rsa_dec_tls_pms.odin @@ -0,0 +1,55 @@ +package rsa + +// Copyright (c) 2016 Thomas Pornin +// 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 +} diff --git a/core/crypto/rsa/rsa_enc_oaep.odin b/core/crypto/rsa/rsa_enc_oaep.odin new file mode 100644 index 000000000..7013b823b --- /dev/null +++ b/core/crypto/rsa/rsa_enc_oaep.odin @@ -0,0 +1,105 @@ +package rsa + +// Copyright (c) 2018 Thomas Pornin +// 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 +} diff --git a/core/crypto/rsa/rsa_int.odin b/core/crypto/rsa/rsa_int.odin new file mode 100644 index 000000000..1e84c1fcb --- /dev/null +++ b/core/crypto/rsa/rsa_int.odin @@ -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 +} diff --git a/core/crypto/rsa/rsa_keygen.odin b/core/crypto/rsa/rsa_keygen.odin new file mode 100644 index 000000000..c0ca12043 --- /dev/null +++ b/core/crypto/rsa/rsa_keygen.odin @@ -0,0 +1,367 @@ +package rsa + +// Copyright (c) 2018 Thomas Pornin +// 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.. 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 +} diff --git a/core/crypto/rsa/rsa_mgf1.odin b/core/crypto/rsa/rsa_mgf1.odin new file mode 100644 index 000000000..70500c65a --- /dev/null +++ b/core/crypto/rsa/rsa_mgf1.odin @@ -0,0 +1,49 @@ +package rsa + +// Copyright (c) 2018 Thomas Pornin +// 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/hash" +import "core:encoding/endian" + +@(private) +mgf1_xor :: proc(data: []byte, hash_algo: hash.Algorithm, seed: []byte) { + tmp: [hash.MAX_DIGEST_SIZE]byte = --- + ctx: hash.Context = --- + + buf, blen := data, len(data) + hlen := hash.DIGEST_SIZES[hash_algo] + digest := tmp[:hlen] + for u, c := int(0), u32(0); u < blen; u, c = u + hlen, c + 1 { + hash.init(&ctx, hash_algo) + hash.update(&ctx, seed) + endian.unchecked_put_u32be(tmp[:], c) + hash.update(&ctx, tmp[:4]) + hash.final(&ctx, digest) + for v in 0..= blen { + break + } + buf[u + v] ~= digest[v] + } + } +} diff --git a/core/crypto/rsa/rsa_modpow_priv.odin b/core/crypto/rsa/rsa_modpow_priv.odin new file mode 100644 index 000000000..2de4ffb39 --- /dev/null +++ b/core/crypto/rsa/rsa_modpow_priv.odin @@ -0,0 +1,165 @@ +package rsa + +// Copyright (c) 2016 Thomas Pornin +// 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 "core:slice" + +@(private, require_results) +private_modpow :: proc(x: []byte, sk: ^Private_Key) -> u32 { + U :: (2 + ((FACTOR_MAX_SIZE + 30) / 31)) + TLEN :: (4 * U) // TLEN is counted in 64-bit words + + ensure(sk._is_initialized, "crypto/rsa: uninitialized private key") + + // Compute the actual lengths of p and q, in bytes. + // These lengths are not considered secret (we cannot really hide + // them anyway in constant-time code). + // + // Note/yawning: The factors should already be the correct size, + // with leading `0x00`s stripped. + p := factor_bytes(&sk._p) + plen := len(p) + for plen > 0 && p[0] == 0 { + p = p[1:] + plen -= 1 + } + q := factor_bytes(&sk._q) + qlen := len(q) + for qlen > 0 && q[0] == 0 { + q = q[1:] + qlen -= 1 + } + + // Compute the maximum factor length, in 31-bit words. + z := max(plen, qlen) << 3 + fwlen := 1 + for z > 0 { + z -= 31 + fwlen += 1 + } + + // Convert size to 62-bit words. + fwlen = (fwlen + 1) >> 1 + + // We need to fit at least 6 values in the stack buffer. + if 6 * fwlen > TLEN { + return 0 + } + + // Compute signature length (in bytes). + xlen := modulus_len(&sk._pub_key._n) + + tmp_: [TLEN]u64 // WARNING: This must be zeroed out. + defer crypto.zero_explicit(&tmp_, size_of(tmp_)) + tmp := tmp_[:] + + // Decode q. + mq := slice.reinterpret([]u32, tmp) + bigint.i31_decode(mq, q) + + // Decode p. + t1 := slice.reinterpret([]u32, tmp[fwlen:]) + bigint.i31_decode(t1, p) + + // Upstream recomputes the public modulus n, but we can just + // decode it as our key representation stores all PKCS#1 + // private key values, + t2 := slice.reinterpret([]u32, tmp[2*fwlen:]) + bigint.i31_decode(t2, modulus_bytes(&sk._pub_key._n)) + + // We encode the modulus into bytes, to perform the comparison + // with bytes. We know that the product length, in bytes, is + // exactly xlen. + // The comparison actually computes the carry when subtracting + // the modulus from the source value; that carry must be 1 for + // a value in the correct range. We keep it in r, which is our + // accumulator for the error code. + m_buf := slice.reinterpret([]byte, tmp[4*fwlen:]) + bigint.i31_encode(m_buf[:xlen], t2) + u := xlen + r: u32 + for u > 0 { + u -= 1 + wn := u32(m_buf[u]) + wx := u32(x[u]) + r = ((wx - (wn + r)) >> 8) & 1 + } + + // Move the decoded p to another temporary buffer. + mp := t2 + copy(mp, t1[:2*fwlen]) + + // Compute s2 = x^dq mod q. + q0i := bigint.i31_ninv31(mq[1]) + s2 := t1 + bigint.i31_decode_reduce(s2, x, mq) + r &= bigint.i62_modpow_opt(s2, factor_bytes(&sk._dq), mq, q0i, tmp[3*fwlen:]) + + // Compute s1 = x^dp mod p. + p0i := bigint.i31_ninv31(mp[1]) + s1 := slice.reinterpret([]u32, tmp[3*fwlen:]) + bigint.i31_decode_reduce(s1, x, mp) + r &= bigint.i62_modpow_opt(s1, factor_bytes(&sk._dp), mp, p0i, tmp[4*fwlen:]) + + // Compute: + // h = (s1 - s2)*(1/q) mod p + // s1 is an integer modulo p, but s2 is modulo q. PKCS#1 is + // unclear about whether p may be lower than q (some existing, + // widely deployed implementations of RSA don't tolerate p < q), + // but we want to support that occurrence, so we need to use the + // reduction function. + // + // Since we use br_i31_decode_reduce() for iq (purportedly, the + // inverse of q modulo p), we also tolerate improperly large + // values for this parameter. + t1 = slice.reinterpret([]u32, tmp[4*fwlen:]) + t2 = slice.reinterpret([]u32, tmp[5*fwlen:]) + bigint.i31_reduce(t2, s2, mp) + _ = bigint.i31_add(s1, mp, bigint.i31_sub(s1, t2, 1)) + bigint.i31_to_monty(s1, mp) + bigint.i31_decode_reduce(t1, factor_bytes(&sk._iq), mp) + bigint.i31_montymul(t2, s1, t1, mp, p0i) + + // h is now in t2. We compute the final result: + // s = s2 + q*h + // All these operations are non-modular. + // + // We need mq, s2 and t2. We use the t3 buffer as destination. + // The buffers mp, s1 and t1 are no longer needed, so we can + // reuse them for t3. Moreover, the first step of the computation + // is to copy s2 into t3, after which s2 is not needed. Right + // now, mq is in slot 0, s2 is in slot 1, and t2 is in slot 5. + // Therefore, we have ample room for t3 by simply using s2. + t3 := s2 + bigint.i31_mulacc(t3, mq, t2) + + // Encode the result. Since we already checked the value of xlen, + // we can just use it right away. + bigint.i31_encode(x, t3) + + // The only error conditions remaining at that point are invalid + // values for p and q (even integers). + return p0i & q0i & r +} diff --git a/core/crypto/rsa/rsa_modpow_pub.odin b/core/crypto/rsa/rsa_modpow_pub.odin new file mode 100644 index 000000000..f40bddf29 --- /dev/null +++ b/core/crypto/rsa/rsa_modpow_pub.odin @@ -0,0 +1,89 @@ +package rsa + +// Copyright (c) 2016 Thomas Pornin +// 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 bigint "core:crypto/_bigint" +import "core:encoding/endian" +import "core:slice" + +@(private, require_results) +public_modpow :: proc(x: []byte, pk: ^Public_Key) -> u32 { + TLEN :: (2 * (2 + ((MODULUS_MAX_SIZE + 30) / 31))) + + ensure(pk._is_initialized, "crypto/rsa: uninitialized public key") + + // Get the actual length of the modulus, and see if it fits within + // our stack buffer. We also check that the length of x[] is valid. + // + // Note/yawning: The modulus should already be the correct size, + // with leading `0x00`s stripped. + n := modulus_bytes(&pk._n) + nlen := modulus_len(&pk._n) + for nlen > 0 && n[0] == 0 { + n = n[1:] + nlen -= 1 + } + if nlen == 0 || nlen > (MODULUS_MAX_SIZE >> 3) || len(x) != nlen { + return 0 + } + z := nlen << 3 + fwlen := 1 + for z > 0 { + z -= 31 + fwlen += 1 + } + // Convert fwlen to a count in 62-bit words. + fwlen = (fwlen + 1) >> 1 + + // The modulus gets decoded into m[]. + // The value to exponentiate goes into a[]. + tmp: [TLEN]u64 // WARNING: This must be zeroed out. + m := slice.reinterpret([]u32, tmp[:fwlen]) + a := slice.reinterpret([]u32, tmp[fwlen:2*fwlen]) + + // Decode the modulus. + bigint.i31_decode(m, n) + m0i := bigint.i31_ninv31(m[1]) + + // Note: if m[] is even, then m0i == 0. Otherwise, m0i must be + // an odd integer. + r := m0i & 1 + + // Decode x[] into a[]; we also check that its value is proper. + r &= bigint.i31_decode_mod(a, x, m) + + // Compute the modular exponentiation. + e_: [EXPONENT_MAX_SIZE >> 3]byte + e_off: int + endian.unchecked_put_u32be(e_[:], pk._e) + if e_[0] == 0 { + // `e = 65537` is the most common and sensible value, so this + // is the most sensible value. + e_off = 1 + } + bigint.i62_modpow_opt(a, e_[e_off:], m, m0i, tmp[2*fwlen:]) + + // Encode the result. + bigint.i31_encode(x, a) + return r +} diff --git a/core/crypto/rsa/rsa_sig_pkcs1.odin b/core/crypto/rsa/rsa_sig_pkcs1.odin new file mode 100644 index 000000000..dd01c654b --- /dev/null +++ b/core/crypto/rsa/rsa_sig_pkcs1.odin @@ -0,0 +1,233 @@ +package rsa + +// Copyright (c) 2017 Thomas Pornin +// 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 "core:crypto/hash" + +// PKCS1_HASH_OIDS maps common hash algorithms to the OIDs for +// use with PKCS#1 signatures. +@(rodata) +PKCS1_HASH_OIDS := #partial [hash.Algorithm][]byte { + // WARNING: Legacy verification ONLY. + .Insecure_SHA1 = []byte{ + 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, + }, + .SHA224 = []byte{ + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, + }, + .SHA256 = []byte{ + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + }, + .SHA384 = []byte{ + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, + }, + .SHA512 = []byte{ + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + }, + .SHA512_256 = []byte{ + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06, + }, +} + +@(private="file", rodata) +PKCS1_SELFTEST_DIGEST_SHA256 := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, +} + +// verify_pkcs1 returns true if and only if (⟺) sig is a valid PKCS#1 +// signature by pub_key over msg, hased using hash_algo. If pre_hashed +// is set to true, it is assumed that msg is already hashed. +@(require_results) +verify_pkcs1 :: proc(pub_key: ^Public_Key, hash_algo: hash.Algorithm, msg, sig: []byte, is_prehashed := false) -> bool { + if !pub_key._is_initialized { + return false + } + if len(sig) != modulus_len(&pub_key._n) { + return false + } + + // Lookup the OID. + oid := PKCS1_HASH_OIDS[hash_algo] + if oid == nil { + return false + } + hash_len := hash.DIGEST_SIZES[hash_algo] + + // Compute the message hash. + msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = --- + msg_hash: []byte + switch is_prehashed { + case true: + if len(msg) != hash_len { + return false + } + msg_hash = msg + case false: + msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:]) + } + + // PKCS #1 V2.2 (RFC 8017) 8.2.2 specifies this as computing + // and comparing the padded hash, with unpadding and extracting + // the hash being an alternative. Upstream BearSSL implements + // the latter, which is not a problem if done correctly (which + // it does), however we will opt to go for implementing this + // as specified as it is more robust against implementation + // errors. + + // Compute the expected hash. + sig_buf, padded_hash_buf: [MODULUS_MAX_SIZE >> 3]byte = ---, --- + if len(sig) > len(sig_buf) { + return false + } + padded_hash_ := padded_hash_buf[:len(sig)] + if pkcs1_sig_pad(oid, msg_hash, padded_hash_) != 1 { + return false + } + + // Compute the signature's padded hash. + sig_ := sig_buf[:len(sig)] + copy(sig_, sig) + if public_modpow(sig_, pub_key) != 1 { + return false + } + + return bytes.equal(sig_, padded_hash_) +} + +// sign_pkcs1 returns true if and only if (⟺) it successfully writes +// the PKCS#1 signature by priv_key over msg, hashed using hash_algo. +// If pre_hashed is set to true, it is assumed that msg is already hashed. +@(require_results) +sign_pkcs1 :: proc(priv_key: ^Private_Key, hash_algo: hash.Algorithm, msg, sig: []byte, is_prehashed := false) -> bool { + if !priv_key._is_initialized { + return false + } + if len(sig) != modulus_len(&priv_key._pub_key._n) { + return false + } + + // Lookup the OID. + oid := PKCS1_HASH_OIDS[hash_algo] + if oid == nil { + return false + } + + // Compute the message hash. + msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = --- + msg_hash: []byte + switch is_prehashed { + case true: + if len(msg) != hash.DIGEST_SIZES[hash_algo] { + return false + } + msg_hash = msg + case false: + msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:]) + } + + if pkcs1_sig_pad(oid, msg_hash, sig) != 1 { + return false + } + + return private_modpow(sig, priv_key) == 1 +} + +@(private="file", require_results) +pkcs1_sig_pad :: proc "contextless" (hash_oid, hash, x: []byte) -> u32 { + // Padded hash value has format: + // 00 01 FF .. FF 00 30 x1 30 x2 06 x3 OID 05 00 04 x4 HASH + // + // with the following rules: + // + // -- Total length is equal to the modulus length (unsigned + // encoding). + // + // -- There must be at least eight bytes of value 0xFF. + // + // -- x4 is equal to the hash length (hash_len). + // + // -- x3 is equal to the encoded OID value length (hash_oid[0]). + // + // -- x2 = x3 + 4. + // + // -- x1 = x2 + x4 + 4 = x3 + x4 + 8. + // + // Note: the "05 00" is optional (signatures with and without + // that sequence exist in practice), but notes in PKCS#1 seem to + // indicate that the presence of that sequence (specifically, + // an ASN.1 NULL value for the hash parameters) may be slightly + // more "standard" than the opposite. + xlen, hash_len := len(x), len(hash) + + // Note/yawning: The hash OID is mandatory, as is the "05 00". + x3 := hash_oid[0] + + // Check that there is enough room for all the elements, + // including at least eight bytes of value 0xFF. + if xlen < int(x3) + hash_len + 21 { + return 0 + } + x[0] = 0x00 + x[1] = 0x01 + u := xlen - int(x3) - hash_len - 11 + for i in 2..< u { + x[i] = 0xff + } + x[u] = 0x00 + x[u + 1] = 0x30 + x[u + 2] = x3 + byte(hash_len) + 8 + x[u + 3] = 0x30 + x[u + 4] = x3 + 4 + x[u + 5] = 0x06 + copy(x[u+6:], hash_oid) + u += int(x3) + 7 + x[u] = 0x05 + u += 1 + x[u] = 0x00 + u += 1 + x[u] = 0x04 + u += 1 + x[u] = byte(hash_len) + u += 1 + copy(x[u:], hash) + + return 1 +} + +@(private) +pkcs1_sig_selftest :: proc(priv_key: ^Private_Key) -> bool { + sig_buf: [MODULUS_MAX_SIZE >> 3]byte = --- + defer crypto.zero_explicit(&sig_buf, size_of(sig_buf)) + + sig := sig_buf[:private_key_size(priv_key)] + if !sign_pkcs1(priv_key, .SHA256, PKCS1_SELFTEST_DIGEST_SHA256, sig, true) { + return false + } + + return verify_pkcs1(&priv_key._pub_key, .SHA256, PKCS1_SELFTEST_DIGEST_SHA256, sig, true) +} diff --git a/core/crypto/rsa/rsa_sig_pss.odin b/core/crypto/rsa/rsa_sig_pss.odin new file mode 100644 index 000000000..a2b19e509 --- /dev/null +++ b/core/crypto/rsa/rsa_sig_pss.odin @@ -0,0 +1,293 @@ +package rsa + +// Copyright (c) 2018 Thomas Pornin +// 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 bigint "core:crypto/_bigint" +import subtle "core:crypto/_subtle" +import "core:crypto/hash" + +// verify_pss returns true if and only if (⟺) sig is a valid PSS +// signature by pub_key over msg, hashed using hash_algo, and MGF1 +// parameterized by mgf1_algo and salt_len. If mgf1_algo is +// unspecified, hash_algo will be used. If pre_hashed is set +// to true, it is assumed that msg is already hashed. +@(require_results) +verify_pss :: proc( + pub_key: ^Public_Key, + hash_algo: hash.Algorithm, + salt_len: int, + msg: []byte, + sig: []byte, + is_prehashed := false, + 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(sig) != modulus_len(&pub_key._n) { + return false + } + + // Compute the message hash. + msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = --- + hash_len := hash.DIGEST_SIZES[hash_algo] + msg_hash: []byte + switch is_prehashed { + case true: + if len(msg) != hash_len { + return false + } + msg_hash = msg + case false: + msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:]) + } + + sig_buf: [MODULUS_MAX_SIZE >> 3]byte = --- + sig_ := sig_buf[:len(sig)] + copy(sig_, sig) + if public_modpow(sig_, pub_key) != 1 { + return false + } + + return pss_sig_unpad(hash_algo, mgf1_algo_, msg_hash, salt_len, pub_key, sig_) == 1 +} + +// sign_pss returns true if and only if (⟺) it successfully writes +// the PKCS#1 signature by priv_key over msg, hashed using hash_algo, and +// MGF1 parameterized by mgf1_algo and salt_len. If mgf1_algo is +// unspecified, hash_algo will be used. If pre_hashed is set to true, +// it is assumed that msg is already hashed. A reasonable choice for +// salt_len is the digest size of hash_algo, and FIPS 140-3 mandates +// that as the maximum permissible size. +// +// This routine will fail if the system entropy source is unavailable. +@(require_results) +sign_pss :: proc( + priv_key: ^Private_Key, + hash_algo: hash.Algorithm, + salt_len: int, + msg: []byte, + sig: []byte, + is_prehashed := false, + mgf1_algo := hash.Algorithm.Invalid, +) -> bool { + if !priv_key._is_initialized { + return false + } + if len(sig) != modulus_len(&priv_key._pub_key._n) { + return false + } + if hash_algo == .Invalid { + return false + } + mgf1_algo_ := mgf1_algo + if mgf1_algo == .Invalid { + mgf1_algo_ = hash_algo + } + if !crypto.HAS_RAND_BYTES && salt_len != 0 { + return false + } + + // Compute the message hash. + msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = --- + hash_len := hash.DIGEST_SIZES[hash_algo] + msg_hash: []byte + switch is_prehashed { + case true: + if len(msg) != hash_len { + return false + } + msg_hash = msg + case false: + msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:]) + } + + // Work out the exact length of n in bits. + n := modulus_bytes(&priv_key._pub_key._n) + assert(len(n) > 0 && n[0] != 0) + n_bitlen := int(bigint._u32_bit_length(u32(n[0]))) + (len(n) - 1) * 8 + + if pss_sig_pad(hash_algo, mgf1_algo_, msg_hash, salt_len, n_bitlen, sig) != 1 { + return false + } + + return private_modpow(sig, priv_key) == 1 +} + +@(private="file", require_results) +pss_sig_unpad :: proc( + data_algo: hash.Algorithm, + mgf1_algo: hash.Algorithm, + digest: []byte, + salt_len: int, + pk: ^Public_Key, + sig: []byte, +) -> u32 { + hash_len := hash.DIGEST_SIZES[data_algo] + x := sig + + // Value r will be set to a non-zero value is any test fails. + r: u32 + + // The value bit length (as an integer) must be strictly less than + // that of the modulus. + // + // Note/yawning: The modulus should already be the correct size, + // with leading `0x00`s stripped. + n := modulus_bytes(&pk._n) + nlen := modulus_len(&pk._n) + u: int + for u = 0; u < nlen; u += 1 { + if n[u] != 0 { + break + } + } + if u == nlen { + return 0 + } + n_bitlen := bigint._u32_bit_length(u32(n[u])) + (u32(nlen - u - 1) << 3) + n_bitlen -= 1 + if (n_bitlen & 7) == 0 { + r |= u32(x[0]) + x = x[1:] + } else { + r |= u32(x[0] & (0xFF << (n_bitlen & 7))) + } + xlen := int((n_bitlen + 7) >> 3) + + // Check that the modulus is large enough for the hash value + // length combined with the intended salt length. + if hash_len > xlen || salt_len > xlen || (hash_len + salt_len + 2) > xlen { + return 0 + } + + // Check value of rightmost byte. + r |= u32(x[xlen - 1] ~ 0xBC) + + // Generate the mask and XOR it into the first bytes to reveal PS; + // we must also mask out the leading bits. + seed := x[xlen - hash_len - 1:] + mgf1_xor(x[:xlen - hash_len - 1], mgf1_algo, seed[:hash_len]) + if (n_bitlen & 7) != 0 { + x[0] &= 0xFF >> (8 - (n_bitlen & 7)) + } + + // Check that all padding bytes have the expected value. + for u = 0; u < (xlen - hash_len - salt_len - 2); u += 1 { + r |= u32(x[u]) + } + r |= u32(x[xlen - hash_len - salt_len - 2] ~ 0x01) + + // Recompute H. + salt := x[xlen - hash_len - salt_len - 1:] + tmp: [hash.MAX_DIGEST_SIZE]byte + h := tmp[:hash_len] + ctx: hash.Context = --- + hash.init(&ctx, data_algo) + hash.update(&ctx, tmp[:8]) + hash.update(&ctx, digest) + hash.update(&ctx, salt[:salt_len]) + hash.final(&ctx, h) + + // Check that the recomputed H value matches the one appearing + // in the string. + x = x[xlen - hash_len - 1:] + r |= subtle.eq0(u32(crypto.compare_constant_time(h, x[:hash_len]))) + + return subtle.eq0(r) +} + +@(private="file", require_results) +pss_sig_pad :: proc( + data_algo: hash.Algorithm, + mgf1_algo: hash.Algorithm, + digest: []byte, + salt_len: int, + n_bitlen_: int, + sig: []byte, +) -> u32 { + x, n_bitlen := sig, n_bitlen_ + hash_len := hash.DIGEST_SIZES[data_algo] + + // The padded string is one bit smaller than the modulus; + // notably, if the modulus length is equal to 1 modulo 8, then + // the padded string will be one _byte_ smaller, and the first + // byte will be set to 0. We apply these transformations here. + n_bitlen -= 1 + if (n_bitlen & 7) == 0 { + x[0] = 0 + x = x[1:] + } + xlen := int((n_bitlen + 7) >> 3) + + // Check that the modulus is large enough for the hash value + // length combined with the intended salt length. + if hash_len > xlen || salt_len > xlen || (hash_len + salt_len + 2) > xlen { + return 0 + } + + // Produce a random salt. + salt := x[xlen - hash_len - salt_len - 1:] + salt = salt[:salt_len] + if salt_len != 0 { + crypto.rand_bytes(salt) + } + + // Compute the seed for MGF1. + seed := x[xlen - hash_len - 1:] + seed = seed[:hash_len] + ctx: hash.Context = --- + hash.init(&ctx, data_algo) + intrinsics.mem_zero(raw_data(seed), 8) + hash.update(&ctx, seed[:8]) + hash.update(&ctx, digest) + hash.update(&ctx, salt) + hash.final(&ctx, seed) + + // Prepare string PS (padded salt). The salt is already at the + // right place. + intrinsics.mem_zero(raw_data(x), xlen - salt_len - hash_len - 2) + x[xlen - salt_len - hash_len - 2] = 0x01 + + // Generate the mask and XOR it into PS. + mgf1_xor(x[:xlen - hash_len - 1], mgf1_algo, seed) + + // Clear the top bits to ensure the value is lower than the + // modulus. + x[0] &= 0xFF >> ((u32(xlen) << 3) - u32(n_bitlen)) + + // The seed (H) is already in the right place. We just set the + // last byte. + x[xlen - 1] = 0xBC + + return 1 +} diff --git a/core/crypto/rsa/rsa_test_key.odin b/core/crypto/rsa/rsa_test_key.odin new file mode 100644 index 000000000..28020d21f --- /dev/null +++ b/core/crypto/rsa/rsa_test_key.odin @@ -0,0 +1,180 @@ +package rsa + +// private_key_set_insecure_test sets the private key to the +// pregenerated INSECURE test key "testRSA2048" from RFC 9500 2.1. +// +// WARNING: This key MUST only be used for testing purposes. +@(require_results) +private_key_set_insecure_test :: proc(priv_key: ^Private_Key) -> bool { + // RFC 9500 2.1 "testRSA2048" + return private_key_set_bytes( + priv_key, + // n + []byte{ + 0xB0, 0xF9, 0xE8, 0x19, 0x43, 0xA7, 0xAE, 0x98, + 0x92, 0xAA, 0xDE, 0x17, 0xCA, 0x7C, 0x40, 0xF8, + 0x74, 0x4F, 0xED, 0x2F, 0x81, 0x48, 0xE6, 0xC8, + 0xEA, 0xA2, 0x7B, 0x7D, 0x00, 0x15, 0x48, 0xFB, + 0x51, 0x92, 0xAB, 0x28, 0xB5, 0x6C, 0x50, 0x60, + 0xB1, 0x18, 0xCC, 0xD1, 0x31, 0xE5, 0x94, 0x87, + 0x4C, 0x6C, 0xA9, 0x89, 0xB5, 0x6C, 0x27, 0x29, + 0x6F, 0x09, 0xFB, 0x93, 0xA0, 0x34, 0xDF, 0x32, + 0xE9, 0x7C, 0x6F, 0xF0, 0x99, 0x8C, 0xFD, 0x8E, + 0x6F, 0x42, 0xDD, 0xA5, 0x8A, 0xCD, 0x1F, 0xA9, + 0x79, 0x86, 0xF1, 0x44, 0xF3, 0xD1, 0x54, 0xD6, + 0x76, 0x50, 0x17, 0x5E, 0x68, 0x54, 0xB3, 0xA9, + 0x52, 0x00, 0x3B, 0xC0, 0x68, 0x87, 0xB8, 0x45, + 0x5A, 0xC2, 0xB1, 0x9F, 0x7B, 0x2F, 0x76, 0x50, + 0x4E, 0xBC, 0x98, 0xEC, 0x94, 0x55, 0x71, 0xB0, + 0x78, 0x92, 0x15, 0x0D, 0xDC, 0x6A, 0x74, 0xCA, + 0x0F, 0xBC, 0xD3, 0x54, 0x97, 0xCE, 0x81, 0x53, + 0x4D, 0xAF, 0x94, 0x18, 0x84, 0x4B, 0x13, 0xAE, + 0xA3, 0x1F, 0x9D, 0x5A, 0x6B, 0x95, 0x57, 0xBB, + 0xDF, 0x61, 0x9E, 0xFD, 0x4E, 0x88, 0x7F, 0x2D, + 0x42, 0xB8, 0xDD, 0x8B, 0xC9, 0x87, 0xEA, 0xE1, + 0xBF, 0x89, 0xCA, 0xB8, 0x5E, 0xE2, 0x1E, 0x35, + 0x63, 0x05, 0xDF, 0x6C, 0x07, 0xA8, 0x83, 0x8E, + 0x3E, 0xF4, 0x1C, 0x59, 0x5D, 0xCC, 0xE4, 0x3D, + 0xAF, 0xC4, 0x91, 0x23, 0xEF, 0x4D, 0x8A, 0xBB, + 0xA9, 0x3D, 0x39, 0x05, 0xE4, 0x02, 0x8D, 0x7B, + 0xA9, 0x14, 0x84, 0xA2, 0x75, 0x96, 0xE0, 0x7B, + 0x4B, 0x6E, 0xD9, 0x92, 0xF0, 0x77, 0xB5, 0x24, + 0xD3, 0xDC, 0xFE, 0x7D, 0xDD, 0x55, 0x49, 0xBE, + 0x7C, 0xCE, 0x8D, 0xA0, 0x35, 0xCF, 0xA0, 0xB3, + 0xFB, 0x8F, 0x9E, 0x46, 0xF7, 0x32, 0xB2, 0xA8, + 0x6B, 0x46, 0x01, 0x65, 0xC0, 0x8F, 0x53, 0x13, + }, + // e + []byte{0x01, 0x00, 0x01}, + // d + []byte{ + 0x41, 0x18, 0x8B, 0x20, 0xCF, 0xDB, 0xDB, 0xC2, + 0xCF, 0x1F, 0xFE, 0x75, 0x2D, 0xCB, 0xAA, 0x72, + 0x39, 0x06, 0x35, 0x2E, 0x26, 0x15, 0xD4, 0x9D, + 0xCE, 0x80, 0x59, 0x7F, 0xCF, 0x0A, 0x05, 0x40, + 0x3B, 0xEF, 0x00, 0xFA, 0x06, 0x51, 0x82, 0xF7, + 0x2D, 0xEC, 0xFB, 0x59, 0x6F, 0x4B, 0x0C, 0xE8, + 0xFF, 0x59, 0x70, 0xBA, 0xF0, 0x7A, 0x89, 0xA5, + 0x19, 0xEC, 0xC8, 0x16, 0xB2, 0xF4, 0xFF, 0xAC, + 0x50, 0x69, 0xAF, 0x1B, 0x06, 0xBF, 0xEF, 0x7B, + 0xF6, 0xBC, 0xD7, 0x9E, 0x4E, 0x81, 0xC8, 0xC5, + 0xA3, 0xA7, 0xD9, 0x13, 0x0D, 0xC3, 0xCF, 0xBA, + 0xDA, 0xE5, 0xF6, 0xD2, 0x88, 0xF9, 0xAE, 0xE3, + 0xF6, 0xFF, 0x92, 0xFA, 0xE0, 0xF8, 0x1A, 0xF5, + 0x97, 0xBE, 0xC9, 0x6A, 0xE9, 0xFA, 0xB9, 0x40, + 0x2C, 0xD5, 0xFE, 0x41, 0xF7, 0x05, 0xBE, 0xBD, + 0xB4, 0x7B, 0xB7, 0x36, 0xD3, 0xFE, 0x6C, 0x5A, + 0x51, 0xE0, 0xE2, 0x07, 0x32, 0xA9, 0x7B, 0x5E, + 0x46, 0xC1, 0xCB, 0xDB, 0x26, 0xD7, 0x48, 0x54, + 0xC6, 0xB6, 0x60, 0x4A, 0xED, 0x46, 0x37, 0x35, + 0xFF, 0x90, 0x76, 0x04, 0x65, 0x57, 0xCA, 0xF9, + 0x49, 0xBF, 0x44, 0x88, 0x95, 0xC2, 0x04, 0x32, + 0xC1, 0xE0, 0x9C, 0x01, 0x4E, 0xA7, 0x56, 0x60, + 0x43, 0x4F, 0x1A, 0x0F, 0x3B, 0xE2, 0x94, 0xBA, + 0xBC, 0x5D, 0x53, 0x0E, 0x6A, 0x10, 0x21, 0x3F, + 0x53, 0xB6, 0x03, 0x75, 0xFC, 0x84, 0xA7, 0x57, + 0x3F, 0x2A, 0xF1, 0x21, 0x55, 0x84, 0xF5, 0xB4, + 0xBD, 0xA6, 0xD4, 0xE8, 0xF9, 0xE1, 0x7A, 0x78, + 0xD9, 0x7E, 0x77, 0xB8, 0x6D, 0xA4, 0xA1, 0x84, + 0x64, 0x75, 0x31, 0x8A, 0x7A, 0x10, 0xA5, 0x61, + 0x01, 0x4E, 0xFF, 0xA2, 0x3A, 0x81, 0xEC, 0x56, + 0xE9, 0xE4, 0x10, 0x9D, 0xEF, 0x8C, 0xB3, 0xF7, + 0x97, 0x22, 0x3F, 0x7D, 0x8D, 0x0D, 0x43, 0x51, + }, + // p + []byte{ + 0xDD, 0x10, 0x57, 0x02, 0x38, 0x2F, 0x23, 0x2B, + 0x36, 0x81, 0xF5, 0x37, 0x91, 0xE2, 0x26, 0x17, + 0xC7, 0xBF, 0x4E, 0x9A, 0xCB, 0x81, 0xED, 0x48, + 0xDA, 0xF6, 0xD6, 0x99, 0x5D, 0xA3, 0xEA, 0xB6, + 0x42, 0x83, 0x9A, 0xFF, 0x01, 0x2D, 0x2E, 0xA6, + 0x28, 0xB9, 0x0A, 0xF2, 0x79, 0xFD, 0x3E, 0x6F, + 0x7C, 0x93, 0xCD, 0x80, 0xF0, 0x72, 0xF0, 0x1F, + 0xF2, 0x44, 0x3B, 0x3E, 0xE8, 0xF2, 0x4E, 0xD4, + 0x69, 0xA7, 0x96, 0x13, 0xA4, 0x1B, 0xD2, 0x40, + 0x20, 0xF9, 0x2F, 0xD1, 0x10, 0x59, 0xBD, 0x1D, + 0x0F, 0x30, 0x1B, 0x5B, 0xA7, 0xA9, 0xD3, 0x63, + 0x7C, 0xA8, 0xD6, 0x5C, 0x1A, 0x98, 0x15, 0x41, + 0x7D, 0x8E, 0xAB, 0x73, 0x4B, 0x0B, 0x4F, 0x3A, + 0x2C, 0x66, 0x1D, 0x9A, 0x1A, 0x82, 0xF3, 0xAC, + 0x73, 0x4C, 0x40, 0x53, 0x06, 0x69, 0xAB, 0x8E, + 0x47, 0x30, 0x45, 0xA5, 0x8E, 0x65, 0x53, 0x9D, + }, + // q + []byte{ + 0xCC, 0xF1, 0xE5, 0xBB, 0x90, 0xC8, 0xE9, 0x78, + 0x1E, 0xA7, 0x5B, 0xEB, 0xF1, 0x0B, 0xC2, 0x52, + 0xE1, 0x1E, 0xB0, 0x23, 0xA0, 0x26, 0x0F, 0x18, + 0x87, 0x55, 0x2A, 0x56, 0x86, 0x3F, 0x4A, 0x64, + 0x21, 0xE8, 0xC6, 0x00, 0xBF, 0x52, 0x3D, 0x6C, + 0xB1, 0xB0, 0xAD, 0xBD, 0xD6, 0x5B, 0xFE, 0xE4, + 0xA8, 0x8A, 0x03, 0x7E, 0x3D, 0x1A, 0x41, 0x5E, + 0x5B, 0xB9, 0x56, 0x48, 0xDA, 0x5A, 0x0C, 0xA2, + 0x6B, 0x54, 0xF4, 0xA6, 0x39, 0x48, 0x52, 0x2C, + 0x3D, 0x5F, 0x89, 0xB9, 0x4A, 0x72, 0xEF, 0xFF, + 0x95, 0x13, 0x4D, 0x59, 0x40, 0xCE, 0x45, 0x75, + 0x8F, 0x30, 0x89, 0x80, 0x90, 0x89, 0x56, 0x58, + 0x8E, 0xEF, 0x57, 0x5B, 0x3E, 0x4B, 0xC4, 0xC3, + 0x68, 0xCF, 0xE8, 0x13, 0xEE, 0x9C, 0x25, 0x2C, + 0x2B, 0x02, 0xE0, 0xDF, 0x91, 0xF1, 0xAA, 0x01, + 0x93, 0x8D, 0x38, 0x68, 0x5D, 0x60, 0xBA, 0x6F, + }, + // dp + []byte{ + 0x09, 0xED, 0x54, 0xEA, 0xED, 0x98, 0xF8, 0x4C, + 0x55, 0x7B, 0x4A, 0x86, 0xBF, 0x4F, 0x57, 0x84, + 0x93, 0xDC, 0xBC, 0x6B, 0xE9, 0x1D, 0xA1, 0x89, + 0x37, 0x04, 0x04, 0xA9, 0x08, 0x72, 0x76, 0xF4, + 0xCE, 0x51, 0xD8, 0xA1, 0x00, 0xED, 0x85, 0x7D, + 0xC2, 0xB0, 0x64, 0x94, 0x74, 0xF3, 0xF1, 0x5C, + 0xD2, 0x4C, 0x54, 0xDB, 0x28, 0x71, 0x10, 0xE5, + 0x6E, 0x5C, 0xB0, 0x08, 0x68, 0x2F, 0x91, 0x68, + 0xAA, 0x81, 0xF3, 0x14, 0x58, 0xB7, 0x43, 0x1E, + 0xCC, 0x1C, 0x44, 0x90, 0x6F, 0xDA, 0x87, 0xCA, + 0x89, 0x47, 0x10, 0xC3, 0x71, 0xE9, 0x07, 0x6C, + 0x1D, 0x49, 0xFB, 0xAE, 0x51, 0x27, 0x69, 0x34, + 0xF2, 0xAD, 0x78, 0x77, 0x89, 0xF4, 0x2D, 0x0F, + 0xA0, 0xB4, 0xC9, 0x39, 0x85, 0x5D, 0x42, 0x12, + 0x09, 0x6F, 0x70, 0x28, 0x0A, 0x4E, 0xAE, 0x7C, + 0x8A, 0x27, 0xD9, 0xC8, 0xD0, 0x77, 0x2E, 0x65, + }, + // dq + []byte{ + 0x8C, 0xB6, 0x85, 0x7A, 0x7B, 0xD5, 0x46, 0x5F, + 0x80, 0x04, 0x7E, 0x9B, 0x87, 0xBC, 0x00, 0x27, + 0x31, 0x84, 0x05, 0x81, 0xE0, 0x62, 0x61, 0x39, + 0x01, 0x2A, 0x5B, 0x50, 0x5F, 0x0A, 0x33, 0x84, + 0x7E, 0xB7, 0xB8, 0xC3, 0x28, 0x99, 0x49, 0xAD, + 0x48, 0x6F, 0x3B, 0x4B, 0x3D, 0x53, 0x9A, 0xB5, + 0xDA, 0x76, 0x30, 0x21, 0xCB, 0xC8, 0x2C, 0x1B, + 0xA2, 0x34, 0xA5, 0x66, 0x8D, 0xED, 0x08, 0x01, + 0xB8, 0x59, 0xF3, 0x43, 0xF1, 0xCE, 0x93, 0x04, + 0xE6, 0xFA, 0xA2, 0xB0, 0x02, 0xCA, 0xD9, 0xB7, + 0x8C, 0xDE, 0x5C, 0xDC, 0x2C, 0x1F, 0xB4, 0x17, + 0x1C, 0x42, 0x42, 0x16, 0x70, 0xA6, 0xAB, 0x0F, + 0x50, 0xCC, 0x4A, 0x19, 0x4E, 0xB3, 0x6D, 0x1C, + 0x91, 0xE9, 0x35, 0xBA, 0x01, 0xB9, 0x59, 0xD8, + 0x72, 0x8B, 0x9E, 0x64, 0x42, 0x6B, 0x3F, 0xC3, + 0xA7, 0x50, 0x6D, 0xEB, 0x52, 0x39, 0xA8, 0xA7, + }, + // iq (aka u) + []byte{ + 0x0A, 0x81, 0xD8, 0xA6, 0x18, 0x31, 0x4A, 0x80, + 0x3A, 0xF6, 0x1C, 0x06, 0x71, 0x1F, 0x2C, 0x39, + 0xB2, 0x66, 0xFF, 0x41, 0x4D, 0x53, 0x47, 0x6D, + 0x1D, 0xA5, 0x2A, 0x43, 0x18, 0xAA, 0xFE, 0x4B, + 0x96, 0xF0, 0xDA, 0x07, 0x15, 0x5F, 0x8A, 0x51, + 0x34, 0xDA, 0xB8, 0x8E, 0xE2, 0x9E, 0x81, 0x68, + 0x07, 0x6F, 0xCD, 0x78, 0xCA, 0x79, 0x1A, 0xC6, + 0x34, 0x42, 0xA8, 0x1C, 0xD0, 0x69, 0x39, 0x27, + 0xD8, 0x08, 0xE3, 0x35, 0xE8, 0xD8, 0xCB, 0xF2, + 0x12, 0x19, 0x07, 0x50, 0x9A, 0x57, 0x75, 0x9B, + 0x4F, 0x9A, 0x18, 0xFA, 0x3A, 0x7B, 0x33, 0x37, + 0x79, 0xED, 0xDE, 0x7A, 0x45, 0x93, 0x84, 0xF8, + 0x44, 0x4A, 0xDA, 0xEC, 0xFF, 0xEC, 0x95, 0xFD, + 0x55, 0x2B, 0x0C, 0xFC, 0xB6, 0xC7, 0xF6, 0x92, + 0x62, 0x6D, 0xDE, 0x1E, 0xF2, 0x68, 0xA4, 0x0D, + 0x2F, 0x67, 0xB5, 0xC8, 0xAA, 0x38, 0x7F, 0xF7, + }, + ) +} \ No newline at end of file diff --git a/examples/all/all_js.odin b/examples/all/all_js.odin index 8dbc320d0..10d2595a0 100644 --- a/examples/all/all_js.odin +++ b/examples/all/all_js.odin @@ -49,6 +49,7 @@ package all @(require) import "core:crypto/pbkdf2" @(require) import "core:crypto/poly1305" @(require) import "core:crypto/ristretto255" +@(require) import "core:crypto/rsa" @(require) import "core:crypto/sha2" @(require) import "core:crypto/sha3" @(require) import "core:crypto/shake" diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index b8655a89e..50466173e 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -54,6 +54,7 @@ package all @(require) import "core:crypto/pbkdf2" @(require) import "core:crypto/poly1305" @(require) import "core:crypto/ristretto255" +@(require) import "core:crypto/rsa" @(require) import "core:crypto/sha2" @(require) import "core:crypto/sha3" @(require) import "core:crypto/shake" diff --git a/tests/benchmark/crypto/benchmark_rsa.odin b/tests/benchmark/crypto/benchmark_rsa.odin new file mode 100644 index 000000000..5954662fc --- /dev/null +++ b/tests/benchmark/crypto/benchmark_rsa.odin @@ -0,0 +1,160 @@ +package benchmark_core_crypto + +import "base:runtime" +import "core:log" +import "core:testing" +import "core:text/table" +import "core:time" + +import "core:crypto" +import "core:crypto/rsa" + +// RSA key generation is time consuming and high variance, so it takes +// an unreasonable amount of time to get a semi-sensible value, so this +// is skipped by default. +RSA_BENCH_KEYGEN: bool : #config(ODIN_BENCHMARK_RSA_KEYGEN, false) + +@(private = "file") +KEYGEN_ITERS :: 100 +@(private = "file") +SIGN_ITERS :: 5000 +@(private = "file") +ENCRYPT_ITERS :: 5000 + +@(test) +benchmark_crypto_rsa :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + tbl: table.Table + table.init(&tbl) + defer table.destroy(&tbl) + + table.caption(&tbl, "RSA") + table.aligned_header_of_values(&tbl, .Right, "Operation", "Avg. Time") + + if RSA_BENCH_KEYGEN { + bench_keygen_2048(&tbl) + table.row_of_values(&tbl) + } + bench_pkcs1_2048(&tbl) + table.row_of_values(&tbl) + bench_pss_2048(&tbl) + table.row_of_values(&tbl) + bench_oaep_2048(&tbl) + + log_table(&tbl) +} + +@(private="file") +bench_keygen_2048 :: proc(tbl: ^table.Table) { + if !crypto.HAS_RAND_BYTES { + log.warnf("rsa: keygen benchmarks skipped, no system entropy source") + } + + priv_key: rsa.Private_Key + start := time.tick_now() + for _ in 0 ..< KEYGEN_ITERS { + ok := rsa.private_key_generate(&priv_key, 2048) + assert(ok, "keygen should succeed") + } + taken := time.tick_since(start) / KEYGEN_ITERS + + append_tbl(tbl, "Keygen/2048", taken) +} + +@(private="file") +bench_pkcs1_2048 :: proc(tbl: ^table.Table) { + priv_key: rsa.Private_Key + _ = rsa.private_key_set_insecure_test(&priv_key) + + msg_bytes := transmute([]byte)(SIG_MSG) + sig_bytes: [2048 >> 3]byte + + start := time.tick_now() + for _ in 0 ..< SIGN_ITERS { + ok := rsa.sign_pkcs1(&priv_key, .SHA256, msg_bytes, sig_bytes[:]) + assert(ok, "signing should succeed") + } + taken := time.tick_since(start) / SIGN_ITERS + + append_tbl(tbl, "PKCS1/2048/SHA256/sign", taken) + + start = time.tick_now() + for _ in 0 ..< KEYGEN_ITERS { + ok := rsa.verify_pkcs1(&priv_key._pub_key, .SHA256, msg_bytes, sig_bytes[:]) + assert(ok, "verify should succeed") + } + taken = time.tick_since(start) / SIGN_ITERS + + append_tbl(tbl, "PKCS1/2048/SHA256/verify", taken) +} + +@(private="file") +bench_pss_2048 :: proc(tbl: ^table.Table) { + priv_key: rsa.Private_Key + _ = rsa.private_key_set_insecure_test(&priv_key) + + msg_bytes := transmute([]byte)(SIG_MSG) + sig_bytes: [2048 >> 3]byte + + start := time.tick_now() + for _ in 0 ..< SIGN_ITERS { + ok := rsa.sign_pss(&priv_key, .SHA256, 32, msg_bytes, sig_bytes[:]) + assert(ok, "signing should succeed") + } + taken := time.tick_since(start) / SIGN_ITERS + + append_tbl(tbl, "PSS/2048/SHA256/sign", taken) + + start = time.tick_now() + for _ in 0 ..< KEYGEN_ITERS { + ok := rsa.verify_pss(&priv_key._pub_key, .SHA256, 32, msg_bytes, sig_bytes[:]) + assert(ok, "verify should succeed") + } + taken = time.tick_since(start) / SIGN_ITERS + + append_tbl(tbl, "PSS/2048/SHA256/verify", taken) +} + +@(private="file") +bench_oaep_2048 :: proc(tbl: ^table.Table) { + if !crypto.HAS_RAND_BYTES { + log.info("rand_bytes not supported - skipping") + return + } + + priv_key: rsa.Private_Key + _ = rsa.private_key_set_insecure_test(&priv_key) + + msg_bytes := transmute([]byte)(SIG_MSG) + ciphertext_bytes: [2048 >> 3]byte + buf: [32]byte + + start := time.tick_now() + for _ in 0 ..< SIGN_ITERS { + ok := rsa.encrypt_oaep(&priv_key._pub_key, .SHA256, msg_bytes, ciphertext_bytes[:]) + assert(ok, "encryption should succeed") + } + taken := time.tick_since(start) / ENCRYPT_ITERS + + append_tbl(tbl, "OAEP/2048/SHA256/encrypt", taken) + + start = time.tick_now() + for _ in 0 ..< KEYGEN_ITERS { + _, ok := rsa.decrypt_oaep(&priv_key, .SHA256, ciphertext_bytes[:], buf[:]) + assert(ok, "decrypt should succeed") + } + taken = time.tick_since(start) / ENCRYPT_ITERS + + append_tbl(tbl, "OAEP/2048/SHA256/decrypt", taken) +} + +@(private="file") +append_tbl :: proc(tbl: ^table.Table, op_name: string, avg_time: time.Duration) { + table.aligned_row_of_values( + tbl, + .Right, + op_name, + table.format(tbl, "%8M", avg_time), + ) +} diff --git a/tests/core/crypto/bigint/int.odin b/tests/core/crypto/bigint/int.odin new file mode 100644 index 000000000..c7db6c09a --- /dev/null +++ b/tests/core/crypto/bigint/int.odin @@ -0,0 +1,310 @@ +// Tests for the constant time RSA primitives +package test_core_crypto_bigint + +import "base:runtime" +import "core:crypto/_bigint" +import "core:log" +import "core:slice" +import "core:testing" + +ROUNDS :: 100_000 + +i31_equal :: proc(a, b: []u32) -> bool { + if a[0] != b[0] { return false } + + bits := uint(a[0]) + idx := 1 + for bits > 0 { + ex := min(bits, 31) + mask := u32(1< N || len(v.b) > N || len(v.res) > N { + log.infof("Skipped %v, not enough scratch space", v) + continue + } + if !(len(v.a) == len(v.b) && len(v.b) == len(v.res)) { + log.infof("Skipped %v, expected `a`, `b` and `res` lengths to be equal", v) + continue + } + + // Copy into writable memory + copy(res[:], v.a[:]) + + // Add b to "a" in place + cc := _bigint.i31_add(res[:], v.b[:], 1) + + testing.expect(t, slice.equal(res[:len(v.res)], v.res)) + testing.expect_value(t, cc, v.carry) + } +} + +@(test) +i31_sub :: proc(t: ^testing.T) { + N :: 5 + res: [N]u32 + + for v in i31_sub_test_vectors { + if len(v.a) > N || len(v.b) > N || len(v.res) > N { + log.infof("Skipped %v, not enough scratch space", v) + continue + } + if !(len(v.a) == len(v.b) && len(v.b) == len(v.res)) { + log.infof("Skipped %v, expected `a`, `b` and `res` lengths to be equal", v) + continue + } + + // Copy into writable memory + copy(res[:], v.a[:]) + + // Add b to "a" in place + cc := _bigint.i31_sub(res[:], v.b[:], 1) + + testing.expect(t, slice.equal(res[:len(v.res)], v.res)) + testing.expect_value(t, cc, v.carry) + } +} + +@(test) +i31_bit_length :: proc (t: ^testing.T) { + for v in i31_add_test_vectors { + a_len := _bigint.i31_bit_length(v.a[1:]) + b_len := _bigint.i31_bit_length(v.b[1:]) + + testing.expect_value(t, a_len, v.a[0]) + testing.expect_value(t, b_len, v.b[0]) + } +} + +@(test) +i31_decode :: proc(t: ^testing.T) { + N :: 10 + res: [N]u32 + + mod := []u32{42, 0x7fff_fffe, 0x3ff, 0} + + for v in i31_decode_test_vectors { + if len(v.decode) > N || len(v.mod) > N { + log.infof("Skipped %v, not enough scratch space", v) + continue + } + + _bigint.i31_decode(res[:], v.src) + testing.expect(t, slice.equal(res[:len(v.decode)], v.decode)) + + slice.zero(res[:]) + + mod_res := _bigint.i31_decode_mod(res[:], v.src, mod) + testing.expect(t, slice.equal(res[:len(v.mod)], v.mod)) + testing.expect_value(t, mod_res, v.mod_res) + + encoded: [32]u8 = 0 + _bigint.i31_encode(encoded[:], v.decode) + testing.expect(t, slice.equal(encoded[32 - len(v.src):], v.src)) + } +} + +@(test) +i31_rshift :: proc(t: ^testing.T) { + N :: 4 + res: [N]u32 + for v in i31_rshift_test_vectors { + if len(v.orig) > N || len(v.res) > N { + log.infof("Skipped %v, not enough scratch space", v) + continue + } + + if v.shift < 0 || v.shift > 31 { + log.infof("Skipped %v, invalid shift amount", v) + continue + } + + copy(res[:], v.orig) + _bigint.i31_rshift(res[:], v.shift) + testing.expect(t, slice.equal(res[:len(v.res)], v.res)) + } +} + +@(test) +i31_reduce :: proc(t: ^testing.T) { + N :: 12 + res: [N]u32 = --- + mod := []u32{42, 0x7fff_fffe, 0x3ff, 0} + + for v in i31_reduce_test_vectors { + if len(v.orig) > N || len(v.res) > N { + log.infof("Skipped %v, not enough scratch space", v) + continue + } + + slice.zero(res[:]) + _bigint.i31_reduce(res[:], v.orig, mod) + testing.expect(t, i31_equal(res[:], v.res)) + } +} + +@(test) +i31_decode_reduce :: proc(t: ^testing.T) { + N :: 4 + res: [N]u32 = 0 + mod := []u32{42, 0x7fff_fffe, 0x3ff, 0} + + for v in i31_decode_reduce_test_vectors { + if len(v.decode) > N { + log.infof("Skipped %v, not enough scratch space", v) + continue + } + + res = 0 + _bigint.i31_decode_reduce(res[:], v.src, mod) + testing.expect(t, i31_equal(res[:], v.decode)) + } +} + +@(test) +i31_muladd_small :: proc(t: ^testing.T) { + mod := []u32{42, 0x7fff_fffe, 0x3ff, 0} + + for v in i31_mul_add_test_vectors { + if len(v.orig) > len(mod) || len(v.res) > len(mod) { + log.infof("Skipped %v, not enough scratch space", v) + continue + } + + res: [3]u32 = 0 + copy(res[:], v.orig) + + _bigint.i31_muladd_small(res[:], v.z, mod) + l := len(v.res) + testing.expect(t, slice.equal(res[:l], v.res[:l])) + } +} + +@(test) +i31_encode :: proc(t: ^testing.T) { + for v in i31_encode_test_vectors { + decoded: [10]u32 = 0 + _bigint.i31_decode(decoded[:], v.encoded) + + l := len(v.orig) + testing.expect(t, slice.equal(decoded[:l], v.orig[:l])) + + encoded: [32]u8 = 0 + _bigint.i31_encode(encoded[:], v.orig) + + testing.expect(t, slice.equal(encoded[:], v.encoded)) + } +} + +@(test) +i31_monty_mul :: proc(t: ^testing.T) { + for v in i31_monty_mul_test_vectors { + res: [6]u32 = 0 + + m0i := _bigint.i31_ninv31(v.m[1]) + if m0i == 0 { + log.infof("Expected _bigint.i31_ninv31(%v) to not be 0, m[1] must be even. Skipped.", v.m[1]) + continue + } + + _bigint.i31_montymul(res[:], v.x, v.y, v.m, m0i) + testing.expect(t, slice.equal(res[:], v.res)) + } +} + +@(test) +i31_to_monty :: proc(t: ^testing.T) { + for v in i31_to_monty_test_vectors { + res: [6]u32 = 0 + copy(res[:], v.orig) + + _bigint.i31_to_monty(res[:], v.m) + testing.expect(t, slice.equal(res[:], v.x)) + + m0i := _bigint.i31_ninv31(v.m[1]) + if m0i == 0 { + log.infof("Expected _bigint.i31_ninv31(%v) to not be 0, m[1] must be even. Skipped.", v.m[1]) + continue + } + + _bigint.i31_from_monty(res[:], v.m, m0i) + testing.expect(t, slice.equal(res[:], v.orig)) + } +} + +@(test) +i31_modpow :: proc(t: ^testing.T) { + for v in i31_mod_pow_test_vectors { + x_out: [6]u32 + temp: [100]u32 + + copy(x_out[:], v.orig) + + m0i := _bigint.i31_ninv31(v.m[1]) + assert(m0i != 0) + _bigint.i31_modpow(x_out[:], v.e, v.m, m0i, temp[:6], temp[6:][:6]) + + testing.expect(t, slice.equal(x_out[:], v.x)) + } +} + +@(test) +i31_mulacc :: proc(t: ^testing.T) { + for v in i31_mul_acc_test_vectors { + res: [12]u32 = 0 + copy(res[:], v.d) + + assert(v.d[0] == v.a[0]) + + _bigint.i31_mulacc(res[:], v.a, v.b) + + testing.expect(t, slice.equal(res[:], v.res)) + } +} + +@(test) +internal_div_rem_u32 :: proc(t: ^testing.T) { + for v in i31_div_rem_test_vectors { + den := u64(v.hi) << 32 + u64(v.lo) + res := u64(v.quo) * u64(v.div) + u64(v.rem) + assert(den == res) + + quo, rem := _bigint.div_rem_u32(v.hi, v.lo, v.div) + testing.expect_value(t, quo, v.quo) + testing.expect_value(t, rem, v.rem) + } +} diff --git a/tests/core/crypto/bigint/test_vectors.odin b/tests/core/crypto/bigint/test_vectors.odin new file mode 100644 index 000000000..94cb41013 --- /dev/null +++ b/tests/core/crypto/bigint/test_vectors.odin @@ -0,0 +1,577 @@ +package test_core_crypto_bigint + +// Generated using BearSSL bindings. + +I31_Test_Vector_Binary :: struct { + a: []u32, + b: []u32, + res: []u32, + carry: u32, +} + +@(rodata) +i31_add_test_vectors := []I31_Test_Vector_Binary { + {a = {127, 0x051558fb, 0x578cb6cf, 0x3d2097aa, 0x7512fbf6}, b = {127, 0x6b2123f1, 0x4ad77a17, 0x705c1500, 0x508d965d}, res = {127, 0x70367cec, 0x226430e6, 0x2d7cacab, 0x45a09254}, carry = 1}, + {a = {127, 0x0248fd90, 0x7825baae, 0x6de2183c, 0x4f5d0505}, b = {125, 0x492ba26a, 0x1bc7df54, 0x5114950e, 0x1ae989b2}, res = {127, 0x4b749ffa, 0x13ed9a02, 0x3ef6ad4b, 0x6a468eb8}, carry = 0}, + {a = {126, 0x4833d6ef, 0x37a112d4, 0x2f71a920, 0x3093d1c3}, b = {127, 0x20574627, 0x39305532, 0x20b76825, 0x6e2eeea3}, res = {126, 0x688b1d16, 0x70d16806, 0x50291145, 0x1ec2c066}, carry = 1}, + {a = {127, 0x55fb7b29, 0x21bef5c8, 0x67fe29e8, 0x798dabac}, b = {127, 0x274221db, 0x562cbe9c, 0x785bf5dc, 0x7058bbfa}, res = {127, 0x7d3d9d04, 0x77ebb464, 0x605a1fc4, 0x69e667a7}, carry = 1}, + {a = {127, 0x2ffa533d, 0x59e20331, 0x59323704, 0x42cf6e75}, b = {127, 0x7ac812c0, 0x32ca1723, 0x15abc385, 0x410265b0}, res = {127, 0x2ac265fd, 0x0cac1a55, 0x6eddfa8a, 0x03d1d425}, carry = 1}, + {a = {126, 0x789b52f9, 0x6535ff6a, 0x2744cf6e, 0x2e4342a6}, b = {127, 0x32dd2f41, 0x64d80309, 0x1ae80d4b, 0x405d1799}, res = {126, 0x2b78823a, 0x4a0e0274, 0x422cdcba, 0x6ea05a3f}, carry = 0}, + {a = {127, 0x62003ba4, 0x2b58e836, 0x4587aced, 0x760bdb36}, b = {126, 0x0201e795, 0x50230e49, 0x30039ab5, 0x36f17032}, res = {127, 0x64022339, 0x7b7bf67f, 0x758b47a2, 0x2cfd4b68}, carry = 1}, + {a = {124, 0x4ef8170f, 0x65e714cc, 0x5500f2a4, 0x090fac28}, b = {127, 0x0a03290f, 0x0d3fafb1, 0x74ebb1b5, 0x7a30e9c6}, res = {124, 0x58fb401e, 0x7326c47d, 0x49eca459, 0x034095ef}, carry = 1}, + {a = {127, 0x34995a2f, 0x58f39080, 0x376ab5ef, 0x555b4fdf}, b = {122, 0x1c0928d9, 0x037fc1ea, 0x35c6d9b0, 0x0206ff68}, res = {127, 0x50a28308, 0x5c73526a, 0x6d318f9f, 0x57624f47}, carry = 0}, + {a = {127, 0x7029eb02, 0x465899f5, 0x71ab22fc, 0x604f1ccf}, b = {124, 0x703e163c, 0x50f72eb1, 0x0a0f0bae, 0x0a2028fe}, res = {127, 0x6068013e, 0x174fc8a7, 0x7bba2eab, 0x6a6f45cd}, carry = 0}, + {a = {125, 0x70cc03f6, 0x4d984b06, 0x4b990b9d, 0x16dd5097}, b = {125, 0x6eed2b72, 0x6001ddd2, 0x12c50807, 0x14f176cb}, res = {125, 0x5fb92f68, 0x2d9a28d9, 0x5e5e13a5, 0x2bcec762}, carry = 0}, + {a = {127, 0x5e6a99af, 0x3ffc66eb, 0x31e47de6, 0x5bcd2f4b}, b = {124, 0x0f6f4335, 0x07d4bf78, 0x61e99233, 0x0fe21745}, res = {127, 0x6dd9dce4, 0x47d12663, 0x13ce1019, 0x6baf4691}, carry = 0}, + {a = {127, 0x45de8b13, 0x010684b5, 0x01c77d90, 0x7464b4c8}, b = {127, 0x672a5a37, 0x33cb5670, 0x4ccc9e85, 0x7c0b88e1}, res = {127, 0x2d08e54a, 0x34d1db26, 0x4e941c15, 0x70703da9}, carry = 1}, + {a = {127, 0x012fe1a4, 0x741e0733, 0x44d3fb4a, 0x79d78453}, b = {126, 0x66d849d4, 0x427b3ff0, 0x5b2b72e8, 0x3c88987d}, res = {127, 0x68082b78, 0x36994723, 0x1fff6e33, 0x36601cd1}, carry = 1}, + {a = {125, 0x3ee54e09, 0x6bc71a68, 0x53cb44c8, 0x1f803699}, b = {124, 0x506ff07c, 0x63b155a0, 0x2c87590f, 0x0c7347e4}, res = {125, 0x0f553e85, 0x4f787009, 0x00529dd8, 0x2bf37e7e}, carry = 0}, + {a = {125, 0x4847d126, 0x4c6af2f2, 0x663970b3, 0x11d97a24}, b = {126, 0x67c8e79a, 0x313d0fc2, 0x786fdac1, 0x23c16f5c}, res = {125, 0x3010b8c0, 0x7da802b5, 0x5ea94b74, 0x359ae981}, carry = 0}, + {a = {127, 0x035a5f5c, 0x5fad2c0b, 0x6df6c2f3, 0x6cf28780}, b = {125, 0x09719328, 0x6adec2fa, 0x07d51f17, 0x1467d4f6}, res = {127, 0x0ccbf284, 0x4a8bef05, 0x75cbe20b, 0x015a5c76}, carry = 1}, + {a = {127, 0x48a484fe, 0x4a2eb1e7, 0x04a04363, 0x52dd3789}, b = {126, 0x136b08b1, 0x4d63762d, 0x52072be9, 0x2f4ef42b}, res = {127, 0x5c0f8daf, 0x17922814, 0x56a76f4d, 0x022c2bb4}, carry = 1}, + {a = {123, 0x119e8d05, 0x36510131, 0x38c3b084, 0x07bfe00d}, b = {127, 0x2ecdd4e9, 0x0afaddad, 0x77058092, 0x6b9f72dd}, res = {123, 0x406c61ee, 0x414bdede, 0x2fc93116, 0x735f52eb}, carry = 0}, + {a = {126, 0x459b051a, 0x6cfaeee6, 0x461ccc17, 0x36a7eb37}, b = {126, 0x53726a35, 0x34443d10, 0x2d29d052, 0x27d37b9d}, res = {126, 0x190d6f4f, 0x213f2bf7, 0x73469c6a, 0x5e7b66d4}, carry = 0}, + {a = {126, 0x73ccf104, 0x352c20d6, 0x580eeeca, 0x28ffb3bc}, b = {126, 0x1aba27fc, 0x31a85479, 0x05e9d689, 0x338a99a8}, res = {126, 0x0e871900, 0x66d47550, 0x5df8c553, 0x5c8a4d64}, carry = 0}, + {a = {127, 0x0220ed88, 0x4ccecbb5, 0x31016917, 0x7bc75c14}, b = {127, 0x19d9b7b6, 0x763136a7, 0x783931cf, 0x58fec945}, res = {127, 0x1bfaa53e, 0x4300025c, 0x293a9ae7, 0x54c6255a}, carry = 1}, + {a = {127, 0x14696b6a, 0x63348532, 0x71d09a50, 0x77ec660c}, b = {126, 0x67251ffd, 0x1f9606e1, 0x1205eb43, 0x32c3b9f7}, res = {127, 0x7b8e8b67, 0x02ca8c13, 0x03d68594, 0x2ab02004}, carry = 1}, + {a = {127, 0x570637d9, 0x22723669, 0x77a7b284, 0x4e1e69ac}, b = {127, 0x32c70945, 0x072c2f80, 0x7ef86c2f, 0x595e375a}, res = {127, 0x09cd411e, 0x299e65ea, 0x76a01eb3, 0x277ca107}, carry = 1}, + {a = {126, 0x47ab18d6, 0x4918a98b, 0x51c48283, 0x39a6cb59}, b = {127, 0x2430ac5b, 0x5328674a, 0x1fdffc7d, 0x69dced9b}, res = {126, 0x6bdbc531, 0x1c4110d5, 0x71a47f01, 0x2383b8f4}, carry = 1}, + {a = {127, 0x30e85990, 0x65afbf0b, 0x3a20ce3d, 0x5a014c71}, b = {127, 0x27d0d3a9, 0x2483d56f, 0x7e96f8e9, 0x54c679f8}, res = {127, 0x58b92d39, 0x0a33947a, 0x38b7c727, 0x2ec7c66a}, carry = 1}, + {a = {127, 0x6ec2ebc5, 0x2fe87e6e, 0x5c214f86, 0x46ce9354}, b = {127, 0x7b6b78e3, 0x0de8a5eb, 0x1cf27da3, 0x43d7077f}, res = {127, 0x6a2e64a8, 0x3dd1245a, 0x7913cd29, 0x0aa59ad3}, carry = 1}, + {a = {127, 0x532479dc, 0x38cb5eb9, 0x6b8afdb8, 0x624a0f13}, b = {127, 0x74ab8b56, 0x27e5b9da, 0x62e7b771, 0x50f115a6}, res = {127, 0x47d00532, 0x60b11894, 0x4e72b529, 0x333b24ba}, carry = 1}, + {a = {126, 0x7fe1b477, 0x4477a7c5, 0x04b0164d, 0x31d00838}, b = {125, 0x321ef997, 0x579ab7cf, 0x54fb3455, 0x133928f5}, res = {126, 0x3200ae0e, 0x1c125f95, 0x59ab4aa3, 0x4509312d}, carry = 0}, + {a = {127, 0x0dd79881, 0x120bcfae, 0x73cc2c5d, 0x43b522e7}, b = {126, 0x0b81b7af, 0x3c00816c, 0x5658e7d3, 0x30483552}, res = {127, 0x19595030, 0x4e0c511a, 0x4a251430, 0x73fd583a}, carry = 0}, + {a = {126, 0x0c52d45e, 0x698fd6df, 0x29a362fc, 0x3b6b5882}, b = {127, 0x5e851c19, 0x72428920, 0x417745c0, 0x437e8c53}, res = {126, 0x6ad7f077, 0x5bd25fff, 0x6b1aa8bd, 0x7ee9e4d5}, carry = 0}, + {a = {126, 0x5d82d3a5, 0x328636c2, 0x175e0f86, 0x270f7afa}, b = {126, 0x7c06e395, 0x21b77837, 0x5fb98ab1, 0x225d6181}, res = {126, 0x5989b73a, 0x543daefa, 0x77179a37, 0x496cdc7b}, carry = 0}, + {a = {127, 0x16be1fea, 0x48971493, 0x4a8f5617, 0x6b2a9968}, b = {127, 0x47387295, 0x2cecd451, 0x59abfdc3, 0x40028930}, res = {127, 0x5df6927f, 0x7583e8e4, 0x243b53da, 0x2b2d2299}, carry = 1}, + {a = {127, 0x3cea792f, 0x62a42211, 0x18aa6284, 0x4ca7e07f}, b = {127, 0x5ea60937, 0x5e7c6b69, 0x5bd789bd, 0x774ddc8e}, res = {127, 0x1b908266, 0x41208d7b, 0x7481ec42, 0x43f5bd0d}, carry = 1}, + {a = {127, 0x0e21cb57, 0x34087c35, 0x7bcad13b, 0x59c8dc35}, b = {125, 0x08a0ae9f, 0x145d8ebb, 0x1bcb683a, 0x1caefdbd}, res = {127, 0x16c279f6, 0x48660af0, 0x17963975, 0x7677d9f3}, carry = 0}, + {a = {127, 0x0b0dd998, 0x1210d9cc, 0x01074e0a, 0x762996da}, b = {126, 0x3c80570a, 0x00fbed3c, 0x77bb5d04, 0x258d2213}, res = {127, 0x478e30a2, 0x130cc708, 0x78c2ab0e, 0x1bb6b8ed}, carry = 1}, + {a = {127, 0x4e88ee76, 0x335e5d85, 0x634e3d26, 0x56cff5de}, b = {126, 0x15f3b966, 0x1c0a3f86, 0x1da6a59b, 0x35edda6f}, res = {127, 0x647ca7dc, 0x4f689d0b, 0x00f4e2c1, 0x0cbdd04e}, carry = 1}, + {a = {127, 0x374f8369, 0x00230d40, 0x76f627a4, 0x45cb7dff}, b = {127, 0x0e437bcd, 0x5524b249, 0x6aa80d40, 0x578666e8}, res = {127, 0x4592ff36, 0x5547bf89, 0x619e34e4, 0x1d51e4e8}, carry = 1}, + {a = {126, 0x7804142f, 0x66b7d326, 0x2f6fbe1b, 0x340e91e8}, b = {127, 0x5650e768, 0x22201fc5, 0x1a849718, 0x608b4950}, res = {126, 0x4e54fb97, 0x08d7f2ec, 0x49f45534, 0x1499db38}, carry = 1}, + {a = {127, 0x409a9e5d, 0x6b53d3a4, 0x284aa18f, 0x60899852}, b = {126, 0x340a5074, 0x05dc9720, 0x6c400c75, 0x2c0a2e6d}, res = {127, 0x74a4eed1, 0x71306ac4, 0x148aae04, 0x0c93c6c0}, carry = 1}, + {a = {127, 0x484f4d07, 0x45bf1a9a, 0x7679ef61, 0x58da55e9}, b = {125, 0x1672599a, 0x25b9fde9, 0x1dd52ce2, 0x1b792755}, res = {127, 0x5ec1a6a1, 0x6b791883, 0x144f1c43, 0x74537d3f}, carry = 0}, + {a = {127, 0x46f0b919, 0x258efe9f, 0x25ac2909, 0x7ee35251}, b = {126, 0x26292ec9, 0x052799f6, 0x26e8cd9f, 0x2a91ea77}, res = {127, 0x6d19e7e2, 0x2ab69895, 0x4c94f6a8, 0x29753cc8}, carry = 1}, + {a = {127, 0x662c1549, 0x7d1d8413, 0x2820005b, 0x640fa366}, b = {127, 0x1a1c703a, 0x7c960a8a, 0x0468ab46, 0x76636006}, res = {127, 0x00488583, 0x79b38e9e, 0x2c88aba2, 0x5a73036c}, carry = 1}, + {a = {127, 0x32c078a6, 0x09144c2e, 0x7216eb1e, 0x575eb4a5}, b = {127, 0x014b7cd8, 0x7374bc3f, 0x79bad310, 0x41c309b3}, res = {127, 0x340bf57e, 0x7c89086d, 0x6bd1be2e, 0x1921be59}, carry = 1}, + {a = {127, 0x5d152378, 0x0db3b779, 0x27cbd808, 0x729e2498}, b = {127, 0x639a2cac, 0x4e2dc8f3, 0x22846201, 0x538c5c6c}, res = {127, 0x40af5024, 0x5be1806d, 0x4a503a09, 0x462a8104}, carry = 1}, + {a = {126, 0x4f52ebe5, 0x2e2ce1fe, 0x740060a5, 0x2b602c28}, b = {127, 0x23613e9d, 0x5236ec37, 0x744c1397, 0x6a0e964f}, res = {126, 0x72b42a82, 0x0063ce35, 0x684c743d, 0x156ec278}, carry = 1}, + {a = {126, 0x7cab43a4, 0x7b2c3d9d, 0x44c551d5, 0x3e795426}, b = {127, 0x25f21ad6, 0x50b3905d, 0x4477f701, 0x64063b12}, res = {126, 0x229d5e7a, 0x4bdfcdfb, 0x093d48d7, 0x227f8f39}, carry = 1}, + {a = {126, 0x54d5908a, 0x4db32799, 0x4e170b52, 0x32d0557d}, b = {126, 0x24f21022, 0x0e431da5, 0x52beb40c, 0x2e5eada4}, res = {126, 0x79c7a0ac, 0x5bf6453e, 0x20d5bf5e, 0x612f0322}, carry = 0}, + {a = {127, 0x2fd0c679, 0x429d2164, 0x3b7d1340, 0x478c197e}, b = {126, 0x5b9f625b, 0x15cfdd9b, 0x0eb39653, 0x3f2a9e1a}, res = {127, 0x0b7028d4, 0x586cff00, 0x4a30a993, 0x06b6b798}, carry = 1}, + {a = {127, 0x47e483cd, 0x635dad05, 0x334178a6, 0x5be2703c}, b = {127, 0x12487678, 0x0b47445a, 0x2b468da6, 0x5cf39115}, res = {127, 0x5a2cfa45, 0x6ea4f15f, 0x5e88064c, 0x38d60151}, carry = 1}, + {a = {127, 0x16bd1190, 0x6f358b1b, 0x43fef060, 0x57246c18}, b = {125, 0x52f9efe3, 0x1d8e35d7, 0x45422cbc, 0x1e9a7d06}, res = {127, 0x69b70173, 0x0cc3c0f2, 0x09411d1d, 0x75bee91f}, carry = 0}, + {a = {124, 0x300566bd, 0x4043d1da, 0x07083a42, 0x0ac4490a}, b = {126, 0x7bc7aaa4, 0x089b207d, 0x7d73159e, 0x20475102}, res = {124, 0x2bcd1161, 0x48def258, 0x047b4fe0, 0x2b0b9a0d}, carry = 0}, + {a = {127, 0x1879a134, 0x2d5dce4b, 0x4fb09e72, 0x6f409165}, b = {127, 0x224335a0, 0x20c2b717, 0x748b2f65, 0x738d9650}, res = {127, 0x3abcd6d4, 0x4e208562, 0x443bcdd7, 0x62ce27b6}, carry = 1}, + {a = {127, 0x49d61045, 0x5b207515, 0x2e191bd8, 0x64903107}, b = {127, 0x08359044, 0x2f1aff57, 0x730d9fb4, 0x7f03816e}, res = {127, 0x520ba089, 0x0a3b746c, 0x2126bb8d, 0x6393b276}, carry = 1}, + {a = {127, 0x623eb7a3, 0x4d52e07d, 0x4a90b87c, 0x4641403a}, b = {125, 0x08f857d7, 0x3761ae9d, 0x46ead119, 0x13a35973}, res = {127, 0x6b370f7a, 0x04b48f1a, 0x117b8996, 0x59e499ae}, carry = 0}, + {a = {126, 0x71b46b67, 0x1edc966c, 0x42558b0c, 0x29be2808}, b = {127, 0x666102d3, 0x573e7bfe, 0x65d18c72, 0x67b4562e}, res = {126, 0x58156e3a, 0x761b126b, 0x2827177e, 0x11727e37}, carry = 1}, + {a = {127, 0x50500966, 0x0c5234da, 0x3e8cf39e, 0x62fbe6ce}, b = {127, 0x6c96a29c, 0x0ffb4fc4, 0x5f3a488f, 0x60d3a913}, res = {127, 0x3ce6ac02, 0x1c4d849f, 0x1dc73c2d, 0x43cf8fe2}, carry = 1}, + {a = {125, 0x508f7e5a, 0x2a6c24f8, 0x7cb4ae59, 0x1816c2cd}, b = {126, 0x1e8cc9df, 0x16ed73e1, 0x5910fa15, 0x3a24b307}, res = {125, 0x6f1c4839, 0x415998d9, 0x55c5a86e, 0x523b75d5}, carry = 0}, + {a = {127, 0x510325a0, 0x1ded9a89, 0x51e31db4, 0x6ddc474b}, b = {127, 0x22f4ab1e, 0x6fb69633, 0x51a5308e, 0x6eb15ef6}, res = {127, 0x73f7d0be, 0x0da430bc, 0x23884e43, 0x5c8da642}, carry = 1}, + {a = {127, 0x12108d0c, 0x386688ec, 0x69d02125, 0x628e00f3}, b = {124, 0x11f211b8, 0x2a6ff08f, 0x33daa305, 0x0c610086}, res = {127, 0x24029ec4, 0x62d6797b, 0x1daac42a, 0x6eef017a}, carry = 0}, + {a = {126, 0x0c264fba, 0x0884a2d3, 0x20569035, 0x304b267d}, b = {126, 0x6ff360e6, 0x12c6ad2b, 0x33f44806, 0x21d94783}, res = {126, 0x7c19b0a0, 0x1b4b4ffe, 0x544ad83b, 0x52246e00}, carry = 0}, + {a = {127, 0x43ab14a8, 0x7c15842c, 0x636094f1, 0x53d57eea}, b = {126, 0x15682230, 0x35fc075d, 0x59910014, 0x23e605cb}, res = {127, 0x591336d8, 0x32118b89, 0x3cf19506, 0x77bb84b6}, carry = 0}, + {a = {126, 0x5a7805ff, 0x32dd0387, 0x75f7a085, 0x3ea23d06}, b = {127, 0x4c55c285, 0x26e33530, 0x03d565c7, 0x724d2fcb}, res = {126, 0x26cdc884, 0x59c038b8, 0x79cd064c, 0x30ef6cd1}, carry = 1}, + {a = {125, 0x1a9e1657, 0x1af68cda, 0x4602b940, 0x17acd817}, b = {127, 0x539ac7fe, 0x54adc300, 0x1fe07458, 0x403f5f12}, res = {125, 0x6e38de55, 0x6fa44fda, 0x65e32d98, 0x57ec3729}, carry = 0}, + {a = {127, 0x62b5821b, 0x2c747aa2, 0x49572e10, 0x691ea596}, b = {127, 0x2e8da32f, 0x1325dd52, 0x08bf290a, 0x7b103bf2}, res = {127, 0x1143254a, 0x3f9a57f5, 0x5216571a, 0x642ee188}, carry = 1}, + {a = {127, 0x215fb007, 0x0dfc51a6, 0x7014c1ee, 0x5e30b9d9}, b = {125, 0x52f164a9, 0x300a98d7, 0x2cab73e3, 0x132af275}, res = {127, 0x745114b0, 0x3e06ea7d, 0x1cc035d1, 0x715bac4f}, carry = 0}, + {a = {127, 0x4dfcc7e5, 0x10d63d11, 0x443f7365, 0x7eb2535a}, b = {127, 0x543fd063, 0x041977bb, 0x294f5562, 0x7fabe576}, res = {127, 0x223c9848, 0x14efb4cd, 0x6d8ec8c7, 0x7e5e38d0}, carry = 1}, + {a = {127, 0x1a8c810b, 0x49c32b54, 0x5dfbd7fe, 0x49ce1a98}, b = {127, 0x5166f071, 0x0bfea258, 0x3325555d, 0x7ebb7c16}, res = {127, 0x6bf3717c, 0x55c1cdac, 0x11212d5b, 0x488996af}, carry = 1}, + {a = {124, 0x4b8a34ea, 0x5b0935d4, 0x3568d516, 0x0d16387a}, b = {126, 0x620a0226, 0x65058ce5, 0x6dd1444f, 0x2f49abb0}, res = {124, 0x2d943710, 0x400ec2ba, 0x233a1966, 0x3c5fe42b}, carry = 0}, + {a = {126, 0x5e834973, 0x4242f578, 0x03a1e339, 0x2c61fc03}, b = {127, 0x04a255ce, 0x01ec5f4b, 0x4e56d825, 0x5d998057}, res = {126, 0x63259f41, 0x442f54c3, 0x51f8bb5e, 0x09fb7c5a}, carry = 1}, + {a = {127, 0x6cb09c4b, 0x293baae1, 0x7e2830b9, 0x4edf0b6b}, b = {127, 0x4a59137b, 0x4b516bec, 0x0e882e9a, 0x67fd68cb}, res = {127, 0x3709afc6, 0x748d16ce, 0x0cb05f53, 0x36dc7437}, carry = 1}, + {a = {126, 0x49d63bd1, 0x20f7d1ab, 0x78eec602, 0x21eea324}, b = {127, 0x4bf2e9c3, 0x55004234, 0x012195b5, 0x725e1f50}, res = {126, 0x15c92594, 0x75f813e0, 0x7a105bb7, 0x144cc274}, carry = 1}, + {a = {118, 0x009708e5, 0x0f6eae6a, 0x22717ea3, 0x0027f75e}, b = {125, 0x73b69ce9, 0x05c9d5e7, 0x079ce44c, 0x1e0e4b57}, res = {118, 0x744da5ce, 0x15388451, 0x2a0e62ef, 0x1e3642b5}, carry = 0}, + {a = {127, 0x7d5fcca1, 0x703581e8, 0x5ebea294, 0x6d59d7b6}, b = {127, 0x072dc186, 0x2556d525, 0x6550936e, 0x483050d7}, res = {127, 0x048d8e27, 0x158c570e, 0x440f3603, 0x358a288e}, carry = 1}, + {a = {125, 0x1ee5e478, 0x6cbc0fd8, 0x5a08ca0d, 0x160799a6}, b = {124, 0x20c5baa0, 0x6893f6b8, 0x2e615746, 0x08b55361}, res = {125, 0x3fab9f18, 0x55500690, 0x086a2154, 0x1ebced08}, carry = 0}, + {a = {126, 0x5ba7f924, 0x69e6da01, 0x7012307c, 0x257f31e9}, b = {124, 0x4f354878, 0x5c6c44df, 0x580de770, 0x088dc09c}, res = {126, 0x2add419c, 0x46531ee1, 0x482017ed, 0x2e0cf286}, carry = 0}, + {a = {126, 0x53c283ee, 0x19ddd382, 0x401fa681, 0x39b9198a}, b = {126, 0x3e9c90df, 0x3091f3aa, 0x1ec070d3, 0x2f2473fa}, res = {126, 0x125f14cd, 0x4a6fc72d, 0x5ee01754, 0x68dd8d84}, carry = 0}, + {a = {125, 0x2aab90f9, 0x0ee5a976, 0x4b5b0203, 0x1672068b}, b = {125, 0x53c43b94, 0x020d4782, 0x260b9c3a, 0x1faca98d}, res = {125, 0x7e6fcc8d, 0x10f2f0f8, 0x71669e3d, 0x361eb018}, carry = 0}, + {a = {127, 0x1741cb5d, 0x42adb37f, 0x16f4e290, 0x7c26149a}, b = {127, 0x5c12de2e, 0x77a67be5, 0x2ef8e62e, 0x4a9fdb63}, res = {127, 0x7354a98b, 0x3a542f64, 0x45edc8bf, 0x46c5effd}, carry = 1}, + {a = {125, 0x21437747, 0x41ce24dd, 0x70efb0ac, 0x1d4aac67}, b = {126, 0x1235886f, 0x7bc15966, 0x1097debf, 0x29249284}, res = {125, 0x3378ffb6, 0x3d8f7e43, 0x01878f6c, 0x466f3eec}, carry = 0}, + {a = {126, 0x0e70b5d5, 0x43bd3fd6, 0x1bdbe98b, 0x2033fc0b}, b = {127, 0x7ce4daf7, 0x16ace9f7, 0x073703bc, 0x5704964f}, res = {126, 0x0b5590cc, 0x5a6a29ce, 0x2312ed47, 0x7738925a}, carry = 0}, + {a = {120, 0x62a94a7e, 0x604154fa, 0x3deeb80c, 0x00df6634}, b = {125, 0x6fd3f3b5, 0x3cfc6cef, 0x63fd704a, 0x1678e57f}, res = {120, 0x527d3e33, 0x1d3dc1ea, 0x21ec2857, 0x17584bb4}, carry = 0}, + {a = {123, 0x7978a2ad, 0x5d491b8a, 0x71574e8a, 0x0663444b}, b = {126, 0x0bc05c34, 0x7c0f280f, 0x015a49fc, 0x38502a7f}, res = {123, 0x0538fee1, 0x5958439a, 0x72b19887, 0x3eb36eca}, carry = 0}, + {a = {125, 0x51d6b6f2, 0x079bd814, 0x6ce81e2d, 0x18c4c981}, b = {127, 0x54f420d0, 0x2dc663dc, 0x13bc81b9, 0x4060575c}, res = {125, 0x26cad7c2, 0x35623bf1, 0x00a49fe6, 0x592520de}, carry = 0}, + {a = {127, 0x36485bbe, 0x09796514, 0x63ee7f58, 0x5625e4e2}, b = {127, 0x21458fe6, 0x7c9fa294, 0x54b76a7e, 0x6d695c7f}, res = {127, 0x578deba4, 0x061907a8, 0x38a5e9d7, 0x438f4162}, carry = 1}, + {a = {127, 0x33d70ec2, 0x1f127a0b, 0x2717bb82, 0x5fd43cd2}, b = {125, 0x3f0797eb, 0x0d5c370b, 0x3d594b1d, 0x1752300b}, res = {127, 0x72dea6ad, 0x2c6eb116, 0x6471069f, 0x77266cdd}, carry = 0}, + {a = {127, 0x37eae763, 0x19004adb, 0x38b7eff2, 0x688fe1c5}, b = {127, 0x2cac471c, 0x37811110, 0x1f3987d5, 0x7fe15712}, res = {127, 0x64972e7f, 0x50815beb, 0x57f177c7, 0x687138d7}, carry = 1}, + {a = {126, 0x6c225971, 0x0437a4c8, 0x1bcb8b3e, 0x28ceb662}, b = {127, 0x48cd5436, 0x7e421cca, 0x29d44db3, 0x55711f42}, res = {126, 0x34efada7, 0x0279c193, 0x459fd8f2, 0x7e3fd5a4}, carry = 0}, + {a = {126, 0x2574a51d, 0x5de32cbd, 0x6c67c9cc, 0x376694a2}, b = {126, 0x138f84b7, 0x46f2b3f1, 0x47583471, 0x3b15472c}, res = {126, 0x390429d4, 0x24d5e0ae, 0x33bffe3e, 0x727bdbcf}, carry = 0}, + {a = {127, 0x588e6a4d, 0x03204eb0, 0x400a06ba, 0x469c62fd}, b = {127, 0x33609fc4, 0x65a624e4, 0x6111951d, 0x7648f1ae}, res = {127, 0x0bef0a11, 0x68c67395, 0x211b9bd7, 0x3ce554ac}, carry = 1}, + {a = {127, 0x03178062, 0x2c0793b9, 0x6b91ed2a, 0x436a75d5}, b = {127, 0x364e9acd, 0x20881e30, 0x609f019a, 0x5d437f92}, res = {127, 0x39661b2f, 0x4c8fb1e9, 0x4c30eec4, 0x20adf568}, carry = 1}, + {a = {126, 0x582502a7, 0x76d9bbd7, 0x6ec18736, 0x3d15fed5}, b = {127, 0x45e8a9e7, 0x0cfae275, 0x4c164029, 0x641d8e58}, res = {126, 0x1e0dac8e, 0x03d49e4d, 0x3ad7c760, 0x21338d2e}, carry = 1}, + {a = {127, 0x31a44c73, 0x4c970755, 0x433f481c, 0x63704395}, b = {127, 0x1a3979dc, 0x0e9d5437, 0x592a1251, 0x5cb4e212}, res = {127, 0x4bddc64f, 0x5b345b8c, 0x1c695a6d, 0x402525a8}, carry = 1}, + {a = {126, 0x31499a0a, 0x2df3b6ac, 0x4cf3e9b4, 0x3fdaa87e}, b = {126, 0x6749f6f2, 0x28c62023, 0x3e54e11b, 0x30a577bf}, res = {126, 0x189390fc, 0x56b9d6d0, 0x0b48cacf, 0x7080203e}, carry = 0}, + {a = {126, 0x2d6e6ed4, 0x3c9f5183, 0x27326fac, 0x35b95ad6}, b = {125, 0x28a46b0d, 0x4143c903, 0x182ab95c, 0x17b09437}, res = {126, 0x5612d9e1, 0x7de31a86, 0x3f5d2908, 0x4d69ef0d}, carry = 0}, + {a = {126, 0x26f20408, 0x2af94d52, 0x440bf192, 0x360df26a}, b = {127, 0x7b2198cb, 0x763bc4c5, 0x78ac7d5a, 0x5de9efc9}, res = {126, 0x22139cd3, 0x21351218, 0x3cb86eed, 0x13f7e234}, carry = 1}, + {a = {127, 0x47128b80, 0x670a3dbb, 0x63efbba7, 0x74a10d1e}, b = {124, 0x7f481747, 0x59187cd8, 0x51e741bc, 0x0a8ea0cd}, res = {127, 0x465aa2c7, 0x4022ba94, 0x35d6fd64, 0x7f2fadec}, carry = 0}, + {a = {127, 0x5a47d043, 0x052b9ec0, 0x401cbe7c, 0x49104294}, b = {127, 0x7427cee8, 0x06334bc8, 0x4528bd14, 0x741e6df6}, res = {127, 0x4e6f9f2b, 0x0b5eea89, 0x05457b90, 0x3d2eb08b}, carry = 1}, + {a = {127, 0x6520f6a0, 0x5b37902f, 0x31d3c910, 0x5f9fe4e6}, b = {127, 0x6aa5d0dc, 0x61ad0b30, 0x3b782608, 0x7dd0cd5f}, res = {127, 0x4fc6c77c, 0x3ce49b60, 0x6d4bef19, 0x5d70b245}, carry = 1}, + {a = {126, 0x34bdc731, 0x2de09f84, 0x3896e197, 0x3fee6ac9}, b = {127, 0x5b5d8627, 0x3c22bdb9, 0x5e7b4aaf, 0x555e4e2a}, res = {126, 0x101b4d58, 0x6a035d3e, 0x17122c46, 0x154cb8f4}, carry = 1}, +} + +@(rodata) +i31_sub_test_vectors := []I31_Test_Vector_Binary { + {a = {126, 0x75832201, 0x72b1ddb3, 0x3e8d7744, 0x325d7cb5}, b = {127, 0x52a23cfc, 0x51a47476, 0x19fd5fa3, 0x5a5e5d48}, res = {126, 0x22e0e505, 0x210d693d, 0x249017a1, 0x57ff1f6d}, carry = 1}, + {a = {127, 0x02585014, 0x15b77a75, 0x66fa4e0f, 0x5368a27c}, b = {126, 0x1aaa3973, 0x288030e7, 0x51ede1ff, 0x37bbad71}, res = {127, 0x67ae16a1, 0x6d37498d, 0x150c6c0f, 0x1bacf50b}, carry = 0}, + {a = {125, 0x3b43d5aa, 0x3de09ea7, 0x18004f89, 0x113b0b5e}, b = {127, 0x3a318699, 0x10ee38f1, 0x6d8c06d8, 0x7ac6ef8f}, res = {125, 0x01124f11, 0x2cf265b6, 0x2a7448b1, 0x16741bce}, carry = 1}, + {a = {127, 0x047e69b6, 0x5455ee19, 0x012e5e13, 0x73a56c35}, b = {127, 0x10606c28, 0x33f1e053, 0x18f48327, 0x69bc2be6}, res = {127, 0x741dfd8e, 0x20640dc5, 0x6839daec, 0x09e9404e}, carry = 0}, + {a = {127, 0x02c391a7, 0x07feb24a, 0x3a32b686, 0x5ee14ddd}, b = {126, 0x3f3b74ff, 0x2c1596d7, 0x677a7df4, 0x2ce6395d}, res = {127, 0x43881ca8, 0x5be91b72, 0x52b83891, 0x31fb147f}, carry = 0}, + {a = {125, 0x4b26152d, 0x24f5c3a3, 0x6f1d3b6f, 0x1586f40a}, b = {125, 0x7380829e, 0x1bd90254, 0x382393b6, 0x1b85a0c5}, res = {125, 0x57a5928f, 0x091cc14e, 0x36f9a7b9, 0x7a015345}, carry = 1}, + {a = {126, 0x4fa59569, 0x643c2005, 0x72e9a332, 0x2e315696}, b = {121, 0x6de61d40, 0x6b932a39, 0x52568d6d, 0x01e8488c}, res = {126, 0x61bf7829, 0x78a8f5cb, 0x209315c4, 0x2c490e0a}, carry = 0}, + {a = {127, 0x5379b430, 0x467ac863, 0x297696ab, 0x70622b6f}, b = {127, 0x37aac8dc, 0x1adda229, 0x58df1d01, 0x505ee996}, res = {127, 0x1bceeb54, 0x2b9d263a, 0x509779aa, 0x200341d8}, carry = 0}, + {a = {127, 0x1ccf6e2c, 0x11dc002c, 0x561b6361, 0x46990123}, b = {127, 0x3e9f8554, 0x42397834, 0x4fdd5416, 0x7e39420f}, res = {127, 0x5e2fe8d8, 0x4fa287f7, 0x063e0f4a, 0x485fbf14}, carry = 1}, + {a = {126, 0x2150add9, 0x26e965d1, 0x5e4ac34d, 0x2a55c9de}, b = {127, 0x6669738d, 0x3d3ff599, 0x582b736f, 0x4dd82557}, res = {126, 0x3ae73a4c, 0x69a97037, 0x061f4fdd, 0x5c7da487}, carry = 1}, + {a = {127, 0x61a3eb42, 0x2796bb5e, 0x5b90dcd6, 0x78085440}, b = {127, 0x51c6a80f, 0x0a65c487, 0x6179dc58, 0x562966ef}, res = {127, 0x0fdd4333, 0x1d30f6d7, 0x7a17007e, 0x21deed50}, carry = 0}, + {a = {127, 0x51d4085f, 0x72b1daa2, 0x7560d03e, 0x47df6842}, b = {125, 0x27993d6b, 0x1b36ba5a, 0x78a56068, 0x1811037d}, res = {127, 0x2a3acaf4, 0x577b2048, 0x7cbb6fd6, 0x2fce64c4}, carry = 0}, + {a = {125, 0x2cb4937a, 0x48c3af52, 0x7c4e70a7, 0x1578b00c}, b = {127, 0x20263089, 0x3feb52a0, 0x1b877614, 0x4f5677bc}, res = {125, 0x0c8e62f1, 0x08d85cb2, 0x60c6fa93, 0x46223850}, carry = 1}, + {a = {126, 0x338493e9, 0x4ff90c99, 0x5dc3827f, 0x2a9898da}, b = {122, 0x588232b4, 0x1c53bb05, 0x17ef86ab, 0x03bce2b4}, res = {126, 0x5b026135, 0x33a55193, 0x45d3fbd4, 0x26dbb626}, carry = 0}, + {a = {124, 0x40625bc9, 0x204b059d, 0x48deea64, 0x0a019e61}, b = {127, 0x661bd15a, 0x48ead2c5, 0x2a0e651f, 0x7999457b}, res = {124, 0x5a468a6f, 0x576032d7, 0x1ed08544, 0x106858e6}, carry = 1}, + {a = {127, 0x5874a542, 0x71947250, 0x054ff642, 0x7d66c0f6}, b = {125, 0x780fcb4e, 0x290dc73d, 0x2687ab55, 0x107555db}, res = {127, 0x6064d9f4, 0x4886ab12, 0x5ec84aed, 0x6cf16b1a}, carry = 0}, + {a = {126, 0x6fd19206, 0x50f14417, 0x7f82f1ed, 0x2bbb502b}, b = {127, 0x0468cf8a, 0x67942b1c, 0x7c399be0, 0x6306501c}, res = {126, 0x6b68c27c, 0x695d18fb, 0x0349560c, 0x48b5000f}, carry = 1}, + {a = {124, 0x51a0623b, 0x7b984ebd, 0x0fb05efe, 0x0fd13b63}, b = {127, 0x2a6b91ec, 0x68eaf51b, 0x3265f0fd, 0x4a4f4fb3}, res = {124, 0x2734d04f, 0x12ad59a2, 0x5d4a6e01, 0x4581ebaf}, carry = 1}, + {a = {127, 0x0f1be1af, 0x76ef0418, 0x489dab08, 0x4998335f}, b = {127, 0x24c297f4, 0x6fabbbe8, 0x01529fff, 0x6c5b3c15}, res = {127, 0x6a5949bb, 0x0743482f, 0x474b0b09, 0x5d3cf74a}, carry = 1}, + {a = {125, 0x53a96664, 0x6e05147a, 0x1017a26d, 0x19d02282}, b = {127, 0x46f7e29b, 0x466f513b, 0x1c9bb1fc, 0x56817f94}, res = {125, 0x0cb183c9, 0x2795c33f, 0x737bf071, 0x434ea2ed}, carry = 1}, + {a = {127, 0x2fcad8be, 0x60aea5d7, 0x63f1ddde, 0x5e392547}, b = {127, 0x3006e08d, 0x560753cd, 0x7e2304f9, 0x48c18d08}, res = {127, 0x7fc3f831, 0x0aa75209, 0x65ced8e5, 0x1577983e}, carry = 0}, + {a = {125, 0x140a0bd3, 0x0af4e07b, 0x1d4d97a3, 0x12530f8a}, b = {124, 0x66378508, 0x26164d79, 0x0b3ff9ba, 0x0b94a13a}, res = {125, 0x2dd286cb, 0x64de9301, 0x120d9de8, 0x06be6e50}, carry = 0}, + {a = {127, 0x16d0c935, 0x6e1afbd1, 0x08ef273a, 0x694e551c}, b = {125, 0x0f34a643, 0x167cbfb7, 0x2c1977e7, 0x1bd4f3cc}, res = {127, 0x079c22f2, 0x579e3c1a, 0x5cd5af53, 0x4d79614f}, carry = 0}, + {a = {127, 0x3d62a534, 0x3c284b2d, 0x53524377, 0x6016b0bd}, b = {125, 0x7cf4d88b, 0x68fb44c3, 0x61c8f0ad, 0x10e57b36}, res = {127, 0x406dcca9, 0x532d0669, 0x718952c9, 0x4f313586}, carry = 0}, + {a = {127, 0x6f015d33, 0x536fbab1, 0x26915f4b, 0x562f74ee}, b = {122, 0x63e75212, 0x68694786, 0x3f82de9a, 0x02f4382a}, res = {127, 0x0b1a0b21, 0x6b06732b, 0x670e80b0, 0x533b3cc3}, carry = 0}, + {a = {127, 0x66a82756, 0x4025f122, 0x42ad0938, 0x58fbe956}, b = {123, 0x150d6a84, 0x23439905, 0x7f8687ac, 0x057badf7}, res = {127, 0x519abcd2, 0x1ce2581d, 0x4326818c, 0x53803b5e}, carry = 0}, + {a = {127, 0x1b0466bd, 0x69accc1e, 0x55da7183, 0x71f64927}, b = {126, 0x12a653b6, 0x4be3b926, 0x174ecdc5, 0x2e28e368}, res = {127, 0x085e1307, 0x1dc912f8, 0x3e8ba3be, 0x43cd65bf}, carry = 0}, + {a = {119, 0x119a6115, 0x130e1918, 0x4e2c62c0, 0x00586779}, b = {127, 0x7042f5f4, 0x6c596133, 0x0b8e23ed, 0x4fded5b4}, res = {119, 0x21576b21, 0x26b4b7e4, 0x429e3ed2, 0x307991c5}, carry = 1}, + {a = {127, 0x05b5cfd3, 0x2ae55120, 0x46abe106, 0x70b46cd4}, b = {127, 0x4a8e2790, 0x5557a5e9, 0x3e14dfb6, 0x62ff997b}, res = {127, 0x3b27a843, 0x558dab36, 0x0897014f, 0x0db4d359}, carry = 0}, + {a = {126, 0x40c5bd09, 0x1bc7d36e, 0x5787d3c1, 0x2dadafd4}, b = {127, 0x7d36195e, 0x67d20645, 0x384c8cf2, 0x7bc9c106}, res = {126, 0x438fa3ab, 0x33f5cd28, 0x1f3b46ce, 0x31e3eece}, carry = 1}, + {a = {121, 0x582c3425, 0x5e591a55, 0x64bc368c, 0x01171496}, b = {127, 0x7ff455d7, 0x1d082b60, 0x69a595c6, 0x7506f22f}, res = {121, 0x5837de4e, 0x4150eef4, 0x7b16a0c6, 0x0c102266}, carry = 1}, + {a = {126, 0x76e444c8, 0x5d162950, 0x0b4c9cb8, 0x350172ce}, b = {126, 0x6fae6b50, 0x457e2833, 0x22e790fd, 0x222f32e8}, res = {126, 0x0735d978, 0x1798011d, 0x68650bbb, 0x12d23fe5}, carry = 0}, + {a = {126, 0x0a94f6cb, 0x7e659900, 0x21e27381, 0x20898f13}, b = {125, 0x6c0e1d51, 0x26db9379, 0x4aa78374, 0x13e8525f}, res = {126, 0x1e86d97a, 0x578a0586, 0x573af00d, 0x0ca13cb3}, carry = 0}, + {a = {126, 0x2df85daa, 0x089e925f, 0x372ad1cd, 0x282da3da}, b = {126, 0x5f6eb57d, 0x666b94e8, 0x72523b11, 0x2e872674}, res = {126, 0x4e89a82d, 0x2232fd76, 0x44d896bb, 0x79a67d65}, carry = 1}, + {a = {127, 0x59c5b936, 0x14069767, 0x00e21797, 0x7ab2135d}, b = {127, 0x4a74250d, 0x65c5e230, 0x5562bf62, 0x5c25dec5}, res = {127, 0x0f519429, 0x2e40b537, 0x2b7f5834, 0x1e8c3497}, carry = 0}, + {a = {125, 0x74ce4c1e, 0x59cb9a18, 0x5e0560c1, 0x142bdb97}, b = {126, 0x0e08a60d, 0x4c39a45f, 0x0929589f, 0x22096dd5}, res = {125, 0x66c5a611, 0x0d91f5b9, 0x54dc0822, 0x72226dc2}, carry = 1}, + {a = {127, 0x65273636, 0x653e4647, 0x2cf1b1f4, 0x74f9fddd}, b = {122, 0x6e960ace, 0x7ea2dfda, 0x35a317bd, 0x0202a322}, res = {127, 0x76912b68, 0x669b666c, 0x774e9a36, 0x72f75aba}, carry = 0}, + {a = {126, 0x45582ae7, 0x6996c2bb, 0x09380ea2, 0x30b4897c}, b = {127, 0x246ce79f, 0x758393b5, 0x00926c53, 0x40dd24dc}, res = {126, 0x20eb4348, 0x74132f06, 0x08a5a24e, 0x6fd764a0}, carry = 1}, + {a = {127, 0x7face3b5, 0x4e099393, 0x1e3f7fe6, 0x50946a47}, b = {126, 0x50b06b20, 0x36db7586, 0x40ec226f, 0x33e64381}, res = {127, 0x2efc7895, 0x172e1e0d, 0x5d535d77, 0x1cae26c5}, carry = 0}, + {a = {124, 0x5bae9309, 0x23a74c8d, 0x4c7f0704, 0x0fd4a29c}, b = {127, 0x07e40cc7, 0x31e632d3, 0x1ae2cbd2, 0x6f8f930a}, res = {124, 0x53ca8642, 0x71c119ba, 0x319c3b31, 0x20450f92}, carry = 1}, + {a = {126, 0x69c3634b, 0x0c4ffbaa, 0x390a326e, 0x25e998b2}, b = {123, 0x4d164145, 0x6cf31f2c, 0x60273791, 0x05ea1210}, res = {126, 0x1cad2206, 0x1f5cdc7e, 0x58e2fadc, 0x1fff86a1}, carry = 0}, + {a = {124, 0x25a0c384, 0x38438ee6, 0x67b92d68, 0x0b8d4052}, b = {127, 0x65152a5c, 0x6af4d890, 0x78f3790f, 0x516ebf7b}, res = {124, 0x408b9928, 0x4d4eb655, 0x6ec5b458, 0x3a1e80d6}, carry = 1}, + {a = {120, 0x27e60366, 0x1158cd09, 0x7f574484, 0x00eb60e2}, b = {126, 0x5ee2ec9b, 0x17d9507a, 0x283fcdf8, 0x3d95a50e}, res = {120, 0x490316cb, 0x797f7c8e, 0x5717768b, 0x4355bbd4}, carry = 1}, + {a = {126, 0x7a6cfbf1, 0x3b47fde4, 0x1f96a1b0, 0x3415815d}, b = {127, 0x6db4865c, 0x68d15d67, 0x29492ffd, 0x55b34d59}, res = {126, 0x0cb87595, 0x5276a07d, 0x764d71b2, 0x5e623403}, carry = 1}, + {a = {126, 0x78bc926b, 0x06872ef9, 0x77dc59ee, 0x341e1fe5}, b = {127, 0x4aafbaf4, 0x0f29aa6c, 0x6e2dcd74, 0x5ae5ab56}, res = {126, 0x2e0cd777, 0x775d848d, 0x09ae8c79, 0x5938748f}, carry = 1}, + {a = {127, 0x5339599f, 0x6d388285, 0x1fb30e96, 0x653d70d1}, b = {127, 0x26e44c38, 0x360bdfc0, 0x317ed433, 0x4e44f71e}, res = {127, 0x2c550d67, 0x372ca2c5, 0x6e343a63, 0x16f879b2}, carry = 0}, + {a = {126, 0x5f57436e, 0x5d7b8fc4, 0x3b913a49, 0x289c171d}, b = {127, 0x63106f0e, 0x181ab5a1, 0x1a35ecdc, 0x4f6fd3de}, res = {126, 0x7c46d460, 0x4560da22, 0x215b4d6d, 0x592c433f}, carry = 1}, + {a = {125, 0x0a9e7523, 0x4011ed51, 0x573e92c1, 0x1465e918}, b = {125, 0x190d6359, 0x595c3a46, 0x76ee73b0, 0x1163ef5f}, res = {125, 0x719111ca, 0x66b5b30a, 0x60501f10, 0x0301f9b8}, carry = 0}, + {a = {126, 0x5b4c4e1c, 0x368e93d7, 0x1d7f383f, 0x3893cc55}, b = {126, 0x15361741, 0x192e05a0, 0x69808f62, 0x3d0b8de0}, res = {126, 0x461636db, 0x1d608e37, 0x33fea8dd, 0x7b883e74}, carry = 1}, + {a = {127, 0x711129f3, 0x1221b384, 0x75a6ce65, 0x75cc9dda}, b = {125, 0x05242048, 0x29449c5e, 0x299b4ec4, 0x1179eca2}, res = {127, 0x6bed09ab, 0x68dd1726, 0x4c0b7fa0, 0x6452b138}, carry = 0}, + {a = {126, 0x23d8928a, 0x6bc1710b, 0x4f1a2853, 0x3920c780}, b = {127, 0x3ed60b0f, 0x639f16e2, 0x26d5472a, 0x7a6d17fb}, res = {126, 0x6502877b, 0x08225a28, 0x2844e129, 0x3eb3af85}, carry = 1}, + {a = {127, 0x0d9cfe5d, 0x619328e7, 0x542c700d, 0x6997d292}, b = {127, 0x1cedd3b7, 0x5edfbdf6, 0x45d76cf4, 0x7357a00a}, res = {127, 0x70af2aa6, 0x02b36af0, 0x0e550319, 0x76403288}, carry = 1}, + {a = {127, 0x03e30b55, 0x4013c48f, 0x729f2a80, 0x58b486a3}, b = {126, 0x786328fe, 0x37cc20dc, 0x25a645e1, 0x27cf6abb}, res = {127, 0x0b7fe257, 0x0847a3b2, 0x4cf8e49f, 0x30e51be8}, carry = 0}, + {a = {125, 0x7ac1580f, 0x2c93cbec, 0x527026f9, 0x124b767e}, b = {127, 0x68ba663a, 0x4df0c1d0, 0x4a5c9da6, 0x5da272db}, res = {125, 0x1206f1d5, 0x5ea30a1c, 0x08138952, 0x34a903a3}, carry = 1}, + {a = {127, 0x56b6b76a, 0x3ece11a5, 0x296c5e68, 0x59ab3003}, b = {127, 0x6e69a755, 0x7099639a, 0x357c87d6, 0x4c9f33fd}, res = {127, 0x684d1015, 0x4e34ae0a, 0x73efd691, 0x0d0bfc05}, carry = 0}, + {a = {127, 0x4404fa8a, 0x5866a5c4, 0x0c22add4, 0x5ef97523}, b = {127, 0x1c21bae1, 0x256e56eb, 0x77662e5f, 0x7376f17a}, res = {127, 0x27e33fa9, 0x32f84ed9, 0x14bc7f75, 0x6b8283a8}, carry = 1}, + {a = {126, 0x2c0d6109, 0x3509e87e, 0x53519777, 0x3c09bbc0}, b = {127, 0x3a60646e, 0x3f868a8e, 0x461edbdd, 0x777a0c2c}, res = {126, 0x71acfc9b, 0x75835def, 0x0d32bb99, 0x448faf94}, carry = 1}, + {a = {126, 0x5f8c1f7a, 0x40041120, 0x463c5e66, 0x35a4cd6e}, b = {127, 0x08cf40d3, 0x2edbcf34, 0x784ab86d, 0x4de2eb71}, res = {126, 0x56bcdea7, 0x112841ec, 0x4df1a5f9, 0x67c1e1fc}, carry = 1}, + {a = {127, 0x2266e65d, 0x0553ee53, 0x0c667741, 0x4fd147e2}, b = {127, 0x3fb1e4a2, 0x7cb1c12a, 0x535dbe75, 0x67bfcc87}, res = {127, 0x62b501bb, 0x08a22d28, 0x3908b8cb, 0x68117b5a}, carry = 1}, + {a = {127, 0x76bfd2fb, 0x40e3235f, 0x7110f948, 0x55262313}, b = {126, 0x56f35745, 0x288792fc, 0x3759df43, 0x36acfcf9}, res = {127, 0x1fcc7bb6, 0x185b9063, 0x39b71a05, 0x1e79261a}, carry = 0}, + {a = {125, 0x79452908, 0x28cb7d35, 0x033fb36e, 0x1e3d1dc2}, b = {126, 0x625452fe, 0x6f0e270e, 0x4a783375, 0x21ed361b}, res = {125, 0x16f0d60a, 0x39bd5627, 0x38c77ff8, 0x7c4fe7a6}, carry = 1}, + {a = {126, 0x7372a659, 0x3cd43111, 0x79c40547, 0x2d1afece}, b = {127, 0x7f8726fd, 0x24d0aa04, 0x1f6ecb14, 0x4e053ba9}, res = {126, 0x73eb7f5c, 0x1803870c, 0x5a553a33, 0x5f15c325}, carry = 1}, + {a = {126, 0x2f15690a, 0x4de81324, 0x3e9f55fe, 0x373cfdd3}, b = {125, 0x02bb7d7f, 0x3f2d94bb, 0x769d851b, 0x10f5260a}, res = {126, 0x2c59eb8b, 0x0eba7e69, 0x4801d0e3, 0x2647d7c8}, carry = 0}, + {a = {125, 0x54d7b5c6, 0x3932181a, 0x53521bf5, 0x1c5cf5ee}, b = {125, 0x03d8109e, 0x4d5cbcea, 0x49faabc0, 0x1d3e3e9a}, res = {125, 0x50ffa528, 0x6bd55b30, 0x09577034, 0x7f1eb754}, carry = 1}, + {a = {125, 0x43482a38, 0x1ce74ab0, 0x624445b9, 0x176cc140}, b = {125, 0x202ec3d7, 0x1cdd0bba, 0x4493150c, 0x13241d43}, res = {125, 0x23196661, 0x000a3ef6, 0x1db130ad, 0x0448a3fd}, carry = 0}, + {a = {126, 0x63a9b9c1, 0x5557f9c9, 0x4a536191, 0x3c4994c8}, b = {127, 0x5ee2af7f, 0x7dda218e, 0x53e931bb, 0x4270e93f}, res = {126, 0x04c70a42, 0x577dd83b, 0x766a2fd5, 0x79d8ab88}, carry = 1}, + {a = {127, 0x4950f74f, 0x6aad0a67, 0x43e36683, 0x76afb633}, b = {127, 0x2213721e, 0x76557c01, 0x299aae91, 0x44c18e55}, res = {127, 0x273d8531, 0x74578e66, 0x1a48b7f1, 0x31ee27de}, carry = 0}, + {a = {126, 0x0935ca8f, 0x1200cc80, 0x2f3661fb, 0x23960e06}, b = {126, 0x4624b76e, 0x14091fd6, 0x6286cb7a, 0x3e1aa872}, res = {126, 0x43111321, 0x7df7aca9, 0x4caf9680, 0x657b6593}, carry = 1}, + {a = {124, 0x52ac128b, 0x45fa9e02, 0x6427aa53, 0x0feaf8b5}, b = {127, 0x18aa3cc4, 0x19e96b87, 0x6c0e4dcc, 0x45da3929}, res = {124, 0x3a01d5c7, 0x2c11327b, 0x78195c87, 0x4a10bf8b}, carry = 1}, + {a = {126, 0x4111b645, 0x1c825213, 0x2a8b75b3, 0x213c3a3e}, b = {126, 0x23f60c22, 0x490b2da9, 0x353a3844, 0x29d5dd47}, res = {126, 0x1d1baa23, 0x5377246a, 0x75513d6e, 0x77665cf6}, carry = 1}, + {a = {127, 0x09c853b4, 0x7541862d, 0x36e91f14, 0x6e89e67d}, b = {127, 0x21d3bfe2, 0x61403f04, 0x554b57fc, 0x76d413df}, res = {127, 0x67f493d2, 0x14014728, 0x619dc718, 0x77b5d29d}, carry = 1}, + {a = {127, 0x67761ecf, 0x3837a139, 0x335e0875, 0x79923e83}, b = {127, 0x76813044, 0x7c0f5491, 0x59acc858, 0x5a7a5795}, res = {127, 0x70f4ee8b, 0x3c284ca7, 0x59b1401c, 0x1f17e6ed}, carry = 0}, + {a = {126, 0x6429a8c8, 0x635056e0, 0x76e76b1d, 0x3837df4c}, b = {127, 0x531005db, 0x115acaa3, 0x69231eee, 0x43d19db2}, res = {126, 0x1119a2ed, 0x51f58c3d, 0x0dc44c2f, 0x7466419a}, carry = 1}, + {a = {127, 0x5014a598, 0x068dbb0f, 0x22fdc5bf, 0x6a670715}, b = {126, 0x7c4f94e9, 0x2e78116b, 0x224d3768, 0x2b95833a}, res = {127, 0x53c510af, 0x5815a9a3, 0x00b08e56, 0x3ed183db}, carry = 0}, + {a = {126, 0x75b94a38, 0x71d62d52, 0x6c557c79, 0x2d8cfb9f}, b = {127, 0x743c6840, 0x3b04bcff, 0x50744d2c, 0x608611a3}, res = {126, 0x017ce1f8, 0x36d17053, 0x1be12f4d, 0x4d06e9fc}, carry = 1}, + {a = {126, 0x04d24ee0, 0x37e1c03c, 0x50704c96, 0x2577ba6b}, b = {127, 0x6dd14735, 0x03c4f523, 0x379aa43a, 0x4fbfbf80}, res = {126, 0x170107ab, 0x341ccb18, 0x18d5a85c, 0x55b7faeb}, carry = 1}, + {a = {123, 0x572d0f53, 0x732fb14c, 0x7975e187, 0x05706d99}, b = {127, 0x51a90823, 0x707dd091, 0x2f6c63b4, 0x789adddf}, res = {123, 0x05840730, 0x02b1e0bb, 0x4a097dd3, 0x0cd58fba}, carry = 1}, + {a = {126, 0x68306d82, 0x51d883c8, 0x52a45e7f, 0x339e6fc7}, b = {127, 0x57da71c8, 0x72d5dd1c, 0x21a11ff7, 0x5d6ea34b}, res = {126, 0x1055fbba, 0x5f02a6ac, 0x31033e87, 0x562fcc7c}, carry = 1}, + {a = {125, 0x47b20e6e, 0x39411f59, 0x0324640b, 0x19996204}, b = {124, 0x4e55588b, 0x64846ee8, 0x57d16f9e, 0x0a3db8bc}, res = {125, 0x795cb5e3, 0x54bcb070, 0x2b52f46c, 0x0f5ba947}, carry = 0}, + {a = {127, 0x5a6ab51b, 0x0051b988, 0x1a192e89, 0x42b3d43a}, b = {125, 0x2daa3aa4, 0x063f37e9, 0x7ba7850f, 0x13dcea8c}, res = {127, 0x2cc07a77, 0x7a12819f, 0x1e71a979, 0x2ed6e9ad}, carry = 0}, + {a = {125, 0x46813953, 0x6920da78, 0x5b6db2f9, 0x12219a9c}, b = {126, 0x5666e4fb, 0x5458ccf2, 0x097952e7, 0x38f4e37f}, res = {125, 0x701a5458, 0x14c80d85, 0x51f46012, 0x592cb71d}, carry = 1}, + {a = {127, 0x47633dea, 0x6405989b, 0x1d019f24, 0x7dab4fa6}, b = {127, 0x686dbe43, 0x5715424a, 0x2027ac93, 0x70c8d29c}, res = {127, 0x5ef57fa7, 0x0cf05650, 0x7cd9f291, 0x0ce27d09}, carry = 0}, + {a = {127, 0x22705ca5, 0x2c180e47, 0x393c2083, 0x40d0fea1}, b = {124, 0x37ce1e5a, 0x17b2b88d, 0x49bd2a90, 0x0d5d9087}, res = {127, 0x6aa23e4b, 0x146555b9, 0x6f7ef5f3, 0x33736e19}, carry = 0}, + {a = {126, 0x5d61d089, 0x1391e7a4, 0x780aeb70, 0x3eb6e8e2}, b = {127, 0x7a54a013, 0x28f31254, 0x3d63ef25, 0x5d2cf7ff}, res = {126, 0x630d3076, 0x6a9ed54f, 0x3aa6fc4a, 0x6189f0e3}, carry = 1}, + {a = {126, 0x505f4672, 0x275eb845, 0x665c7a55, 0x31c26f48}, b = {127, 0x3af113f0, 0x6a0358aa, 0x009cd3d5, 0x551cd2f6}, res = {126, 0x156e3282, 0x3d5b5f9b, 0x65bfa67f, 0x5ca59c52}, carry = 1}, + {a = {126, 0x1a313b22, 0x04332012, 0x4cfb19fe, 0x3aa25e6c}, b = {127, 0x0525c494, 0x1a177649, 0x10bd52f2, 0x5d114ab1}, res = {126, 0x150b768e, 0x6a1ba9c9, 0x3c3dc70b, 0x5d9113bb}, carry = 1}, + {a = {126, 0x52981c2d, 0x4c46b130, 0x37b3c3cf, 0x231f6fc4}, b = {126, 0x76da8c93, 0x5e52130f, 0x7efb6a4f, 0x2e3c4907}, res = {126, 0x5bbd8f9a, 0x6df49e20, 0x38b8597f, 0x74e326bc}, carry = 1}, + {a = {127, 0x05600262, 0x0bb65331, 0x19f8d9cb, 0x5ddc2c62}, b = {127, 0x687b227e, 0x27348976, 0x072f2464, 0x4fbe7ebe}, res = {127, 0x1ce4dfe4, 0x6481c9ba, 0x12c9b566, 0x0e1dada4}, carry = 0}, + {a = {127, 0x6a2d2e4a, 0x1b8b56dd, 0x50946d73, 0x64b0886d}, b = {122, 0x68ceaba5, 0x5344cea7, 0x7aeae02f, 0x03444737}, res = {127, 0x015e82a5, 0x48468836, 0x55a98d43, 0x616c4135}, carry = 0}, + {a = {126, 0x2be80971, 0x7a1ffbd6, 0x7d5cd745, 0x310e6aa8}, b = {126, 0x31c62e38, 0x08a3a28d, 0x4d67f9a3, 0x30493db3}, res = {126, 0x7a21db39, 0x717c5948, 0x2ff4dda2, 0x00c52cf5}, carry = 0}, + {a = {125, 0x030ba3d3, 0x162c1b78, 0x3f878cb3, 0x1a5108ff}, b = {127, 0x1f37aec7, 0x01e1153a, 0x325aab1e, 0x4b9a6619}, res = {125, 0x63d3f50c, 0x144b063d, 0x0d2ce195, 0x4eb6a2e6}, carry = 1}, + {a = {126, 0x15a0528b, 0x29cbf0ad, 0x3048884b, 0x2c03d1c4}, b = {127, 0x5c32f40c, 0x192de971, 0x21a7f87d, 0x558c0542}, res = {126, 0x396d5e7f, 0x109e073b, 0x0ea08fce, 0x5677cc82}, carry = 1}, + {a = {126, 0x0b48d92b, 0x5b09cd3a, 0x73960e62, 0x3f0ed1cd}, b = {124, 0x7a3d70fe, 0x5ff9c6b1, 0x4bd0e555, 0x0cc51487}, res = {126, 0x110b682d, 0x7b100688, 0x27c5290c, 0x3249bd46}, carry = 0}, + {a = {127, 0x1bf5bd1c, 0x54104aef, 0x0c7ce316, 0x420fe407}, b = {126, 0x409409a2, 0x3d04fecd, 0x3e06417a, 0x3078c80b}, res = {127, 0x5b61b37a, 0x170b4c21, 0x4e76a19c, 0x11971bfb}, carry = 0}, + {a = {125, 0x33d00217, 0x1c394fa4, 0x574f8331, 0x1d57d174}, b = {127, 0x6630d00d, 0x666f444c, 0x1a90af22, 0x484d8ca0}, res = {125, 0x4d9f320a, 0x35ca0b57, 0x3cbed40e, 0x550a44d4}, carry = 1}, + {a = {127, 0x166e17c2, 0x1ecc3694, 0x1d8ad0ad, 0x5cfd9dd8}, b = {125, 0x130183de, 0x537699d8, 0x353fa45d, 0x114feeeb}, res = {127, 0x036c93e4, 0x4b559cbc, 0x684b2c4f, 0x4badaeec}, carry = 0}, + {a = {127, 0x2736ea4c, 0x34a77f3b, 0x5d62185c, 0x49f65789}, b = {126, 0x10ef92f4, 0x6d50c8cd, 0x3b2c8da0, 0x26d91a49}, res = {127, 0x16475758, 0x4756b66e, 0x22358abb, 0x231d3d40}, carry = 0}, + {a = {127, 0x7c90f6d6, 0x6e8c2441, 0x56303ec7, 0x648b71e2}, b = {127, 0x5efcffab, 0x35738e3f, 0x6a0010b5, 0x54fd6ef0}, res = {127, 0x1d93f72b, 0x39189602, 0x6c302e12, 0x0f8e02f1}, carry = 0}, + {a = {127, 0x09d4aa13, 0x39bee362, 0x48f85dfd, 0x54d4270d}, b = {121, 0x75609829, 0x5a20f480, 0x2a01c588, 0x0146be72}, res = {127, 0x147411ea, 0x5f9deee1, 0x1ef69874, 0x538d689b}, carry = 0}, + {a = {123, 0x5ca62e2f, 0x5bd68bd9, 0x009238b5, 0x04524739}, b = {126, 0x0f74d025, 0x68d0ff9b, 0x3cd174c2, 0x23b408c2}, res = {123, 0x4d315e0a, 0x73058c3e, 0x43c0c3f2, 0x609e3e76}, carry = 1}, +} + +I31_Test_Vector_Decode :: struct { + src: []byte, + decode: []u32, + mod: []u32, + mod_res: u32, +} + +@(rodata) +i31_decode_test_vectors := []I31_Test_Vector_Decode { + {src = {0x49}, decode = {7, 0x00000049}, mod = {42, 0x00000049}, mod_res = 1}, + {src = {0x27, 0xdb}, decode = {14, 0x000027db}, mod = {42, 0x000027db}, mod_res = 1}, + {src = {0xb8, 0xd7, 0x72}, decode = {24, 0x00b8d772}, mod = {42, 0x00b8d772}, mod_res = 1}, + {src = {0xa0, 0x94, 0x6e, 0xac}, decode = {33, 0x20946eac, 0x00000001}, mod = {42, 0x20946eac, 0x00000001}, mod_res = 1}, + {src = {0xed, 0x8a, 0xc2, 0xe4, 0x45}, decode = {41, 0x0ac2e445, 0x000001db}, mod = {42, 0x0ac2e445, 0x000001db}, mod_res = 1}, + {src = {0xfb, 0x37, 0x36, 0x46, 0x28, 0x4e}, decode = {49, 0x3646284e, 0x0001f66e}, mod = {42, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xd8, 0xcc, 0xbb, 0xf2, 0x13, 0x66, 0xdf}, decode = {57, 0x721366df, 0x01b19977}, mod = {42, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xcb, 0xc3, 0xe2, 0xa8, 0x4c, 0xfd, 0x9e, 0x8a}, decode = {66, 0x4cfd9e8a, 0x1787c550, 0x00000003}, mod = {42, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x0d, 0xd5, 0x76, 0xc1, 0xb4, 0x02, 0xdb, 0xb4, 0xb9}, decode = {70, 0x02dbb4b9, 0x2aed8368, 0x00000037}, mod = {42, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xf6, 0xb6, 0x08, 0xb3, 0xfb, 0x0c, 0x7e, 0x8e, 0x58, 0xa7}, decode = {82, 0x7e8e58a7, 0x1167f618, 0x0003dad8}, mod = {42, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xf9, 0x51, 0xde, 0x6a, 0x29, 0x54, 0x48, 0xf5, 0x3c, 0x78, 0x85}, decode = {90, 0x753c7885, 0x5452a891, 0x03e54779}, mod = {42, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xb5, 0x5a, 0x92, 0x18, 0x67, 0x09, 0x6e, 0x21, 0x5d, 0x84, 0x10, 0xce}, decode = {99, 0x5d8410ce, 0x4e12dc42, 0x556a4861, 0x00000005}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xd3, 0xa4, 0x7b, 0xa0, 0x90, 0x26, 0x36, 0x73, 0xa6, 0x7f, 0x28, 0x66, 0x4e}, decode = {107, 0x7f28664e, 0x4c6ce74c, 0x11ee8240, 0x0000069d}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x23, 0xfa, 0xe7, 0x0c, 0x6a, 0x07, 0x00, 0x86, 0xa2, 0xcf, 0x22, 0xa1, 0x1f, 0x62}, decode = {113, 0x22a11f62, 0x010d459e, 0x1c31a81c, 0x00011fd7}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x9c, 0x39, 0xf8, 0x67, 0x8a, 0x9f, 0x85, 0xb4, 0xdb, 0x92, 0x4b, 0xe6, 0xdc, 0x12, 0xa5}, decode = {123, 0x66dc12a5, 0x69b72497, 0x1e2a7e16, 0x04e1cfc3}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x8f, 0xc7, 0xcc, 0xda, 0x43, 0x21, 0x0b, 0x39, 0x0e, 0xc3, 0x51, 0xb0, 0xbd, 0xce, 0xbb, 0x73}, decode = {132, 0x3dcebb73, 0x1d86a361, 0x0c842ce4, 0x7e3e66d2, 0x00000008}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xc5, 0x8d, 0xd3, 0x1b, 0xda, 0x0d, 0x21, 0xfd, 0xb2, 0x69, 0xca, 0x12, 0xd4, 0x47, 0x44, 0x4e, 0x61}, decode = {140, 0x47444e61, 0x539425a8, 0x3487f6c9, 0x6e98ded0, 0x00000c58}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x4f, 0x00, 0x7f, 0x9c, 0x00, 0x11, 0x29, 0x8d, 0x94, 0xd6, 0x7e, 0x1c, 0xc3, 0x26, 0x78, 0xb7, 0xfb, 0xbb}, decode = {147, 0x78b7fbbb, 0x7c39864c, 0x26365359, 0x7ce00089, 0x0004f007}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xb8, 0x85, 0x3f, 0xe0, 0x03, 0x89, 0x20, 0x72, 0xf8, 0xa6, 0x66, 0x48, 0x0f, 0xdb, 0x63, 0x19, 0x8e, 0xbb, 0x00}, decode = {156, 0x198ebb00, 0x101fb6c6, 0x4be29999, 0x001c4903, 0x0b8853fe}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x3c, 0x88, 0x99, 0xf2, 0x23, 0xc8, 0x65, 0x6d, 0x6a, 0x93, 0xa0, 0xf6, 0xef, 0x8d, 0x28, 0xd8, 0x85, 0x9e, 0x8f, 0x85}, decode = {163, 0x059e8f85, 0x5f1a51b1, 0x2a4e83db, 0x1e432b6b, 0x48899f22, 0x00000007}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xd8, 0x28, 0x9e, 0xae, 0x7c, 0xc2, 0xf6, 0x9c, 0x72, 0xe7, 0x25, 0xf6, 0x0c, 0xec, 0xcc, 0x78, 0x5d, 0xf9, 0xb0, 0xe9, 0x9f}, decode = {173, 0x79b0e99f, 0x5998f0bb, 0x1c97d833, 0x17b4e397, 0x09eae7cc, 0x00001b05}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xec, 0x5f, 0x68, 0xa7, 0xb6, 0xa4, 0x7d, 0x01, 0x88, 0x39, 0xeb, 0x88, 0x3a, 0x33, 0x60, 0x8a, 0x59, 0x67, 0xaa, 0xfe, 0x67, 0xc2}, decode = {181, 0x2afe67c2, 0x4114b2cf, 0x2e20e8cd, 0x680c41cf, 0x0a7b6a47, 0x001d8bed}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x34, 0x8a, 0x87, 0xdb, 0xbf, 0x60, 0xa6, 0x58, 0x23, 0xe3, 0x99, 0x9a, 0x43, 0x5c, 0x43, 0x9e, 0xfe, 0xb2, 0xbd, 0xd7, 0xd2, 0xa3, 0x74}, decode = {187, 0x57d2a374, 0x3dfd657b, 0x690d710e, 0x411f1ccc, 0x3bf60a65, 0x069150fb}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xe6, 0xe7, 0x48, 0x95, 0x31, 0x9e, 0xdb, 0x97, 0xd7, 0x37, 0x17, 0x1a, 0x9a, 0x51, 0xde, 0x02, 0xa4, 0x7f, 0x84, 0xbd, 0x25, 0x02, 0xc7, 0xe1}, decode = {198, 0x2502c7e1, 0x48ff097a, 0x6947780a, 0x39b8b8d4, 0x19edb97d, 0x5ce912a6, 0x00000039}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x5c, 0xd5, 0xa2, 0x66, 0xf4, 0x01, 0x12, 0xba, 0xe8, 0x05, 0x63, 0xa0, 0xc0, 0xfa, 0xaf, 0xe1, 0x3d, 0xe4, 0xc5, 0x21, 0xab, 0xa9, 0x52, 0x99, 0x3f}, decode = {205, 0x2952993f, 0x498a4357, 0x6abf84f7, 0x2b1d0607, 0x112bae80, 0x344cde80, 0x00001735}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x8f, 0x93, 0xfc, 0x0c, 0x73, 0x29, 0x7d, 0x28, 0xdc, 0x71, 0x7b, 0x01, 0x53, 0x4f, 0x87, 0xdd, 0xf5, 0x43, 0xe2, 0xb0, 0x97, 0x1d, 0xe1, 0xf7, 0x7f, 0xcf}, decode = {214, 0x61f77fcf, 0x45612e3b, 0x1f77d50f, 0x580a9a7c, 0x528dc717, 0x018e652f, 0x0023e4ff}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xd7, 0x86, 0x9b, 0x8d, 0x91, 0x97, 0xff, 0x98, 0xbe, 0xa7, 0x67, 0x73, 0xda, 0x7a, 0x07, 0xe7, 0x07, 0xf3, 0x61, 0xc4, 0x2d, 0x01, 0x96, 0x68, 0xbf, 0xd6, 0x36}, decode = {222, 0x68bfd636, 0x085a032c, 0x1c1fcd87, 0x1ed3d03f, 0x0bea7677, 0x3232fff3, 0x35e1a6e3}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xf7, 0xec, 0xfa, 0x44, 0x18, 0x5e, 0x6a, 0x51, 0x98, 0x3d, 0x3b, 0xc9, 0xcf, 0xb5, 0x9f, 0x97, 0xa8, 0x7d, 0xdd, 0xda, 0xf3, 0xf3, 0x1c, 0x02, 0x9c, 0x40, 0x5c, 0x8a}, decode = {231, 0x1c405c8a, 0x67e63805, 0x21f7776b, 0x7dacfcbd, 0x03d3bc9c, 0x0bcd4a33, 0x7b3e9106, 0x0000007b}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0xca, 0x32, 0x04, 0xef, 0xc8, 0x40, 0x55, 0xc9, 0x67, 0x2e, 0x3b, 0x1a, 0xbe, 0x2c, 0xf1, 0xdd, 0xe0, 0xaa, 0x46, 0x31, 0x40, 0xc5, 0xf6, 0xaa, 0x25, 0x88, 0x0d, 0x0f, 0x30}, decode = {239, 0x080d0f30, 0x0bed544b, 0x2918c503, 0x678eef05, 0x63b1abe2, 0x0ab92ce5, 0x013bf210, 0x00006519}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x96, 0xb5, 0x62, 0x03, 0x50, 0x9f, 0x04, 0xfc, 0xc3, 0x06, 0x25, 0xc1, 0xa7, 0x21, 0x6d, 0xeb, 0x60, 0x8d, 0xb0, 0x02, 0x34, 0x18, 0xaf, 0xd1, 0xb0, 0x23, 0x0f, 0x6a, 0xe6, 0x63}, decode = {247, 0x0f6ae663, 0x5fa36046, 0x4008d062, 0x6f5b046d, 0x5c1a7216, 0x1f9860c4, 0x00d427c1, 0x004b5ab1}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, + {src = {0x5c, 0x00, 0x1a, 0x67, 0x07, 0x8e, 0x49, 0x72, 0x30, 0x6f, 0x2b, 0xa6, 0x93, 0x37, 0x6a, 0x2b, 0x5a, 0x8f, 0xb4, 0x30, 0x65, 0x89, 0xa1, 0x7b, 0xbc, 0xf9, 0x85, 0x3f, 0x44, 0x64, 0x48}, decode = {254, 0x3f446448, 0x7779f30a, 0x41962685, 0x5ad47da1, 0x693376a2, 0x460de574, 0x41e3925c, 0x2e000d33, 0x00000000}, mod = {42, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, mod_res = 0}, +} + +I31_Test_RShift :: struct { + orig: []u32, + res: []u32, + shift: i32, +} + +@(rodata) +i31_rshift_test_vectors := []I31_Test_RShift { + {orig = {92, 0x4b451fff, 0x2874869d, 0x0d1b97a7}, res = {92, 0x4b451fff, 0x2874869d, 0x0d1b97a7}, shift = 0}, + {orig = {94, 0x44709cd4, 0x60d36574, 0x3a587e31}, res = {94, 0x22384e6a, 0x7069b2ba, 0x1d2c3f18}, shift = 1}, + {orig = {94, 0x77ea5771, 0x78324ed5, 0x3d9ad39d}, res = {94, 0x3dfa95dc, 0x3e0c93b5, 0x0f66b4e7}, shift = 2}, + {orig = {94, 0x631db445, 0x248c243a, 0x3f1cc589}, res = {94, 0x2c63b688, 0x14918487, 0x07e398b1}, shift = 3}, + {orig = {95, 0x6f23a8ed, 0x7d5a3c91, 0x7e913a13}, res = {95, 0x0ef23a8e, 0x1fd5a3c9, 0x07e913a1}, shift = 4}, + {orig = {94, 0x03dd8abf, 0x5f02ee65, 0x249f3f58}, res = {94, 0x141eec55, 0x62f81773, 0x0124f9fa}, shift = 5}, + {orig = {94, 0x2927bee6, 0x4f308116, 0x3279228a}, res = {94, 0x2ca49efb, 0x153cc204, 0x00c9e48a}, shift = 6}, + {orig = {95, 0x22687024, 0x73cb5fd3, 0x52d71799}, res = {95, 0x5344d0e0, 0x19e796bf, 0x00a5ae2f}, shift = 7}, + {orig = {94, 0x71a3bca0, 0x27642d52, 0x23e2059f}, res = {94, 0x2971a3bc, 0x4fa7642d, 0x0023e205}, shift = 8}, + {orig = {95, 0x180d85aa, 0x12a8516d, 0x4797741e}, res = {95, 0x5b4c06c2, 0x07895428, 0x0023cbba}, shift = 9}, + {orig = {95, 0x7b30aeeb, 0x3ababf4f, 0x6a446aff}, res = {95, 0x69fecc2b, 0x5feeaeaf, 0x001a911a}, shift = 10}, + {orig = {94, 0x3b2d5f09, 0x7bde80e9, 0x33ed8ce0}, res = {94, 0x0e9765ab, 0x4e0f7bd0, 0x00067db1}, shift = 11}, + {orig = {93, 0x62c89828, 0x20d78d11, 0x14bbd081}, res = {93, 0x688e2c89, 0x040a0d78, 0x00014bbd}, shift = 12}, + {orig = {95, 0x102c28e6, 0x31f2a99f, 0x6c337ed1}, res = {95, 0x267c8161, 0x7b458f95, 0x0003619b}, shift = 13}, + {orig = {94, 0x78097c9b, 0x6b3f99c8, 0x2ea10fbb}, res = {94, 0x3391e025, 0x1f77acfe, 0x0000ba84}, shift = 14}, + {orig = {92, 0x4684f360, 0x56d54074, 0x0dd7bfa7}, res = {92, 0x40748d09, 0x3fa7adaa, 0x00001baf}, shift = 15}, + {orig = {93, 0x59e9b272, 0x1792ca1d, 0x134ce6d8}, res = {93, 0x650ed9e9, 0x736c1792, 0x0000134c}, shift = 16}, + {orig = {94, 0x2be2d592, 0x566508a1, 0x3a8622ab}, res = {94, 0x422855f1, 0x08aaeb32, 0x00001d43}, shift = 17}, + {orig = {95, 0x3082be20, 0x118cde6a, 0x57a1832c}, res = {95, 0x1bcd4c20, 0x30658463, 0x000015e8}, shift = 18}, + {orig = {95, 0x7b7ec9fc, 0x30304a84, 0x7cb524f7}, res = {95, 0x04a84f6f, 0x524f7606, 0x00000f96}, shift = 19}, + {orig = {94, 0x7a038786, 0x5902ae15, 0x2ee36b72}, res = {94, 0x1570afa0, 0x1b5b9590, 0x000002ee}, shift = 20}, + {orig = {95, 0x2a127f63, 0x5d1b323f, 0x7dc23bd7}, res = {95, 0x6cc8fd50, 0x08ef5ee8, 0x000003ee}, shift = 21}, + {orig = {95, 0x3cf76a47, 0x30310973, 0x69bc9430}, res = {95, 0x6212e6f3, 0x792860c0, 0x000001a6}, shift = 22}, + {orig = {94, 0x3ca70baa, 0x07747e80, 0x3a710be4}, res = {94, 0x747e8079, 0x710be40e, 0x00000074}, shift = 23}, + {orig = {95, 0x6bc0e3b9, 0x77b2972f, 0x5b9f893c}, res = {95, 0x594b97eb, 0x4fc49e77, 0x0000005b}, shift = 24}, + {orig = {90, 0x49e59ccc, 0x7d67d332, 0x02b4f127}, res = {90, 0x59f4cca4, 0x2d3c49fe, 0x00000001}, shift = 25}, + {orig = {94, 0x55e1b1d7, 0x7a1c0bdb, 0x217e8547}, res = {94, 0x43817b75, 0x2fd0a8fe, 0x00000008}, shift = 26}, + {orig = {94, 0x67fb980c, 0x20a9410b, 0x25de2ffd}, res = {94, 0x0a9410bc, 0x5de2ffd4, 0x00000004}, shift = 27}, + {orig = {92, 0x18363569, 0x0b492aba, 0x0a37f42c}, res = {92, 0x5a4955d1, 0x51bfa160, 0x00000000}, shift = 28}, + {orig = {95, 0x23bb0e53, 0x4bb6c3aa, 0x77932915}, res = {95, 0x2edb0ea9, 0x5e4ca456, 0x00000003}, shift = 29}, + {orig = {95, 0x725acb18, 0x1da81862, 0x6d6c306b}, res = {95, 0x3b5030c5, 0x5ad860d6, 0x00000001}, shift = 30}, + {orig = {95, 0x70d51c12, 0x44e8b652, 0x4a0d2a6b}, res = {95, 0x44e8b652, 0x4a0d2a6b, 0x00000000}, shift = 31}, +} + +I31_Test_Reduce :: struct { + orig: []u32, + res: []u32, +} + +@(rodata) +i31_reduce_test_vectors := []I31_Test_Reduce { + {orig = {62, 0x27da8fd9, 0x2fea2339}, res = {42, 0x27f284e9, 0x00000339}}, + {orig = {95, 0x37856cc1, 0x54ad3e73, 0x718777f1}, res = {42, 0x33efdfc1, 0x0000022f, 0x718777f1}}, + {orig = {123, 0x4787b519, 0x47fa51cf, 0x11ef98ae, 0x058b1a99}, res = {42, 0x567ed6bd, 0x000002fe, 0x11ef98ae, 0x058b1a99}}, + {orig = {158, 0x328fbfb6, 0x6e9ec225, 0x0241df84, 0x4a3627d0, 0x3a79e4a4}, res = {42, 0x005f59f0, 0x000001aa, 0x0241df84, 0x4a3627d0, 0x3a79e4a4}}, + {orig = {191, 0x451321f5, 0x45f677b9, 0x2c43b0b8, 0x7cb722c1, 0x70e594e6, 0x4c81757c}, res = {42, 0x684ba25d, 0x0000008d, 0x2c43b0b8, 0x7cb722c1, 0x70e594e6, 0x4c81757c}}, + {orig = {222, 0x0749fe0b, 0x3c45795b, 0x21e6f14c, 0x265b264d, 0x37def307, 0x35019b6b, 0x25ab0a3e}, res = {42, 0x36c99d3d, 0x00000173, 0x21e6f14c, 0x265b264d, 0x37def307, 0x35019b6b, 0x25ab0a3e}}, + {orig = {255, 0x1c2630cd, 0x0c0d9a7e, 0x375154d0, 0x67249adf, 0x0df9ec39, 0x73b5ad9e, 0x396d0f52, 0x4ed9b56a}, res = {42, 0x79997cc9, 0x000002b4, 0x375154d0, 0x67249adf, 0x0df9ec39, 0x73b5ad9e, 0x396d0f52, 0x4ed9b56a}}, + {orig = {286, 0x35b7eae2, 0x033002a6, 0x149766aa, 0x5d4a5a16, 0x7d09704c, 0x380c8cf2, 0x249df2ff, 0x03caabad, 0x214fc645}, res = {42, 0x20f488ba, 0x000003e1, 0x149766aa, 0x5d4a5a16, 0x7d09704c, 0x380c8cf2, 0x249df2ff, 0x03caabad, 0x214fc645}}, + {orig = {315, 0x7a5813dc, 0x76309cbf, 0x03e432bc, 0x218df9a1, 0x1a0d0525, 0x793ad550, 0x280cbf6e, 0x18356492, 0x4b6f39a1, 0x04adeb0d}, res = {42, 0x0016ad0c, 0x000000a8, 0x03e432bc, 0x218df9a1, 0x1a0d0525, 0x793ad550, 0x280cbf6e, 0x18356492, 0x4b6f39a1, 0x04adeb0d}}, + {orig = {351, 0x0ded15c8, 0x3875535e, 0x4627ebc1, 0x101a8369, 0x7300acd4, 0x22113509, 0x2a441bc0, 0x25902fec, 0x230133c0, 0x7ecaa587, 0x439bfdcd}, res = {42, 0x60547c04, 0x00000144, 0x4627ebc1, 0x101a8369, 0x7300acd4, 0x22113509, 0x2a441bc0, 0x25902fec, 0x230133c0, 0x7ecaa587, 0x439bfdcd}}, +} + +@(rodata) +i31_decode_reduce_test_vectors := []I31_Test_Vector_Decode { + {src = {171, 54, 46}, decode = {42, 0x00ab362e, 0x00000000, 0x00000000}}, + {src = {87, 80, 187, 242}, decode = {42, 0x5750bbf2, 0x00000000, 0x00000000}}, + {src = {181, 9, 43, 65, 203}, decode = {42, 0x092b41cb, 0x0000016a, 0x00000000}}, + {src = {196, 160, 88, 214, 25, 234}, decode = {42, 0x58d61aae, 0x00000140, 0x00000000}}, + {src = {223, 248, 213, 188, 56, 226, 125}, decode = {42, 0x3c39c275, 0x000001ab, 0x00000000}}, + {src = {252, 152, 213, 96, 59, 205, 38, 68}, decode = {42, 0x3cc9bf18, 0x000002c0, 0x00000000}}, + {src = {61, 151, 139, 227, 56, 129, 245, 7, 245}, decode = {42, 0x3f8c93d7, 0x00000271, 0x00000000}}, + {src = {50, 242, 36, 43, 28, 76, 75, 80, 191, 213}, decode = {42, 0x3d74eaf1, 0x000000fe, 0x00000000}}, + {src = {45, 174, 63, 171, 108, 66, 180, 223, 196, 24, 202}, decode = {42, 0x1f6f853a, 0x000000c6, 0x00000000}}, + {src = {106, 140, 119, 25, 145, 178, 50, 134, 118, 181, 241, 205}, decode = {42, 0x10480e8b, 0x000001fb, 0x00000000}}, +} + +I31_Test_Mul_Add_Small :: struct { + orig: []u32, + res: []u32, + z: u32, +} + +@(rodata) +i31_mul_add_test_vectors := []I31_Test_Mul_Add_Small { + {orig = {63, 0x157df37a, 0x5e97b10e}, res = {63, 0x438abf24, 0x7fffff7a}, z = 42}, + {orig = {63, 0x6c11d92a, 0x74b6b943}, res = {63, 0x50f60940, 0x0000012a}, z = 84}, + {orig = {63, 0x0bf17f5a, 0x756c85bb}, res = {63, 0x6ec5f93e, 0x7fffff5a}, z = 126}, + {orig = {63, 0x0d24e4eb, 0x5891203e}, res = {63, 0x0f86931a, 0x000000eb}, z = 168}, + {orig = {63, 0x41dda071, 0x6deb6411}, res = {63, 0x0460efa2, 0x00000071}, z = 210}, + {orig = {63, 0x29793d56, 0x4d5204e5}, res = {63, 0x3954bd9a, 0x00000156}, z = 252}, + {orig = {58, 0x40e72062, 0x039ba033}, res = {58, 0x0ce074b6, 0x00000062}, z = 294}, + {orig = {63, 0x6e9b147b, 0x60dcf3ef}, res = {63, 0x7bf74edc, 0x7ffffc7c}, z = 336}, + {orig = {62, 0x74572099, 0x3af8369a}, res = {62, 0x26ba2d0a, 0x0000009a}, z = 378}, +} + +I31_Test_Vector_Encode :: struct { + orig: []u32, + encoded: []u8, +} + +@(rodata) +i31_encode_test_vectors := []I31_Test_Vector_Encode { + {orig = {30, 0x2003ca33}, encoded = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 3, 202, 51}}, + {orig = {61, 0x13d86a42, 0x1c0fc55a}, encoded = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 7, 226, 173, 19, 216, 106, 66}}, + {orig = {93, 0x0adffd1b, 0x3a03c1bc, 0x19682bb9}, encoded = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 90, 10, 238, 93, 1, 224, 222, 10, 223, 253, 27}}, + {orig = {127, 0x193841a2, 0x5cf0aa2f, 0x57594f6d, 0x4fc77899}, encoded = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 248, 239, 19, 53, 214, 83, 219, 110, 120, 85, 23, 153, 56, 65, 162}}, + {orig = {158, 0x219d0fd8, 0x623e21ae, 0x5c9ad413, 0x6dc292d4, 0x2b2f85fe}, encoded = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 178, 248, 95, 237, 184, 82, 90, 151, 38, 181, 4, 241, 31, 16, 215, 33, 157, 15, 216}}, + {orig = {189, 0x69363092, 0x4b751e5d, 0x663b4745, 0x2e93e19e, 0x1a3dabce, 0x1d4caa3f}, encoded = {0, 0, 0, 0, 0, 0, 0, 0, 0, 234, 101, 81, 249, 163, 218, 188, 229, 210, 124, 51, 217, 142, 209, 209, 101, 186, 143, 46, 233, 54, 48, 146}}, + {orig = {222, 0x016b86ed, 0x29c31304, 0x15cca452, 0x6abefa4a, 0x7410f562, 0x450559df, 0x28ad9252}, encoded = {0, 0, 0, 0, 0, 162, 182, 73, 74, 40, 42, 206, 255, 65, 15, 86, 45, 87, 223, 73, 69, 115, 41, 20, 148, 225, 137, 130, 1, 107, 134, 237}}, + {orig = {253, 0x4b6d0cd3, 0x357f8b7d, 0x4bd86436, 0x5838a86c, 0x10280aa0, 0x22f9b902, 0x4bdb5c1c, 0x19a20f9e}, encoded = {0, 51, 68, 31, 61, 47, 109, 112, 113, 23, 205, 200, 17, 2, 128, 170, 11, 7, 21, 13, 146, 246, 25, 13, 154, 191, 197, 190, 203, 109, 12, 211}}, +} + +I31_Test_Ninv :: struct { + orig: []u32, + ninv: []u32, +} + +@(rodata) +i31_ninv_test_vectors := I31_Test_Ninv { + orig = {0x00000c5c, 0x47faf728, 0x69a8e9d5, 0x49f5015c, 0x4ea9aea5, 0x164bcf32, 0x3fc395b4, 0x1c0a908a, 0x795f47f2, 0x79aa0c9a, 0x1a37680f, 0x2834cf17, 0x499235c8, 0x6d239632, 0x0560438b, 0x2cf4b82b, 0x5133fd25, 0x3b63a8b9, 0x45b66eca, 0x0213c13c, 0x6cd589f3, 0x4ed03f68, 0x722eb913, 0x670c76f4, 0x444d31d0, 0x1809da41, 0x3656a4af, 0x2ab43d09, 0x281c0e85, 0x426b3fd3, 0x680413c0, 0x065884c2, 0x55e4db17, 0x3839a23f, 0x3cc508b0, 0x3c492cda, 0x43325992, 0x5bc31283, 0x3891deaa, 0x5ddddd43, 0x64314225, 0x43a1c73b, 0x5e0ec431, 0x27f583bf, 0x491a73dc, 0x377982b8, 0x0b760791, 0x7835c61d, 0x0192b0f7, 0x129ca7ca, 0x16333e2d, 0x39c07864, 0x4d4b0d1b, 0x252fdb31, 0x2ca1bef8, 0x0358e216, 0x6aeda289, 0x201bca8f, 0x6ded1451, 0x5fbb04bb, 0x03d86c3b, 0x2f402c96, 0x4567a0ad, 0x6159d4de, 0x494f89b9, 0x7035fbdf, 0x5c3cfbd6, 0x40bfdbc8, 0x5e27b49c, 0x4fee508e, 0x76bd2f29, 0x45b49e47, 0x7da53921, 0x3d9380aa, 0x5d5bce86, 0x212b2912, 0x2e3e0a61, 0x218cfe04, 0x22ebe499, 0x70713a1c, 0x5b690632, 0x3f5c8dc1, 0x323815a5, 0x1f9f4c8f, 0x56a54a21, 0x469a2122, 0x6fb2beea, 0x643ac847, 0x01d2c5bc, 0x7dc51683, 0x0b876ebe, 0x214a069a, 0x535258ed, 0x529b3dce, 0x2760b2c2, 0x78cf5cd3, 0x19d1d3f2, 0x572ebc02, 0x1ab91604, 0x0b5f09a0}, + ninv = {0x00000000, 0x00000000, 0x66e2f883, 0x00000000, 0x210176d3, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x40ad7911, 0x3954a759, 0x00000000, 0x00000000, 0x657333dd, 0x11c1b97d, 0x1c122953, 0x40642277, 0x00000000, 0x00000000, 0x7f3dc8c5, 0x00000000, 0x166a06e5, 0x00000000, 0x00000000, 0x605cca3f, 0x3548cdb1, 0x35b19ec7, 0x681845b3, 0x1c438fa5, 0x00000000, 0x00000000, 0x2beff359, 0x79f2b241, 0x00000000, 0x00000000, 0x00000000, 0x6040b3d5, 0x00000000, 0x1ee96895, 0x1f18f653, 0x7f80860d, 0x3901eb2f, 0x56ff93c1, 0x00000000, 0x00000000, 0x562e668f, 0x72bad3cb, 0x3de0ef39, 0x00000000, 0x6be23e5b, 0x00000000, 0x3a327aed, 0x0fae622f, 0x00000000, 0x00000000, 0x004d8c47, 0x5a1feb91, 0x76f34b4f, 0x68675f8d, 0x7dec730d, 0x00000000, 0x5a953cdb, 0x00000000, 0x65605377, 0x7aa67fe1, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x4eaf22e7, 0x4a585489, 0x3fa2751f, 0x00000000, 0x00000000, 0x00000000, 0x7107e65f, 0x00000000, 0x5f81d057, 0x00000000, 0x00000000, 0x2d7b7dbf, 0x0e2f35d3, 0x75e7ad91, 0x4ed7461f, 0x00000000, 0x00000000, 0x4a199e89, 0x00000000, 0x45bf97d5, 0x00000000, 0x00000000, 0x4ada3b1b, 0x00000000, 0x00000000, 0x7456a4a5, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +} + +I31_Test_Monty_Mul :: struct { + x: []u32, + y: []u32, + m: []u32, + res: []u32, +} + +@(rodata) +i31_monty_mul_test_vectors := []I31_Test_Monty_Mul { + {x = {127, 0x7c5a766e, 0x6ce75a35, 0x1c64cb75, 0x51263ee0, 0x00000000}, y = {127, 0x75063ae3, 0x3f506a6f, 0x1d1ad6d5, 0x47a85d53, 0x00000000}, m = {159, 0x7e0a5a33, 0x41c36cc5, 0x065bfbbd, 0x4608e748, 0x69ac6740, 0x00000000}, res = {159, 0x3e2b1a07, 0x4990ba63, 0x15f1a1ea, 0x6e8ff678, 0x13ba5b66}}, + {x = {124, 0x5232b140, 0x341f2854, 0x3e6bde54, 0x092324e1, 0x00000000}, y = {127, 0x6c66ba58, 0x51577b9e, 0x72a600d4, 0x53bd0450, 0x00000000}, m = {159, 0x153010a5, 0x7d0ddec2, 0x13c7f180, 0x1cd216b3, 0x5818bdd7, 0x00000000}, res = {159, 0x6de73758, 0x21d8ff38, 0x69b18e1e, 0x5d1c7d47, 0x41c75523}}, + {x = {126, 0x176e0f5b, 0x0a98100a, 0x475e0a72, 0x348f9141, 0x00000000}, y = {127, 0x334e2dfe, 0x2c988363, 0x73e23cf8, 0x606dcc5f, 0x00000000}, m = {159, 0x7023de77, 0x54103e3b, 0x338cd291, 0x0e2c1e77, 0x6fa41147, 0x00000000}, res = {159, 0x488db6ba, 0x3ec6b26b, 0x0dddf8b5, 0x4e0334db, 0x2125e61c}}, + {x = {127, 0x1018738e, 0x233c04d7, 0x46a665c0, 0x79c84e0a, 0x00000000}, y = {127, 0x783e44fe, 0x14a1ea17, 0x31bea107, 0x66c49111, 0x00000000}, m = {158, 0x580ca6c1, 0x61ad5aad, 0x238b9bdb, 0x0ce14c93, 0x2749fc5c, 0x00000000}, res = {158, 0x37557af5, 0x5ca2f6fb, 0x7fdca455, 0x4d436f9b, 0x18d60f4f}}, + {x = {126, 0x45da293a, 0x1665982a, 0x0e8fc303, 0x231c2772, 0x00000000}, y = {126, 0x542485c1, 0x3f2f7d3f, 0x684047a6, 0x3abaed33, 0x00000000}, m = {159, 0x03d31fd1, 0x6f0c8ff3, 0x12825696, 0x199fda81, 0x76457daf, 0x00000000}, res = {159, 0x6c6852a3, 0x0d1fd2b0, 0x67e96590, 0x768c4fca, 0x3ad66382}}, + {x = {126, 0x21d23a0e, 0x5ba5d464, 0x29697487, 0x338d2cff, 0x00000000}, y = {125, 0x29e49a2c, 0x144564ae, 0x3d17958d, 0x101f7e41, 0x00000000}, m = {158, 0x4545452b, 0x6e68f705, 0x2302e752, 0x07b5d5d6, 0x3657a220, 0x00000000}, res = {158, 0x3e90a0ed, 0x457cb17b, 0x53b4f9f5, 0x7a6324ce, 0x35d2cdd7}}, + {x = {125, 0x4732d8b6, 0x45573e7d, 0x07f4963f, 0x12a290dd, 0x00000000}, y = {127, 0x39baf88d, 0x5d858830, 0x4580eb58, 0x6d196a63, 0x00000000}, m = {159, 0x0cb6812b, 0x482b08af, 0x4f1b863c, 0x3cef2e92, 0x50f7c7f1, 0x00000000}, res = {159, 0x6ad503c6, 0x27c67766, 0x5fca2845, 0x15bbcb6b, 0x46c3cb6c}}, + {x = {127, 0x354a4328, 0x651d0b7f, 0x43a452fb, 0x7bd74c98, 0x00000000}, y = {126, 0x3cfc2842, 0x71256a54, 0x3719176f, 0x34407271, 0x00000000}, m = {159, 0x306eba87, 0x772cb87d, 0x4907c954, 0x5dc5e91b, 0x74d5eb17, 0x00000000}, res = {159, 0x49e12a70, 0x7e89a281, 0x1ea3548a, 0x54711053, 0x713bfe04}}, + {x = {126, 0x4affb101, 0x52a6864d, 0x7152422e, 0x3cca5f4e, 0x00000000}, y = {126, 0x3f4b1e18, 0x32aaa89a, 0x635dcdc7, 0x3c44b1de, 0x00000000}, m = {158, 0x01432ccf, 0x2bcf7f6d, 0x7561291c, 0x7064f0cb, 0x3381e843, 0x00000000}, res = {158, 0x03895437, 0x6792e4bb, 0x68391714, 0x5a6104d6, 0x16bbc670}}, + {x = {127, 0x56c42650, 0x2723c136, 0x4d64beac, 0x5e656e91, 0x00000000}, y = {126, 0x1aa1d10f, 0x01fffab5, 0x44fd097e, 0x3483b271, 0x00000000}, m = {159, 0x2f77d067, 0x1f191f5b, 0x1a1a8568, 0x52026c09, 0x4692f6e6, 0x00000000}, res = {159, 0x1b1bfa15, 0x3726bd88, 0x1af57d8e, 0x2de30fb3, 0x3f96e86f}}, +} + +I31_Test_To_Monty :: struct { + orig: []u32, + x: []u32, + m: []u32, +} + +@(rodata) +i31_to_monty_test_vectors := []I31_Test_To_Monty { + {orig = {123, 0x3f3aff0e, 0x459fe053, 0x4bba523d, 0x04b91a78, 0x00000000}, x = {123, 0x2eab2260, 0x4d3d8e5d, 0x2943569d, 0x01d1c5f3, 0x00000000}, m = {123, 0x3f3aff1b, 0x459fe061, 0x4bba524b, 0x04b91a78, 0x00000000}}, + {orig = {127, 0x111641a2, 0x4fcfb2b4, 0x1cf1576b, 0x6b088658, 0x00000000}, x = {127, 0x25c08f99, 0x3bdc976a, 0x3a808f23, 0x5be48931, 0x00000000}, m = {127, 0x111641af, 0x4fcfb2c1, 0x1cf15779, 0x6b088658, 0x00000000}}, + {orig = {126, 0x03accd4a, 0x34a7e2d7, 0x749af3f3, 0x25febe05, 0x00000000}, x = {126, 0x763fca85, 0x61a5498c, 0x28eb0856, 0x1dab3aee, 0x00000000}, m = {126, 0x03accd57, 0x34a7e2e5, 0x749af401, 0x25febe05, 0x00000000}}, + {orig = {127, 0x6d8c4617, 0x01cffa24, 0x3475f32e, 0x7c9ba743, 0x00000000}, x = {127, 0x7304a0f6, 0x653ea916, 0x6c412db9, 0x427e57a8, 0x00000000}, m = {127, 0x6d8c4625, 0x01cffa31, 0x3475f33b, 0x7c9ba743, 0x00000000}}, + {orig = {127, 0x2da2a286, 0x152f4795, 0x3f383173, 0x6622cb58, 0x00000000}, x = {127, 0x1e118541, 0x63461082, 0x57a7e060, 0x3529988e, 0x00000000}, m = {127, 0x2da2a293, 0x152f47a3, 0x3f383181, 0x6622cb58, 0x00000000}}, + {orig = {127, 0x2a17a287, 0x52b707dd, 0x30d2b30f, 0x6334fa45, 0x00000000}, x = {127, 0x06823214, 0x2c2ddf09, 0x286443db, 0x231af948, 0x00000000}, m = {127, 0x2a17a295, 0x52b707eb, 0x30d2b31d, 0x6334fa45, 0x00000000}}, + {orig = {127, 0x41152b43, 0x0a898780, 0x5a517da6, 0x72eb605e, 0x00000000}, x = {127, 0x2761082a, 0x1e1ff23b, 0x3f2516a3, 0x0b165b25, 0x00000000}, m = {127, 0x41152b51, 0x0a89878d, 0x5a517db3, 0x72eb605e, 0x00000000}}, + {orig = {127, 0x6b29c7ff, 0x6297d9f9, 0x3225bebb, 0x71bc4932, 0x00000000}, x = {127, 0x4bb7368d, 0x12320331, 0x5cc9ce0e, 0x1eb2ad59, 0x00000000}, m = {127, 0x6b29c80d, 0x6297da07, 0x3225bec9, 0x71bc4932, 0x00000000}}, + {orig = {127, 0x6cb514c0, 0x3ff230d8, 0x6a6915f5, 0x73655603, 0x00000000}, x = {127, 0x1ebae285, 0x28f81b38, 0x37f80ed8, 0x21dc723e, 0x00000000}, m = {127, 0x6cb514cd, 0x3ff230e5, 0x6a691603, 0x73655603, 0x00000000}}, + {orig = {127, 0x632339fa, 0x30508879, 0x038d4f85, 0x4bff3ced, 0x00000000}, x = {127, 0x3a3502a9, 0x4364df08, 0x3c8b1fdc, 0x20b2fa04, 0x00000000}, m = {127, 0x63233a07, 0x30508887, 0x038d4f93, 0x4bff3ced, 0x00000000}}, +} + +I31_Test_Mod_Pow :: struct { + orig: []u32, + x: []u32, + e: []u8, + m: []u32, +} + +@(rodata) +i31_mod_pow_test_vectors := []I31_Test_Mod_Pow { + {orig = {123, 0x2eab2260, 0x4d3d8e5d, 0x2943569d, 0x01d1c5f3, 0x00000000}, x = {123, 0x030692d4, 0x14836f1f, 0x7c343636, 0x014fdd77, 0x00000000}, e = {81, 188, 252, 55, 213, 144, 254, 197, 116, 215, 162}, m = {123, 0x3f3aff1b, 0x459fe061, 0x4bba524b, 0x04b91a78, 0x00000000}}, + {orig = {127, 0x25c08f99, 0x3bdc976a, 0x3a808f23, 0x5be48931, 0x00000000}, x = {127, 0x3cd4569e, 0x6474f743, 0x23fc7461, 0x05e72909, 0x00000000}, e = {145, 145, 164, 213, 254, 94, 63, 192, 72, 205, 16}, m = {127, 0x111641af, 0x4fcfb2c1, 0x1cf15779, 0x6b088658, 0x00000000}}, + {orig = {126, 0x763fca85, 0x61a5498c, 0x28eb0856, 0x1dab3aee, 0x00000000}, x = {126, 0x0eb01d51, 0x178d8313, 0x044cbdfa, 0x022d38b4, 0x00000000}, e = {254, 224, 156, 88, 224, 176, 213, 125, 112, 43, 72}, m = {126, 0x03accd57, 0x34a7e2e5, 0x749af401, 0x25febe05, 0x00000000}}, + {orig = {127, 0x7304a0f6, 0x653ea916, 0x6c412db9, 0x427e57a8, 0x00000000}, x = {127, 0x3b1cad0e, 0x15e996b3, 0x6ccc145e, 0x63b7aa04, 0x00000000}, e = {250, 142, 147, 53, 107, 204, 201, 168, 122, 147, 143}, m = {127, 0x6d8c4625, 0x01cffa31, 0x3475f33b, 0x7c9ba743, 0x00000000}}, + {orig = {127, 0x1e118541, 0x63461082, 0x57a7e060, 0x3529988e, 0x00000000}, x = {127, 0x3ed716f7, 0x09f828bb, 0x00840e9d, 0x19c59712, 0x00000000}, e = {244, 245, 26, 174, 174, 246, 9, 106, 176, 161, 120}, m = {127, 0x2da2a293, 0x152f47a3, 0x3f383181, 0x6622cb58, 0x00000000}}, + {orig = {127, 0x06823214, 0x2c2ddf09, 0x286443db, 0x231af948, 0x00000000}, x = {127, 0x79062be3, 0x5787887e, 0x59a8999f, 0x3f1c71f1, 0x00000000}, e = {212, 118, 203, 181, 125, 193, 238, 33, 133, 239, 12}, m = {127, 0x2a17a295, 0x52b707eb, 0x30d2b31d, 0x6334fa45, 0x00000000}}, + {orig = {127, 0x2761082a, 0x1e1ff23b, 0x3f2516a3, 0x0b165b25, 0x00000000}, x = {127, 0x7a632df1, 0x1775c47f, 0x30b8030b, 0x562da059, 0x00000000}, e = {139, 161, 176, 68, 175, 27, 137, 101, 59, 37, 210}, m = {127, 0x41152b51, 0x0a89878d, 0x5a517db3, 0x72eb605e, 0x00000000}}, + {orig = {127, 0x4bb7368d, 0x12320331, 0x5cc9ce0e, 0x1eb2ad59, 0x00000000}, x = {127, 0x00db078c, 0x078b7daf, 0x2c19dc27, 0x5e45284f, 0x00000000}, e = {154, 81, 208, 248, 214, 247, 12, 230, 127, 200, 184}, m = {127, 0x6b29c80d, 0x6297da07, 0x3225bec9, 0x71bc4932, 0x00000000}}, + {orig = {127, 0x1ebae285, 0x28f81b38, 0x37f80ed8, 0x21dc723e, 0x00000000}, x = {127, 0x1f004d80, 0x15576afe, 0x7dccb09d, 0x31928c9a, 0x00000000}, e = {164, 131, 3, 178, 189, 171, 249, 186, 241, 118, 179}, m = {127, 0x6cb514cd, 0x3ff230e5, 0x6a691603, 0x73655603, 0x00000000}}, + {orig = {127, 0x3a3502a9, 0x4364df08, 0x3c8b1fdc, 0x20b2fa04, 0x00000000}, x = {127, 0x13718bdd, 0x3ea119db, 0x1d2488e5, 0x02324bed, 0x00000000}, e = {107, 45, 140, 104, 190, 245, 72, 158, 102, 160, 168}, m = {127, 0x63233a07, 0x30508887, 0x038d4f93, 0x4bff3ced, 0x00000000}}, +} + +I31_Test_Mul_Acc :: struct { + res: []u32, + d: []u32, + a: []u32, + b: []u32, +} + +@(rodata) +i31_mul_acc_test_vectors := []I31_Test_Mul_Acc { + {res = {317, 0x535a1b12, 0x44ccd6ec, 0x1fa7b212, 0x584b46e2, 0x222bf023, 0x4580794b, 0x67411ea6, 0x47b4ac1d, 0x229d50a9, 0x1818f1a8, 0x00000000}, d = {157, 0x79ef7e23, 0x4387243c, 0x0f107f29, 0x3567d945, 0x24e08c78, 0x00000000}, a = {157, 0x06deb1d1, 0x7bba988f, 0x46c80867, 0x490dafbc, 0x1f998ea4, 0x00000000}, b = {159, 0x1c1f52bf, 0x56cb5e69, 0x4f743315, 0x56bd7776, 0x619c427b, 0x00000000}}, + {res = {317, 0x3ef33eed, 0x50471631, 0x244d5b99, 0x3aace6d9, 0x3152bb58, 0x13b7c355, 0x0e321425, 0x567217ce, 0x31b3e5b9, 0x0d74845e, 0x00000000}, d = {157, 0x736962be, 0x7712398c, 0x7b098d1f, 0x43804b77, 0x3bd3f8bf, 0x00000000}, a = {157, 0x12a5d163, 0x4ae379f1, 0x1387fd7f, 0x1dec4842, 0x195d1d4c, 0x00000000}, b = {159, 0x6023c9c5, 0x3740c9c3, 0x612c755b, 0x6f3d0941, 0x43e704ff, 0x00000000}}, + {res = {318, 0x5c85ea39, 0x54f32864, 0x507dd4d4, 0x6250b7ce, 0x1334f373, 0x445b332d, 0x14557fc2, 0x7e1738be, 0x21bab745, 0x2f0aa313, 0x00000000}, d = {158, 0x77f935c9, 0x1e13e902, 0x43c028fd, 0x4937d854, 0x7e23bf79, 0x00000000}, a = {158, 0x1d618801, 0x79681bf4, 0x7f92559f, 0x2f1cccaf, 0x3faefa11, 0x00000000}, b = {159, 0x32413470, 0x5aff5bf1, 0x05f98105, 0x53055486, 0x5e8cf955, 0x00000000}}, + {res = {319, 0x3e013eac, 0x07fe4a8a, 0x65782090, 0x5b880ea5, 0x7408456c, 0x5bf575a0, 0x2a3cd440, 0x23d148fa, 0x418bc6d7, 0x31cc34a1, 0x00000000}, d = {159, 0x102ef9f4, 0x1bc81608, 0x02f901f1, 0x668b0bd6, 0x283c9d2b, 0x00000000}, a = {159, 0x7da4773e, 0x360df7cb, 0x2b3be4f2, 0x79faa607, 0x5e09cc20, 0x00000000}, b = {159, 0x57ef4024, 0x32c96c31, 0x4559d5d3, 0x3b4d08ae, 0x43c831a5, 0x00000000}}, + {res = {317, 0x2588c09a, 0x7a89223b, 0x41126c82, 0x321cdef0, 0x3faf4f56, 0x54b95fd5, 0x44abcdbd, 0x4c9acfbb, 0x5d732ab6, 0x1e44b843, 0x00000000}, d = {158, 0x524131a8, 0x6388c83c, 0x4a50e580, 0x0df9f5b8, 0x4fb833b3, 0x00000000}, a = {158, 0x523a18b9, 0x465a2251, 0x2dd0ebec, 0x7a14c3c3, 0x3caa78eb, 0x00000000}, b = {158, 0x30ec8982, 0x37707644, 0x7f244b46, 0x6f958529, 0x3fdd26db, 0x00000000}}, + {res = {318, 0x61bad587, 0x761aa039, 0x6ab9a7d4, 0x41f18ba1, 0x0f77af87, 0x557292c8, 0x3428f9a7, 0x7c028945, 0x405a01c3, 0x1f3c11e1, 0x00000000}, d = {159, 0x62a6cc33, 0x07c52956, 0x4b78094d, 0x0210300b, 0x59a0cc24, 0x00000000}, a = {159, 0x6538ecfc, 0x666ac7d2, 0x62ada152, 0x1b6f0537, 0x40b88005, 0x00000000}, b = {158, 0x4f69016b, 0x2b90ac7e, 0x547c3f0e, 0x2f1de4c9, 0x3dc60ec7, 0x00000000}}, + {res = {318, 0x7c1d72ed, 0x4c4d8e1f, 0x461bb74f, 0x38b75f85, 0x0c340951, 0x37882272, 0x5bb93d3e, 0x5f8ed31e, 0x7bd1add0, 0x258ae081, 0x00000000}, d = {158, 0x007d10b7, 0x7e03550f, 0x28189fea, 0x3407d2f0, 0x44f16726, 0x00000000}, a = {158, 0x152afd5f, 0x71ab1c22, 0x6fb41945, 0x522bd719, 0x3025871d, 0x00000000}, b = {159, 0x7272538a, 0x795fd146, 0x7111af44, 0x2b400ac9, 0x63cef8dc, 0x00000000}}, + {res = {317, 0x6ac9a927, 0x37bf839e, 0x72166cf4, 0x66e97192, 0x0f1010a8, 0x475dd251, 0x730eb0ff, 0x295b1515, 0x340bb235, 0x14da57c9, 0x00000000}, d = {157, 0x026ce5ab, 0x49a7c219, 0x7d86a513, 0x47701297, 0x56e7b169, 0x00000000}, a = {157, 0x6f6a9a0e, 0x7e018fa4, 0x23b35254, 0x2566e0a9, 0x158799e8, 0x00000000}, b = {159, 0x12e6bed2, 0x3b186bb1, 0x3bddcfc9, 0x75ca6013, 0x7bf9ee6e, 0x00000000}}, + {res = {317, 0x277fe2cd, 0x00e40ce6, 0x2c35b175, 0x59bd9a99, 0x2a53689e, 0x3972c269, 0x4db20926, 0x67d98c0d, 0x57bbbc3f, 0x0bf1a543, 0x00000000}, d = {159, 0x17d3efaf, 0x59a9c4ff, 0x69349a09, 0x535b0031, 0x677b2a0d, 0x00000000}, a = {159, 0x67e1ef0e, 0x7d1e406e, 0x5111e847, 0x321572bc, 0x547d3b3a, 0x00000000}, b = {157, 0x1c685fb9, 0x2f3d09a2, 0x6611e3c9, 0x25575675, 0x12184aec, 0x00000000}}, + {res = {317, 0x11c8df23, 0x0d48a117, 0x0a6b8c50, 0x0fef02e9, 0x3eb1ae96, 0x32cd060b, 0x0cdb0651, 0x2dc87323, 0x16ea6bb3, 0x09cf6776, 0x00000000}, d = {159, 0x53b8638b, 0x4e180172, 0x3a3cdcac, 0x614f28ca, 0x0f644498, 0x00000000}, a = {159, 0x72ac2c95, 0x196ed06f, 0x43412b53, 0x59441827, 0x4364974f, 0x00000000}, b = {157, 0x0e220f38, 0x02785026, 0x6566e3fe, 0x1a270c17, 0x12a1eeaa, 0x00000000}}, +} + +I31_Test_Div_Rem :: struct { + hi: u32, + lo: u32, + div: u32, + quo: u32, + rem: u32, +} + +@(rodata) +i31_div_rem_test_vectors := []I31_Test_Div_Rem { + {hi = 0x632c115c, lo = 0x4b2bf821, div = 0xb8481290, quo = 0x89c490d1, rem = 0x07a3d091}, + {hi = 0x1cb7e3aa, lo = 0x63e1d659, div = 0xd94a9eb0, quo = 0x21d58fe1, rem = 0x02380da9}, + {hi = 0xbe78690c, lo = 0x88cc1bd7, div = 0xbf1211a9, quo = 0xff32200f, rem = 0x4a85f2f0}, + {hi = 0xa91488a1, lo = 0x2f8f64ac, div = 0xb5027f82, quo = 0xef20d04a, rem = 0x86fce918}, + {hi = 0x074838b4, lo = 0xc1b7bbbe, div = 0x0cc1ef1e, quo = 0x92207a42, rem = 0x0c03ca02}, + {hi = 0x415c651d, lo = 0xe696b3e5, div = 0x5a782289, quo = 0xb8f37d3e, rem = 0x149671b7}, + {hi = 0x26454389, lo = 0x986fc51a, div = 0xebc02a27, quo = 0x298ec804, rem = 0x27dea47e}, + {hi = 0x5a3b3e87, lo = 0xf475705b, div = 0x5e6f1d1a, quo = 0xf49b708c, rem = 0x4c382623}, + {hi = 0x8b1580f2, lo = 0x4ca1db17, div = 0xafd69ac8, quo = 0xca7d730d, rem = 0x938c26ef}, + {hi = 0x0dd6f07e, lo = 0xde23b70c, div = 0x5bd60fda, quo = 0x26943342, rem = 0x05c332d8}, + {hi = 0xede68f1d, lo = 0x07813b19, div = 0xff2b167e, quo = 0xeead1023, rem = 0x1c0f47df}, + {hi = 0x5c5ec31f, lo = 0xd985218f, div = 0xae020a42, quo = 0x87e50b9b, rem = 0x6cce1599}, + {hi = 0x6639e3c0, lo = 0xe8690a35, div = 0x6c241512, quo = 0xf1ff7b0a, rem = 0x69f29181}, + {hi = 0x0a45fb42, lo = 0x113dd8cc, div = 0x2ee12611, quo = 0x3819d4aa, rem = 0x0c8b7d82}, + {hi = 0x3ea35d11, lo = 0x5e2a590f, div = 0xd465b470, quo = 0x4b7f3d2b, rem = 0x21865a3f}, + {hi = 0xcb7cee11, lo = 0x25cea707, div = 0xe191debc, quo = 0xe6f0751f, rem = 0x7218c243}, + {hi = 0x816b74f4, lo = 0xb66d7312, div = 0x83e048ef, quo = 0xfb3b4f1d, rem = 0x6b6e6eff}, + {hi = 0x4863d8a6, lo = 0x7de42a8b, div = 0x588fc2d3, quo = 0xd140fa7c, rem = 0x3c3fbe57}, + {hi = 0x2540246a, lo = 0xe24d9d05, div = 0x8320712a, quo = 0x48b98123, rem = 0x047dfa47}, + {hi = 0xc074131e, lo = 0xdc4d465e, div = 0xe1dc958c, quo = 0xda22448b, rem = 0x8d36e35a}, + {hi = 0xf6ef68bc, lo = 0xc22f3f3e, div = 0xfa5c4162, quo = 0xfc7f66e0, rem = 0x07cafd7e}, + {hi = 0x061d173b, lo = 0xeccf58ec, div = 0xb792ff7a, quo = 0x08869175, rem = 0x3a107c2a}, + {hi = 0xb2ee2b9d, lo = 0x7805c28d, div = 0xf610e48b, quo = 0xba276d3a, rem = 0xb7b5cc0f}, + {hi = 0x38849ac0, lo = 0x67df2bd4, div = 0xa314f95e, quo = 0x58b84824, rem = 0x0739aa9c}, + {hi = 0x32dc45d0, lo = 0x2c7f42e9, div = 0xfafc8a2a, quo = 0x33e05b19, rem = 0xa1f8d6cf}, + {hi = 0x4240d91f, lo = 0xec3b2dd8, div = 0x7a1bc3fe, quo = 0x8ae65d59, rem = 0x602cc48a}, + {hi = 0x3c0836b4, lo = 0x275cd2a6, div = 0x871c1085, quo = 0x71bf0a5e, rem = 0x6a2e8fd0}, + {hi = 0x49de09c7, lo = 0xf62ac65b, div = 0x9b7ae8a1, quo = 0x799f9edf, rem = 0x1587c41c}, + {hi = 0x3da17dbc, lo = 0xe4437bf3, div = 0xf64f9bca, quo = 0x400e1fa2, rem = 0x5cf9701f}, + {hi = 0x4e22607b, lo = 0xa8798c83, div = 0xfe90aa77, quo = 0x4e931fa6, rem = 0xedb19a59}, + {hi = 0x43dd8e64, lo = 0xd2aee2d3, div = 0xfa05bf14, quo = 0x457ceca2, rem = 0x5d35882b}, + {hi = 0x74ae120e, lo = 0x4a567457, div = 0x80e12b3b, quo = 0xe7c46e8b, rem = 0x3954a14e}, + {hi = 0x25c33e85, lo = 0xd4fe503d, div = 0xab5e1c7a, quo = 0x38698d75, rem = 0x4f421a7b}, + {hi = 0xba818477, lo = 0xf1edb77a, div = 0xf5028225, quo = 0xc2df32d7, rem = 0x472c3067}, + {hi = 0xce85c4d5, lo = 0xc1530b0c, div = 0xe5a6d9ab, quo = 0xe63795d9, rem = 0x94770219}, + {hi = 0x08adb5bb, lo = 0xa277cb09, div = 0xab1ee4c6, quo = 0x0cfbb916, rem = 0x045b0c05}, + {hi = 0x8dc1580e, lo = 0xe73d2e39, div = 0xadf26902, quo = 0xd09f833f, rem = 0x349b50bb}, + {hi = 0x4435ddf6, lo = 0x42a05702, div = 0x594f5cd1, quo = 0xc3850b8d, rem = 0x3d583ce5}, + {hi = 0x6cb4f9c6, lo = 0xcc9b6e48, div = 0xa7bbed72, quo = 0xa5e948ca, rem = 0x00c80254}, + {hi = 0x3cb3b260, lo = 0xf2f7a398, div = 0xecf16edf, quo = 0x419586bc, rem = 0x6ad67dd4}, + {hi = 0x1720b73c, lo = 0x347ff447, div = 0x1724839d, quo = 0xffd5fbb6, rem = 0x0ede73a9}, + {hi = 0xe3003f2c, lo = 0xe1cb1bdf, div = 0xf2298637, quo = 0xeff8ef99, rem = 0x04648c00}, + {hi = 0x558a5621, lo = 0x1f717ddc, div = 0xf03e2bca, quo = 0x5b269cff, rem = 0xa0d8c7a6}, + {hi = 0x949fb36a, lo = 0xce76c920, div = 0xd215ba56, quo = 0xb51b3884, rem = 0x456de4c8}, + {hi = 0xa0f2fc11, lo = 0xd0259ff1, div = 0xc8626cac, quo = 0xcd9ea15a, rem = 0xa90b3f79}, + {hi = 0x70c3fa84, lo = 0x74d29bb9, div = 0xf9e5a764, quo = 0x7384fbe9, rem = 0x9c1e35b5}, + {hi = 0x2da900de, lo = 0xcf74abff, div = 0x99abc1da, quo = 0x4c10ae3a, rem = 0x6b28949b}, + {hi = 0x3664397e, lo = 0x2ba486dc, div = 0x7ea469c9, quo = 0x6df304d3, rem = 0x39af3231}, + {hi = 0x095d67a6, lo = 0xe3b31742, div = 0xdbe7549c, quo = 0x0ae6ee14, rem = 0x88cf7312}, + {hi = 0x9a402804, lo = 0x462e5dcd, div = 0xa736e128, quo = 0xec272359, rem = 0x76399ee5}, + {hi = 0x12809274, lo = 0x09011d2f, div = 0xac4c1332, quo = 0x1b7d9f66, rem = 0x7d5b6943}, + {hi = 0x6a897aed, lo = 0x1d121571, div = 0xef42fe4b, quo = 0x71fd7b8c, rem = 0xa921fb6d}, + {hi = 0x55ed8b10, lo = 0x9090ea62, div = 0x9cd2c065, quo = 0x8c45083c, rem = 0x21efaab6}, + {hi = 0x3930a3f2, lo = 0x541cf31f, div = 0xa7c1fef1, quo = 0x5745c1d4, rem = 0x884d228b}, + {hi = 0xa7167582, lo = 0x0abb9b9f, div = 0xf63dc17e, quo = 0xadb5a66e, rem = 0x3ca4c37b}, + {hi = 0x74b72666, lo = 0x4abb1c94, div = 0x7ee27c4a, quo = 0xeb7b903b, rem = 0x0110d786}, + {hi = 0x88a89e33, lo = 0x2dada2d6, div = 0xe8725e91, quo = 0x9681856f, rem = 0x5be44cf7}, + {hi = 0x5affad0b, lo = 0x68c7b744, div = 0x8bf402e7, quo = 0xa6740fb8, rem = 0x6a8e183c}, + {hi = 0x18400a64, lo = 0x91c19fee, div = 0xececa37b, quo = 0x1a33df48, rem = 0xbd4a8056}, + {hi = 0x0ca1d509, lo = 0x5ace5b38, div = 0x405fe35b, quo = 0x323c1088, rem = 0x1a53e2e0}, + {hi = 0x49c3d579, lo = 0xb57e3052, div = 0x8fe8c087, quo = 0x833871ec, rem = 0x1b691cde}, + {hi = 0x055e153e, lo = 0x876ba83b, div = 0xd0a7b0e1, quo = 0x0695deae, rem = 0x1119514d}, + {hi = 0x1ab22cf1, lo = 0xc9c28af3, div = 0x626e63e5, quo = 0x456e54c0, rem = 0x18ca7b33}, + {hi = 0x5ce6ba97, lo = 0x46dbc829, div = 0xbf4a582f, quo = 0x7c53ef3f, rem = 0x6fff3398}, + {hi = 0x280dd6e5, lo = 0x13c19c79, div = 0xe67fd7e2, quo = 0x2c7c3e0b, rem = 0xcc8299c3}, + {hi = 0x1c7c2a51, lo = 0x8aecb540, div = 0x3decec9d, quo = 0x75c1d104, rem = 0x02afd5cc}, + {hi = 0x3a6e806b, lo = 0x142a6ad0, div = 0x56b24a8d, quo = 0xac89eeee, rem = 0x17a505ba}, + {hi = 0x23a1b1d0, lo = 0xda933d16, div = 0x85938fe1, quo = 0x4449c850, rem = 0x08e57ec6}, + {hi = 0x1f177f2e, lo = 0x107c53c1, div = 0xdef2389a, quo = 0x23b390c4, rem = 0x7d845dd9}, + {hi = 0x735cb354, lo = 0xbcf96584, div = 0xacc0d360, quo = 0xaaf3fec8, rem = 0x107b0284}, + {hi = 0x5ecb810e, lo = 0x51b4a6d1, div = 0x84db55d8, quo = 0xb6a8bd00, rem = 0x7d942ed1}, + {hi = 0x4765b107, lo = 0xded18aa4, div = 0x540b7c9f, quo = 0xd979b39b, rem = 0x4592e95f}, + {hi = 0xddb19fc7, lo = 0xe30727aa, div = 0xfaf579d9, quo = 0xe225a80a, rem = 0xcf1cfd30}, + {hi = 0x85f9707c, lo = 0x5511a037, div = 0x8e318efd, quo = 0xf133d2a5, rem = 0x5e6ded26}, + {hi = 0x0c2c41f1, lo = 0xbf357e4a, div = 0x62d254f1, quo = 0x1f88bf7d, rem = 0x421a359d}, + {hi = 0x335d8188, lo = 0x00fd4b21, div = 0xfcf1e085, quo = 0x33fc54b7, rem = 0x507e280e}, + {hi = 0x4eef80e4, lo = 0xfcd9e9de, div = 0xf2d2e5c0, quo = 0x53380265, rem = 0x07d9c51e}, + {hi = 0x431aeca5, lo = 0x07963290, div = 0xe6ca948b, quo = 0x4a6f5427, rem = 0xcfb6f563}, + {hi = 0x13638612, lo = 0x71f2880c, div = 0x25f44434, quo = 0x82c6db4c, rem = 0x0eddcc9c}, + {hi = 0x24dd96bb, lo = 0xd6b8ffca, div = 0xb3697b2b, quo = 0x349a55fc, rem = 0x61207a76}, + {hi = 0x3201b855, lo = 0x0e6b7026, div = 0x65a232ae, quo = 0x7df5a4a0, rem = 0x2a0e4b66}, + {hi = 0x1e2536d5, lo = 0xfbef9964, div = 0x97c9027e, quo = 0x32d7cea9, rem = 0x54699036}, + {hi = 0x03486ba3, lo = 0x4ae30d34, div = 0x5f768217, quo = 0x08cdbad7, rem = 0x1f6c15e3}, + {hi = 0x7000b42d, lo = 0xd9357d65, div = 0xc0b7f01f, quo = 0x94c7bd20, rem = 0x61d79685}, + {hi = 0x2f088bd1, lo = 0x098fe330, div = 0x3a2fc060, quo = 0xceee1d21, rem = 0x075d36d0}, + {hi = 0x30ede94b, lo = 0x1200278e, div = 0x42fc5880, quo = 0xbafe65ba, rem = 0x08bd5a8e}, + {hi = 0x5ed1fc0f, lo = 0x0537fbbf, div = 0x6ae246f5, quo = 0xe31b295f, rem = 0x630b69d4}, + {hi = 0x1d2496a2, lo = 0xecca6436, div = 0xaa60d4ea, quo = 0x2bc9d3a0, rem = 0x270e73f6}, + {hi = 0x97ae9a4e, lo = 0xba69862d, div = 0xea9984e9, quo = 0xa584c095, rem = 0x563c6a90}, + {hi = 0x12cf1435, lo = 0x99a99414, div = 0x2644910d, quo = 0x7dd36379, rem = 0x1871fdef}, + {hi = 0x7fc1828d, lo = 0x70c995ca, div = 0xdebea8f1, quo = 0x92d45c4d, rem = 0x9326294d}, + {hi = 0x32a003d1, lo = 0x6c09aff4, div = 0xd460becc, quo = 0x3d05fb48, rem = 0x7fc60294}, + {hi = 0x3add17a2, lo = 0xb4e2c499, div = 0x45b4eb3a, quo = 0xd82db19b, rem = 0x20833e7b}, + {hi = 0xc4a6d715, lo = 0xb1576cc0, div = 0xcf15af86, quo = 0xf31a46cc, rem = 0xb874e9f8}, + {hi = 0x2da93388, lo = 0x980b3f98, div = 0xf8e18784, quo = 0x2ef78f93, rem = 0x3b7bb2cc}, + {hi = 0x5d77f50a, lo = 0xc8f5c09b, div = 0xaa3cc6c4, quo = 0x8c8e703a, rem = 0x2f82f833}, + {hi = 0x4898d91b, lo = 0xe7956338, div = 0x4e4a2805, quo = 0xed62bc94, rem = 0x3c689454}, + {hi = 0xb9620ae8, lo = 0x0e9ed51d, div = 0xc281fe4d, quo = 0xf3fd8e35, rem = 0x458d792c}, + {hi = 0xc342d71a, lo = 0xecafbf43, div = 0xfbfa840c, quo = 0xc6609971, rem = 0x2db049f7}, + {hi = 0x4e2d4c4b, lo = 0x4609de2e, div = 0xc737fab5, quo = 0x64757da5, rem = 0x40d1e685}, +} diff --git a/tests/core/crypto/common/common.odin b/tests/core/crypto/common/common.odin index 8cef8ce86..a1fdef824 100644 --- a/tests/core/crypto/common/common.odin +++ b/tests/core/crypto/common/common.odin @@ -1,6 +1,7 @@ package test_crypto_common import "core:bytes" +import "core:encoding/base64" import "core:encoding/hex" // Common helpers for cryptography tests. @@ -23,3 +24,13 @@ hexbytes_decode :: proc(x: Hex_Bytes, allocator := context.allocator) -> []byte return dst } +Jwk_Bytes :: string + +jwkbytes_decode :: proc(s: Jwk_Bytes, allocator := context.allocator) -> []byte { + dst, err := base64.decode(s, base64.DEC_URL_TABLE, allocator = allocator) + if err != nil { + panic("Jwk_Bytes: invalid hex encoding") + } + + return dst +} diff --git a/tests/core/crypto/test_core_crypto_rsa.odin b/tests/core/crypto/test_core_crypto_rsa.odin new file mode 100644 index 000000000..525c440e3 --- /dev/null +++ b/tests/core/crypto/test_core_crypto_rsa.odin @@ -0,0 +1,419 @@ +#+build !riscv64 +package test_core_crypto + +import "core:bytes" +import "core:encoding/hex" +import "core:log" +import "core:testing" + +import "core:crypto" +import "core:crypto/rsa" + +// BUG/yawning: RISC-V fails the PSS test in CI with a nonsensical +// bounds-checking error suggesting something spooky, as the failure +// was not reproducible on qemu whole system emulation. + +@(private="file") +TEST_MSG: string : "don't let them immanentize the eschaton" +@(private="file", rodata) +TEST_MSG_SHA256 := []byte{ + 0x50, 0x95, 0x66, 0x8e, 0x7c, 0xd0, 0xd5, 0x8e, + 0x9d, 0x59, 0xf8, 0x4a, 0x1a, 0x46, 0x5a, 0x8a, + 0x2e, 0x69, 0xcc, 0xad, 0x6a, 0xca, 0x8b, 0xb7, + 0x55, 0x55, 0x15, 0x71, 0x9b, 0xc7, 0x50, 0x88, +} +@(private="file", rodata) +TEST_TLS_PMS := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, +} + +@(test) +test_rsa_private_key :: proc(t: ^testing.T) { + priv_key: rsa.Private_Key + if !testing.expectf( + t, + rsa.private_key_set_insecure_test(&priv_key), + "rsa: failed to set test key", + ) { + return + } + + if !crypto.HAS_RAND_BYTES { + log.info("rand_bytes not supported - skipping generate") + return + } + + if !testing.expectf( + t, + rsa.private_key_generate(&priv_key), + "rsa: failed to generate private key", + ) { + return + } + + log.debugf("n=0x%s", hex.encode(priv_key._pub_key._n.v[:priv_key._pub_key._n.v_len], context.temp_allocator)) + log.debugf("e=%d", priv_key._pub_key._e) + log.debugf("d=0x%s", hex.encode(priv_key._d.v[:priv_key._d.v_len], context.temp_allocator)) + log.debugf("p=0x%s", hex.encode(priv_key._p.v[:priv_key._p.v_len], context.temp_allocator)) + log.debugf("q=0x%s", hex.encode(priv_key._q.v[:priv_key._q.v_len], context.temp_allocator)) + log.debugf("dp=0x%s", hex.encode(priv_key._dp.v[:priv_key._dp.v_len], context.temp_allocator)) + log.debugf("dq=0x%s", hex.encode(priv_key._dq.v[:priv_key._dq.v_len], context.temp_allocator)) + log.debugf("iq=0x%s", hex.encode(priv_key._iq.v[:priv_key._iq.v_len], context.temp_allocator)) + + pub_key: rsa.Public_Key + rsa.public_key_set_priv(&pub_key, &priv_key) + + if !testing.expectf( + t, + rsa.public_key_equal(&priv_key._pub_key, &pub_key), + "rsa: failed clone public key", + ) { + return + } +} + +@(test) +test_rsa_sign_pkcs1 :: proc(t: ^testing.T) { + test_msg := transmute([]byte)(TEST_MSG) + + priv_key: rsa.Private_Key + _ = rsa.private_key_set_insecure_test(&priv_key) + + pub_key: rsa.Public_Key + rsa.public_key_set_priv(&pub_key, &priv_key) + + // Generated with Go's crypto code. Signatures are + // deterministic. + expected_sig := []byte{ + 0x65, 0x0e, 0xa9, 0xd1, 0x66, 0xb3, 0x61, 0x4c, + 0x67, 0x8f, 0xd6, 0x9d, 0x2e, 0xc9, 0x24, 0xdd, + 0xcc, 0xa1, 0x0b, 0xdc, 0xbb, 0x35, 0x60, 0xc5, + 0x03, 0xb1, 0xa7, 0x10, 0x64, 0x53, 0x83, 0x02, + 0x7a, 0x8b, 0xc2, 0x83, 0x7f, 0xd6, 0xfc, 0xc3, + 0xe1, 0x4d, 0x33, 0x57, 0x90, 0x81, 0x52, 0xea, + 0xcd, 0x7c, 0xaa, 0xa5, 0x98, 0x59, 0x90, 0xd1, + 0x88, 0x21, 0x87, 0xc2, 0x9d, 0x51, 0x4d, 0x45, + 0x18, 0x06, 0xa2, 0xde, 0x7a, 0xc9, 0xc9, 0x1b, + 0x3d, 0x27, 0x07, 0xe4, 0xad, 0x46, 0xe8, 0x09, + 0xe1, 0xd5, 0xbd, 0x33, 0x1a, 0x9c, 0x7f, 0x3e, + 0x1b, 0x22, 0xc0, 0xfa, 0xa1, 0x30, 0xfb, 0xda, + 0x3b, 0x09, 0x9f, 0x6f, 0x44, 0x0e, 0xa8, 0x9b, + 0x65, 0x36, 0x48, 0x07, 0x95, 0xfc, 0xf7, 0x2b, + 0x61, 0xa0, 0x95, 0x36, 0x94, 0x66, 0x01, 0x90, + 0x5f, 0xf6, 0x72, 0x05, 0x64, 0x0c, 0x50, 0x8a, + 0xce, 0xfc, 0x75, 0xc7, 0x1e, 0x05, 0x62, 0xd9, + 0x8e, 0xdf, 0x85, 0x7e, 0x5d, 0x84, 0x8c, 0x0b, + 0x18, 0x88, 0x71, 0x4a, 0x94, 0x11, 0x5e, 0x16, + 0x53, 0xd0, 0xdc, 0x8e, 0x62, 0x73, 0x9d, 0x94, + 0x66, 0x1a, 0xdf, 0xf4, 0x01, 0xaa, 0xa5, 0x33, + 0xb3, 0x0c, 0x14, 0x53, 0x5d, 0xe7, 0xdb, 0x66, + 0xf0, 0x3e, 0xf0, 0x08, 0x81, 0x77, 0x40, 0x79, + 0xa5, 0xc6, 0x48, 0x68, 0xcc, 0xa2, 0xb6, 0xf3, + 0x1f, 0xf9, 0xd5, 0xb5, 0x26, 0x04, 0x97, 0xbc, + 0x93, 0xa3, 0x19, 0x29, 0x26, 0x86, 0x89, 0xf8, + 0x11, 0xe5, 0xdd, 0xbf, 0x35, 0x6b, 0x96, 0x25, + 0xa5, 0x78, 0xab, 0x02, 0x69, 0xdf, 0x54, 0x95, + 0xc5, 0x6b, 0x95, 0xcc, 0x38, 0x7b, 0x11, 0xfa, + 0x8c, 0x98, 0xa9, 0x95, 0x7e, 0x39, 0x7c, 0xf5, + 0x2f, 0xea, 0x42, 0x6d, 0xf5, 0xaa, 0xb3, 0x16, + 0x10, 0x2f, 0x29, 0xf3, 0x4a, 0xbc, 0x47, 0xfb, + } + + // Note: The key generate/set routines use the prehashed + // mode. + sig: [2048 >> 3]byte + ok := rsa.sign_pkcs1( + &priv_key, + .SHA256, + test_msg, + sig[:], + ) + if !testing.expectf(t, ok, "rsa/pkcs1: signing failed") { + return + } + + if !testing.expectf( + t, + bytes.equal(sig[:], expected_sig), + "rsa/pkcs1: signature mismatch: %x (expected %x)", + sig[:], + expected_sig, + ) { + return + } + + ok = rsa.verify_pkcs1( + &pub_key, + .SHA256, + test_msg, + expected_sig, + ) + if !testing.expectf(t, ok, "rsa/pkcs1: verify failed") { + return + } +} + +@(test) +test_rsa_sign_pss :: proc(t: ^testing.T) { + test_msg := transmute([]byte)(TEST_MSG) + + priv_key: rsa.Private_Key + _ = rsa.private_key_set_insecure_test(&priv_key) + + pub_key: rsa.Public_Key + rsa.public_key_set_priv(&pub_key, &priv_key) + + // Generated with Go's crypto code. Signatures are + // NOT deterministic. + test_sig := []byte{ + 0x46, 0xd3, 0x7e, 0xb2, 0x2c, 0x82, 0xe0, 0x68, + 0x80, 0x58, 0x8c, 0x07, 0x0e, 0xe9, 0x41, 0x3f, + 0xde, 0x45, 0x4a, 0xa0, 0x3f, 0xff, 0xa3, 0xa8, + 0xc2, 0x76, 0x74, 0xf0, 0xe8, 0x44, 0x07, 0x2d, + 0xdb, 0x12, 0xd5, 0x57, 0xcc, 0x28, 0x15, 0xfa, + 0xeb, 0xb6, 0x14, 0x76, 0x10, 0x3f, 0xfc, 0xba, + 0xb1, 0x6e, 0x7f, 0x65, 0x6c, 0x9b, 0x1a, 0x62, + 0x60, 0xc4, 0xfa, 0xb0, 0x03, 0x07, 0x2b, 0x6b, + 0x6a, 0x5a, 0x84, 0x10, 0xe4, 0xf5, 0x64, 0xad, + 0xfa, 0xd3, 0x5f, 0xc9, 0xf4, 0xac, 0x70, 0xde, + 0x54, 0x06, 0x14, 0x59, 0xf7, 0x60, 0x15, 0xc9, + 0xa2, 0xe2, 0x54, 0xbc, 0x79, 0xa8, 0x02, 0x72, + 0xba, 0x6a, 0x68, 0x08, 0x15, 0x6b, 0xcb, 0xc8, + 0x55, 0x83, 0x63, 0x06, 0xe4, 0x28, 0x36, 0x6b, + 0xe6, 0x15, 0x2a, 0x21, 0xc9, 0x4f, 0xc0, 0x0e, + 0x30, 0x54, 0x3b, 0xf1, 0xa4, 0x79, 0x83, 0xb9, + 0x02, 0xd9, 0x2e, 0xa1, 0x31, 0xf4, 0x5b, 0x1b, + 0xbe, 0x44, 0x17, 0xd8, 0x42, 0x6d, 0xb3, 0x38, + 0x3f, 0x23, 0x82, 0x9e, 0x76, 0x52, 0x0c, 0x5e, + 0xa0, 0xcd, 0xd1, 0xcc, 0xe2, 0x5b, 0x71, 0xb5, + 0xca, 0x28, 0x33, 0xc3, 0x03, 0x30, 0xc5, 0xa6, + 0xdc, 0x6e, 0xfd, 0xd7, 0x34, 0x0a, 0xd2, 0x30, + 0x2c, 0x80, 0x2d, 0x31, 0xea, 0xe9, 0x44, 0x2a, + 0x7e, 0x1d, 0xde, 0x71, 0xa3, 0x7e, 0xe2, 0x5e, + 0x8a, 0x91, 0x61, 0x23, 0x5b, 0x26, 0x6d, 0x3b, + 0x44, 0xfc, 0x6b, 0x36, 0x40, 0xb1, 0xdb, 0xe7, + 0xf9, 0xe7, 0x8a, 0x12, 0x7c, 0xba, 0xd8, 0x33, + 0x1b, 0xac, 0x70, 0x59, 0x24, 0x83, 0x3a, 0x8b, + 0x2a, 0x51, 0x1b, 0x97, 0xa3, 0x8e, 0x34, 0xd1, + 0xbb, 0xc1, 0x4e, 0x00, 0xab, 0x21, 0x12, 0x53, + 0xeb, 0xda, 0x36, 0x77, 0x30, 0xea, 0x82, 0x92, + 0xb5, 0xfb, 0x07, 0xb1, 0x34, 0x37, 0x78, 0x69, + } + + ok := rsa.verify_pss( + &pub_key, + .SHA256, + 32, + test_msg, + test_sig, + ) + if !testing.expectf(t, ok, "rsa/pss: verify (pregenerated) failed") { + return + } + + if !crypto.HAS_RAND_BYTES { + log.info("rand_bytes not supported - skipping round trip tests") + return + } + + // Just do a simple round-trip test, as the failure + // cases are covered by wycheproof, and de-randomizing + // PSS is an API nightmare. + + sig: [2048 >> 3]byte + ok = rsa.sign_pss( + &priv_key, + .SHA256, + 32, + TEST_MSG_SHA256, + sig[:], + true, + ) + if !testing.expectf(t, ok, "rsa/pss: signing failed") { + return + } + + ok = rsa.verify_pss( + &pub_key, + .SHA256, + 32, + TEST_MSG_SHA256, + sig[:], + true, + ) + if !testing.expectf(t, ok, "rsa/pss: verify failed") { + return + } +} + +@(test) +test_rsa_enc_dec_oaep :: proc(t: ^testing.T) { + test_msg := transmute([]byte)(TEST_MSG) + + priv_key: rsa.Private_Key + _ = rsa.private_key_set_insecure_test(&priv_key) + + pub_key: rsa.Public_Key + rsa.public_key_set_priv(&pub_key, &priv_key) + + // Generated with Go's crypto code. Ciphertexts are + // NOT deterministic. + test_ciphertext := []byte{ + 0x51, 0x7e, 0x55, 0xe8, 0xf8, 0x69, 0x9e, 0x68, + 0x8e, 0x2f, 0x38, 0xec, 0x11, 0xfb, 0x5f, 0x1e, + 0xf1, 0x9b, 0x2c, 0xea, 0x8a, 0xfb, 0x13, 0x04, + 0x2a, 0xbd, 0x4f, 0x69, 0xca, 0x11, 0x31, 0x1b, + 0xb0, 0x00, 0x18, 0x69, 0x32, 0x88, 0xe3, 0x07, + 0x82, 0xe8, 0x1d, 0x34, 0x09, 0x26, 0xdf, 0x41, + 0x1c, 0xc3, 0xf1, 0x39, 0x31, 0x45, 0xb6, 0x67, + 0xa5, 0x7c, 0xa4, 0xaf, 0x48, 0x5e, 0x96, 0x26, + 0x9b, 0x78, 0x76, 0x4e, 0xd2, 0x6e, 0x53, 0xd0, + 0x51, 0xc9, 0x80, 0x71, 0xea, 0x67, 0x8a, 0x44, + 0x1e, 0xb0, 0x81, 0x2e, 0xce, 0x43, 0x9a, 0xd9, + 0x1c, 0xea, 0x5c, 0x8b, 0x94, 0x3e, 0x1e, 0x5c, + 0xb0, 0x17, 0xb9, 0x50, 0x44, 0x22, 0xc9, 0x17, + 0xd0, 0x73, 0x54, 0x2b, 0x15, 0x68, 0xe2, 0xcf, + 0xbe, 0x0b, 0xef, 0x91, 0x11, 0xfc, 0xa6, 0x78, + 0x14, 0xdd, 0x62, 0xf3, 0xba, 0x8c, 0x8d, 0x4b, + 0x7f, 0x4b, 0xfa, 0x8b, 0x9c, 0x91, 0x08, 0x9f, + 0x39, 0x47, 0x27, 0xba, 0x9a, 0xfd, 0x2a, 0xb8, + 0x1e, 0x70, 0xa3, 0x9c, 0xe1, 0x23, 0x21, 0xc5, + 0xca, 0x00, 0x2a, 0x9b, 0x23, 0x0f, 0x15, 0xe2, + 0x9a, 0x62, 0xc2, 0x20, 0xb6, 0xe8, 0x85, 0x5f, + 0x94, 0xba, 0x72, 0x06, 0x55, 0xcf, 0x5a, 0xd6, + 0xc6, 0xc0, 0x89, 0xff, 0xd3, 0x72, 0xf9, 0x34, + 0x7a, 0x12, 0xfc, 0xe3, 0x74, 0x64, 0x00, 0xfe, + 0xa1, 0x35, 0x78, 0x66, 0x56, 0x1a, 0xde, 0x6a, + 0x83, 0x6b, 0x20, 0x06, 0xe2, 0x51, 0xae, 0xc7, + 0x27, 0x44, 0x5b, 0x21, 0x4f, 0xdf, 0xf6, 0x52, + 0x8e, 0x3a, 0x84, 0x07, 0x26, 0xc8, 0xe3, 0x6a, + 0x18, 0xd4, 0x49, 0x44, 0xd8, 0x24, 0x08, 0x94, + 0xe1, 0x67, 0xde, 0x4a, 0x8e, 0x6a, 0x2a, 0x28, + 0x72, 0x0e, 0x68, 0x9c, 0x7f, 0x55, 0x13, 0x54, + 0x13, 0x32, 0xdb, 0xe7, 0x31, 0x84, 0x90, 0xaf, + } + + buf: [2048 >> 3]byte + + derived_msg, ok := rsa.decrypt_oaep( + &priv_key, + .SHA256, + test_ciphertext, + buf[:], + ) + if !testing.expectf(t, ok, "rsa/oaep: decryption (pregenerated) failed") { + return + } + if !testing.expectf( + t, + bytes.equal(test_msg, derived_msg), + "rsa/oaep: unexpected plaintext: %x", + derived_msg, + ) { + return + } + + if !crypto.HAS_RAND_BYTES { + log.info("rand_bytes not supported - skipping") + return + } + + // Just do a simple round-trip test, as the failure + // cases are covered by wycheproof, and de-randomizing + // OAEP is an API nightmare. + + ok = rsa.encrypt_oaep( + &pub_key, + .SHA512, + test_msg, + buf[:], + ) + if !testing.expectf(t, ok, "rsa/oaep: encryption failed") { + return + } + + derived_msg, ok = rsa.decrypt_oaep( + &priv_key, + .SHA512, + buf[:], + buf[:], + ) + if !testing.expectf(t, ok, "rsa/oaep: decryption failed") { + return + } + if !testing.expectf( + t, + bytes.equal(test_msg, derived_msg), + "rsa/oaep: unexpected plaintext: %x", + derived_msg, + ) { + return + } +} + +@(test) +test_rsa_dec_pms :: proc(t: ^testing.T) { + priv_key: rsa.Private_Key + _ = rsa.private_key_set_insecure_test(&priv_key) + + // Generated with Go's crypto code. Go's PKCS1v15 + // encryption output is NOT deterministic. + test_ciphertext := []byte{ + 0x0c, 0x18, 0x48, 0x23, 0x0b, 0x4a, 0xa9, 0x20, + 0xbb, 0xa6, 0x38, 0xbf, 0x25, 0xda, 0x16, 0xd7, + 0x92, 0x72, 0x3a, 0x81, 0xbc, 0xee, 0x74, 0x6b, + 0xd8, 0x55, 0x26, 0x72, 0x81, 0x70, 0x73, 0x32, + 0xd4, 0x30, 0x56, 0xe2, 0xeb, 0x1b, 0x57, 0xf3, + 0xf5, 0x7d, 0xea, 0x3e, 0x3c, 0x62, 0x35, 0x78, + 0x06, 0x88, 0x27, 0x4d, 0x6e, 0xa0, 0x45, 0x91, + 0x62, 0x8c, 0xe9, 0x93, 0xd0, 0xde, 0x85, 0x71, + 0x01, 0x24, 0x7a, 0x1a, 0x5d, 0x03, 0x49, 0x92, + 0x73, 0x5a, 0x5b, 0x1e, 0x1e, 0x3b, 0xf0, 0xf2, + 0xea, 0x04, 0x9a, 0x87, 0x32, 0x20, 0x52, 0xbd, + 0x72, 0xc8, 0xa8, 0x38, 0xd1, 0x29, 0x97, 0x87, + 0x0b, 0x76, 0xc1, 0x68, 0xe5, 0x05, 0x72, 0xb4, + 0x4d, 0xd1, 0x95, 0xb2, 0xa8, 0x19, 0x3f, 0xc3, + 0x1e, 0xee, 0x34, 0x19, 0x72, 0xac, 0x1e, 0x4b, + 0x01, 0xd7, 0x60, 0xeb, 0x27, 0xf1, 0x12, 0x7c, + 0xbc, 0x07, 0x5d, 0xf6, 0xb9, 0xac, 0xf9, 0xdb, + 0x1a, 0xaf, 0x47, 0x13, 0x22, 0x16, 0xb5, 0x05, + 0x5a, 0x9c, 0x45, 0x69, 0x0a, 0xf1, 0x36, 0x6b, + 0xab, 0x96, 0xd7, 0x7f, 0x66, 0xff, 0x16, 0x3c, + 0x29, 0xc4, 0x10, 0x03, 0xe1, 0x35, 0xcc, 0xae, + 0x71, 0x08, 0x14, 0xff, 0x57, 0x4f, 0x3d, 0x79, + 0x7a, 0xa7, 0x19, 0x2e, 0x23, 0x08, 0xad, 0xb2, + 0xe5, 0xb1, 0xe8, 0x47, 0x6d, 0xe1, 0x24, 0x4a, + 0xd8, 0x1f, 0xe4, 0x52, 0x21, 0x3e, 0xf1, 0xcd, + 0x07, 0x72, 0xa4, 0xb8, 0x06, 0x98, 0x3a, 0x17, + 0xfd, 0xca, 0x74, 0x93, 0xb1, 0x2b, 0xd8, 0x76, + 0x7c, 0x6f, 0x71, 0xfc, 0x16, 0xef, 0x99, 0xa1, + 0xf9, 0x13, 0xeb, 0xfc, 0x34, 0x90, 0xb5, 0x00, + 0xbf, 0xdc, 0x19, 0x99, 0xb4, 0x12, 0x85, 0x25, + 0x3a, 0x49, 0x70, 0x63, 0x2f, 0xfc, 0xbc, 0xca, + 0x38, 0x2f, 0x7a, 0x0e, 0x78, 0x8d, 0x7b, 0x87, + } + + ok := rsa.unsafe_decrypt_tls_pms(&priv_key, test_ciphertext) + if !testing.expectf( + t, + ok == 1, + "rsa/tls_pms: decryption failed: %x", + test_ciphertext[:48], + ) { + return + } + + if !testing.expectf( + t, + bytes.equal(test_ciphertext[:48], TEST_TLS_PMS), + "rsa/tls_pms: unexpected plaintext: %x", + test_ciphertext[:48], + ) { + return + } +} diff --git a/tests/core/crypto/wycheproof/main.odin b/tests/core/crypto/wycheproof/main.odin index bfb4884cd..3c7e765e2 100644 --- a/tests/core/crypto/wycheproof/main.odin +++ b/tests/core/crypto/wycheproof/main.odin @@ -51,6 +51,45 @@ import "core:testing" // - pbkdf2_hmacsha256_test.json // - pbkdf2_hmacsha384_test.json // - pbkdf2_hmacsha512_test.json +// - crypto/rsa +// - rsa_pkcs1_1024_sig_gen_test.json +// - rsa_pkcs1_1536_sig_gen_test.json +// - rsa_pkcs1_2048_sig_gen_test.json +// - rsa_pkcs1_3072_sig_gen_test.json +// - rsa_pkcs1_4096_sig_gen_test.json +// - rsa_pss_2048_sha1_mgf1_20_test.json +// - rsa_pss_2048_sha256_mgf1_0_test.json +// - rsa_pss_2048_sha256_mgf1_32_test.json +// - rsa_pss_2048_sha256_mgf1sha1_20_test.json +// - rsa_pss_2048_sha384_mgf1_48_test.json +// - rsa_pss_2048_sha512_256_mgf1_32_test.json +// - rsa_pss_3072_sha256_mgf1_32_test.json +// - rsa_pss_4096_sha256_mgf1_32_test.json +// - rsa_pss_4096_sha384_mgf1_48_test.json +// - rsa_pss_4096_sha512_mgf1_32_test.json +// - rsa_pss_4096_sha512_mgf1_64_test.json +// - rsa_pss_misc_test.json +// - rsa_oaep_2048_sha1_mgf1sha1_test.json +// - rsa_oaep_2048_sha224_mgf1sha1_test.json +// - rsa_oaep_2048_sha224_mgf1sha224_test.json +// - rsa_oaep_2048_sha256_mgf1sha1_test.json +// - rsa_oaep_2048_sha256_mgf1sha256_test.json +// - rsa_oaep_2048_sha384_mgf1sha1_test.json +// - rsa_oaep_2048_sha384_mgf1sha384_test.json +// - rsa_oaep_2048_sha512_224_mgf1sha1_test.json +// - rsa_oaep_2048_sha512_mgf1sha1_test.json +// - rsa_oaep_2048_sha512_mgf1sha512_test.json +// - rsa_oaep_3072_sha256_mgf1sha1_test.json +// - rsa_oaep_3072_sha256_mgf1sha256_test.json +// - rsa_oaep_3072_sha512_256_mgf1sha1_test.json +// - rsa_oaep_3072_sha512_256_mgf1sha512_256_test.json +// - rsa_oaep_3072_sha512_mgf1sha1_test.json +// - rsa_oaep_3072_sha512_mgf1sha512_test.json +// - rsa_oaep_4096_sha256_mgf1sha1_test.json +// - rsa_oaep_4096_sha256_mgf1sha256_test.json +// - rsa_oaep_4096_sha512_mgf1sha1_test.json +// - rsa_oaep_4096_sha512_mgf1sha512_test.json +// - rsa_oaep_misc_test.json // - crypto/siphash // - siphash_1_3_test.json // - siphash_2_4_test.json diff --git a/tests/core/crypto/wycheproof/rsa.odin b/tests/core/crypto/wycheproof/rsa.odin new file mode 100644 index 000000000..00561c407 --- /dev/null +++ b/tests/core/crypto/wycheproof/rsa.odin @@ -0,0 +1,541 @@ +package test_wycheproof + +import "core:encoding/hex" +import "core:fmt" +import "core:log" +import "core:mem" +import "core:os" +import "core:testing" + +import "core:crypto/rsa" + +import "../common" + +@(test) +test_rsa_pkcs1_signature :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + log.debug("rsa/pkcs1/signatures: starting") + + files := []string { + "rsa_pkcs1_1024_sig_gen_test.json", + "rsa_pkcs1_1536_sig_gen_test.json", + "rsa_pkcs1_2048_sig_gen_test.json", + "rsa_pkcs1_3072_sig_gen_test.json", + "rsa_pkcs1_4096_sig_gen_test.json", + } + for f in files { + mem.free_all() + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Rsa_Pkcs1_Sig_Test_Group) + load_ok := load(&test_vectors, fn) + if !testing.expectf(t, load_ok, "Unable to load {}", f) { + continue + } + + testing.expectf(t, test_rsa_pkcs1_sig(t, &test_vectors), "RSA PKCS1 signature failed") + } +} + +test_rsa_pkcs1_sig :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Rsa_Pkcs1_Sig_Test_Group)) -> bool { + JWK_KTY :: "RSA" + + params_str := fmt.aprintf("RSA-%d/PKCS1/Signature", test_vectors.test_groups[0].key_size) + log.debugf("%s: starting", params_str) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group, tg_id in test_vectors.test_groups { + hash_str := test_group.sha + hash_alg, _ := hash_name_to_algorithm(hash_str) + if hash_alg == .Invalid { + log.infof("%s: unsupported hash: %s", params_str, hash_str) + num_ran += len(test_group.tests) + num_skipped += len(test_group.tests) + continue + } + + priv_key: rsa.Private_Key + pub_key := &priv_key._pub_key + + ok, have_priv: bool + if test_group.private_key_jwk.kty == JWK_KTY { + ok = jwk_to_private_key(&test_group.private_key_jwk, &priv_key) + have_priv = true + } else { + ok = rsa.public_key_set_bytes( + pub_key, + common.hexbytes_decode(test_group.private_key.modulus), + common.hexbytes_decode(test_group.private_key.public_exponent), + ) + } + + if !testing.expectf(t, ok, "%s/%d: invalid RSA key: %v", params_str, tg_id, test_group.private_key) { + num_ran += len(test_group.tests) + num_failed += len(test_group.tests) + continue + } + + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "%s/%s/%d/%d: %s: %+v", + params_str, + hash_str, + tg_id, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("%s/%s/%d/%d: %+v", params_str, hash_str, tg_id, test_vector.tc_id, test_vector.flags) + } + + msg := common.hexbytes_decode(test_vector.msg) + sig := common.hexbytes_decode(test_vector.sig) + + verify_ok := rsa.verify_pkcs1(pub_key, hash_alg, msg, sig) + if !testing.expectf( + t, + result_check(test_vector.result, verify_ok, false), + "%s/%s/%d/%d: verify failed: expected %s actual %v", + params_str, + hash_str, + tg_id, + test_vector.tc_id, + test_vector.result, + verify_ok, + ) { + num_failed += 1 + continue + } + + if have_priv && verify_ok { + sign_ok := rsa.sign_pkcs1(&priv_key, hash_alg, msg, sig) + if !testing.expectf( + t, + sign_ok, + "%s/%s/%d/%d: sign failed", + params_str, + hash_str, + tg_id, + test_vector.tc_id, + ) { + num_failed += 1 + continue + } + if !testing.expectf( + t, + common.hexbytes_compare(test_vector.sig, sig), + "%s/%s/%d/%d: sign failed: expected %s actual %s", + params_str, + hash_str, + tg_id, + test_vector.tc_id, + test_vector.sig, + hex.encode(sig), + ) { + num_failed += 1 + continue + } + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "%s: ran %d, passed %d, failed %d, skipped %d", + params_str, + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} + +@(test) +test_rsa_pss_signature :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + log.debug("rsa/pss/signatures: starting") + + files := []string { + "rsa_pss_2048_sha1_mgf1_20_test.json", + "rsa_pss_2048_sha256_mgf1_0_test.json", + "rsa_pss_2048_sha256_mgf1_32_test.json", + "rsa_pss_2048_sha256_mgf1sha1_20_test.json", + "rsa_pss_2048_sha384_mgf1_48_test.json", + "rsa_pss_2048_sha512_256_mgf1_32_test.json", + "rsa_pss_3072_sha256_mgf1_32_test.json", + "rsa_pss_4096_sha256_mgf1_32_test.json", + "rsa_pss_4096_sha384_mgf1_48_test.json", + "rsa_pss_4096_sha512_mgf1_32_test.json", + "rsa_pss_4096_sha512_mgf1_64_test.json", + "rsa_pss_misc_test.json", + + // These tests include the MGF1 parameters in the public key, + // which we do not support with our existing API. + // + // "rsa_pss_2048_sha1_mgf1_20_params_test.json", + // "rsa_pss_2048_sha256_mgf1_0_params_test.json", + // "rsa_pss_2048_sha256_mgf1_32_params_test.json", + // "rsa_pss_2048_sha512_mgf1sha256_32_params_test.json", + // "rsa_pss_3072_sha256_mgf1_32_params_test.json", + // "rsa_pss_4096_sha512_mgf1_32_params_test.json", + // "rsa_pss_4096_sha512_mgf1_64_params_test.json", + // "rsa_pss_misc_params_test.json", + + // Unsupported hash algorithm: + // "rsa_pss_2048_sha512_224_mgf1_28_test.json", + } + for f in files { + mem.free_all() + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Rsa_Pss_Sig_Test_Group) + load_ok := load(&test_vectors, fn) + if !testing.expectf(t, load_ok, "Unable to load {}", f) { + continue + } + + testing.expectf(t, test_rsa_pss_sig(t, &test_vectors), "RSA PSS signature failed") + } +} + +test_rsa_pss_sig :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Rsa_Pss_Sig_Test_Group)) -> bool { + MGF1 :: "MGF1" + + params_str := fmt.aprintf("RSA-%d/PSS/Signature", test_vectors.test_groups[0].key_size) + log.debugf("%s: starting", params_str) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group, tg_id in test_vectors.test_groups { + if test_group.mgf != MGF1 { + log.infof("%s: unsupported MGF: %s", params_str, test_group.mgf) + num_ran += len(test_group.tests) + num_skipped += len(test_group.tests) + continue + } + + hash_str := test_group.sha + hash_alg, _ := hash_name_to_algorithm(hash_str) + if hash_alg == .Invalid { + log.infof("%s: unsupported hash: %s", params_str, hash_str) + num_ran += len(test_group.tests) + num_skipped += len(test_group.tests) + continue + } + + mgf_hash_str := test_group.mfg_sha + mgf_hash_alg, _ := hash_name_to_algorithm(mgf_hash_str) + if mgf_hash_alg == .Invalid { + log.infof("%s: unsupported MGF hash: %s", params_str, mgf_hash_str) + num_ran += len(test_group.tests) + num_skipped += len(test_group.tests) + continue + } + + hash_params_str := fmt.aprintf("%s/%s(%s)", hash_str, test_group.mgf, mgf_hash_str) + + pub_key: rsa.Public_Key + ok := rsa.public_key_set_bytes( + &pub_key, + common.hexbytes_decode(test_group.public_key.modulus), + common.hexbytes_decode(test_group.public_key.public_exponent), + ) + if !testing.expectf(t, ok, "%s/%d: invalid RSA key: %v", params_str, tg_id, test_group.public_key) { + num_ran += len(test_group.tests) + num_failed += len(test_group.tests) + continue + } + + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "%s/%s/%d/%d: %s: %+v", + params_str, + hash_params_str, + tg_id, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("%s/%s/%d/%d: %+v", params_str, hash_str, tg_id, test_vector.tc_id, test_vector.flags) + } + + msg := common.hexbytes_decode(test_vector.msg) + sig := common.hexbytes_decode(test_vector.sig) + + verify_ok := rsa.verify_pss( + &pub_key, + hash_alg, + test_group.s_len, + msg, + sig, + false, + mgf_hash_alg, + ) + if !testing.expectf( + t, + result_check(test_vector.result, verify_ok, false), + "%s/%s/%d/%d: verify failed: expected %s actual %v", + params_str, + hash_params_str, + tg_id, + test_vector.tc_id, + test_vector.result, + verify_ok, + ) { + num_failed += 1 + continue + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "%s: ran %d, passed %d, failed %d, skipped %d", + params_str, + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} + +@(test) +test_rsa_oaep_decryption :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + log.debug("rsa/oaep/decryption: starting") + + files := []string { + "rsa_oaep_2048_sha1_mgf1sha1_test.json", + "rsa_oaep_2048_sha224_mgf1sha1_test.json", + "rsa_oaep_2048_sha224_mgf1sha224_test.json", + "rsa_oaep_2048_sha256_mgf1sha1_test.json", + "rsa_oaep_2048_sha256_mgf1sha256_test.json", + "rsa_oaep_2048_sha384_mgf1sha1_test.json", + "rsa_oaep_2048_sha384_mgf1sha384_test.json", + "rsa_oaep_2048_sha512_224_mgf1sha1_test.json", + "rsa_oaep_2048_sha512_mgf1sha1_test.json", + "rsa_oaep_2048_sha512_mgf1sha512_test.json", + "rsa_oaep_3072_sha256_mgf1sha1_test.json", + "rsa_oaep_3072_sha256_mgf1sha256_test.json", + "rsa_oaep_3072_sha512_256_mgf1sha1_test.json", + "rsa_oaep_3072_sha512_256_mgf1sha512_256_test.json", + "rsa_oaep_3072_sha512_mgf1sha1_test.json", + "rsa_oaep_3072_sha512_mgf1sha512_test.json", + "rsa_oaep_4096_sha256_mgf1sha1_test.json", + "rsa_oaep_4096_sha256_mgf1sha256_test.json", + "rsa_oaep_4096_sha512_mgf1sha1_test.json", + "rsa_oaep_4096_sha512_mgf1sha512_test.json", + "rsa_oaep_misc_test.json", + + // Unsupported hash algorithm: + // "rsa_oaep_2048_sha512_224_mgf1sha512_224_test.json", + } + for f in files { + mem.free_all() + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Rsa_Oaep_Dec_Test_Group) + load_ok := load(&test_vectors, fn) + if !testing.expectf(t, load_ok, "Unable to load {}", f) { + continue + } + + testing.expectf(t, test_rsa_oaep_dec(t, &test_vectors), "RSA OAEP decryption failed") + } +} + +test_rsa_oaep_dec :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Rsa_Oaep_Dec_Test_Group)) -> bool { + MGF1 :: "MGF1" + + params_str := fmt.aprintf("RSA-%d/OAEP/Decryption", test_vectors.test_groups[0].key_size) + log.debugf("%s: starting", params_str) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group, tg_id in test_vectors.test_groups { + if test_group.key_size > rsa.MODULUS_MAX_SIZE { + log.infof("%s: unsupported key size: %d", params_str, test_group.key_size) + num_ran += len(test_group.tests) + num_skipped += len(test_group.tests) + continue + } + if test_group.mgf != MGF1 { + log.infof("%s: unsupported MGF: %s", params_str, test_group.mgf) + num_ran += len(test_group.tests) + num_skipped += len(test_group.tests) + continue + } + + hash_str := test_group.sha + hash_alg, _ := hash_name_to_algorithm(hash_str) + if hash_alg == .Invalid { + log.infof("%s: unsupported hash: %s", params_str, hash_str) + num_ran += len(test_group.tests) + num_skipped += len(test_group.tests) + continue + } + + mgf_hash_str := test_group.mfg_sha + mgf_hash_alg, _ := hash_name_to_algorithm(mgf_hash_str) + if mgf_hash_alg == .Invalid { + log.infof("%s: unsupported MGF hash: %s", params_str, mgf_hash_str) + num_ran += len(test_group.tests) + num_skipped += len(test_group.tests) + continue + } + + hash_params_str := fmt.aprintf("%s/%s(%s)", hash_str, test_group.mgf, mgf_hash_str) + + priv_key: rsa.Private_Key + ok := json_to_private_key(&test_group.private_key, &priv_key) + if !testing.expectf(t, ok, "%s/%d: invalid RSA key: %v", params_str, tg_id, test_group.private_key) { + num_ran += len(test_group.tests) + num_failed += len(test_group.tests) + continue + } + + dst: [rsa.MODULUS_MAX_SIZE >> 3]byte = --- + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "%s/%s/%d/%d: %s: %+v", + params_str, + hash_params_str, + tg_id, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("%s/%s/%d/%d: %+v", params_str, hash_str, tg_id, test_vector.tc_id, test_vector.flags) + } + + label := common.hexbytes_decode(test_vector.label) + ct := common.hexbytes_decode(test_vector.ct) + + pt, decrypt_ok := rsa.decrypt_oaep( + &priv_key, + hash_alg, + ct, + dst[:], + label, + mgf_hash_alg, + ) + + if !testing.expectf( + t, + result_check(test_vector.result, decrypt_ok, false), + "%s/%s/%d/%d: decrypt failed: expected %s actual %v", + params_str, + hash_params_str, + tg_id, + test_vector.tc_id, + test_vector.result, + decrypt_ok, + ) { + num_failed += 1 + continue + } + if decrypt_ok { + if !testing.expectf( + t, + common.hexbytes_compare(test_vector.msg, pt), + "%s/%s/%d/%d: decrypt failed: expected %s actual %s", + params_str, + hash_str, + tg_id, + test_vector.tc_id, + test_vector.msg, + hex.encode(pt), + ) { + num_failed += 1 + continue + } + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "%s: ran %d, passed %d, failed %d, skipped %d", + params_str, + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} + +@(private="file") +jwk_to_private_key :: proc(jwk: ^Rsa_Jwk_Private_Key, priv_key: ^rsa.Private_Key) -> bool { + return rsa.private_key_set_bytes( + priv_key, + common.jwkbytes_decode(jwk.n), + common.jwkbytes_decode(jwk.e), + common.jwkbytes_decode(jwk.d), + common.jwkbytes_decode(jwk.p), + common.jwkbytes_decode(jwk.q), + common.jwkbytes_decode(jwk.dp), + common.jwkbytes_decode(jwk.dq), + common.jwkbytes_decode(jwk.qi), + ) +} + +@(private="file") +json_to_private_key :: proc(json: ^Rsa_Private_Key, priv_key: ^rsa.Private_Key) -> bool { + return rsa.private_key_set_bytes( + priv_key, + common.hexbytes_decode(json.modulus), + common.hexbytes_decode(json.public_exponent), + common.hexbytes_decode(json.private_exponent), + common.hexbytes_decode(json.prime1), + common.hexbytes_decode(json.prime2), + common.hexbytes_decode(json.exponent1), + common.hexbytes_decode(json.exponent2), + common.hexbytes_decode(json.coefficient), + ) +} \ No newline at end of file diff --git a/tests/core/crypto/wycheproof/schemas.odin b/tests/core/crypto/wycheproof/schemas.odin index 2e72a1098..39270a379 100644 --- a/tests/core/crypto/wycheproof/schemas.odin +++ b/tests/core/crypto/wycheproof/schemas.odin @@ -150,10 +150,10 @@ Eddsa_Key :: struct { } Eddsa_Jwk :: struct { - kid: string `json:"kid"`, - crv: string `json:"crv"`, - kty: string `json:"kty"`, - x: string `json:"x"`, + kid: string `json:"kid"`, + crv: string `json:"crv"`, + kty: string `json:"kty"`, + x: common.Jwk_Bytes `json:"x"`, } Ecdsa_Key :: struct { @@ -241,3 +241,96 @@ Mldsa_Test_Vector :: struct { result: Result `json:"result"`, flags: []string `json:"flags"`, } + +Rsa_Pkcs1_Sig_Test_Group :: struct { + private_key: Rsa_Private_Key `json:"privateKey"`, + key_asn: common.Hex_Bytes `json:"keyAsn"`, + key_der: common.Hex_Bytes `json:"keyDer"`, + key_pem: string `json:"keyPem"`, + key_size: int `json:"keySize"`, + private_key_jwk: Rsa_Jwk_Private_Key `json:"privateKeyJwk"`, + private_key_pem: string `json:"privateKeyPem"`, + private_key_pkcs8: common.Hex_Bytes `json:"privateKeyPkcs8"`, + sha: string `json:"sha"`, + type: string `json:"type"`, + source: Test_Group_Source `json:"source"`, + tests: []Rsa_Sig_Test_Vector `json:"tests"`, +} + +Rsa_Sig_Test_Vector :: struct { + tc_id: int `json:"tcId"`, + comment: string `json:"comment"`, + msg: common.Hex_Bytes `json:"msg"`, + sig: common.Hex_Bytes `json:"sig"`, + result: Result `json:"result"`, + flags: []string `json:"flags"`, +} + +Rsa_Pss_Sig_Test_Group :: struct { + public_key: Rsa_Public_Key `json:"publicKey"`, + public_key_asn: common.Hex_Bytes `json:"publicKeyAsn"`, + public_key_der: common.Hex_Bytes `json:"publicKeyDer"`, + public_key_pem: string `json:"publicKeyPem"`, + key_size: int `json:"keySize"`, + sha: string `json:"sha"`, + mgf: string `json:"mgf"`, + mfg_sha: string `json:"mgfSha"`, + s_len: int `json:"sLen"`, + type: string `json:"type"`, + source: Test_Group_Source `json:"source"`, + tests: []Rsa_Sig_Test_Vector `json:"tests"`, +} + +Rsa_Oaep_Dec_Test_Group :: struct { + key_size: int `json:"keySize"`, + private_key: Rsa_Private_Key `json:"privateKey"`, + private_key_jwk: Rsa_Jwk_Private_Key `json:"privateKeyJwk"`, + private_key_pem: string `json:"privateKeyPem"`, + private_key_pkcs8: common.Hex_Bytes `json:"privateKeyPkcs8"`, + sha: string `json:"sha"`, + mgf: string `json:"mgf"`, + mfg_sha: string `json:"mgfSha"`, + type: string `json:"type"`, + source: Test_Group_Source `json:"source"`, + tests: []Rsa_Oaep_Test_Vector `json:"tests"`, +} + +Rsa_Oaep_Test_Vector :: struct { + tc_id: int `json:"tcId"`, + comment: string `json:"comment"`, + msg: common.Hex_Bytes `json:"msg"`, + ct: common.Hex_Bytes `json:"ct"`, + label: common.Hex_Bytes `json:"label"`, + result: Result `json:"result"`, + flags: []string `json:"flags"`, +} + +Rsa_Public_Key :: struct { + modulus: common.Hex_Bytes `json:"modulus"`, + public_exponent: common.Hex_Bytes `json:"publicExponent"`, +} + +Rsa_Private_Key :: struct { + modulus: common.Hex_Bytes `json:"modulus"`, + private_exponent: common.Hex_Bytes `json:"privateExponent"`, + public_exponent: common.Hex_Bytes `json:"publicExponent"`, + prime1: common.Hex_Bytes `json:"prime1"`, + prime2: common.Hex_Bytes `json:"prime2"`, + exponent1: common.Hex_Bytes `json:"exponent1"`, + exponent2: common.Hex_Bytes `json:"exponent2"`, + coefficient: common.Hex_Bytes `json:"coefficient"`, +} + +Rsa_Jwk_Private_Key :: struct { + akg: string `json:"alg"`, + kid: string `json:"kid"`, + kty: string `json:"kty"`, + d: common.Jwk_Bytes `json:"d"`, + dp: common.Jwk_Bytes `json:"dp"`, + dq: common.Jwk_Bytes `json:"dq"`, + e: common.Jwk_Bytes `json:"e"`, + n: common.Jwk_Bytes `json:"n"`, + p: common.Jwk_Bytes `json:"p"`, + q: common.Jwk_Bytes `json:"q"`, + qi: common.Jwk_Bytes `json:"qi"`, +} diff --git a/tests/core/speed.odin b/tests/core/speed.odin index 5ea9bbc21..0da7c1b82 100644 --- a/tests/core/speed.odin +++ b/tests/core/speed.odin @@ -2,6 +2,7 @@ package tests_core @(require) import "crypto" +@(require) import "crypto/bigint" @(require) import "hash" @(require) import "image" @(require) import "math/big" \ No newline at end of file