// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package testutil provides common methods needed in test code.
package testutil

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/elliptic"
	"crypto/rand"
	"encoding/gob"
	"errors"
	"fmt"
	"log"
	"math"
	"strconv"
	"strings"

	"google.golang.org/protobuf/proto"
	"github.com/tink-crypto/tink-go/v2/core/registry"
	subtledaead "github.com/tink-crypto/tink-go/v2/daead/subtle"
	"github.com/tink-crypto/tink-go/v2/keyset"
	"github.com/tink-crypto/tink-go/v2/mac"
	"github.com/tink-crypto/tink-go/v2/subtle/random"
	"github.com/tink-crypto/tink-go/v2/subtle"
	"github.com/tink-crypto/tink-go/v2/tink"

	cmacpb "github.com/tink-crypto/tink-go/v2/proto/aes_cmac_go_proto"
	aescmacprfpb "github.com/tink-crypto/tink-go/v2/proto/aes_cmac_prf_go_proto"
	ctrhmacpb "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_hmac_streaming_go_proto"
	gcmpb "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_go_proto"
	gcmhkdfpb "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_hkdf_streaming_go_proto"
	gcmsivpb "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_siv_go_proto"
	aspb "github.com/tink-crypto/tink-go/v2/proto/aes_siv_go_proto"
	commonpb "github.com/tink-crypto/tink-go/v2/proto/common_go_proto"
	ecdsapb "github.com/tink-crypto/tink-go/v2/proto/ecdsa_go_proto"
	eciespb "github.com/tink-crypto/tink-go/v2/proto/ecies_aead_hkdf_go_proto"
	ed25519pb "github.com/tink-crypto/tink-go/v2/proto/ed25519_go_proto"
	hkdfprfpb "github.com/tink-crypto/tink-go/v2/proto/hkdf_prf_go_proto"
	hmacpb "github.com/tink-crypto/tink-go/v2/proto/hmac_go_proto"
	hmacprfpb "github.com/tink-crypto/tink-go/v2/proto/hmac_prf_go_proto"
	tinkpb "github.com/tink-crypto/tink-go/v2/proto/tink_go_proto"
)

// DummyAEADKeyManager is a dummy implementation of the KeyManager interface.
// It returns DummyAEAD when GetPrimitive() functions are called.
type DummyAEADKeyManager struct{}

var _ registry.KeyManager = (*DummyAEADKeyManager)(nil)

// Primitive constructs a primitive instance for the key given in
// serializedKey, which must be a serialized key protocol buffer handled by this manager.
func (km *DummyAEADKeyManager) Primitive(serializedKey []byte) (any, error) {
	return new(DummyAEAD), nil
}

// NewKey generates a new key according to specification in serializedKeyFormat.
func (km *DummyAEADKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
	return nil, fmt.Errorf("not implemented")
}

// NewKeyData generates a new KeyData according to specification in serializedkeyFormat.
func (km *DummyAEADKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
	return nil, fmt.Errorf("not implemented")
}

// DoesSupport returns true iff this KeyManager supports key type identified by typeURL.
func (km *DummyAEADKeyManager) DoesSupport(typeURL string) bool {
	return typeURL == AESGCMTypeURL
}

// TypeURL returns the type URL.
func (km *DummyAEADKeyManager) TypeURL() string {
	return AESGCMTypeURL
}

// DummyAEAD is a dummy implementation of AEAD interface. It "encrypts" data
// with a simple serialization capturing the dummy name, plaintext, and
// associated data, and "decrypts" it by reversing this and checking that the
// name and associated data match.
type DummyAEAD struct {
	Name string
}

type dummyAEADData struct {
	Name           string
	Plaintext      []byte
	AssociatedData []byte
}

// Encrypt encrypts the plaintext.
func (a *DummyAEAD) Encrypt(plaintext []byte, associatedData []byte) ([]byte, error) {
	buf := new(bytes.Buffer)
	encoder := gob.NewEncoder(buf)
	err := encoder.Encode(dummyAEADData{
		Name:           a.Name,
		Plaintext:      plaintext,
		AssociatedData: associatedData,
	})
	if err != nil {
		return nil, fmt.Errorf("dummy aead encrypt: %v", err)
	}
	return buf.Bytes(), nil
}

// Decrypt decrypts the ciphertext.
func (a *DummyAEAD) Decrypt(ciphertext []byte, associatedData []byte) ([]byte, error) {
	data := dummyAEADData{}
	decoder := gob.NewDecoder(bytes.NewBuffer(ciphertext))
	if err := decoder.Decode(&data); err != nil {
		return nil, fmt.Errorf("dummy aead decrypt: invalid data: %v", err)
	}
	if data.Name != a.Name || !bytes.Equal(data.AssociatedData, associatedData) {
		return nil, errors.New("dummy aead encrypt: name/associated data mismatch")
	}
	return data.Plaintext, nil
}

// AlwaysFailingAead fails encryption and decryption operations.
type AlwaysFailingAead struct {
	Error error
}

var _ (tink.AEAD) = (*AlwaysFailingAead)(nil)

// NewAlwaysFailingAead creates a new always failing AEAD.
func NewAlwaysFailingAead(err error) tink.AEAD {
	return &AlwaysFailingAead{Error: err}
}

// Encrypt returns an error on encryption.
func (a *AlwaysFailingAead) Encrypt(plaintext []byte, associatedData []byte) ([]byte, error) {
	return nil, fmt.Errorf("AlwaysFailingAead will always fail on encryption: %v", a.Error)
}

// Decrypt returns an error on decryption.
func (a *AlwaysFailingAead) Decrypt(ciphertext []byte, associatedData []byte) ([]byte, error) {
	return nil, fmt.Errorf("AlwaysFailingAead will always fail on decryption: %v", a.Error)
}

// AlwaysFailingDeterministicAead fails encryption and decryption operations.
type AlwaysFailingDeterministicAead struct {
	Error error
}

var _ (tink.DeterministicAEAD) = (*AlwaysFailingDeterministicAead)(nil)

// NewAlwaysFailingDeterministicAead creates a new always failing AEAD.
func NewAlwaysFailingDeterministicAead(err error) tink.DeterministicAEAD {
	return &AlwaysFailingDeterministicAead{Error: err}
}

// EncryptDeterministically returns an error on encryption.
func (a *AlwaysFailingDeterministicAead) EncryptDeterministically(plaintext []byte, associatedData []byte) ([]byte, error) {
	return nil, fmt.Errorf("AlwaysFailingDeterministicAead will always fail on encryption: %v", a.Error)
}

// DecryptDeterministically returns an error on decryption.
func (a *AlwaysFailingDeterministicAead) DecryptDeterministically(ciphertext []byte, associatedData []byte) ([]byte, error) {
	return nil, fmt.Errorf("AlwaysFailingDeterministicAead will always fail on decryption: %v", a.Error)
}

// TestKeyManager is key manager which can be setup to return an arbitrary primitive for a type URL
// useful for testing.
type TestKeyManager struct {
	primitive any
	typeURL   string
}

var _ registry.KeyManager = (*TestKeyManager)(nil)

// NewTestKeyManager creates a new key manager that returns a specific primitive for a typeURL.
func NewTestKeyManager(primitive any, typeURL string) registry.KeyManager {
	return &TestKeyManager{
		primitive: primitive,
		typeURL:   typeURL,
	}
}

// Primitive constructs a primitive instance for the key given input key.
func (km *TestKeyManager) Primitive(serializedKey []byte) (any, error) {
	return km.primitive, nil
}

// NewKey generates a new key according to specification in serializedKeyFormat.
func (km *TestKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
	return nil, fmt.Errorf("TestKeyManager: not implemented")
}

// NewKeyData generates a new KeyData according to specification in serializedkeyFormat.
func (km *TestKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
	return nil, fmt.Errorf("TestKeyManager: not implemented")
}

// DoesSupport returns true if this KeyManager supports key type identified by typeURL.
func (km *TestKeyManager) DoesSupport(typeURL string) bool {
	return typeURL == km.typeURL
}

// TypeURL returns the type URL.
func (km *TestKeyManager) TypeURL() string {
	return km.typeURL
}

// DummySigner is a dummy implementation of the Signer interface.
type DummySigner struct {
	aead DummyAEAD
}

// NewDummySigner creates a new dummy signer with the specified name. The name
// is used to pair with the DummyVerifier.
func NewDummySigner(name string) *DummySigner {
	return &DummySigner{DummyAEAD{Name: "dummy public key:" + name}}
}

// Sign signs data.
func (s *DummySigner) Sign(data []byte) ([]byte, error) {
	return s.aead.Encrypt(nil, data)
}

// DummyVerifier is a dummy implementation of the Signer interface.
type DummyVerifier struct {
	aead DummyAEAD
}

// Verify verifies data.
func (v *DummyVerifier) Verify(sig, data []byte) error {
	_, err := v.aead.Decrypt(sig, data)
	return err
}

// NewDummyVerifier creates a new dummy verifier with the specified name. The
// name is used to pair with the DummySigner.
func NewDummyVerifier(name string) *DummyVerifier {
	return &DummyVerifier{DummyAEAD{Name: "dummy public key:" + name}}
}

// DummyMAC is a dummy implementation of Mac interface.
type DummyMAC struct {
	Name string
}

// ComputeMAC computes an insecure message authentication code (MAC) for data.
func (h *DummyMAC) ComputeMAC(data []byte) ([]byte, error) {
	return makeDummyMAC(data, h.Name), nil
}

func makeDummyMAC(data []byte, name string) []byte {
	m := make([]byte, 0, len(data)+len(name))
	m = append(m, data...)
	return append(m, name...)
}

// VerifyMAC verifies whether mac is a correct, although insecure, message
// authentication code (MAC) for data.
func (h *DummyMAC) VerifyMAC(mac []byte, data []byte) error {
	want := makeDummyMAC(data, h.Name)
	if bytes.Equal(mac, want) {
		return nil
	}
	// This is intended for test use. If it fails, describe what MAC would be
	// required to succeed.
	return fmt.Errorf("VerifyMAC: mac = %x, requires %x", mac, want)
}

// DummyKMSClient is a dummy implementation of a KMS Client.
type DummyKMSClient struct{}

var _ registry.KMSClient = (*DummyKMSClient)(nil)

// Supported true if this client does support keyURI
func (d *DummyKMSClient) Supported(keyURI string) bool {
	return keyURI == "dummy"
}

// GetAEAD gets an Aead backend by keyURI.
func (d *DummyKMSClient) GetAEAD(keyURI string) (tink.AEAD, error) {
	return &DummyAEAD{}, nil
}

// NewTestAESGCMKeyset creates a new Keyset containing an AESGCMKey.
func NewTestAESGCMKeyset(primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
	keyData := NewAESGCMKeyData(16)
	return NewTestKeyset(keyData, primaryOutputPrefixType)
}

// NewTestAESGCMSIVKeyset creates a new Keyset containing an AESGCMSIVKey.
func NewTestAESGCMSIVKeyset(primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
	keyData := NewAESGCMSIVKeyData(16)
	return NewTestKeyset(keyData, primaryOutputPrefixType)
}

// NewTestAESSIVKeyset creates a new Keyset containing an AesSivKey.
func NewTestAESSIVKeyset(primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
	keyValue := random.GetRandomBytes(subtledaead.AESSIVKeySize)
	key := &aspb.AesSivKey{
		Version:  AESSIVKeyVersion,
		KeyValue: keyValue,
	}
	serializedKey, err := proto.Marshal(key)
	if err != nil {
		log.Fatalf("failed serializing proto: %v", err)
	}
	keyData := NewKeyData(AESSIVTypeURL, serializedKey, tinkpb.KeyData_SYMMETRIC)
	return NewTestKeyset(keyData, primaryOutputPrefixType)
}

// NewTestHMACKeyset creates a new Keyset containing a HMACKey.
func NewTestHMACKeyset(tagSize uint32, primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
	keyData := NewHMACKeyData(commonpb.HashType_SHA256, tagSize)
	return NewTestKeyset(keyData, primaryOutputPrefixType)
}

// NewTestAESGCMHKDFKeyset creates a new Keyset containing an AESGCMHKDFKey.
func NewTestAESGCMHKDFKeyset() *tinkpb.Keyset {
	const (
		keySize               = 16
		derivedKeySize        = 16
		ciphertextSegmentSize = 4096
	)
	keyData := NewAESGCMHKDFKeyData(keySize, derivedKeySize, commonpb.HashType_SHA256, ciphertextSegmentSize)
	return NewTestKeyset(keyData, tinkpb.OutputPrefixType_RAW)
}

// NewTestKeyset creates a new test Keyset.
func NewTestKeyset(keyData *tinkpb.KeyData, primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
	primaryKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 42, primaryOutputPrefixType)
	rawKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 43, tinkpb.OutputPrefixType_RAW)
	legacyKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 44, tinkpb.OutputPrefixType_LEGACY)
	tinkKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 45, tinkpb.OutputPrefixType_TINK)
	crunchyKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 46, tinkpb.OutputPrefixType_CRUNCHY)
	keys := []*tinkpb.Keyset_Key{primaryKey, rawKey, legacyKey, tinkKey, crunchyKey}
	return NewKeyset(primaryKey.KeyId, keys)
}

// NewDummyKey returns a dummy key that doesn't contain actual key material.
func NewDummyKey(keyID int, status tinkpb.KeyStatusType, outputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset_Key {
	return &tinkpb.Keyset_Key{
		KeyData:          new(tinkpb.KeyData),
		Status:           status,
		KeyId:            uint32(keyID),
		OutputPrefixType: outputPrefixType,
	}
}

// NewECDSAParams creates a ECDSAParams with the specified parameters.
func NewECDSAParams(hashType commonpb.HashType, curve commonpb.EllipticCurveType, encoding ecdsapb.EcdsaSignatureEncoding) *ecdsapb.EcdsaParams {
	return &ecdsapb.EcdsaParams{
		HashType: hashType,
		Curve:    curve,
		Encoding: encoding,
	}
}

// NewECDSAKeyFormat creates a ECDSAKeyFormat with the specified parameters.
func NewECDSAKeyFormat(params *ecdsapb.EcdsaParams) *ecdsapb.EcdsaKeyFormat {
	return &ecdsapb.EcdsaKeyFormat{Params: params}
}

// NewECDSAPrivateKey creates a ECDSAPrivateKey with the specified paramaters.
func NewECDSAPrivateKey(version uint32, publicKey *ecdsapb.EcdsaPublicKey, keyValue []byte) *ecdsapb.EcdsaPrivateKey {
	return &ecdsapb.EcdsaPrivateKey{
		Version:   version,
		PublicKey: publicKey,
		KeyValue:  keyValue,
	}
}

// NewECDSAPublicKey creates a ECDSAPublicKey with the specified paramaters.
func NewECDSAPublicKey(version uint32, params *ecdsapb.EcdsaParams, x, y []byte) *ecdsapb.EcdsaPublicKey {
	return &ecdsapb.EcdsaPublicKey{
		Version: version,
		Params:  params,
		X:       x,
		Y:       y,
	}
}

// NewRandomECDSAPrivateKey creates an ECDSAPrivateKey with randomly generated key material.
func NewRandomECDSAPrivateKey(hashType commonpb.HashType, curve commonpb.EllipticCurveType) *ecdsapb.EcdsaPrivateKey {
	curveName := commonpb.EllipticCurveType_name[int32(curve)]
	priv, err := ecdsa.GenerateKey(subtle.GetCurve(curveName), rand.Reader)
	if err != nil {
		panic(fmt.Sprintf("ecdsa.GenerateKey() failed: %v", err))
	}
	params := NewECDSAParams(hashType, curve, ecdsapb.EcdsaSignatureEncoding_DER)
	publicKey := NewECDSAPublicKey(ECDSAVerifierKeyVersion, params, priv.X.Bytes(), priv.Y.Bytes())
	return NewECDSAPrivateKey(ECDSASignerKeyVersion, publicKey, priv.D.Bytes())
}

// NewRandomECDSAPublicKey creates an ECDSAPublicKey with randomly generated key material.
func NewRandomECDSAPublicKey(hashType commonpb.HashType, curve commonpb.EllipticCurveType) *ecdsapb.EcdsaPublicKey {
	return NewRandomECDSAPrivateKey(hashType, curve).PublicKey
}

// GetECDSAParamNames returns the string representations of each parameter in
// the given ECDSAParams.
func GetECDSAParamNames(params *ecdsapb.EcdsaParams) (string, string, string) {
	hashName := commonpb.HashType_name[int32(params.HashType)]
	curveName := commonpb.EllipticCurveType_name[int32(params.Curve)]
	encodingName := ecdsapb.EcdsaSignatureEncoding_name[int32(params.Encoding)]
	return hashName, curveName, encodingName
}

// NewED25519PrivateKey creates an ED25519PrivateKey with randomly generated key material.
func NewED25519PrivateKey() *ed25519pb.Ed25519PrivateKey {
	public, private, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(fmt.Sprintf("ed25519.GenerateKey() failed: %v", err))
	}
	publicProto := &ed25519pb.Ed25519PublicKey{
		Version:  ED25519SignerKeyVersion,
		KeyValue: public,
	}
	return &ed25519pb.Ed25519PrivateKey{
		Version:   ED25519SignerKeyVersion,
		PublicKey: publicProto,
		KeyValue:  private.Seed(),
	}
}

// NewED25519PublicKey creates an ED25519PublicKey with randomly generated key material.
func NewED25519PublicKey() *ed25519pb.Ed25519PublicKey {
	return NewED25519PrivateKey().PublicKey
}

// NewAESGCMKey creates a randomly generated AESGCMKey.
func NewAESGCMKey(keyVersion uint32, keySize uint32) *gcmpb.AesGcmKey {
	keyValue := random.GetRandomBytes(keySize)
	return &gcmpb.AesGcmKey{
		Version:  keyVersion,
		KeyValue: keyValue,
	}
}

// NewAESGCMKeyData creates a KeyData containing a randomly generated AESGCMKey.
func NewAESGCMKeyData(keySize uint32) *tinkpb.KeyData {
	serializedKey, err := proto.Marshal(NewAESGCMKey(AESGCMKeyVersion, keySize))
	if err != nil {
		log.Fatalf("failed serializing proto: %v", err)
	}
	return NewKeyData(AESGCMTypeURL, serializedKey, tinkpb.KeyData_SYMMETRIC)
}

// NewAESGCMKeyFormat returns a new AESGCMKeyFormat.
func NewAESGCMKeyFormat(keySize uint32) *gcmpb.AesGcmKeyFormat {
	return &gcmpb.AesGcmKeyFormat{
		KeySize: keySize,
	}
}

// NewAESGCMSIVKey creates a randomly generated AESGCMSIVKey.
func NewAESGCMSIVKey(keyVersion, keySize uint32) *gcmsivpb.AesGcmSivKey {
	keyValue := random.GetRandomBytes(keySize)
	return &gcmsivpb.AesGcmSivKey{
		Version:  keyVersion,
		KeyValue: keyValue,
	}
}

// NewAESGCMSIVKeyData creates a KeyData containing a randomly generated AESGCMSIVKey.
func NewAESGCMSIVKeyData(keySize uint32) *tinkpb.KeyData {
	serializedKey, err := proto.Marshal(NewAESGCMSIVKey(AESGCMKeyVersion, keySize))
	if err != nil {
		log.Fatalf("NewAESGCMSIVKeyData(keySize=%d): Failed serializing proto; err=%v", keySize, err)
	}
	return NewKeyData(AESGCMTypeURL, serializedKey, tinkpb.KeyData_SYMMETRIC)
}

// NewAESGCMSIVKeyFormat returns a new AESGCMKeyFormat.
func NewAESGCMSIVKeyFormat(keySize uint32) *gcmsivpb.AesGcmSivKeyFormat {
	return &gcmsivpb.AesGcmSivKeyFormat{
		KeySize: keySize,
	}
}

// NewAESGCMHKDFKey creates a randomly generated AESGCMHKDFKey.
func NewAESGCMHKDFKey(keyVersion, keySize, derivedKeySize uint32, hkdfHashType commonpb.HashType, ciphertextSegmentSize uint32) *gcmhkdfpb.AesGcmHkdfStreamingKey {
	keyValue := random.GetRandomBytes(keySize)
	return &gcmhkdfpb.AesGcmHkdfStreamingKey{
		Version:  keyVersion,
		KeyValue: keyValue,
		Params: &gcmhkdfpb.AesGcmHkdfStreamingParams{
			CiphertextSegmentSize: ciphertextSegmentSize,
			DerivedKeySize:        derivedKeySize,
			HkdfHashType:          hkdfHashType,
		},
	}
}

// NewAESGCMHKDFKeyData creates a KeyData containing a randomly generated AESGCMHKDFKey.
func NewAESGCMHKDFKeyData(keySize, derivedKeySize uint32, hkdfHashType commonpb.HashType, ciphertextSegmentSize uint32) *tinkpb.KeyData {
	serializedKey, err := proto.Marshal(NewAESGCMHKDFKey(AESGCMHKDFKeyVersion, keySize, derivedKeySize, hkdfHashType, ciphertextSegmentSize))
	if err != nil {
		log.Fatalf("failed serializing proto: %v", err)
	}
	return NewKeyData(AESGCMHKDFTypeURL, serializedKey, tinkpb.KeyData_SYMMETRIC)
}

// NewAESGCMHKDFKeyFormat returns a new AESGCMHKDFKeyFormat.
func NewAESGCMHKDFKeyFormat(keySize, derivedKeySize uint32, hkdfHashType commonpb.HashType, ciphertextSegmentSize uint32) *gcmhkdfpb.AesGcmHkdfStreamingKeyFormat {
	return &gcmhkdfpb.AesGcmHkdfStreamingKeyFormat{
		KeySize: keySize,
		Params: &gcmhkdfpb.AesGcmHkdfStreamingParams{
			CiphertextSegmentSize: ciphertextSegmentSize,
			DerivedKeySize:        derivedKeySize,
			HkdfHashType:          hkdfHashType,
		},
	}
}

// NewAESCTRHMACKey creates a randomly generated AESCTRHMACKey.
func NewAESCTRHMACKey(keyVersion, keySize uint32, hkdfHashType commonpb.HashType, derivedKeySize uint32, hashType commonpb.HashType, tagSize, ciphertextSegmentSize uint32) *ctrhmacpb.AesCtrHmacStreamingKey {
	keyValue := random.GetRandomBytes(keySize)
	return &ctrhmacpb.AesCtrHmacStreamingKey{
		Version:  keyVersion,
		KeyValue: keyValue,
		Params: &ctrhmacpb.AesCtrHmacStreamingParams{
			CiphertextSegmentSize: ciphertextSegmentSize,
			DerivedKeySize:        derivedKeySize,
			HkdfHashType:          hkdfHashType,
			HmacParams: &hmacpb.HmacParams{
				Hash:    hashType,
				TagSize: tagSize,
			},
		},
	}
}

// NewAESCTRHMACKeyFormat returns a new AESCTRHMACKeyFormat.
func NewAESCTRHMACKeyFormat(keySize uint32, hkdfHashType commonpb.HashType, derivedKeySize uint32, hashType commonpb.HashType, tagSize, ciphertextSegmentSize uint32) *ctrhmacpb.AesCtrHmacStreamingKeyFormat {
	return &ctrhmacpb.AesCtrHmacStreamingKeyFormat{
		KeySize: keySize,
		Params: &ctrhmacpb.AesCtrHmacStreamingParams{
			CiphertextSegmentSize: ciphertextSegmentSize,
			DerivedKeySize:        derivedKeySize,
			HkdfHashType:          hkdfHashType,
			HmacParams: &hmacpb.HmacParams{
				Hash:    hashType,
				TagSize: tagSize,
			},
		},
	}
}

// NewHMACParams returns a new HMACParams.
func NewHMACParams(hashType commonpb.HashType, tagSize uint32) *hmacpb.HmacParams {
	return &hmacpb.HmacParams{
		Hash:    hashType,
		TagSize: tagSize,
	}
}

// NewHMACKey creates a new HMACKey with the specified parameters.
func NewHMACKey(hashType commonpb.HashType, tagSize uint32) *hmacpb.HmacKey {
	params := NewHMACParams(hashType, tagSize)
	keyValue := random.GetRandomBytes(20)
	return &hmacpb.HmacKey{
		Version:  HMACKeyVersion,
		Params:   params,
		KeyValue: keyValue,
	}
}

// NewHMACKeyFormat creates a new HMACKeyFormat with the specified parameters.
func NewHMACKeyFormat(hashType commonpb.HashType, tagSize uint32) *hmacpb.HmacKeyFormat {
	params := NewHMACParams(hashType, tagSize)
	keySize := uint32(20)
	return &hmacpb.HmacKeyFormat{
		Params:  params,
		KeySize: keySize,
	}
}

// NewAESCMACParams returns a new AESCMACParams.
func NewAESCMACParams(tagSize uint32) *cmacpb.AesCmacParams {
	return &cmacpb.AesCmacParams{
		TagSize: tagSize,
	}
}

// NewAESCMACKey creates a new AESCMACKey with the specified parameters.
func NewAESCMACKey(tagSize uint32) *cmacpb.AesCmacKey {
	params := NewAESCMACParams(tagSize)
	keyValue := random.GetRandomBytes(32)
	return &cmacpb.AesCmacKey{
		Version:  AESCMACKeyVersion,
		Params:   params,
		KeyValue: keyValue,
	}
}

// NewAESCMACKeyFormat creates a new AESCMACKeyFormat with the specified parameters.
func NewAESCMACKeyFormat(tagSize uint32) *cmacpb.AesCmacKeyFormat {
	params := NewAESCMACParams(tagSize)
	keySize := uint32(32)
	return &cmacpb.AesCmacKeyFormat{
		Params:  params,
		KeySize: keySize,
	}
}

// NewHMACKeysetManager returns a new KeysetManager that contains a HMACKey.
func NewHMACKeysetManager() *keyset.Manager {
	ksm := keyset.NewManager()
	kt := mac.HMACSHA256Tag128KeyTemplate()
	keyID, err := ksm.Add(kt)
	if err != nil {
		panic(fmt.Sprintf("cannot add key: %v", err))
	}
	err = ksm.SetPrimary(keyID)
	if err != nil {
		panic(fmt.Sprintf("cannot set primary key: %v", err))
	}
	return ksm
}

// NewHMACKeyData returns a new KeyData that contains a HMACKey.
func NewHMACKeyData(hashType commonpb.HashType, tagSize uint32) *tinkpb.KeyData {
	key := NewHMACKey(hashType, tagSize)
	serializedKey, err := proto.Marshal(key)
	if err != nil {
		log.Fatalf("failed serializing proto: %v", err)
	}
	return &tinkpb.KeyData{
		TypeUrl:         HMACTypeURL,
		Value:           serializedKey,
		KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
	}
}

// NewHMACPRFParams returns a new HMACPRFParams.
func NewHMACPRFParams(hashType commonpb.HashType) *hmacprfpb.HmacPrfParams {
	return &hmacprfpb.HmacPrfParams{
		Hash: hashType,
	}
}

// NewHMACPRFKey creates a new HMACPRFKey with the specified parameters.
func NewHMACPRFKey(hashType commonpb.HashType) *hmacprfpb.HmacPrfKey {
	params := NewHMACPRFParams(hashType)
	keyValue := random.GetRandomBytes(32)
	return &hmacprfpb.HmacPrfKey{
		Version:  HMACPRFKeyVersion,
		Params:   params,
		KeyValue: keyValue,
	}
}

// NewHMACPRFKeyFormat creates a new HMACPRFKeyFormat with the specified parameters.
func NewHMACPRFKeyFormat(hashType commonpb.HashType) *hmacprfpb.HmacPrfKeyFormat {
	params := NewHMACPRFParams(hashType)
	keySize := uint32(32)
	return &hmacprfpb.HmacPrfKeyFormat{
		Params:  params,
		KeySize: keySize,
	}
}

// NewHKDFPRFParams returns a new HKDFPRFParams.
func NewHKDFPRFParams(hashType commonpb.HashType, salt []byte) *hkdfprfpb.HkdfPrfParams {
	return &hkdfprfpb.HkdfPrfParams{
		Hash: hashType,
		Salt: salt,
	}
}

// NewHKDFPRFKey creates a new HKDFPRFKey with the specified parameters.
func NewHKDFPRFKey(hashType commonpb.HashType, salt []byte) *hkdfprfpb.HkdfPrfKey {
	params := NewHKDFPRFParams(hashType, salt)
	keyValue := random.GetRandomBytes(32)
	return &hkdfprfpb.HkdfPrfKey{
		Version:  HKDFPRFKeyVersion,
		Params:   params,
		KeyValue: keyValue,
	}
}

// NewHKDFPRFKeyFormat creates a new HKDFPRFKeyFormat with the specified parameters.
func NewHKDFPRFKeyFormat(hashType commonpb.HashType, salt []byte) *hkdfprfpb.HkdfPrfKeyFormat {
	params := NewHKDFPRFParams(hashType, salt)
	keySize := uint32(32)
	return &hkdfprfpb.HkdfPrfKeyFormat{
		Params:  params,
		KeySize: keySize,
	}
}

// NewAESCMACPRFKey creates a new AESCMACPRFKey with the specified parameters.
func NewAESCMACPRFKey() *aescmacprfpb.AesCmacPrfKey {
	keyValue := random.GetRandomBytes(32)
	return &aescmacprfpb.AesCmacPrfKey{
		Version:  AESCMACPRFKeyVersion,
		KeyValue: keyValue,
	}
}

// NewAESCMACPRFKeyFormat creates a new AESCMACPRFKeyFormat with the specified parameters.
func NewAESCMACPRFKeyFormat() *aescmacprfpb.AesCmacPrfKeyFormat {
	keySize := uint32(32)
	return &aescmacprfpb.AesCmacPrfKeyFormat{
		KeySize: keySize,
	}
}

// NewKeyData creates a new KeyData with the specified parameters.
func NewKeyData(typeURL string, value []byte, materialType tinkpb.KeyData_KeyMaterialType) *tinkpb.KeyData {
	return &tinkpb.KeyData{
		TypeUrl:         typeURL,
		Value:           value,
		KeyMaterialType: materialType,
	}
}

// NewKey creates a new Key with the specified parameters.
func NewKey(keyData *tinkpb.KeyData, status tinkpb.KeyStatusType, keyID uint32, prefixType tinkpb.OutputPrefixType) *tinkpb.Keyset_Key {
	return &tinkpb.Keyset_Key{
		KeyData:          keyData,
		Status:           status,
		KeyId:            keyID,
		OutputPrefixType: prefixType,
	}
}

// NewKeyset creates a new Keyset with the specified parameters.
func NewKeyset(primaryKeyID uint32, keys []*tinkpb.Keyset_Key) *tinkpb.Keyset {
	return &tinkpb.Keyset{
		PrimaryKeyId: primaryKeyID,
		Key:          keys,
	}
}

// GenerateMutations generates different byte mutations for a given byte array.
func GenerateMutations(src []byte) (all [][]byte) {
	// Flip bits
	for i := 0; i < len(src); i++ {
		for j := 0; j < 8; j++ {
			n := make([]byte, len(src))
			copy(n, src)
			n[i] = n[i] ^ (1 << uint8(j))
			all = append(all, n)
		}
	}

	// Truncate bytes
	for i := 1; i < len(src); i++ {
		n := make([]byte, len(src[i:]))
		copy(n, src[i:])
		all = append(all, n)
	}

	// Append extra byte
	m := make([]byte, len(src)+1)
	copy(m, src)
	all = append(all, m)
	return
}

// ZTestUniformString uses a z test on the given byte string, expecting all
// bits to be uniformly set with probability 1/2. Returns non ok status if the
// z test fails by more than 10 standard deviations.
//
// With less statistics jargon: This counts the number of bits set and expects
// the number to be roughly half of the length of the string. The law of large
// numbers suggests that we can assume that the longer the string is, the more
// accurate that estimate becomes for a random string. This test is useful to
// detect things like strings that are entirely zero.
//
// Note: By itself, this is a very weak test for randomness.
func ZTestUniformString(bytes []byte) error {
	expected := float64(len(bytes)) * 8.0 / 2.0
	stddev := math.Sqrt(float64(len(bytes)) * 8.0 / 4.0)
	numSetBits := int64(0)
	for _, b := range bytes {
		// Counting the number of bits set in byte:
		for b != 0 {
			numSetBits++
			b = b & (b - 1)
		}
	}
	// Check that the number of bits is within 10 stddevs.
	if math.Abs(float64(numSetBits)-expected) < 10.0*stddev {
		return nil
	}
	return fmt.Errorf("Z test for uniformly distributed variable out of bounds; "+
		"Actual number of set bits was %d expected was %0.00f, 10 * standard deviation is 10 * %0.00f = %0.00f",
		numSetBits, expected, stddev, 10.0*stddev)
}

func rotate(bytes []byte) []byte {
	result := make([]byte, len(bytes))
	for i := 0; i < len(bytes); i++ {
		prev := i
		if i == 0 {
			prev = len(bytes)
		}
		result[i] = (bytes[i] >> 1) |
			(bytes[prev-1] << 7)
	}
	return result
}

// ZTestCrosscorrelationUniformStrings tests that the crosscorrelation of two
// strings of equal length points to independent and uniformly distributed
// strings. Returns non ok status if the z test fails by more than 10 standard
// deviations.
//
// With less statistics jargon: This xors two strings and then performs the
// ZTestUniformString on the result. If the two strings are independent and
// uniformly distributed, the xor'ed string is as well. A cross correlation test
// will find whether two strings overlap more or less than it would be expected.
//
// Note: Having a correlation of zero is only a necessary but not sufficient
// condition for independence.
func ZTestCrosscorrelationUniformStrings(bytes1, bytes2 []byte) error {
	if len(bytes1) != len(bytes2) {
		return fmt.Errorf(
			"Strings are not of equal length")
	}
	crossed := make([]byte, len(bytes1))
	for i := 0; i < len(bytes1); i++ {
		crossed[i] = bytes1[i] ^ bytes2[i]
	}
	return ZTestUniformString(crossed)
}

// ZTestAutocorrelationUniformString tests that the autocorrelation of a string
// points to the bits being independent and uniformly distributed.
// Rotates the string in a cyclic fashion. Returns non ok status if the z test
// fails by more than 10 standard deviations.
//
// With less statistics jargon: This rotates the string bit by bit and performs
// ZTestCrosscorrelationUniformStrings on each of the rotated strings and the
// original. This will find self similarity of the input string, especially
// periodic self similarity. For example, it is a decent test to find English
// text (needs about 180 characters with the current settings).
//
// Note: Having a correlation of zero is only a necessary but not sufficient
// condition for independence.
func ZTestAutocorrelationUniformString(bytes []byte) error {
	rotated := make([]byte, len(bytes))
	copy(rotated, bytes)
	violations := []string{}
	for i := 1; i < len(bytes)*8; i++ {
		rotated = rotate(rotated)
		err := ZTestCrosscorrelationUniformStrings(bytes, rotated)
		if err != nil {
			violations = append(violations, strconv.Itoa(i))
		}
	}
	if len(violations) == 0 {
		return nil
	}
	return fmt.Errorf("Autocorrelation exceeded 10 standard deviation at %d indices: %s", len(violations), strings.Join(violations, ", "))
}

// eciesAEADHKDFPublicKey returns a EciesAeadHkdfPublicKey with specified parameters.
func eciesAEADHKDFPublicKey(c commonpb.EllipticCurveType, ht commonpb.HashType, ptfmt commonpb.EcPointFormat, dekT *tinkpb.KeyTemplate, x, y, salt []byte) *eciespb.EciesAeadHkdfPublicKey {
	return &eciespb.EciesAeadHkdfPublicKey{
		Version: 0,
		Params: &eciespb.EciesAeadHkdfParams{
			KemParams: &eciespb.EciesHkdfKemParams{
				CurveType:    c,
				HkdfHashType: ht,
				HkdfSalt:     salt,
			},
			DemParams: &eciespb.EciesAeadDemParams{
				AeadDem: dekT,
			},
			EcPointFormat: ptfmt,
		},
		X: x,
		Y: y,
	}
}

// eciesAEADHKDFPrivateKey returns a EciesAeadHkdfPrivateKey with specified parameters
func eciesAEADHKDFPrivateKey(p *eciespb.EciesAeadHkdfPublicKey, d []byte) *eciespb.EciesAeadHkdfPrivateKey {
	return &eciespb.EciesAeadHkdfPrivateKey{
		Version:   0,
		PublicKey: p,
		KeyValue:  d,
	}
}

// curveFromProtoEnum returns the [elliptic.Curve] for a given
// [commonpb.EllipticCurveType].
func curveFromProtoEnum(c commonpb.EllipticCurveType) (elliptic.Curve, error) {
	switch c {
	case commonpb.EllipticCurveType_NIST_P256:
		return elliptic.P256(), nil
	case commonpb.EllipticCurveType_NIST_P384:
		return elliptic.P384(), nil
	case commonpb.EllipticCurveType_NIST_P521:
		return elliptic.P521(), nil
	default:
		return nil, errors.New("unsupported curve")
	}
}

// GenerateECIESAEADHKDFPrivateKey generates a new EC key pair and returns the private key proto.
func GenerateECIESAEADHKDFPrivateKey(c commonpb.EllipticCurveType, ht commonpb.HashType, ptfmt commonpb.EcPointFormat, dekT *tinkpb.KeyTemplate, salt []byte) (*eciespb.EciesAeadHkdfPrivateKey, error) {
	curve, err := curveFromProtoEnum(c)
	if err != nil {
		return nil, err
	}
	d, x, y, err := elliptic.GenerateKey(curve, rand.Reader)
	if err != nil {
		return nil, err
	}
	pubKey := eciesAEADHKDFPublicKey(c, ht, ptfmt, dekT, x.Bytes(), y.Bytes(), salt)
	return eciesAEADHKDFPrivateKey(pubKey, d), nil
}
