// 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 (
	
	
)

// 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 ( *[16]byte,  ...[]byte) []byte {
	fips140.RecordNonApproved()
	var  [gcmBlockSize]byte
	ghash(&, , ...)
	return [:]
}

// 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(,  *[gcmBlockSize]byte,  ...[]byte) {
	// productTable contains the first sixteen powers of the key, H.
	// However, they are in bit reversed order.
	var  [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.
	 := gcmFieldElement{
		byteorder.BEUint64([:8]),
		byteorder.BEUint64([8:]),
	}
	[reverseBits(1)] = 

	for  := 2;  < 16;  += 2 {
		[reverseBits()] = ghashDouble(&[reverseBits(/2)])
		[reverseBits(+1)] = ghashAdd(&[reverseBits()], &)
	}

	var  gcmFieldElement
	for ,  := range  {
		ghashUpdate(&, &, )
	}

	byteorder.BEPutUint64([:], .low)
	byteorder.BEPutUint64([8:], .high)
}

// reverseBits reverses the order of the bits of 4-bit number in i.
func reverseBits( int) int {
	 = (( << 2) & 0xc) | (( >> 2) & 0x3)
	 = (( << 1) & 0xa) | (( >> 1) & 0x5)
	return 
}

// ghashAdd adds two elements of GF(2¹²⁸) and returns the sum.
func ghashAdd(,  *gcmFieldElement) gcmFieldElement {
	// Addition in a characteristic 2 field is just XOR.
	return gcmFieldElement{.low ^ .low, .high ^ .high}
}

// ghashDouble returns the result of doubling an element of GF(2¹²⁸).
func ghashDouble( *gcmFieldElement) ( gcmFieldElement) {
	 := .high&1 == 1

	// Because of the bit-ordering, doubling is actually a right shift.
	.high = .high >> 1
	.high |= .low << 63
	.low = .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  {
		.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( *[16]gcmFieldElement,  *gcmFieldElement) {
	var  gcmFieldElement

	for  := 0;  < 2; ++ {
		 := .high
		if  == 1 {
			 = .low
		}

		// Multiplication works by multiplying z by 16 and adding in
		// one of the precomputed multiples of H.
		for  := 0;  < 64;  += 4 {
			 := .high & 0xf
			.high >>= 4
			.high |= .low << 60
			.low >>= 4
			.low ^= uint64(ghashReductionTable[]) << 48

			// the values in |table| are ordered for little-endian bit
			// positions. See the comment in New.
			 := [&0xf]

			.low ^= .low
			.high ^= .high
			 >>= 4
		}
	}

	* = 
}

// 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( *[16]gcmFieldElement,  *gcmFieldElement,  []byte) {
	for len() > 0 {
		.low ^= byteorder.BEUint64()
		.high ^= byteorder.BEUint64([8:])
		ghashMul(, )
		 = [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( *[16]gcmFieldElement,  *gcmFieldElement,  []byte) {
	 := (len() >> 4) << 4
	updateBlocks(, , [:])

	if len() !=  {
		var  [gcmBlockSize]byte
		copy([:], [:])
		updateBlocks(, , [:])
	}
}