ecdsa.go raw

   1  package ecdsa
   2  
   3  import (
   4  	"errors"
   5  
   6  	"next.orly.dev/pkg/p256k1"
   7  )
   8  
   9  // Signature represents an ECDSA signature.
  10  // This is a value object - immutable once created.
  11  type Signature struct {
  12  	sig p256k1.ECDSASignature
  13  }
  14  
  15  // CompactSignature is a 64-byte compact signature format (r || s).
  16  type CompactSignature = p256k1.ECDSASignatureCompact
  17  
  18  // RecoverableSignature includes a recovery ID for public key recovery.
  19  type RecoverableSignature struct {
  20  	sig p256k1.ECDSARecoverableSignature
  21  }
  22  
  23  // PublicKey is an alias for the core public key type.
  24  type PublicKey = p256k1.PublicKey
  25  
  26  // =============================================================================
  27  // Signature Creation (Domain Service)
  28  // =============================================================================
  29  
  30  // Sign creates an ECDSA signature for a 32-byte message hash using a 32-byte
  31  // private key.
  32  //
  33  // The signature uses RFC 6979 for deterministic nonce generation and is
  34  // automatically normalized to low-S form (BIP-146 compliant).
  35  //
  36  // Returns an error if:
  37  //   - messageHash is not exactly 32 bytes
  38  //   - privateKey is not exactly 32 bytes
  39  //   - privateKey is invalid (zero or >= curve order)
  40  func Sign(messageHash, privateKey []byte) (*Signature, error) {
  41  	var sig p256k1.ECDSASignature
  42  	if err := p256k1.ECDSASign(&sig, messageHash, privateKey); err != nil {
  43  		return nil, err
  44  	}
  45  	return &Signature{sig: sig}, nil
  46  }
  47  
  48  // SignCompact creates a compact 64-byte signature.
  49  func SignCompact(messageHash, privateKey []byte) (*CompactSignature, error) {
  50  	var compact CompactSignature
  51  	if err := p256k1.ECDSASignCompact(&compact, messageHash, privateKey); err != nil {
  52  		return nil, err
  53  	}
  54  	return &compact, nil
  55  }
  56  
  57  // SignDER creates a DER-encoded signature.
  58  func SignDER(messageHash, privateKey []byte) ([]byte, error) {
  59  	return p256k1.ECDSASignDER(messageHash, privateKey)
  60  }
  61  
  62  // SignRecoverable creates a signature with recovery ID.
  63  func SignRecoverable(messageHash, privateKey []byte) (*RecoverableSignature, error) {
  64  	var sig p256k1.ECDSARecoverableSignature
  65  	if err := p256k1.ECDSASignRecoverable(&sig, messageHash, privateKey); err != nil {
  66  		return nil, err
  67  	}
  68  	return &RecoverableSignature{sig: sig}, nil
  69  }
  70  
  71  // =============================================================================
  72  // Signature Verification (Domain Service)
  73  // =============================================================================
  74  
  75  // Verify verifies an ECDSA signature against a message hash and public key.
  76  //
  77  // Returns true if the signature is valid, false otherwise.
  78  // Rejects high-S signatures (BIP-146 enforcement).
  79  func Verify(sig *Signature, messageHash []byte, pubkey *PublicKey) bool {
  80  	return p256k1.ECDSAVerify(&sig.sig, messageHash, pubkey)
  81  }
  82  
  83  // VerifyCompact verifies a compact 64-byte signature.
  84  func VerifyCompact(compact *CompactSignature, messageHash []byte, pubkey *PublicKey) bool {
  85  	return p256k1.ECDSAVerifyCompact(compact, messageHash, pubkey)
  86  }
  87  
  88  // VerifyDER verifies a DER-encoded signature.
  89  func VerifyDER(sigDER, messageHash []byte, pubkey *PublicKey) bool {
  90  	return p256k1.ECDSAVerifyDER(sigDER, messageHash, pubkey)
  91  }
  92  
  93  // =============================================================================
  94  // Public Key Recovery (Domain Service)
  95  // =============================================================================
  96  
  97  // Recover recovers the public key from a recoverable signature.
  98  func Recover(sig *RecoverableSignature, messageHash []byte) (*PublicKey, error) {
  99  	var pubkey PublicKey
 100  	if err := p256k1.ECDSARecover(&pubkey, &sig.sig, messageHash); err != nil {
 101  		return nil, err
 102  	}
 103  	return &pubkey, nil
 104  }
 105  
 106  // RecoverCompact recovers the public key from a compact signature with recovery ID.
 107  func RecoverCompact(sig64 []byte, recid int, messageHash []byte) (*PublicKey, error) {
 108  	var pubkey PublicKey
 109  	if err := p256k1.ECDSARecoverCompact(&pubkey, sig64, recid, messageHash); err != nil {
 110  		return nil, err
 111  	}
 112  	return &pubkey, nil
 113  }
 114  
 115  // =============================================================================
 116  // Signature Serialization (Value Object Methods)
 117  // =============================================================================
 118  
 119  // ToCompact converts a signature to compact 64-byte format.
 120  func (s *Signature) ToCompact() *CompactSignature {
 121  	return s.sig.ToCompact()
 122  }
 123  
 124  // FromCompact parses a compact 64-byte signature.
 125  func FromCompact(compact *CompactSignature) (*Signature, error) {
 126  	var sig p256k1.ECDSASignature
 127  	if err := sig.FromCompact(compact); err != nil {
 128  		return nil, err
 129  	}
 130  	return &Signature{sig: sig}, nil
 131  }
 132  
 133  // SerializeDER serializes the signature in DER format.
 134  func (s *Signature) SerializeDER() []byte {
 135  	return s.sig.SerializeDER()
 136  }
 137  
 138  // ParseDER parses a DER-encoded signature.
 139  func ParseDER(der []byte) (*Signature, error) {
 140  	var sig p256k1.ECDSASignature
 141  	if err := sig.ParseDER(der); err != nil {
 142  		return nil, err
 143  	}
 144  	return &Signature{sig: sig}, nil
 145  }
 146  
 147  // R returns the R component of the signature as a 32-byte slice.
 148  func (s *Signature) R() []byte {
 149  	return s.sig.GetR()
 150  }
 151  
 152  // S returns the S component of the signature as a 32-byte slice.
 153  func (s *Signature) S() []byte {
 154  	return s.sig.GetS()
 155  }
 156  
 157  // IsLowS returns true if the S value is in the lower half of the curve order.
 158  func (s *Signature) IsLowS() bool {
 159  	return s.sig.IsLowS()
 160  }
 161  
 162  // NormalizeLowS ensures the S value is in the lower half of the curve order.
 163  func (s *Signature) NormalizeLowS() {
 164  	s.sig.NormalizeLowS()
 165  }
 166  
 167  // =============================================================================
 168  // Recoverable Signature Methods
 169  // =============================================================================
 170  
 171  // ToCompact returns the 64-byte compact signature and recovery ID.
 172  func (s *RecoverableSignature) ToCompact() ([]byte, int) {
 173  	return s.sig.ToCompact()
 174  }
 175  
 176  // FromCompactRecoverable parses a compact signature with recovery ID.
 177  func FromCompactRecoverable(compact []byte, recid int) (*RecoverableSignature, error) {
 178  	if len(compact) != 64 {
 179  		return nil, errors.New("compact signature must be 64 bytes")
 180  	}
 181  	var sig p256k1.ECDSARecoverableSignature
 182  	if err := sig.FromCompact(compact, recid); err != nil {
 183  		return nil, err
 184  	}
 185  	return &RecoverableSignature{sig: sig}, nil
 186  }
 187