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