Files
did-it/crypto/p384/public.go

157 lines
4.4 KiB
Go
Raw Normal View History

2025-06-25 15:53:29 +02:00
package p384
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/ucan-wg/go-did-it/crypto"
helpers "github.com/ucan-wg/go-did-it/crypto/internal"
2025-06-25 15:53:29 +02:00
)
2025-07-09 17:58:09 +02:00
var _ crypto.PublicKeySigningBytes = &PublicKey{}
var _ crypto.PublicKeySigningASN1 = &PublicKey{}
2025-07-03 15:55:58 +02:00
var _ crypto.PublicKeyToBytes = &PublicKey{}
2025-06-25 15:53:29 +02:00
2025-06-25 16:46:43 +02:00
type PublicKey struct {
k *ecdsa.PublicKey
}
2025-06-25 15:53:29 +02:00
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
// This compact serialization format is the raw key material, without metadata or structure.
// It errors if the slice is not the right size.
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
if len(b) != PublicKeyBytesSize {
return nil, fmt.Errorf("invalid P-384 public key size")
}
x, y := elliptic.UnmarshalCompressed(elliptic.P384(), b)
if x == nil {
return nil, fmt.Errorf("invalid P-384 public key")
}
2025-06-25 16:46:43 +02:00
return &PublicKey{k: &ecdsa.PublicKey{Curve: elliptic.P384(), X: x, Y: y}}, nil
2025-06-25 15:53:29 +02:00
}
// PublicKeyFromXY converts x and y coordinates into a PublicKey.
2025-06-26 16:56:22 +02:00
func PublicKeyFromXY(x, y []byte) (*PublicKey, error) {
pub := &PublicKey{k: &ecdsa.PublicKey{
Curve: elliptic.P384(),
X: new(big.Int).SetBytes(x),
Y: new(big.Int).SetBytes(y),
}}
if !elliptic.P384().IsOnCurve(pub.k.X, pub.k.Y) {
2025-06-25 15:53:29 +02:00
return nil, fmt.Errorf("invalid P-384 public key")
}
2025-06-26 16:56:22 +02:00
return pub, nil
2025-06-25 15:53:29 +02:00
}
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
if err != nil {
return nil, err
}
if code != MultibaseCode {
return nil, fmt.Errorf("invalid code")
}
return PublicKeyFromBytes(bytes)
}
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
pub, err := x509.ParsePKIXPublicKey(bytes)
if err != nil {
return nil, err
}
ecdsaPub, ok := pub.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("invalid public key")
}
return &PublicKey{k: ecdsaPub}, nil
2025-06-25 15:53:29 +02:00
}
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPubBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PublicKeyFromX509DER(block.Bytes)
}
func (p *PublicKey) XBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
(p.k).X.FillBytes(buf[:])
return buf[:]
}
func (p *PublicKey) YBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
(p.k).Y.FillBytes(buf[:])
return buf[:]
}
2025-06-25 15:53:29 +02:00
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
if other, ok := other.(*PublicKey); ok {
2025-06-25 16:46:43 +02:00
return p.k.Equal(other.k)
2025-06-25 15:53:29 +02:00
}
return false
}
func (p *PublicKey) ToBytes() []byte {
2025-06-25 16:46:43 +02:00
return elliptic.MarshalCompressed(elliptic.P384(), p.k.X, p.k.Y)
2025-06-25 15:53:29 +02:00
}
func (p *PublicKey) ToPublicKeyMultibase() string {
2025-06-25 16:46:43 +02:00
bytes := elliptic.MarshalCompressed(elliptic.P384(), p.k.X, p.k.Y)
2025-06-25 15:53:29 +02:00
return helpers.PublicKeyMultibaseEncode(MultibaseCode, bytes)
}
func (p *PublicKey) ToX509DER() []byte {
2025-06-25 16:46:43 +02:00
res, _ := x509.MarshalPKIXPublicKey(p.k)
2025-06-25 15:53:29 +02:00
return res
}
func (p *PublicKey) ToX509PEM() string {
der := p.ToX509DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPubBlockType,
Bytes: der,
}))
}
// The default signing hash is SHA-384.
func (p *PublicKey) VerifyBytes(message, signature []byte, opts ...crypto.SigningOption) bool {
2025-06-25 15:53:29 +02:00
if len(signature) != SignatureBytesSize {
return false
}
2025-06-26 16:56:22 +02:00
// For some reason, the go crypto library in ecdsa.Verify() encodes the signature as ASN.1 to then decode it.
// This means it's actually more efficient to encode the signature as ASN.1 here.
sigAsn1, err := helpers.EncodeSignatureToASN1(signature[:SignatureBytesSize/2], signature[SignatureBytesSize/2:])
if err != nil {
return false
}
2025-06-25 15:53:29 +02:00
return p.VerifyASN1(message, sigAsn1, opts...)
2025-06-25 15:53:29 +02:00
}
// The default signing hash is SHA-384.
func (p *PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA384).New()
hasher.Write(message)
hash := hasher.Sum(nil)
2025-06-25 15:53:29 +02:00
2025-06-25 16:46:43 +02:00
return ecdsa.VerifyASN1(p.k, hash[:], signature)
2025-06-25 15:53:29 +02:00
}