pubkey_test.go raw

   1  // Copyright (c) 2013-2016 The btcsuite developers
   2  // Copyright (c) 2015-2020 The Decred developers
   3  // Use of this source code is governed by an ISC
   4  // license that can be found in the LICENSE file.
   5  
   6  package secp256k1
   7  
   8  import (
   9  	"errors"
  10  	"testing"
  11  
  12  	"next.orly.dev/pkg/nostr/utils"
  13  )
  14  
  15  // TestParsePubKey ensures that public keys are properly parsed according
  16  // to the spec including both the positive and negative cases.
  17  func TestParsePubKey(t *testing.T) {
  18  	tests := []struct {
  19  		name  string // test description
  20  		key   string // hex encoded public key
  21  		err   error  // expected error
  22  		wantX string // expected x coordinate
  23  		wantY string // expected y coordinate
  24  	}{
  25  		{
  26  			name: "uncompressed ok",
  27  			key: "04" +
  28  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
  29  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
  30  			err:   nil,
  31  			wantX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
  32  			wantY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
  33  		}, {
  34  			name: "uncompressed x changed (not on curve)",
  35  			key: "04" +
  36  				"15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
  37  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
  38  			err: ErrPubKeyNotOnCurve,
  39  		}, {
  40  			name: "uncompressed y changed (not on curve)",
  41  			key: "04" +
  42  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
  43  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4",
  44  			err: ErrPubKeyNotOnCurve,
  45  		}, {
  46  			name: "uncompressed claims compressed",
  47  			key: "03" +
  48  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
  49  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
  50  			err: ErrPubKeyInvalidFormat,
  51  		}, {
  52  			name: "uncompressed as hybrid ok (ybit = 0)",
  53  			key: "06" +
  54  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
  55  				"4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
  56  			err:   nil,
  57  			wantX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
  58  			wantY: "4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
  59  		}, {
  60  			name: "uncompressed as hybrid ok (ybit = 1)",
  61  			key: "07" +
  62  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
  63  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
  64  			err:   nil,
  65  			wantX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
  66  			wantY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
  67  		}, {
  68  			name: "uncompressed as hybrid wrong oddness",
  69  			key: "06" +
  70  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
  71  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
  72  			err: ErrPubKeyMismatchedOddness,
  73  		}, {
  74  			name: "compressed ok (ybit = 0)",
  75  			key: "02" +
  76  				"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
  77  			err:   nil,
  78  			wantX: "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
  79  			wantY: "0890ff84d7999d878a57bee170e19ef4b4803b4bdede64503a6ac352b03c8032",
  80  		}, {
  81  			name: "compressed ok (ybit = 1)",
  82  			key: "03" +
  83  				"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
  84  			err:   nil,
  85  			wantX: "2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
  86  			wantY: "499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f",
  87  		}, {
  88  			name: "compressed claims uncompressed (ybit = 0)",
  89  			key: "04" +
  90  				"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
  91  			err: ErrPubKeyInvalidFormat,
  92  		}, {
  93  			name: "compressed claims uncompressed (ybit = 1)",
  94  			key: "04" +
  95  				"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
  96  			err: ErrPubKeyInvalidFormat,
  97  		}, {
  98  			name: "compressed claims hybrid (ybit = 0)",
  99  			key: "06" +
 100  				"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
 101  			err: ErrPubKeyInvalidFormat,
 102  		}, {
 103  			name: "compressed claims hybrid (ybit = 1)",
 104  			key: "07" +
 105  				"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
 106  			err: ErrPubKeyInvalidFormat,
 107  		}, {
 108  			name: "compressed with invalid x coord (ybit = 0)",
 109  			key: "03" +
 110  				"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4c",
 111  			err: ErrPubKeyNotOnCurve,
 112  		}, {
 113  			name: "compressed with invalid x coord (ybit = 1)",
 114  			key: "03" +
 115  				"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448d",
 116  			err: ErrPubKeyNotOnCurve,
 117  		}, {
 118  			name: "empty",
 119  			key:  "",
 120  			err:  ErrPubKeyInvalidLen,
 121  		}, {
 122  			name: "wrong length",
 123  			key:  "05",
 124  			err:  ErrPubKeyInvalidLen,
 125  		}, {
 126  			name: "uncompressed x == p",
 127  			key: "04" +
 128  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" +
 129  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
 130  			err: ErrPubKeyXTooBig,
 131  		}, {
 132  			// The y coordinate produces a valid point for x == 1 (mod p), but it
 133  			// should fail to parse instead of wrapping around.
 134  			name: "uncompressed x > p (p + 1 -- aka 1)",
 135  			key: "04" +
 136  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30" +
 137  				"bde70df51939b94c9c24979fa7dd04ebd9b3572da7802290438af2a681895441",
 138  			err: ErrPubKeyXTooBig,
 139  		}, {
 140  			name: "uncompressed y == p",
 141  			key: "04" +
 142  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
 143  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
 144  			err: ErrPubKeyYTooBig,
 145  		}, {
 146  			// The x coordinate produces a valid point for y == 1 (mod p), but it
 147  			// should fail to parse instead of wrapping around.
 148  			name: "uncompressed y > p (p + 1 -- aka 1)",
 149  			key: "04" +
 150  				"1fe1e5ef3fceb5c135ab7741333ce5a6e80d68167653f6b2b24bcbcfaaaff507" +
 151  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30",
 152  			err: ErrPubKeyYTooBig,
 153  		}, {
 154  			name: "compressed x == p (ybit = 0)",
 155  			key: "02" +
 156  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
 157  			err: ErrPubKeyXTooBig,
 158  		}, {
 159  			name: "compressed x == p (ybit = 1)",
 160  			key: "03" +
 161  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
 162  			err: ErrPubKeyXTooBig,
 163  		}, {
 164  			// This would be valid for x == 2 (mod p), but it should fail to parse
 165  			// instead of wrapping around.
 166  			name: "compressed x > p (p + 2 -- aka 2) (ybit = 0)",
 167  			key: "02" +
 168  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc31",
 169  			err: ErrPubKeyXTooBig,
 170  		}, {
 171  			// This would be valid for x == 1 (mod p), but it should fail to parse
 172  			// instead of wrapping around.
 173  			name: "compressed x > p (p + 1 -- aka 1) (ybit = 1)",
 174  			key: "03" +
 175  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30",
 176  			err: ErrPubKeyXTooBig,
 177  		}, {
 178  			name: "hybrid x == p (ybit = 1)",
 179  			key: "07" +
 180  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" +
 181  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
 182  			err: ErrPubKeyXTooBig,
 183  		}, {
 184  			// The y coordinate produces a valid point for x == 1 (mod p), but it
 185  			// should fail to parse instead of wrapping around.
 186  			name: "hybrid x > p (p + 1 -- aka 1) (ybit = 0)",
 187  			key: "06" +
 188  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30" +
 189  				"bde70df51939b94c9c24979fa7dd04ebd9b3572da7802290438af2a681895441",
 190  			err: ErrPubKeyXTooBig,
 191  		}, {
 192  			name: "hybrid y == p (ybit = 0 when mod p)",
 193  			key: "06" +
 194  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
 195  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
 196  			err: ErrPubKeyYTooBig,
 197  		}, {
 198  			// The x coordinate produces a valid point for y == 1 (mod p), but it
 199  			// should fail to parse instead of wrapping around.
 200  			name: "hybrid y > p (p + 1 -- aka 1) (ybit = 1 when mod p)",
 201  			key: "07" +
 202  				"1fe1e5ef3fceb5c135ab7741333ce5a6e80d68167653f6b2b24bcbcfaaaff507" +
 203  				"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30",
 204  			err: ErrPubKeyYTooBig,
 205  		},
 206  	}
 207  	for _, test := range tests {
 208  		pubKeyBytes := hexToBytes(test.key)
 209  		pubKey, err := ParsePubKey(pubKeyBytes)
 210  		if !errors.Is(err, test.err) {
 211  			t.Errorf(
 212  				"%s mismatched e -- got %v, want %v", test.name, err,
 213  				test.err,
 214  			)
 215  			continue
 216  		}
 217  		if err != nil {
 218  			continue
 219  		}
 220  		// Ensure the x and y coordinates match the expected values upon
 221  		// successful parse.
 222  		wantX, wantY := hexToFieldVal(test.wantX), hexToFieldVal(test.wantY)
 223  		if !pubKey.x.Equals(wantX) {
 224  			t.Errorf(
 225  				"%s: mismatched x coordinate -- got %v, want %v",
 226  				test.name, pubKey.x, wantX,
 227  			)
 228  			continue
 229  		}
 230  		if !pubKey.y.Equals(wantY) {
 231  			t.Errorf(
 232  				"%s: mismatched y coordinate -- got %v, want %v",
 233  				test.name, pubKey.y, wantY,
 234  			)
 235  			continue
 236  		}
 237  	}
 238  }
 239  
 240  // TestPubKeySerialize ensures that serializing public keys works as expected
 241  // for both the compressed and uncompressed cases.
 242  func TestPubKeySerialize(t *testing.T) {
 243  	tests := []struct {
 244  		name     string // test description
 245  		pubX     string // hex encoded x coordinate for pubkey to serialize
 246  		pubY     string // hex encoded y coordinate for pubkey to serialize
 247  		compress bool   // whether to serialize compressed or uncompressed
 248  		expected string // hex encoded expected pubkey serialization
 249  	}{
 250  		{
 251  			name:     "uncompressed (ybit = 0)",
 252  			pubX:     "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
 253  			pubY:     "4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
 254  			compress: false,
 255  			expected: "04" +
 256  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
 257  				"4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
 258  		}, {
 259  			name:     "uncompressed (ybit = 1)",
 260  			pubX:     "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
 261  			pubY:     "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
 262  			compress: false,
 263  			expected: "04" +
 264  				"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
 265  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
 266  		}, {
 267  			// It's invalid to parse pubkeys that are not on the curve, however it
 268  			// is possible to manually create them and they should serialize
 269  			// correctly.
 270  			name:     "uncompressed not on the curve due to x coord",
 271  			pubX:     "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
 272  			pubY:     "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
 273  			compress: false,
 274  			expected: "04" +
 275  				"15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
 276  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
 277  		}, {
 278  			// It's invalid to parse pubkeys that are not on the curve, however it
 279  			// is possible to manually create them and they should serialize
 280  			// correctly.
 281  			name:     "uncompressed not on the curve due to y coord",
 282  			pubX:     "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
 283  			pubY:     "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4",
 284  			compress: false,
 285  			expected: "04" +
 286  				"15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
 287  				"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4",
 288  		}, {
 289  			name:     "compressed (ybit = 0)",
 290  			pubX:     "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
 291  			pubY:     "0890ff84d7999d878a57bee170e19ef4b4803b4bdede64503a6ac352b03c8032",
 292  			compress: true,
 293  			expected: "02" +
 294  				"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
 295  		}, {
 296  			name:     "compressed (ybit = 1)",
 297  			pubX:     "2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
 298  			pubY:     "499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f",
 299  			compress: true,
 300  			expected: "03" +
 301  				"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
 302  		}, {
 303  			// It's invalid to parse pubkeys that are not on the curve, however it
 304  			// is possible to manually create them and they should serialize
 305  			// correctly.
 306  			name:     "compressed not on curve (ybit = 0)",
 307  			pubX:     "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4c",
 308  			pubY:     "0890ff84d7999d878a57bee170e19ef4b4803b4bdede64503a6ac352b03c8032",
 309  			compress: true,
 310  			expected: "02" +
 311  				"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4c",
 312  		}, {
 313  			// It's invalid to parse pubkeys that are not on the curve, however it
 314  			// is possible to manually create them and they should serialize
 315  			// correctly.
 316  			name:     "compressed not on curve (ybit = 1)",
 317  			pubX:     "2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448d",
 318  			pubY:     "499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f",
 319  			compress: true,
 320  			expected: "03" +
 321  				"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448d",
 322  		},
 323  	}
 324  	for _, test := range tests {
 325  		// Parse the test data.
 326  		x, y := hexToFieldVal(test.pubX), hexToFieldVal(test.pubY)
 327  		pubKey := NewPublicKey(x, y)
 328  		// Serialize with the correct method and ensure the result matches the
 329  		// expected value.
 330  		var serialized []byte
 331  		if test.compress {
 332  			serialized = pubKey.SerializeCompressed()
 333  		} else {
 334  			serialized = pubKey.SerializeUncompressed()
 335  		}
 336  		expected := hexToBytes(test.expected)
 337  		if !utils.FastEqual(serialized, expected) {
 338  			t.Errorf(
 339  				"%s: mismatched serialized public key -- got %x, want %x",
 340  				test.name, serialized, expected,
 341  			)
 342  			continue
 343  		}
 344  	}
 345  }
 346  
 347  // TestPublicKeyIsEqual ensures that equality testing between two public keys
 348  // works as expected.
 349  func TestPublicKeyIsEqual(t *testing.T) {
 350  	pubKey1 := &PublicKey{
 351  		x: *hexToFieldVal("2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e"),
 352  		y: *hexToFieldVal("499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f"),
 353  	}
 354  	pubKey1Copy := &PublicKey{
 355  		x: *hexToFieldVal("2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e"),
 356  		y: *hexToFieldVal("499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f"),
 357  	}
 358  	pubKey2 := &PublicKey{
 359  		x: *hexToFieldVal("ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d"),
 360  		y: *hexToFieldVal("0890ff84d7999d878a57bee170e19ef4b4803b4bdede64503a6ac352b03c8032"),
 361  	}
 362  
 363  	if !pubKey1.IsEqual(pubKey1) {
 364  		t.Fatalf(
 365  			"bad self public key equality check: (%v, %v)", pubKey1.x,
 366  			pubKey1.y,
 367  		)
 368  	}
 369  	if !pubKey1.IsEqual(pubKey1Copy) {
 370  		t.Fatalf(
 371  			"bad public key equality check: (%v, %v) == (%v, %v)",
 372  			pubKey1.x, pubKey1.y, pubKey1Copy.x, pubKey1Copy.y,
 373  		)
 374  	}
 375  
 376  	if pubKey1.IsEqual(pubKey2) {
 377  		t.Fatalf(
 378  			"bad public key equality check: (%v, %v) != (%v, %v)",
 379  			pubKey1.x, pubKey1.y, pubKey2.x, pubKey2.y,
 380  		)
 381  	}
 382  }
 383  
 384  // TestPublicKeyAsJacobian ensures converting a public key to a jacobian point
 385  // with a Z coordinate of 1 works as expected.
 386  func TestPublicKeyAsJacobian(t *testing.T) {
 387  	tests := []struct {
 388  		name   string // test description
 389  		pubKey string // hex encoded serialized compressed pubkey
 390  		wantX  string // hex encoded expected X coordinate
 391  		wantY  string // hex encoded expected Y coordinate
 392  	}{
 393  		{
 394  			name:   "public key for secret key 0x01",
 395  			pubKey: "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
 396  			wantX:  "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
 397  			wantY:  "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
 398  		}, {
 399  			name:   "public for secret key 0x03",
 400  			pubKey: "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
 401  			wantX:  "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
 402  			wantY:  "388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672",
 403  		}, {
 404  			name:   "public for secret key 0x06",
 405  			pubKey: "03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556",
 406  			wantX:  "fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556",
 407  			wantY:  "ae12777aacfbb620f3be96017f45c560de80f0f6518fe4a03c870c36b075f297",
 408  		},
 409  	}
 410  	for _, test := range tests {
 411  		// Parse the test data.
 412  		pubKeyBytes := hexToBytes(test.pubKey)
 413  		wantX := hexToFieldVal(test.wantX)
 414  		wantY := hexToFieldVal(test.wantY)
 415  		pubKey, err := ParsePubKey(pubKeyBytes)
 416  		if err != nil {
 417  			t.Errorf("%s: failed to parse public key: %v", test.name, err)
 418  			continue
 419  		}
 420  		// Convert the public key to a jacobian point and ensure the coordinates
 421  		// match the expected values.
 422  		var point JacobianPoint
 423  		pubKey.AsJacobian(&point)
 424  		if !point.Z.IsOne() {
 425  			t.Errorf(
 426  				"%s: invalid Z coordinate -- got %v, want 1", test.name,
 427  				point.Z,
 428  			)
 429  			continue
 430  		}
 431  		if !point.X.Equals(wantX) {
 432  			t.Errorf(
 433  				"%s: invalid X coordinate - got %v, want %v", test.name,
 434  				point.X, wantX,
 435  			)
 436  			continue
 437  		}
 438  		if !point.Y.Equals(wantY) {
 439  			t.Errorf(
 440  				"%s: invalid Y coordinate - got %v, want %v", test.name,
 441  				point.Y, wantY,
 442  			)
 443  			continue
 444  		}
 445  	}
 446  }
 447  
 448  // TestPublicKeyIsOnCurve ensures testing if a public key is on the curve works
 449  // as expected.
 450  func TestPublicKeyIsOnCurve(t *testing.T) {
 451  	tests := []struct {
 452  		name string // test description
 453  		pubX string // hex encoded x coordinate for pubkey to serialize
 454  		pubY string // hex encoded y coordinate for pubkey to serialize
 455  		want bool   // expected result
 456  	}{
 457  		{
 458  			name: "valid with even y",
 459  			pubX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
 460  			pubY: "4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
 461  			want: true,
 462  		}, {
 463  			name: "valid with odd y",
 464  			pubX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
 465  			pubY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
 466  			want: true,
 467  		}, {
 468  			name: "invalid due to x coord",
 469  			pubX: "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
 470  			pubY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
 471  			want: false,
 472  		}, {
 473  			name: "invalid due to y coord",
 474  			pubX: "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
 475  			pubY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4",
 476  			want: false,
 477  		},
 478  	}
 479  	for _, test := range tests {
 480  		// Parse the test data.
 481  		x, y := hexToFieldVal(test.pubX), hexToFieldVal(test.pubY)
 482  		pubKey := NewPublicKey(x, y)
 483  
 484  		result := pubKey.IsOnCurve()
 485  		if result != test.want {
 486  			t.Errorf(
 487  				"%s: mismatched is on curve result -- got %v, want %v",
 488  				test.name, result, test.want,
 489  			)
 490  			continue
 491  		}
 492  	}
 493  }
 494