0
0
mirror of https://github.com/XTLS/REALITY.git synced 2025-08-22 22:48:36 +00:00
XTLS_REALITY/gcm/ghash.go
2025-05-05 11:40:53 -04:00

162 lines
5.2 KiB
Go

// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gcm
import (
"encoding/binary"
)
// gcmFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM
// standard and make binary.BigEndian suitable for marshaling these values, the
// bits are stored in big endian order. For example:
//
// the coefficient of x⁰ can be obtained by v.low >> 63.
// the coefficient of x⁶³ can be obtained by v.low & 1.
// the coefficient of x⁶⁴ can be obtained by v.high >> 63.
// the coefficient of x¹²⁷ can be obtained by v.high & 1.
type gcmFieldElement struct {
low, high uint64
}
// GHASH is exposed to allow crypto/cipher to implement non-AES GCM modes.
// It is not allowed as a stand-alone operation in FIPS mode because it
// is not ACVP tested.
func GHASH(key *[16]byte, inputs ...[]byte) []byte {
// fips140.RecordNonApproved()
var out [gcmBlockSize]byte
ghash(&out, key, inputs...)
return out[:]
}
// ghash is a variable-time generic implementation of GHASH, which shouldn't
// be used on any architecture with hardware support for AES-GCM.
//
// Each input is zero-padded to 128-bit before being absorbed.
func ghash(out, H *[gcmBlockSize]byte, inputs ...[]byte) {
// productTable contains the first sixteen powers of the key, H.
// However, they are in bit reversed order.
var productTable [16]gcmFieldElement
// We precompute 16 multiples of H. However, when we do lookups
// into this table we'll be using bits from a field element and
// therefore the bits will be in the reverse order. So normally one
// would expect, say, 4*H to be in index 4 of the table but due to
// this bit ordering it will actually be in index 0010 (base 2) = 2.
x := gcmFieldElement{
binary.BigEndian.Uint64(H[:8]),
binary.BigEndian.Uint64(H[8:]),
}
productTable[reverseBits(1)] = x
for i := 2; i < 16; i += 2 {
productTable[reverseBits(i)] = ghashDouble(&productTable[reverseBits(i/2)])
productTable[reverseBits(i+1)] = ghashAdd(&productTable[reverseBits(i)], &x)
}
var y gcmFieldElement
for _, input := range inputs {
ghashUpdate(&productTable, &y, input)
}
binary.BigEndian.PutUint64(out[:], y.low)
binary.BigEndian.PutUint64(out[8:], y.high)
}
// reverseBits reverses the order of the bits of 4-bit number in i.
func reverseBits(i int) int {
i = ((i << 2) & 0xc) | ((i >> 2) & 0x3)
i = ((i << 1) & 0xa) | ((i >> 1) & 0x5)
return i
}
// ghashAdd adds two elements of GF(2¹²⁸) and returns the sum.
func ghashAdd(x, y *gcmFieldElement) gcmFieldElement {
// Addition in a characteristic 2 field is just XOR.
return gcmFieldElement{x.low ^ y.low, x.high ^ y.high}
}
// ghashDouble returns the result of doubling an element of GF(2¹²⁸).
func ghashDouble(x *gcmFieldElement) (double gcmFieldElement) {
msbSet := x.high&1 == 1
// Because of the bit-ordering, doubling is actually a right shift.
double.high = x.high >> 1
double.high |= x.low << 63
double.low = x.low >> 1
// If the most-significant bit was set before shifting then it,
// conceptually, becomes a term of x^128. This is greater than the
// irreducible polynomial so the result has to be reduced. The
// irreducible polynomial is 1+x+x^2+x^7+x^128. We can subtract that to
// eliminate the term at x^128 which also means subtracting the other
// four terms. In characteristic 2 fields, subtraction == addition ==
// XOR.
if msbSet {
double.low ^= 0xe100000000000000
}
return
}
var ghashReductionTable = []uint16{
0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0,
}
// ghashMul sets y to y*H, where H is the GCM key, fixed during New.
func ghashMul(productTable *[16]gcmFieldElement, y *gcmFieldElement) {
var z gcmFieldElement
for i := 0; i < 2; i++ {
word := y.high
if i == 1 {
word = y.low
}
// Multiplication works by multiplying z by 16 and adding in
// one of the precomputed multiples of H.
for j := 0; j < 64; j += 4 {
msw := z.high & 0xf
z.high >>= 4
z.high |= z.low << 60
z.low >>= 4
z.low ^= uint64(ghashReductionTable[msw]) << 48
// the values in |table| are ordered for little-endian bit
// positions. See the comment in New.
t := productTable[word&0xf]
z.low ^= t.low
z.high ^= t.high
word >>= 4
}
}
*y = z
}
// updateBlocks extends y with more polynomial terms from blocks, based on
// Horner's rule. There must be a multiple of gcmBlockSize bytes in blocks.
func updateBlocks(productTable *[16]gcmFieldElement, y *gcmFieldElement, blocks []byte) {
for len(blocks) > 0 {
y.low ^= binary.BigEndian.Uint64(blocks)
y.high ^= binary.BigEndian.Uint64(blocks[8:])
ghashMul(productTable, y)
blocks = blocks[gcmBlockSize:]
}
}
// ghashUpdate extends y with more polynomial terms from data. If data is not a
// multiple of gcmBlockSize bytes long then the remainder is zero padded.
func ghashUpdate(productTable *[16]gcmFieldElement, y *gcmFieldElement, data []byte) {
fullBlocks := (len(data) >> 4) << 4
updateBlocks(productTable, y, data[:fullBlocks])
if len(data) != fullBlocks {
var partialBlock [gcmBlockSize]byte
copy(partialBlock[:], data[fullBlocks:])
updateBlocks(productTable, y, partialBlock[:])
}
}