2025-06-25 15:53:29 +02:00
|
|
|
package p384
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/ecdsa"
|
|
|
|
|
"crypto/elliptic"
|
|
|
|
|
"crypto/x509"
|
|
|
|
|
"encoding/pem"
|
|
|
|
|
"fmt"
|
|
|
|
|
"math/big"
|
|
|
|
|
|
2025-07-10 15:56:45 +02:00
|
|
|
"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
|
|
|
|
|
}
|
2025-07-09 18:55:14 +02:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-25 16:54:48 +02:00
|
|
|
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,
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 12:57:06 +02:00
|
|
|
// 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
|
|
|
|
2025-07-08 12:57:06 +02:00
|
|
|
return p.VerifyASN1(message, sigAsn1, opts...)
|
2025-06-25 15:53:29 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-08 12:57:06 +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
|
|
|
}
|