Skip to main content

bsv/primitives/
public_key.rs

1//! Public key type derived from a secp256k1 private key.
2//!
3//! PublicKey wraps a Point on the secp256k1 curve and provides
4//! DER encoding/decoding, address derivation, and signature
5//! verification. Mirrors the TS SDK PublicKey.ts API.
6
7use crate::primitives::base_point::BasePoint;
8use crate::primitives::big_number::{BigNumber, Endian};
9use crate::primitives::ecdsa::ecdsa_verify;
10use crate::primitives::error::PrimitivesError;
11use crate::primitives::hash::{hash160, sha256, sha256_hmac};
12use crate::primitives::point::Point;
13use crate::primitives::private_key::PrivateKey;
14use crate::primitives::signature::Signature;
15use crate::primitives::utils::{base58_check_encode, from_hex, to_hex};
16
17/// A secp256k1 public key (a point on the curve).
18///
19/// Uses composition with Point, following Rust conventions.
20/// The TS SDK uses class inheritance (PublicKey extends Point);
21/// we mirror the public API names.
22#[derive(Clone, Debug)]
23pub struct PublicKey {
24    point: Point,
25}
26
27impl PublicKey {
28    /// Create a PublicKey from a Point.
29    pub fn from_point(point: Point) -> Self {
30        PublicKey { point }
31    }
32
33    /// Derive a public key from a private key.
34    pub fn from_private_key(key: &crate::primitives::private_key::PrivateKey) -> Self {
35        key.to_public_key()
36    }
37
38    /// Parse a public key from a hex string (compressed or uncompressed DER).
39    ///
40    /// Compressed: 33 bytes (66 hex chars), starts with 02 or 03
41    /// Uncompressed: 65 bytes (130 hex chars), starts with 04
42    pub fn from_string(s: &str) -> Result<Self, PrimitivesError> {
43        let bytes = from_hex(s)?;
44        Self::from_der_bytes(&bytes)
45    }
46
47    /// Parse a public key from DER-encoded bytes.
48    pub fn from_der_bytes(bytes: &[u8]) -> Result<Self, PrimitivesError> {
49        if bytes.is_empty() {
50            return Err(PrimitivesError::InvalidPublicKey(
51                "empty public key data".to_string(),
52            ));
53        }
54
55        match bytes[0] {
56            0x02 | 0x03 => {
57                // Compressed: prefix(1) + x(32) = 33 bytes
58                if bytes.len() != 33 {
59                    return Err(PrimitivesError::InvalidPublicKey(format!(
60                        "compressed key should be 33 bytes, got {}",
61                        bytes.len()
62                    )));
63                }
64                let odd = bytes[0] == 0x03;
65                let x = BigNumber::from_bytes(&bytes[1..], Endian::Big);
66                let point = Point::from_x(&x, odd)?;
67                Ok(PublicKey { point })
68            }
69            0x04 => {
70                // Uncompressed: prefix(1) + x(32) + y(32) = 65 bytes
71                if bytes.len() != 65 {
72                    return Err(PrimitivesError::InvalidPublicKey(format!(
73                        "uncompressed key should be 65 bytes, got {}",
74                        bytes.len()
75                    )));
76                }
77                let x = BigNumber::from_bytes(&bytes[1..33], Endian::Big);
78                let y = BigNumber::from_bytes(&bytes[33..], Endian::Big);
79                let point = Point::new(x, y);
80
81                if !point.validate() {
82                    return Err(PrimitivesError::InvalidPublicKey(
83                        "point not on curve".to_string(),
84                    ));
85                }
86
87                Ok(PublicKey { point })
88            }
89            prefix => Err(PrimitivesError::InvalidPublicKey(format!(
90                "unknown prefix byte: 0x{:02x}",
91                prefix
92            ))),
93        }
94    }
95
96    /// Encode the public key in compressed DER format (33 bytes).
97    ///
98    /// Format: prefix(1) || x(32)
99    /// prefix = 0x02 if y is even, 0x03 if y is odd
100    pub fn to_der(&self) -> Vec<u8> {
101        self.point.to_der(true)
102    }
103
104    /// Encode the public key in compressed DER format as a hex string.
105    pub fn to_der_hex(&self) -> String {
106        to_hex(&self.to_der())
107    }
108
109    /// Encode the public key in uncompressed DER format (65 bytes).
110    ///
111    /// Format: 0x04 || x(32) || y(32)
112    pub fn to_der_uncompressed(&self) -> Vec<u8> {
113        self.point.to_der(false)
114    }
115
116    /// Hash the compressed public key with hash160 (RIPEMD-160(SHA-256)).
117    ///
118    /// Returns 20 bytes -- the public key hash used in P2PKH addresses.
119    pub fn to_hash(&self) -> Vec<u8> {
120        let der = self.to_der();
121        hash160(&der).to_vec()
122    }
123
124    /// Derive a P2PKH Bitcoin address from this public key.
125    ///
126    /// Format: Base58Check(prefix || hash160(compressed_der))
127    /// Default prefix `[0x00]` for mainnet.
128    pub fn to_address(&self, prefix: &[u8]) -> String {
129        let pkh = self.to_hash();
130        base58_check_encode(&pkh, prefix)
131    }
132
133    /// Verify a message signature using this public key.
134    ///
135    /// The message is hashed with SHA-256 before verification.
136    pub fn verify(&self, message: &[u8], signature: &Signature) -> bool {
137        let msg_hash = sha256(message);
138        ecdsa_verify(&msg_hash, signature, &self.point)
139    }
140
141    /// Compute ECDH shared secret: private_key.bn * self.point.
142    ///
143    /// Returns the resulting point on the curve.
144    pub fn derive_shared_secret(&self, private_key: &PrivateKey) -> Result<Point, PrimitivesError> {
145        private_key.derive_shared_secret(self)
146    }
147
148    /// Derive a child public key using Type-42 key derivation (BRC-42).
149    ///
150    /// Computes: child_point = self.point + G * HMAC-SHA256(shared_secret_compressed, invoice_number)
151    /// where shared_secret = private_key * self.
152    pub fn derive_child(
153        &self,
154        private_key: &PrivateKey,
155        invoice_number: &str,
156    ) -> Result<PublicKey, PrimitivesError> {
157        let shared_secret = private_key.derive_shared_secret(self)?;
158        self.derive_child_with_secret(&shared_secret, invoice_number)
159    }
160
161    /// Derive a child public key from a *precomputed* ECDH shared secret.
162    ///
163    /// The per-counterparty ECDH point-multiply (`private_key * self`) is the
164    /// caller's responsibility and is passed in as `shared_secret`; it does NOT
165    /// depend on `invoice_number`, so it can be cached and reused across
166    /// messages. Note this method still performs ONE per-message point-multiply
167    /// (`hmac * G`) and a point-add, which are NOT cacheable because the HMAC
168    /// embeds the per-message nonce via `invoice_number`. Output is
169    /// bit-identical to [`derive_child`] for the same inputs.
170    pub fn derive_child_with_secret(
171        &self,
172        shared_secret: &Point,
173        invoice_number: &str,
174    ) -> Result<PublicKey, PrimitivesError> {
175        let shared_secret_bytes = shared_secret.to_der(true); // 33-byte compressed
176        let hmac_result = sha256_hmac(&shared_secret_bytes, invoice_number.as_bytes());
177        let hmac_bn = BigNumber::from_bytes(&hmac_result, Endian::Big);
178        let base_point = BasePoint::instance();
179        let offset_point = base_point.mul(&hmac_bn);
180        let child_point = self.point.add(&offset_point);
181
182        Ok(PublicKey::from_point(child_point))
183    }
184
185    /// Access the underlying Point.
186    pub fn point(&self) -> &Point {
187        &self.point
188    }
189}
190
191impl PartialEq for PublicKey {
192    fn eq(&self, other: &Self) -> bool {
193        self.point.eq(&other.point)
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use crate::primitives::private_key::PrivateKey;
201
202    // -----------------------------------------------------------------------
203    // PublicKey: from_private_key
204    // -----------------------------------------------------------------------
205
206    #[test]
207    fn test_public_key_from_private_key() {
208        let priv_key = PrivateKey::from_hex("1").unwrap();
209        let pub_key = PublicKey::from_private_key(&priv_key);
210
211        // G point compressed
212        assert_eq!(
213            pub_key.to_der_hex(),
214            "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
215        );
216    }
217
218    // -----------------------------------------------------------------------
219    // PublicKey: from_string (compressed and uncompressed)
220    // -----------------------------------------------------------------------
221
222    #[test]
223    fn test_public_key_from_string_compressed() {
224        let hex = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
225        let pub_key = PublicKey::from_string(hex).unwrap();
226        assert_eq!(pub_key.to_der_hex(), hex);
227    }
228
229    #[test]
230    fn test_public_key_from_string_uncompressed() {
231        let hex = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";
232        let pub_key = PublicKey::from_string(hex).unwrap();
233        // Should produce the same compressed key
234        assert_eq!(
235            pub_key.to_der_hex(),
236            "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
237        );
238    }
239
240    // -----------------------------------------------------------------------
241    // PublicKey: DER compression roundtrip
242    // -----------------------------------------------------------------------
243
244    #[test]
245    fn test_public_key_der_roundtrip() {
246        let priv_key = PrivateKey::from_hex("ff").unwrap();
247        let pub_key = PublicKey::from_private_key(&priv_key);
248
249        let der_hex = pub_key.to_der_hex();
250        let recovered = PublicKey::from_string(&der_hex).unwrap();
251        assert_eq!(pub_key, recovered, "DER compression roundtrip should work");
252    }
253
254    // -----------------------------------------------------------------------
255    // PublicKey: to_hash
256    // -----------------------------------------------------------------------
257
258    #[test]
259    fn test_public_key_to_hash() {
260        let priv_key = PrivateKey::from_hex("1").unwrap();
261        let pub_key = PublicKey::from_private_key(&priv_key);
262        let hash = pub_key.to_hash();
263        assert_eq!(hash.len(), 20, "hash160 should be 20 bytes");
264    }
265
266    // -----------------------------------------------------------------------
267    // PublicKey: to_address
268    // -----------------------------------------------------------------------
269
270    #[test]
271    fn test_public_key_to_address_mainnet() {
272        // Key = 1 -> G -> known address
273        let priv_key = PrivateKey::from_hex("1").unwrap();
274        let pub_key = PublicKey::from_private_key(&priv_key);
275        let address = pub_key.to_address(&[0x00]);
276        assert_eq!(address, "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
277    }
278
279    // -----------------------------------------------------------------------
280    // PublicKey: verify
281    // -----------------------------------------------------------------------
282
283    #[test]
284    fn test_public_key_verify() {
285        let priv_key = PrivateKey::from_hex("1").unwrap();
286        let pub_key = PublicKey::from_private_key(&priv_key);
287        let sig = priv_key.sign(b"test verify", true).unwrap();
288
289        assert!(
290            pub_key.verify(b"test verify", &sig),
291            "Should verify valid signature"
292        );
293        assert!(
294            !pub_key.verify(b"wrong message", &sig),
295            "Should reject wrong message"
296        );
297    }
298
299    // -----------------------------------------------------------------------
300    // PublicKey: uncompressed encoding
301    // -----------------------------------------------------------------------
302
303    #[test]
304    fn test_public_key_uncompressed() {
305        let priv_key = PrivateKey::from_hex("1").unwrap();
306        let pub_key = PublicKey::from_private_key(&priv_key);
307        let uncompressed = pub_key.to_der_uncompressed();
308        assert_eq!(uncompressed.len(), 65);
309        assert_eq!(uncompressed[0], 0x04);
310    }
311
312    // -----------------------------------------------------------------------
313    // PublicKey: test vectors from JSON
314    // -----------------------------------------------------------------------
315
316    #[test]
317    fn test_public_key_der_vectors() {
318        use serde::Deserialize;
319
320        #[derive(Deserialize)]
321        struct DerVector {
322            private_key_hex: String,
323            public_key_compressed: String,
324            public_key_uncompressed: String,
325            address_mainnet: String,
326            #[allow(dead_code)]
327            address_prefix: String,
328            #[allow(dead_code)]
329            description: String,
330        }
331
332        let data = include_str!("../../test-vectors/public_key_der.json");
333        let vectors: Vec<DerVector> = serde_json::from_str(data).unwrap();
334
335        for (i, v) in vectors.iter().enumerate() {
336            let priv_key = PrivateKey::from_hex(&v.private_key_hex).unwrap();
337            let pub_key = PublicKey::from_private_key(&priv_key);
338
339            // Compressed DER
340            assert_eq!(
341                pub_key.to_der_hex(),
342                v.public_key_compressed,
343                "Vector {}: compressed mismatch",
344                i
345            );
346
347            // Uncompressed DER
348            let uncompressed_hex = to_hex(&pub_key.to_der_uncompressed());
349            assert_eq!(
350                uncompressed_hex, v.public_key_uncompressed,
351                "Vector {}: uncompressed mismatch",
352                i
353            );
354
355            // Address
356            let address = pub_key.to_address(&[0x00]);
357            assert_eq!(address, v.address_mainnet, "Vector {}: address mismatch", i);
358        }
359    }
360
361    // -----------------------------------------------------------------------
362    // PublicKey: sign then verify roundtrip with multiple keys
363    // -----------------------------------------------------------------------
364
365    #[test]
366    fn test_sign_verify_roundtrip_multiple_keys() {
367        for i in 1..=5 {
368            let priv_key = PrivateKey::from_hex(&format!("{:064x}", i * 1000)).unwrap();
369            let pub_key = PublicKey::from_private_key(&priv_key);
370            let msg = format!("Message number {}", i);
371
372            let sig = priv_key.sign(msg.as_bytes(), true).unwrap();
373            assert!(
374                pub_key.verify(msg.as_bytes(), &sig),
375                "Key {} should verify",
376                i
377            );
378        }
379    }
380}