// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package secp256k1

import (
	"math/big"
	"math/rand"
	"testing"
	"time"

	"lol.mleku.dev/chk"
)

// randBytes returns a byte slice of the required size created from a random
// value generated by the passed rng.
func randBytes(t *testing.T, rng *rand.Rand, numBytes uint8) []byte {
	t.Helper()

	buf := make([]byte, numBytes)
	if _, err := rng.Read(buf); chk.T(err) {
		t.Fatalf("failed to read random: %v", err)
	}

	return buf
}

// TestIsOnCurveAdaptor ensures the IsOnCurve method used to satisfy the
// elliptic.Curve interface works as intended.
func TestIsOnCurveAdaptor(t *testing.T) {
	s256 := S256()
	if !s256.IsOnCurve(s256.Params().Gx, s256.Params().Gy) {
		t.Fatal("generator point does not claim to be on the curve")
	}
}

// isValidAffinePoint returns true if the point (x,y) is on the secp256k1 curve
// or is the point at infinity.
func isValidAffinePoint(x, y *big.Int) bool {
	if x.Sign() == 0 && y.Sign() == 0 {
		return true
	}
	return S256().IsOnCurve(x, y)
}

// TestAddAffineAdaptor tests addition of points in affine coordinates via the
// method used to satisfy the elliptic.Curve interface works as intended for
// some edge cases and known good values.
func TestAddAffineAdaptor(t *testing.T) {
	tests := []struct {
		name   string // test description
		x1, y1 string // hex encoded coordinates of first point to add
		x2, y2 string // hex encoded coordinates of second point to add
		x3, y3 string // hex encoded coordinates of expected point
	}{
		{
			// Addition with the point at infinity (left hand side).
			name: "∞ + P = P",
			x1:   "0",
			y1:   "0",
			x2:   "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
			y2:   "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
			x3:   "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
			y3:   "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
		}, {
			// Addition with the point at infinity (right hand side).
			name: "P + ∞ = P",
			x1:   "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
			y1:   "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
			x2:   "0",
			y2:   "0",
			x3:   "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
			y3:   "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
		}, {
			// Addition with different x values.
			name: "P(x1, y1) + P(x2, y2)",
			x1:   "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
			y1:   "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
			x2:   "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
			y2:   "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
			x3:   "fd5b88c21d3143518d522cd2796f3d726793c88b3e05636bc829448e053fed69",
			y3:   "21cf4f6a5be5ff6380234c50424a970b1f7e718f5eb58f68198c108d642a137f",
		}, {
			// Addition with same x opposite y.
			name: "P(x, y) + P(x, -y) = ∞",
			x1:   "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
			y1:   "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
			x2:   "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
			y2:   "f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd",
			x3:   "0",
			y3:   "0",
		}, {
			// Addition with same point.
			name: "P(x, y) + P(x, y) = 2P",
			x1:   "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
			y1:   "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
			x2:   "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
			y2:   "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
			x3:   "59477d88ae64a104dbb8d31ec4ce2d91b2fe50fa628fb6a064e22582196b365b",
			y3:   "938dc8c0f13d1e75c987cb1a220501bd614b0d3dd9eb5c639847e1240216e3b6",
		},
	}
	curve := S256()
	for _, test := range tests {
		// Parse the test data.
		x1, y1 := fromHex(test.x1), fromHex(test.y1)
		x2, y2 := fromHex(test.x2), fromHex(test.y2)
		x3, y3 := fromHex(test.x3), fromHex(test.y3)
		// Ensure the test data is using points that are actually on the curve
		// (or the point at infinity).
		if !isValidAffinePoint(x1, y1) {
			t.Errorf("%s: first point is not on curve", test.name)
			continue
		}
		if !isValidAffinePoint(x2, y2) {
			t.Errorf("%s: second point is not on curve", test.name)
			continue
		}
		if !isValidAffinePoint(x3, y3) {
			t.Errorf("%s: expected point is not on curve", test.name)
			continue
		}
		// Add the two points and ensure the result matches expected.
		rx, ry := curve.Add(x1, y1, x2, y2)
		if rx.Cmp(x3) != 0 || ry.Cmp(y3) != 0 {
			t.Errorf(
				"%s: wrong result\ngot: (%x, %x)\nwant: (%x, %x)",
				test.name, rx, ry, x3, y3,
			)
			continue
		}
	}
}

// TestDoubleAffineAdaptor tests doubling of points in affine coordinates via
// the method used to satisfy the elliptic.Curve interface works as intended for
// some edge cases and known good values.
func TestDoubleAffineAdaptor(t *testing.T) {
	tests := []struct {
		name   string // test description
		x1, y1 string // hex encoded coordinates of point to double
		x3, y3 string // hex encoded coordinates of expected point
	}{
		{
			// Doubling the point at infinity is still the point at infinity.
			name: "2*∞ = ∞ (point at infinity)",
			x1:   "0",
			y1:   "0",
			x3:   "0",
			y3:   "0",
		}, {
			name: "random point 1",
			x1:   "e41387ffd8baaeeb43c2faa44e141b19790e8ac1f7ff43d480dc132230536f86",
			y1:   "1b88191d430f559896149c86cbcb703193105e3cf3213c0c3556399836a2b899",
			x3:   "88da47a089d333371bd798c548ef7caae76e737c1980b452d367b3cfe3082c19",
			y3:   "3b6f659b09a362821dfcfefdbfbc2e59b935ba081b6c249eb147b3c2100b1bc1",
		}, {
			name: "random point 2",
			x1:   "b3589b5d984f03ef7c80aeae444f919374799edf18d375cab10489a3009cff0c",
			y1:   "c26cf343875b3630e15bccc61202815b5d8f1fd11308934a584a5babe69db36a",
			x3:   "e193860172998751e527bb12563855602a227fc1f612523394da53b746bb2fb1",
			y3:   "2bfcf13d2f5ab8bb5c611fab5ebbed3dc2f057062b39a335224c22f090c04789",
		}, {
			name: "random point 3",
			x1:   "2b31a40fbebe3440d43ac28dba23eee71c62762c3fe3dbd88b4ab82dc6a82340",
			y1:   "9ba7deb02f5c010e217607fd49d58db78ec273371ea828b49891ce2fd74959a1",
			x3:   "2c8d5ef0d343b1a1a48aa336078eadda8481cb048d9305dc4fdf7ee5f65973a2",
			y3:   "bb4914ac729e26d3cd8f8dc8f702f3f4bb7e0e9c5ae43335f6e94c2de6c3dc95",
		}, {
			name: "random point 4",
			x1:   "61c64b760b51981fab54716d5078ab7dffc93730b1d1823477e27c51f6904c7a",
			y1:   "ef6eb16ea1a36af69d7f66524c75a3a5e84c13be8fbc2e811e0563c5405e49bd",
			x3:   "5f0dcdd2595f5ad83318a0f9da481039e36f135005420393e72dfca985b482f4",
			y3:   "a01c849b0837065c1cb481b0932c441f49d1cab1b4b9f355c35173d93f110ae0",
		},
	}
	curve := S256()
	for _, test := range tests {
		// Parse test data.
		x1, y1 := fromHex(test.x1), fromHex(test.y1)
		x3, y3 := fromHex(test.x3), fromHex(test.y3)
		// Ensure the test data is using points that are actually on
		// the curve (or the point at infinity).
		if !isValidAffinePoint(x1, y1) {
			t.Errorf("%s: first point is not on the curve", test.name)
			continue
		}
		if !isValidAffinePoint(x3, y3) {
			t.Errorf("%s: expected point is not on the curve", test.name)
			continue
		}
		// Double the point and ensure the result matches expected.
		rx, ry := curve.Double(x1, y1)
		if rx.Cmp(x3) != 0 || ry.Cmp(y3) != 0 {
			t.Errorf(
				"%s: wrong result\ngot: (%x, %x)\nwant: (%x, %x)",
				test.name, rx, ry, x3, y3,
			)
			continue
		}
	}
}

// TestScalarBaseMultAdaptor ensures the ScalarBaseMult method used to satisfy
// the elliptic.Curve interface works as intended for some edge cases and known
// good values.
func TestScalarBaseMultAdaptor(t *testing.T) {
	tests := []struct {
		name   string // test description
		k      string // hex encoded scalar
		rx, ry string // hex encoded coordinates of expected point
	}{
		{
			name: "zero",
			k:    "0000000000000000000000000000000000000000000000000000000000000000",
			rx:   "0000000000000000000000000000000000000000000000000000000000000000",
			ry:   "0000000000000000000000000000000000000000000000000000000000000000",
		}, {
			name: "one (aka 1*G = G)",
			k:    "0000000000000000000000000000000000000000000000000000000000000001",
			rx:   "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
			ry:   "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
		}, {
			name: "group order - 1 (aka -1*G = -G)",
			k:    "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
			rx:   "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
			ry:   "b7c52588d95c3b9aa25b0403f1eef75702e84bb7597aabe663b82f6f04ef2777",
		}, {
			name: "known good point 1",
			k:    "aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522",
			rx:   "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
			ry:   "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
		}, {
			name: "known good point 2",
			k:    "7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3",
			rx:   "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
			ry:   "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
		}, {
			name: "known good point 3",
			k:    "6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d",
			rx:   "e8aecc370aedd953483719a116711963ce201ac3eb21d3f3257bb48668c6a72f",
			ry:   "c25caf2f0eba1ddb2f0f3f47866299ef907867b7d27e95b3873bf98397b24ee1",
		}, {
			name: "known good point 4",
			k:    "376a3a2cdcd12581efff13ee4ad44c4044b8a0524c42422a7e1e181e4deeccec",
			rx:   "14890e61fcd4b0bd92e5b36c81372ca6fed471ef3aa60a3e415ee4fe987daba1",
			ry:   "297b858d9f752ab42d3bca67ee0eb6dcd1c2b7b0dbe23397e66adc272263f982",
		}, {
			name: "known good point 5",
			k:    "1b22644a7be026548810c378d0b2994eefa6d2b9881803cb02ceff865287d1b9",
			rx:   "f73c65ead01c5126f28f442d087689bfa08e12763e0cec1d35b01751fd735ed3",
			ry:   "f449a8376906482a84ed01479bd18882b919c140d638307f0c0934ba12590bde",
		},
	}
	curve := S256()
	for _, test := range tests {
		// Parse the test data.
		k := fromHex(test.k)
		xWant, yWant := fromHex(test.rx), fromHex(test.ry)
		// Ensure the test data is using points that are actually on the curve
		// (or the point at infinity).
		if !isValidAffinePoint(xWant, yWant) {
			t.Errorf("%s: expected point is not on curve", test.name)
			continue
		}
		rx, ry := curve.ScalarBaseMult(k.Bytes())
		if rx.Cmp(xWant) != 0 || ry.Cmp(yWant) != 0 {
			t.Errorf(
				"%s: wrong result:\ngot (%x, %x)\nwant (%x, %x)",
				test.name, rx, ry, xWant, yWant,
			)
		}
	}
}

// TestScalarBaseMultAdaptorRandom ensures that the ScalarBaseMult method used
// to satisfy the elliptic.Curve interface works as intended for
// randomly-generated scalars of all lengths up to 40 bytes.
func TestScalarBaseMultAdaptorRandom(t *testing.T) {
	// Use a unique random seed each test instance and log it if the tests fail.
	seed := time.Now().Unix()
	rng := rand.New(rand.NewSource(seed))
	defer func(t *testing.T, seed int64) {
		if t.Failed() {
			t.Logf("random seed: %d", seed)
		}
	}(t, seed)
	s256 := S256()
	const maxBytes = 40
	const iterations = 10
	for numBytes := uint8(1); numBytes < maxBytes; numBytes++ {
		for i := 0; i < iterations; i++ {
			// Generate a random scalar of the current length.
			k := randBytes(t, rng, numBytes)
			// Ensure the correct results by performing the multiplication with
			// both the func under test as well as the generic scalar mult func.
			x, y := s256.ScalarBaseMult(k)
			xWant, yWant := s256.ScalarMult(s256.Gx, s256.Gy, k)
			if x.Cmp(xWant) != 0 || y.Cmp(yWant) != 0 {
				t.Errorf(
					"bad output for %x: got (%x, %x), want (%x, %x)", k,
					x, y, xWant, yWant,
				)
				continue
			}
		}
	}
}

// TestScalarMultAdaptor ensures the ScalarMult method used to satisfy the
// elliptic.Curve interface works as intended for some edge cases and known good
// values.
func TestScalarMultAdaptor(t *testing.T) {
	tests := []struct {
		name   string // test description
		k      string // hex encoded scalar
		x, y   string // hex encoded coordinates of point to multiply
		rx, ry string // hex encoded coordinates of expected point
	}{
		{
			name: "0*P = ∞ (point at infinity)",
			k:    "0",
			x:    "7e660beda020e9cc20391cef85374576853b0f22b8925d5d81c5845bb834c21e",
			y:    "2d114a5edb320cc9806527d1daf1bbb96a8fedc6f9e8ead421eaef2c7208e409",
			rx:   "0",
			ry:   "0",
		}, {
			name: "1*P = P",
			k:    "1",
			x:    "c00be8830995d1e44f1420dd3b90d3441fb66f6861c84a35f959c495a3be5440",
			y:    "ecf9665e6eba45720de652a340600c7356efe24d228bfe6ea2043e7791c51bb7",
			rx:   "c00be8830995d1e44f1420dd3b90d3441fb66f6861c84a35f959c495a3be5440",
			ry:   "ecf9665e6eba45720de652a340600c7356efe24d228bfe6ea2043e7791c51bb7",
		}, {
			name: "(group order - 1)*P = -P (aka -1*P = -P)",
			k:    "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
			x:    "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9",
			y:    "cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08",
			rx:   "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9",
			ry:   "339ea810e73639c329e6c27c9ce4415ff6c1f6976bd173cc2a8c80276f1f2127",
		}, {
			name: "(group order - 1)*-P = P (aka -1*-P = -P, with P from prev test)",
			k:    "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
			x:    "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9",
			y:    "339ea810e73639c329e6c27c9ce4415ff6c1f6976bd173cc2a8c80276f1f2127",
			rx:   "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9",
			ry:   "cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08",
		}, {
			name: "known good point from base mult tests (aka k*G)",
			k:    "aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522",
			x:    "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
			y:    "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
			rx:   "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
			ry:   "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
		}, {
			name: "known good result 1",
			k:    "7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3",
			x:    "1697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5",
			y:    "b9c398f186806f5d27561506e4557433a2cf15009e498ae7adee9d63d01b2396",
			rx:   "6951f3b50aafbc63e21707dd53623b7f42badd633a0567ef1b37f6e42a4237ad",
			ry:   "9c930796a49110122fbfdedc36418af726197ed950b783a2d29058f8c02130de",
		}, {
			name: "known good result 2",
			k:    "6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d",
			x:    "659214ac1a1790023f53c4cf55a0a63b9e20c1151efa971215b395a558aa151",
			y:    "b126363aa4243d2759320a356230569a4eea355d9dabd94ed7f4590701e5364d",
			rx:   "4ffad856833396ef753c0bd4ea40319295f107c476793df0adac2caea53b3df4",
			ry:   "586fa6b1e9a3ff7df8a2b9b3698badcf40aa06af5600fefc56dd8ae4db5451c5",
		}, {
			name: "known good result 3",
			k:    "376a3a2cdcd12581efff13ee4ad44c4044b8a0524c42422a7e1e181e4deeccec",
			x:    "3f0e80e574456d8f8fa64e044b2eb72ea22eb53fe1efe3a443933aca7f8cb0e3",
			y:    "cb66d7d7296cbc91e90b9c08485d01b39501253aa65b53a4cb0289e2ea5f404f",
			rx:   "35ae6480b18e48070709d9276ed97a50c6ee1fc05ac44386c85826533233d28f",
			ry:   "f88abee3efabd95e80ce8c664bbc3d4d12b24e1a0f4d2b98ba6542789c6715fd",
		}, {
			name: "known good result 4",
			k:    "1b22644a7be026548810c378d0b2994eefa6d2b9881803cb02ceff865287d1b9",
			x:    "d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e",
			y:    "581e2872a86c72a683842ec228cc6defea40af2bd896d3a5c504dc9ff6a26b58",
			rx:   "cca7f9a4b0d379c31c438050e163a8945f2f910498bd3b545be20ed862bd6cd9",
			ry:   "cfc7bbf37bef62da6e5753ed419168fa1376a3fe949c139a8dd0f5303f4ae947",
		}, {
			name: "known good result 5",
			k:    "7f5b2cb4b43840c75e4afad83d792e1965d8c21c1109505f45c7d46df422d73e",
			x:    "bce74de6d5f98dc027740c2bbff05b6aafe5fd8d103f827e48894a2bd3460117",
			y:    "5bea1fa17a41b115525a3e7dbf0d8d5a4f7ce5c6fc73a6f4f216512417c9f6b4",
			rx:   "3d96b9290fe6c4f2d62fe2175f4333907d0c3637fada1010b45c7d80690e16de",
			ry:   "d59c0e8192d7fbd4846172d6479630b751cd03d0d9be0dca2759c6212b70575d",
		}, {
			// From btcd issue #709.
			name: "early implementation regression point",
			k:    "a2e8ba2e8ba2e8ba2e8ba2e8ba2e8ba219b51835b55cc30ebfe2f6599bc56f58",
			x:    "000000000000000000000000000000000000000000000000000000000000002c",
			y:    "420e7a99bba18a9d3952597510fd2b6728cfeafc21a4e73951091d4d8ddbe94e",
			rx:   "a2112dcdfbcd10ae1133a358de7b82db68e0a3eb4b492cc8268d1e7118c98788",
			ry:   "27fc7463b7bb3c5f98ecf2c84a6272bb1681ed553d92c69f2dfe25a9f9fd3836",
		},
	}
	curve := S256()
	for _, test := range tests {
		// Parse the test data.
		k := fromHex(test.k)
		x, y := fromHex(test.x), fromHex(test.y)
		xWant, yWant := fromHex(test.rx), fromHex(test.ry)
		// Ensure the test data is using points that are actually on the curve
		// (or the point at infinity).
		if !isValidAffinePoint(x, y) {
			t.Errorf("%s: point is not on curve", test.name)
			continue
		}
		if !isValidAffinePoint(xWant, yWant) {
			t.Errorf("%s: expected point is not on curve", test.name)
			continue
		}
		// Perform scalar point multiplication ensure the result matches
		// expected.
		rx, ry := curve.ScalarMult(x, y, k.Bytes())
		if rx.Cmp(xWant) != 0 || ry.Cmp(yWant) != 0 {
			t.Errorf(
				"%s: wrong result\ngot: (%x, %x)\nwant: (%x, %x)",
				test.name, rx, ry, xWant, yWant,
			)
		}
	}
}
