Files
p256k1/pubkey.go
2025-11-02 02:45:59 +00:00

192 lines
4.1 KiB
Go

package p256k1
import (
"errors"
)
// PublicKey represents a secp256k1 public key
type PublicKey struct {
data [64]byte // Internal representation
}
// Compression flags for public key serialization
const (
ECCompressed = 0x02
ECUncompressed = 0x04
)
// ECPubkeyParse parses a public key from bytes
func ECPubkeyParse(pubkey *PublicKey, input []byte) error {
if len(input) == 0 {
return errors.New("input cannot be empty")
}
var point GroupElementAffine
switch len(input) {
case 33:
// Compressed format
if input[0] != 0x02 && input[0] != 0x03 {
return errors.New("invalid compressed public key prefix")
}
// Extract X coordinate
var x FieldElement
if err := x.setB32(input[1:33]); err != nil {
return err
}
// Determine Y coordinate from X and parity
odd := input[0] == 0x03
if !point.setXOVar(&x, odd) {
return errors.New("invalid public key")
}
case 65:
// Uncompressed format
if input[0] != 0x04 {
return errors.New("invalid uncompressed public key prefix")
}
// Extract X and Y coordinates
var x, y FieldElement
if err := x.setB32(input[1:33]); err != nil {
return err
}
if err := y.setB32(input[33:65]); err != nil {
return err
}
point.setXY(&x, &y)
default:
return errors.New("invalid public key length")
}
// Validate the point is on the curve
if !point.isValid() {
return errors.New("public key not on curve")
}
// Store in internal format
point.toBytes(pubkey.data[:])
return nil
}
// ECPubkeySerialize serializes a public key to bytes
func ECPubkeySerialize(output []byte, pubkey *PublicKey, flags uint) int {
// Load the public key
var point GroupElementAffine
point.fromBytes(pubkey.data[:])
if point.isInfinity() {
return 0 // Invalid public key
}
// Normalize coordinates
point.x.normalize()
point.y.normalize()
if flags == ECCompressed {
if len(output) < 33 {
return 0 // Buffer too small
}
// Compressed format: 0x02/0x03 + X coordinate
if point.y.isOdd() {
output[0] = 0x03
} else {
output[0] = 0x02
}
point.x.getB32(output[1:33])
return 33
} else if flags == ECUncompressed {
if len(output) < 65 {
return 0 // Buffer too small
}
// Uncompressed format: 0x04 + X + Y coordinates
output[0] = 0x04
point.x.getB32(output[1:33])
point.y.getB32(output[33:65])
return 65
} else {
return 0 // Invalid flags
}
}
// ECPubkeyCmp compares two public keys
func ECPubkeyCmp(pubkey1, pubkey2 *PublicKey) int {
// Load both public keys
var point1, point2 GroupElementAffine
point1.fromBytes(pubkey1.data[:])
point2.fromBytes(pubkey2.data[:])
if point1.equal(&point2) {
return 0
}
// For ordering, compare the serialized forms
var buf1, buf2 [33]byte
ECPubkeySerialize(buf1[:], pubkey1, ECCompressed)
ECPubkeySerialize(buf2[:], pubkey2, ECCompressed)
for i := 0; i < 33; i++ {
if buf1[i] < buf2[i] {
return -1
}
if buf1[i] > buf2[i] {
return 1
}
}
return 0
}
// ECPubkeyCreate creates a public key from a private key
func ECPubkeyCreate(pubkey *PublicKey, seckey []byte) error {
if len(seckey) != 32 {
return errors.New("private key must be 32 bytes")
}
// Parse the private key as a scalar
var scalar Scalar
if !scalar.setB32Seckey(seckey) {
return errors.New("invalid private key")
}
// Compute pubkey = scalar * G
var point GroupElementJacobian
EcmultGen(&point, &scalar)
// Convert to affine and store directly - optimize by avoiding intermediate copy
var affine GroupElementAffine
affine.setGEJ(&point)
// Normalize in-place and write directly to pubkey.data to avoid copy allocation
affine.x.normalize()
affine.y.normalize()
affine.x.getB32(pubkey.data[:32])
affine.y.getB32(pubkey.data[32:64])
// Clear sensitive data
scalar.clear()
point.clear()
affine.clear()
return nil
}
// pubkeyLoad loads a public key from internal format (helper function)
func pubkeyLoad(point *GroupElementAffine, pubkey *PublicKey) {
point.fromBytes(pubkey.data[:])
}
// pubkeySave saves a public key to internal format (helper function)
func pubkeySave(pubkey *PublicKey, point *GroupElementAffine) {
point.toBytes(pubkey.data[:])
}