| 1 | module poly1305 |
| 2 | |
| 3 | import math.bits |
| 4 | import math.unsigned |
| 5 | |
| 6 | const uint192_zero = Uint192{} |
| 7 | |
| 8 | // Uint192 is a structure representing 192 bits of unsigned integer. |
| 9 | // It serves as a custom allocator for the poly1305 implementation to use. |
| 10 | // However, it can be used to complement and expand the standard `math.unsigned` |
| 11 | // module with a more complete and extensive range of handling of unsigned integers. |
| 12 | struct Uint192 { |
| 13 | mut: |
| 14 | lo u64 |
| 15 | mi u64 |
| 16 | hi u64 |
| 17 | } |
| 18 | |
| 19 | // Here, we define several required functionalities of the custom allocator. |
| 20 | |
| 21 | // add_checked returns u+v with carry |
| 22 | fn (u Uint192) add_checked(v Uint192, c u64) (Uint192, u64) { |
| 23 | lo, c0 := bits.add_64(u.lo, v.lo, c) |
| 24 | mi, c1 := bits.add_64(u.mi, v.mi, c0) |
| 25 | hi, c2 := bits.add_64(u.hi, v.hi, c1) |
| 26 | x := Uint192{lo, mi, hi} |
| 27 | return x, c2 |
| 28 | } |
| 29 | |
| 30 | // add_128_checked returns u+v with carry |
| 31 | fn (u Uint192) add_128_checked(v unsigned.Uint128, c u64) (Uint192, u64) { |
| 32 | lo, c0 := bits.add_64(u.lo, v.lo, c) |
| 33 | mi, c1 := bits.add_64(u.mi, v.hi, c0) |
| 34 | hi, c2 := bits.add_64(u.hi, 0, c1) |
| 35 | x := Uint192{lo, mi, hi} |
| 36 | return x, c2 |
| 37 | } |
| 38 | |
| 39 | fn (u Uint192) add_64_checked(v u64, c u64) (Uint192, u64) { |
| 40 | lo, c0 := bits.add_64(u.lo, v, c) |
| 41 | mi, c1 := bits.add_64(u.mi, 0, c0) |
| 42 | hi, c2 := bits.add_64(u.hi, 0, c1) |
| 43 | x := Uint192{lo, mi, hi} |
| 44 | return x, c2 |
| 45 | } |
| 46 | |
| 47 | fn (u Uint192) sub_checked(v Uint192) (Uint192, u64) { |
| 48 | lo, b0 := bits.sub_64(u.lo, v.lo, 0) |
| 49 | mi, b1 := bits.sub_64(u.mi, v.mi, b0) |
| 50 | hi, b2 := bits.sub_64(u.hi, v.hi, b1) |
| 51 | return Uint192{lo, mi, hi}, b2 |
| 52 | } |
| 53 | |
| 54 | // mul_64_checked returns `u*v` even if the result size is over > 192 bits. |
| 55 | // It returns a `(Uin192, u64)` pair where the former stores the low 192 bits and |
| 56 | // the rest of high bits stored in the `u64` part. You can check the value of the `u64` part |
| 57 | // for `c != 0`, it's mean, the product of `u*v` is overflowing 192 bits. |
| 58 | fn (u Uint192) mul_64_checked(v u64) (Uint192, u64) { |
| 59 | // see mul_128_checked for the logic |
| 60 | m0 := u128_from_64_mul(u.lo, v) |
| 61 | m1 := u128_from_64_mul(u.mi, v) |
| 62 | m2 := u128_from_64_mul(u.hi, v) |
| 63 | // propagates carry |
| 64 | t0, c0 := bits.add_64(m0.lo, 0, 0) |
| 65 | t1, c1 := bits.add_64(m0.hi, m1.lo, c0) |
| 66 | t2, c2 := bits.add_64(m1.hi, m2.lo, c1) |
| 67 | t3, c3 := bits.add_64(m2.hi, 0, c2) |
| 68 | // something bad happen if the last c3 is non null |
| 69 | if c3 != 0 { |
| 70 | panic('Uint192: unexpected overflow') |
| 71 | } |
| 72 | x := Uint192{ |
| 73 | lo: t0 |
| 74 | mi: t1 |
| 75 | hi: t2 |
| 76 | } |
| 77 | return x, t3 |
| 78 | } |
| 79 | |
| 80 | // mul_128_checked is a generic product of 192 bits u by 128 bits v. |
| 81 | // Its returns u*v even if the result is over > 192 bits, and stores the remaining |
| 82 | // high bits of the result into the Uint128 structure. |
| 83 | fn (u Uint192) mul_128_checked(v unsigned.Uint128) (Uint192, unsigned.Uint128) { |
| 84 | // u.hi u.mi u.lo |
| 85 | // v.hi v.lo |
| 86 | // ---------------------------------------------------------- x |
| 87 | // uhi*vlo umi*vlo ulo*vlo |
| 88 | // uhi*vhi umi*vhi ulo*vhi |
| 89 | // ========================================================== |
| 90 | // m3 m2 m1 m0 // Uint128 |
| 91 | // |
| 92 | // ---------------------------------------------------------- + |
| 93 | // m3.hi m2.hi m1.hi m0.hi |
| 94 | // m3.lo m2.lo m1.lo m0.lo |
| 95 | // ========================================================== |
| 96 | // t4 t3 t2 t1 t0 |
| 97 | // |
| 98 | ulovlo := u128_from_64_mul(u.lo, v.lo) |
| 99 | umivlo := u128_from_64_mul(u.mi, v.lo) |
| 100 | uhivlo := u128_from_64_mul(u.hi, v.lo) |
| 101 | |
| 102 | ulovhi := u128_from_64_mul(u.lo, v.hi) |
| 103 | umivhi := u128_from_64_mul(u.mi, v.hi) |
| 104 | uhivhi := u128_from_64_mul(u.hi, v.hi) |
| 105 | |
| 106 | m0 := ulovlo |
| 107 | m1, c1 := unsigned.add_128(umivlo, ulovhi, 0) |
| 108 | m2, c2 := unsigned.add_128(uhivlo, umivhi, c1) |
| 109 | m3, c3 := unsigned.add_128(uhivhi, unsigned.uint128_zero, c2) |
| 110 | if c3 != 0 { |
| 111 | panic('Uint192: unexpected overflow') |
| 112 | } |
| 113 | // Note about the multiplication results in Poly1305 context. |
| 114 | // In the properly clamped 128 bits of v, (called "r" in the poly1305 context) and |
| 115 | // safely reduced form of high part of the 192 bits accumulator u (u.hi), where only |
| 116 | // maximum of four low bits of u.hi is set, and we can assume (and confirm with tests) |
| 117 | // if the high bit part of the product of uhi*vhi and uhi*vlo is not set, |
| 118 | // ie, x = (uhi*vhi).hi == 0 and y = (uhi*vlo).hi == 0. |
| 119 | // and the 128 bits addition of `m2 = uhi*vlo + umi*vhi`, would also not overflow 128 bits, |
| 120 | // thats also mean, the last carry is null for the reason and m3 = (uhi*vhi).hi is also null |
| 121 | // |
| 122 | t0 := m0.lo |
| 123 | t1, c4 := bits.add_64(m0.hi, m1.lo, 0) |
| 124 | t2, c5 := bits.add_64(m1.hi, m2.lo, c4) |
| 125 | t3, c6 := bits.add_64(m2.hi, m3.lo, c5) |
| 126 | t4, c7 := bits.add_64(m3.hi, 0, c6) |
| 127 | if c7 != 0 { |
| 128 | panic('Uint192: unexpected overflow') |
| 129 | } |
| 130 | // based on previous notes, for poly1305 context, it tells us if the product |
| 131 | // doesn't have a fitfh limb (t4), ie t4 == null, and we can safely ignore it. |
| 132 | // |
| 133 | x := Uint192{ |
| 134 | lo: t0 |
| 135 | mi: t1 |
| 136 | hi: t2 |
| 137 | } |
| 138 | hb := unsigned.uint128_new(t3, t4) |
| 139 | return x, hb |
| 140 | } |
| 141 | |
| 142 | // u128_from_64_mul creates new Uint128 from 64x64 bit product of x*y |
| 143 | fn u128_from_64_mul(x u64, y u64) unsigned.Uint128 { |
| 144 | hi, lo := bits.mul_64(x, y) |
| 145 | return unsigned.uint128_new(lo, hi) |
| 146 | } |
| 147 | |
| 148 | // select_64 returns x if v == 1 and y if v == 0, in constant time. |
| 149 | fn select_64(v u64, x u64, y u64) u64 { |
| 150 | return ~(v - 1) & x | (v - 1) & y |
| 151 | } |
| 152 | |
| 153 | fn shift_right_by2(mut a unsigned.Uint128) unsigned.Uint128 { |
| 154 | a.lo = a.lo >> 2 | (a.hi & 3) << 62 |
| 155 | a.hi = a.hi >> 2 |
| 156 | return a |
| 157 | } |
| 158 | |