220 lines
6.1 KiB
Go
220 lines
6.1 KiB
Go
package secp256k1
|
|
|
|
import (
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"fmt"
|
|
|
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
|
|
|
|
"github.com/ucan-wg/go-did-it/crypto"
|
|
)
|
|
|
|
var _ crypto.PrivateKeySigningBytes = &PrivateKey{}
|
|
var _ crypto.PrivateKeySigningASN1 = &PrivateKey{}
|
|
var _ crypto.PrivateKeyKeyExchange = &PrivateKey{}
|
|
|
|
type PrivateKey struct {
|
|
k *secp256k1.PrivateKey
|
|
}
|
|
|
|
// PrivateKeyFromBytes converts a serialized public key to a PrivateKey.
|
|
// This compact serialization format is the raw key material, without metadata or structure.
|
|
// It errors if the slice is not the right size.
|
|
func PrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
|
|
if len(b) != PrivateKeyBytesSize {
|
|
return nil, fmt.Errorf("invalid secp256k1 private key size")
|
|
}
|
|
return &PrivateKey{k: secp256k1.PrivKeyFromBytes(b)}, nil
|
|
}
|
|
|
|
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
|
|
func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
|
|
// Parse the PKCS#8 structure
|
|
var pkcs8 struct {
|
|
Version int
|
|
Algo pkix.AlgorithmIdentifier
|
|
PrivateKey []byte
|
|
}
|
|
if _, err := asn1.Unmarshal(bytes, &pkcs8); err != nil {
|
|
return nil, fmt.Errorf("failed to parse PKCS#8 structure: %w", err)
|
|
}
|
|
|
|
// Check if this is an Elliptic curve public key (OID: 1.2.840.10045.2.1)
|
|
if !pkcs8.Algo.Algorithm.Equal(oidPublicKeyECDSA) {
|
|
return nil, fmt.Errorf("not an EC private key, got OID: %v", pkcs8.Algo.Algorithm)
|
|
}
|
|
|
|
// Extract the curve OID from parameters
|
|
var namedCurveOID asn1.ObjectIdentifier
|
|
if _, err := asn1.Unmarshal(pkcs8.Algo.Parameters.FullBytes, &namedCurveOID); err != nil {
|
|
return nil, fmt.Errorf("failed to parse curve parameters: %w", err)
|
|
}
|
|
|
|
// Check if the curve is secp256k1 (OID: 1.3.132.0.10)
|
|
if !namedCurveOID.Equal(oidSecp256k1) {
|
|
return nil, fmt.Errorf("unsupported curve, expected secp256k1 (1.3.132.0.10), got: %v", namedCurveOID)
|
|
}
|
|
|
|
// Parse the EC private key structure (RFC 5915)
|
|
var ecPrivKey struct {
|
|
Version int
|
|
PrivateKey []byte
|
|
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
|
|
}
|
|
|
|
if _, err := asn1.Unmarshal(pkcs8.PrivateKey, &ecPrivKey); err != nil {
|
|
return nil, fmt.Errorf("failed to parse alliptic curve private key: %w", err)
|
|
}
|
|
|
|
// Validate the EC private key version
|
|
if ecPrivKey.Version != 1 {
|
|
return nil, fmt.Errorf("unsupported EC private key version: %d", ecPrivKey.Version)
|
|
}
|
|
|
|
// Validate private key length
|
|
if len(ecPrivKey.PrivateKey) != PrivateKeyBytesSize {
|
|
return nil, fmt.Errorf("invalid secp256k1 private key length: %d, expected %d", len(ecPrivKey.PrivateKey), PrivateKeyBytesSize)
|
|
}
|
|
|
|
// Create the secp256k1 private key
|
|
privKeySecp256k1 := secp256k1.PrivKeyFromBytes(ecPrivKey.PrivateKey)
|
|
|
|
return &PrivateKey{k: privKeySecp256k1}, nil
|
|
}
|
|
|
|
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
|
|
func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
|
|
block, _ := pem.Decode([]byte(str))
|
|
if block == nil {
|
|
return nil, fmt.Errorf("failed to decode PEM block")
|
|
}
|
|
if block.Type != pemPrivBlockType {
|
|
return nil, fmt.Errorf("incorrect PEM block type")
|
|
}
|
|
return PrivateKeyFromPKCS8DER(block.Bytes)
|
|
}
|
|
|
|
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
|
|
if other, ok := other.(*PrivateKey); ok {
|
|
return p.k.PubKey().IsEqual(other.k.PubKey())
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *PrivateKey) Public() crypto.PublicKey {
|
|
return &PublicKey{k: p.k.PubKey()}
|
|
}
|
|
|
|
func (p *PrivateKey) ToBytes() []byte {
|
|
return p.k.Serialize()
|
|
}
|
|
|
|
func (p *PrivateKey) ToPKCS8DER() []byte {
|
|
pubkeyBytes := p.k.PubKey().SerializeUncompressed()
|
|
|
|
// Create the EC private key structure
|
|
// This follows RFC 5915 format for EC private keys
|
|
ecPrivateKey := struct {
|
|
Version int
|
|
PrivateKey []byte
|
|
Parameters asn1.RawValue `asn1:"optional,explicit,tag:0"`
|
|
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
|
|
}{
|
|
Version: 1,
|
|
PrivateKey: p.k.Serialize(),
|
|
// Parameters are omitted since they're specified in the algorithm identifier
|
|
|
|
// Pubkey could be omitted, but we include it to match openssl behavior
|
|
PublicKey: asn1.BitString{
|
|
Bytes: pubkeyBytes,
|
|
BitLength: 8 * len(pubkeyBytes),
|
|
},
|
|
}
|
|
|
|
ecPrivKeyDER, err := asn1.Marshal(ecPrivateKey)
|
|
if err != nil {
|
|
panic(err) // This should not happen with valid key data
|
|
}
|
|
|
|
// Create the PKCS#8 structure
|
|
pkcs8 := struct {
|
|
Version int
|
|
Algo pkix.AlgorithmIdentifier
|
|
PrivateKey []byte
|
|
}{
|
|
Version: 0,
|
|
Algo: pkix.AlgorithmIdentifier{
|
|
// Elliptic curve public key (OID: 1.2.840.10045.2.1)
|
|
Algorithm: oidPublicKeyECDSA,
|
|
Parameters: asn1.RawValue{
|
|
FullBytes: must(asn1.Marshal(oidSecp256k1)),
|
|
},
|
|
},
|
|
PrivateKey: ecPrivKeyDER,
|
|
}
|
|
|
|
der, err := asn1.Marshal(pkcs8)
|
|
if err != nil {
|
|
panic(err) // This should not happen with valid key data
|
|
}
|
|
|
|
return der
|
|
}
|
|
|
|
func (p *PrivateKey) ToPKCS8PEM() string {
|
|
der := p.ToPKCS8DER()
|
|
return string(pem.EncodeToMemory(&pem.Block{
|
|
Type: pemPrivBlockType,
|
|
Bytes: der,
|
|
}))
|
|
}
|
|
|
|
// The default signing hash is SHA-256.
|
|
func (p *PrivateKey) SignToBytes(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
|
|
params := crypto.CollectSigningOptions(opts)
|
|
|
|
hasher := params.HashOrDefault(crypto.SHA256).New()
|
|
hasher.Write(message)
|
|
hash := hasher.Sum(nil)
|
|
|
|
sig := ecdsa.Sign(p.k, hash)
|
|
r := sig.R()
|
|
s := sig.S()
|
|
|
|
res := make([]byte, SignatureBytesSize)
|
|
r.PutBytesUnchecked(res[:SignatureBytesSize/2])
|
|
s.PutBytesUnchecked(res[SignatureBytesSize/2:])
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// The default signing hash is SHA-256.
|
|
func (p *PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
|
|
params := crypto.CollectSigningOptions(opts)
|
|
|
|
hasher := params.HashOrDefault(crypto.SHA256).New()
|
|
hasher.Write(message)
|
|
hash := hasher.Sum(nil)
|
|
|
|
sig := ecdsa.Sign(p.k, hash)
|
|
|
|
return sig.Serialize(), nil
|
|
}
|
|
|
|
func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool {
|
|
if _, ok := remote.(*PublicKey); ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *PrivateKey) KeyExchange(remote crypto.PublicKey) ([]byte, error) {
|
|
if remote, ok := remote.(*PublicKey); ok {
|
|
return secp256k1.GenerateSharedSecret(p.k, remote.k), nil
|
|
}
|
|
return nil, fmt.Errorf("incompatible public key")
|
|
}
|