xref: /aosp_15_r20/external/tink/go/aead/subtle/polyval.go (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1*e7b1675dSTing-Kang Chang// Copyright 2020 Google LLC
2*e7b1675dSTing-Kang Chang//
3*e7b1675dSTing-Kang Chang// Licensed under the Apache License, Version 2.0 (the "License");
4*e7b1675dSTing-Kang Chang// you may not use this file except in compliance with the License.
5*e7b1675dSTing-Kang Chang// You may obtain a copy of the License at
6*e7b1675dSTing-Kang Chang//
7*e7b1675dSTing-Kang Chang//      http://www.apache.org/licenses/LICENSE-2.0
8*e7b1675dSTing-Kang Chang//
9*e7b1675dSTing-Kang Chang// Unless required by applicable law or agreed to in writing, software
10*e7b1675dSTing-Kang Chang// distributed under the License is distributed on an "AS IS" BASIS,
11*e7b1675dSTing-Kang Chang// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*e7b1675dSTing-Kang Chang// See the License for the specific language governing permissions and
13*e7b1675dSTing-Kang Chang// limitations under the License.
14*e7b1675dSTing-Kang Chang//
15*e7b1675dSTing-Kang Chang////////////////////////////////////////////////////////////////////////////////
16*e7b1675dSTing-Kang Chang
17*e7b1675dSTing-Kang Changpackage subtle
18*e7b1675dSTing-Kang Chang
19*e7b1675dSTing-Kang Changimport (
20*e7b1675dSTing-Kang Chang	"encoding/binary"
21*e7b1675dSTing-Kang Chang	"fmt"
22*e7b1675dSTing-Kang Chang)
23*e7b1675dSTing-Kang Chang
24*e7b1675dSTing-Kang Changconst (
25*e7b1675dSTing-Kang Chang	// PolyvalBlockSize is the block size (in bytes) that POLYVAL uses.
26*e7b1675dSTing-Kang Chang	PolyvalBlockSize = 16
27*e7b1675dSTing-Kang Chang
28*e7b1675dSTing-Kang Chang	u32Sel0 uint32 = 0x11111111
29*e7b1675dSTing-Kang Chang	u32Sel1 uint32 = 0x22222222
30*e7b1675dSTing-Kang Chang	u32Sel2 uint32 = 0x44444444
31*e7b1675dSTing-Kang Chang	u32Sel3 uint32 = 0x88888888
32*e7b1675dSTing-Kang Chang
33*e7b1675dSTing-Kang Chang	u64Sel0 uint64 = 0x1111111111111111
34*e7b1675dSTing-Kang Chang	u64Sel1 uint64 = 0x2222222222222222
35*e7b1675dSTing-Kang Chang	u64Sel2 uint64 = 0x4444444444444444
36*e7b1675dSTing-Kang Chang	u64Sel3 uint64 = 0x8888888888888888
37*e7b1675dSTing-Kang Chang)
38*e7b1675dSTing-Kang Chang
39*e7b1675dSTing-Kang Chang// Polyval (RFC 8452) is a universal hash function which operates on GF(2^128)
40*e7b1675dSTing-Kang Chang// and can be used for constructing a Message Authentication Code (MAC).
41*e7b1675dSTing-Kang Chang// See Section 3 of go/rfc/8452 for definition.
42*e7b1675dSTing-Kang Changtype Polyval interface {
43*e7b1675dSTing-Kang Chang	// update the accumulator in the object with the blocks from data. If data
44*e7b1675dSTing-Kang Chang	// is not a multiple of 16 bytes, it is automatically zero padded.
45*e7b1675dSTing-Kang Chang	Update(data []byte)
46*e7b1675dSTing-Kang Chang
47*e7b1675dSTing-Kang Chang	// finish completes the polyval computation and returns the result.
48*e7b1675dSTing-Kang Chang	Finish() [PolyvalBlockSize]byte
49*e7b1675dSTing-Kang Chang}
50*e7b1675dSTing-Kang Chang
51*e7b1675dSTing-Kang Chang// fieldElement represents a value in GF(2^128).
52*e7b1675dSTing-Kang Chang// In order to reflect the Polyval standard and make binary.LittleEndian suitable
53*e7b1675dSTing-Kang Chang// for marshaling these values, the bits are stored in little endian order.
54*e7b1675dSTing-Kang Chang// For example:
55*e7b1675dSTing-Kang Chang//   the coefficient of x^0 can be obtained by v.lo & 1.
56*e7b1675dSTing-Kang Chang//   the coefficient of x^63 can be obtained by v.lo >> 63.
57*e7b1675dSTing-Kang Chang//   the coefficient of x^64 can be obtained by v.hi & 1.
58*e7b1675dSTing-Kang Chang//   the coefficient of x^127 can be obtained by v.hi >> 63.
59*e7b1675dSTing-Kang Changtype fieldElement struct {
60*e7b1675dSTing-Kang Chang	lo, hi uint64
61*e7b1675dSTing-Kang Chang}
62*e7b1675dSTing-Kang Chang
63*e7b1675dSTing-Kang Chang// polyval implements the POLYVAL function as defined by go/rfc/8452.
64*e7b1675dSTing-Kang Changtype polyval struct {
65*e7b1675dSTing-Kang Chang	key fieldElement
66*e7b1675dSTing-Kang Chang	acc fieldElement
67*e7b1675dSTing-Kang Chang}
68*e7b1675dSTing-Kang Chang
69*e7b1675dSTing-Kang Chang// Assert that polyval implements Polyval interface
70*e7b1675dSTing-Kang Changvar _ Polyval = (*polyval)(nil)
71*e7b1675dSTing-Kang Chang
72*e7b1675dSTing-Kang Chang// mul32 multiplies two 32 bit polynomials in GF(2^128) using Karatsuba multiplication.
73*e7b1675dSTing-Kang Changfunc mul32(a uint32, b uint32) uint64 {
74*e7b1675dSTing-Kang Chang	a0 := uint64(a & u32Sel0)
75*e7b1675dSTing-Kang Chang	a1 := uint64(a & u32Sel1)
76*e7b1675dSTing-Kang Chang	a2 := uint64(a & u32Sel2)
77*e7b1675dSTing-Kang Chang	a3 := uint64(a & u32Sel3)
78*e7b1675dSTing-Kang Chang
79*e7b1675dSTing-Kang Chang	b0 := uint64(b & u32Sel0)
80*e7b1675dSTing-Kang Chang	b1 := uint64(b & u32Sel1)
81*e7b1675dSTing-Kang Chang	b2 := uint64(b & u32Sel2)
82*e7b1675dSTing-Kang Chang	b3 := uint64(b & u32Sel3)
83*e7b1675dSTing-Kang Chang
84*e7b1675dSTing-Kang Chang	c0 := (a0 * b0) ^ (a1 * b3) ^ (a2 * b2) ^ (a3 * b1)
85*e7b1675dSTing-Kang Chang	c1 := (a0 * b1) ^ (a1 * b0) ^ (a2 * b3) ^ (a3 * b2)
86*e7b1675dSTing-Kang Chang	c2 := (a0 * b2) ^ (a1 * b1) ^ (a2 * b0) ^ (a3 * b3)
87*e7b1675dSTing-Kang Chang	c3 := (a0 * b3) ^ (a1 * b2) ^ (a2 * b1) ^ (a3 * b0)
88*e7b1675dSTing-Kang Chang
89*e7b1675dSTing-Kang Chang	return (c0 & u64Sel0) | (c1 & u64Sel1) | (c2 & u64Sel2) | (c3 & u64Sel3)
90*e7b1675dSTing-Kang Chang}
91*e7b1675dSTing-Kang Chang
92*e7b1675dSTing-Kang Chang// mul64 multiplies two 64 bit polynomials in GF(2^128) using Karatsuba multiplication.
93*e7b1675dSTing-Kang Changfunc mul64(a uint64, b uint64) fieldElement {
94*e7b1675dSTing-Kang Chang	a0 := uint32(a & 0xffffffff)
95*e7b1675dSTing-Kang Chang	a1 := uint32(a >> 32)
96*e7b1675dSTing-Kang Chang
97*e7b1675dSTing-Kang Chang	b0 := uint32(b & 0xffffffff)
98*e7b1675dSTing-Kang Chang	b1 := uint32(b >> 32)
99*e7b1675dSTing-Kang Chang
100*e7b1675dSTing-Kang Chang	lo := mul32(a0, b0)
101*e7b1675dSTing-Kang Chang	hi := mul32(a1, b1)
102*e7b1675dSTing-Kang Chang	mid := mul32(a0^a1, b0^b1) ^ lo ^ hi
103*e7b1675dSTing-Kang Chang
104*e7b1675dSTing-Kang Chang	return fieldElement{lo: lo ^ (mid << 32), hi: hi ^ (mid >> 32)}
105*e7b1675dSTing-Kang Chang}
106*e7b1675dSTing-Kang Chang
107*e7b1675dSTing-Kang Chang// polyvalDot implements the dot operation defined by go/rfc/8452.
108*e7b1675dSTing-Kang Chang// dot(a, b) = a * b * x^-128.
109*e7b1675dSTing-Kang Chang// The value of the field element x^-128 is equal to x^127 + x^124 + x^121 + x^114 + 1.
110*e7b1675dSTing-Kang Chang// The result of this multiplication, dot(a, b), is another field element.
111*e7b1675dSTing-Kang Chang// The implementation here is inspired from BoringSSL's implementation of gcm_polyval_nohw().
112*e7b1675dSTing-Kang Chang// Ref: https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/modes/gcm_nohw.c
113*e7b1675dSTing-Kang Changfunc polyvalDot(a fieldElement, b fieldElement) fieldElement {
114*e7b1675dSTing-Kang Chang	// Karatsuba multiplication. The product of |a| and |b| is stored in |r0| and |r1|
115*e7b1675dSTing-Kang Chang	// Note there is no byte or bit reversal because we are evaluating POLYVAL.
116*e7b1675dSTing-Kang Chang	r0 := mul64(a.lo, b.lo)
117*e7b1675dSTing-Kang Chang	r1 := mul64(a.hi, b.hi)
118*e7b1675dSTing-Kang Chang
119*e7b1675dSTing-Kang Chang	mid := mul64(a.lo^a.hi, b.lo^b.hi)
120*e7b1675dSTing-Kang Chang	mid.lo ^= r0.lo ^ r1.lo
121*e7b1675dSTing-Kang Chang	mid.hi ^= r0.hi ^ r1.hi
122*e7b1675dSTing-Kang Chang
123*e7b1675dSTing-Kang Chang	r1.lo ^= mid.hi
124*e7b1675dSTing-Kang Chang	r0.hi ^= mid.lo
125*e7b1675dSTing-Kang Chang
126*e7b1675dSTing-Kang Chang	// Now we multiply our 256-bit result by x^-128 and reduce.
127*e7b1675dSTing-Kang Chang	// |r1| shifts into position and we must multiply |r0| by x^-128. We have:
128*e7b1675dSTing-Kang Chang	//
129*e7b1675dSTing-Kang Chang	//       1 = x^121 + x^126 + x^127 + x^128
130*e7b1675dSTing-Kang Chang	//  x^-128 = x^-7 + x^-2 + x^-1 + 1
131*e7b1675dSTing-Kang Chang	//
132*e7b1675dSTing-Kang Chang	// This is the GHASH reduction step, but with bits flowing in reverse.
133*e7b1675dSTing-Kang Chang	// The x^-7, x^-2, and x^-1 terms shift bits past x^0, which would require
134*e7b1675dSTing-Kang Chang	// another reduction steps. Instead, we gather the excess bits, incorporate
135*e7b1675dSTing-Kang Chang	// them into |r0| and reduce once.
136*e7b1675dSTing-Kang Chang	// Ref: slides 17-19 of https://crypto.stanford.edu/RealWorldCrypto/slides/gueron.pdf.
137*e7b1675dSTing-Kang Chang	r0.hi ^= (r0.lo << 63) ^ (r0.lo << 62) ^ (r0.lo << 57)
138*e7b1675dSTing-Kang Chang
139*e7b1675dSTing-Kang Chang	// 1
140*e7b1675dSTing-Kang Chang	r1.lo ^= r0.lo
141*e7b1675dSTing-Kang Chang	r1.hi ^= r0.hi
142*e7b1675dSTing-Kang Chang
143*e7b1675dSTing-Kang Chang	// x^-1
144*e7b1675dSTing-Kang Chang	r1.lo ^= r0.lo >> 1
145*e7b1675dSTing-Kang Chang	r1.lo ^= r0.hi << 63
146*e7b1675dSTing-Kang Chang	r1.hi ^= r0.hi >> 1
147*e7b1675dSTing-Kang Chang
148*e7b1675dSTing-Kang Chang	// x^-2
149*e7b1675dSTing-Kang Chang	r1.lo ^= r0.lo >> 2
150*e7b1675dSTing-Kang Chang	r1.lo ^= r0.hi << 62
151*e7b1675dSTing-Kang Chang	r1.hi ^= r0.hi >> 2
152*e7b1675dSTing-Kang Chang
153*e7b1675dSTing-Kang Chang	// x^-7
154*e7b1675dSTing-Kang Chang	r1.lo ^= r0.lo >> 7
155*e7b1675dSTing-Kang Chang	r1.lo ^= r0.hi << 57
156*e7b1675dSTing-Kang Chang	r1.hi ^= r0.hi >> 7
157*e7b1675dSTing-Kang Chang
158*e7b1675dSTing-Kang Chang	return r1
159*e7b1675dSTing-Kang Chang}
160*e7b1675dSTing-Kang Chang
161*e7b1675dSTing-Kang Chang// NewPolyval returns a Polyval instance.
162*e7b1675dSTing-Kang Changfunc NewPolyval(key []byte) (Polyval, error) {
163*e7b1675dSTing-Kang Chang	if len(key) != PolyvalBlockSize {
164*e7b1675dSTing-Kang Chang		return nil, fmt.Errorf("polyval: Invalid key size: %d", len(key))
165*e7b1675dSTing-Kang Chang	}
166*e7b1675dSTing-Kang Chang
167*e7b1675dSTing-Kang Chang	return &polyval{
168*e7b1675dSTing-Kang Chang		key: fieldElement{
169*e7b1675dSTing-Kang Chang			lo: binary.LittleEndian.Uint64(key[:8]),
170*e7b1675dSTing-Kang Chang			hi: binary.LittleEndian.Uint64(key[8:]),
171*e7b1675dSTing-Kang Chang		},
172*e7b1675dSTing-Kang Chang	}, nil
173*e7b1675dSTing-Kang Chang}
174*e7b1675dSTing-Kang Chang
175*e7b1675dSTing-Kang Changfunc (p *polyval) Update(data []byte) {
176*e7b1675dSTing-Kang Chang	var block []byte
177*e7b1675dSTing-Kang Chang	for len(data) > 0 {
178*e7b1675dSTing-Kang Chang		if len(data) >= PolyvalBlockSize {
179*e7b1675dSTing-Kang Chang			block = data[:PolyvalBlockSize]
180*e7b1675dSTing-Kang Chang			data = data[PolyvalBlockSize:]
181*e7b1675dSTing-Kang Chang		} else {
182*e7b1675dSTing-Kang Chang			var partialBlock [PolyvalBlockSize]byte
183*e7b1675dSTing-Kang Chang			copy(partialBlock[:], data)
184*e7b1675dSTing-Kang Chang			block = partialBlock[:]
185*e7b1675dSTing-Kang Chang			data = data[len(data):]
186*e7b1675dSTing-Kang Chang		}
187*e7b1675dSTing-Kang Chang
188*e7b1675dSTing-Kang Chang		p.acc.lo ^= binary.LittleEndian.Uint64(block[:8])
189*e7b1675dSTing-Kang Chang		p.acc.hi ^= binary.LittleEndian.Uint64(block[8:])
190*e7b1675dSTing-Kang Chang		p.acc = polyvalDot(p.acc, p.key)
191*e7b1675dSTing-Kang Chang	}
192*e7b1675dSTing-Kang Chang}
193*e7b1675dSTing-Kang Chang
194*e7b1675dSTing-Kang Changfunc (p *polyval) Finish() (hash [PolyvalBlockSize]byte) {
195*e7b1675dSTing-Kang Chang	binary.LittleEndian.PutUint64(hash[:8], p.acc.lo)
196*e7b1675dSTing-Kang Chang	binary.LittleEndian.PutUint64(hash[8:], p.acc.hi)
197*e7b1675dSTing-Kang Chang	return
198*e7b1675dSTing-Kang Chang}
199