lightning_signer/util/
crypto_utils.rs

1use crate::prelude::*;
2use bitcoin::hashes::sha256::Hash as BitcoinSha256;
3use bitcoin::hashes::{sha256d, Hash, HashEngine, Hmac, HmacEngine};
4use bitcoin::key::XOnlyPublicKey;
5use bitcoin::secp256k1::constants::SCHNORR_SIGNATURE_SIZE;
6use bitcoin::secp256k1::{
7    self, ecdsa::Signature, schnorr, Message, PublicKey, Secp256k1, SecretKey,
8};
9use bitcoin::sighash::{EcdsaSighashType, TapSighash};
10use bitcoin::taproot::TapTweakHash;
11use bitcoin::PrivateKey;
12use lightning::ln::channel_keys::{RevocationBasepoint, RevocationKey};
13
14fn hkdf_extract_expand(salt: &[u8], secret: &[u8], info: &[u8], output: &mut [u8]) {
15    let mut hmac = HmacEngine::<BitcoinSha256>::new(salt);
16    hmac.input(secret);
17    let prk = Hmac::from_engine(hmac).to_byte_array();
18
19    let mut t = [0; 32];
20    let mut n: u8 = 0;
21
22    for chunk in output.chunks_mut(32) {
23        let mut hmac = HmacEngine::<BitcoinSha256>::new(&prk[..]);
24        n = n.checked_add(1).expect("HKDF size limit exceeded.");
25        if n != 1 {
26            hmac.input(&t);
27        }
28        hmac.input(&info);
29        hmac.input(&[n]);
30        t = Hmac::from_engine(hmac).to_byte_array();
31        chunk.copy_from_slice(&t);
32    }
33}
34
35/// derive a secret from another secret using HKDF-SHA256
36pub fn hkdf_sha256(secret: &[u8], info: &[u8], salt: &[u8]) -> [u8; 32] {
37    let mut result = [0u8; 32];
38    hkdf_extract_expand(salt, secret, info, &mut result);
39    result
40}
41
42pub(crate) fn hkdf_sha256_keys(secret: &[u8], info: &[u8], salt: &[u8]) -> [u8; 32 * 6] {
43    let mut result = [0u8; 32 * 6];
44    hkdf_extract_expand(salt, secret, info, &mut result);
45    result
46}
47
48pub(crate) fn derive_public_key<T: secp256k1::Signing>(
49    secp_ctx: &Secp256k1<T>,
50    per_commitment_point: &PublicKey,
51    base_point: &PublicKey,
52) -> Result<PublicKey, secp256k1::Error> {
53    let mut sha = BitcoinSha256::engine();
54    sha.input(&per_commitment_point.serialize());
55    sha.input(&base_point.serialize());
56    let res = BitcoinSha256::from_engine(sha).to_byte_array();
57
58    let hashkey = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&res)?);
59    base_point.combine(&hashkey)
60}
61
62/// Convert a [Signature] to Bitcoin signature bytes, with SIGHASH_ALL
63pub fn signature_to_bitcoin_vec(sig: Signature) -> Vec<u8> {
64    let mut sigvec = sig.serialize_der().to_vec();
65    sigvec.push(EcdsaSighashType::All as u8);
66    sigvec
67}
68
69/// Convert a [Signature] to Bitcoin signature bytes, with SIGHASH_ALL
70pub fn schnorr_signature_to_bitcoin_vec(sig: schnorr::Signature) -> Vec<u8> {
71    // taproot sighash type defaults to ALL
72    let mut sigvec = Vec::with_capacity(SCHNORR_SIGNATURE_SIZE);
73    sigvec.extend_from_slice(&sig[..]);
74    sigvec
75}
76
77/// Convert a Bitcoin signature bytes, with the specified EcdsaSighashType, to [Signature]
78pub fn bitcoin_vec_to_signature(
79    sigvec: &[u8],
80    sighash_type: EcdsaSighashType,
81) -> Result<Signature, secp256k1::Error> {
82    let len = sigvec.len();
83    if len == 0 {
84        return Err(secp256k1::Error::InvalidSignature);
85    }
86    let mut sv = sigvec.to_vec();
87    let mode = sv.pop().ok_or_else(|| secp256k1::Error::InvalidSignature)?;
88    if mode != sighash_type as u8 {
89        return Err(secp256k1::Error::InvalidSignature);
90    }
91    Ok(Signature::from_der(&sv[..])?)
92}
93
94/// Use the provided seed, or generate a random one
95pub fn maybe_generate_seed(seed_opt: Option<[u8; 32]>) -> [u8; 32] {
96    seed_opt.unwrap_or_else(generate_seed)
97}
98
99/// Generate a seed
100pub fn generate_seed() -> [u8; 32] {
101    #[cfg(feature = "std")]
102    {
103        use secp256k1::rand::RngCore;
104        let mut seed = [0; 32];
105        let mut rng = secp256k1::rand::rngs::OsRng;
106        rng.fill_bytes(&mut seed);
107        seed
108    }
109    #[cfg(not(feature = "std"))]
110    unimplemented!("no RNG available in no_std environments yet");
111}
112
113/// Hash the serialized heartbeat message for signing
114pub fn sighash_from_heartbeat(ser_heartbeat: &[u8]) -> Message {
115    let mut sha = BitcoinSha256::engine();
116    sha.input("vls".as_bytes());
117    sha.input("heartbeat".as_bytes());
118    sha.input(ser_heartbeat);
119    let hash = BitcoinSha256::from_engine(sha);
120    Message::from_digest(hash.to_byte_array())
121}
122
123pub(crate) fn ecdsa_sign(
124    secp_ctx: &Secp256k1<secp256k1::All>,
125    privkey: &PrivateKey,
126    sighash: sha256d::Hash,
127) -> Signature {
128    let message = Message::from_digest(sighash.to_byte_array());
129    secp_ctx.sign_ecdsa(&message, &privkey.inner)
130}
131
132pub(crate) fn taproot_sign(
133    secp_ctx: &Secp256k1<secp256k1::All>,
134    privkey: &PrivateKey,
135    sighash: TapSighash,
136    aux_rand: &[u8; 32],
137) -> schnorr::Signature {
138    let message = Message::from(sighash);
139    let keypair = secp256k1::Keypair::from_secret_key(secp_ctx, &privkey.inner);
140    let (internal_key, _parity) = XOnlyPublicKey::from_keypair(&keypair);
141    let tweak = TapTweakHash::from_key_and_tweak(internal_key, None);
142    let tweaked_keypair = keypair.add_xonly_tweak(secp_ctx, &tweak.to_scalar()).unwrap();
143
144    secp_ctx.sign_schnorr_with_aux_rand(&message, &tweaked_keypair, aux_rand)
145}
146
147/// Derives a per-commitment-transaction revocation public key from its constituent parts. This is
148/// the public equivalent of derive_private_revocation_key - using only public keys to derive a
149/// public key instead of private keys.
150///
151/// Only the cheating participant owns a valid witness to propagate a revoked
152/// commitment transaction, thus per_commitment_point always come from cheater
153/// and revocation_base_point always come from punisher, which is the broadcaster
154/// of the transaction spending with this key knowledge.
155pub(crate) fn derive_public_revocation_key<T: secp256k1::Verification>(
156    secp_ctx: &Secp256k1<T>,
157    per_commitment_point: &PublicKey,
158    countersignatory_revocation_base_point: &RevocationBasepoint,
159) -> Result<RevocationKey, ()> {
160    let revocation_key = RevocationKey::from_basepoint(
161        secp_ctx,
162        &countersignatory_revocation_base_point,
163        per_commitment_point,
164    );
165    Ok(revocation_key)
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use bitcoin::Network;
172
173    #[test]
174    fn hkdf_tests() {
175        let secret = [1u8];
176        let info = [2u8];
177        let salt = [3u8];
178        let mut output = [0u8; 32 * 6];
179        hkdf_extract_expand(&salt, &secret, &info, &mut output);
180        assert_eq!(hex::encode(output), "13a04658302cc5173a8077f2f296662a7a3ddb2359be92770b13e0b9e63a23d0efbbb13e74af4687137801e1628d1d1876d251b31d1321383568a9387da7c0baa7dee83ba374bba3774ef01140e4c4293791a512e536764bf4405aea511be32d5fd71a0b7a7ef3638312e476eb323fbac5f3d549ccf0fe0eabb38fe7bc16ad01db2288e57de45eabecd561ede4dc89164099ed7f0b0db5250e2b377e2aa84f520838612dccbde870f7b06a1e03f3cd79d30da717c55e15442a0b4dd02aafcd86");
181        let mut output = [0u8; 32];
182        hkdf_extract_expand(&salt, &secret, &info, &mut output);
183        assert_eq!(
184            hex::encode(output),
185            "13a04658302cc5173a8077f2f296662a7a3ddb2359be92770b13e0b9e63a23d0"
186        );
187
188        let secret = [1u8];
189        let info = [2u8];
190        let salt = [3u8];
191        let result = hkdf_sha256(&secret, &info, &salt);
192        assert_eq!(
193            hex::encode(result),
194            "13a04658302cc5173a8077f2f296662a7a3ddb2359be92770b13e0b9e63a23d0"
195        );
196
197        let secret = [1u8];
198        let info = [2u8];
199        let salt = [3u8];
200        let result = hkdf_sha256_keys(&secret, &info, &salt);
201        assert_eq!(result.len(), 32 * 6);
202        let expected_prefix = "13a04658302cc5173a8077f2f296662a7a3ddb2359be92770b13e0b9e63a23d0";
203        assert_eq!(hex::encode(&result[..32]), expected_prefix);
204    }
205
206    #[test]
207    fn test_schnorr_signature_to_bitcoin_vec() {
208        let test_signature_bytes: Vec<u8> = vec![0; 64];
209        let test_signature = schnorr::Signature::from_slice(&test_signature_bytes).unwrap();
210        let result = schnorr_signature_to_bitcoin_vec(test_signature);
211        assert_eq!(test_signature_bytes, result);
212    }
213
214    #[test]
215    fn test_bitcoin_vec_to_signature() {
216        let sighash_type = EcdsaSighashType::All;
217        let sigvec: Vec<u8> = vec![];
218        let result = bitcoin_vec_to_signature(&sigvec, sighash_type);
219        assert_eq!(result, Err(secp256k1::Error::InvalidSignature));
220
221        let mut sigvec = hex::decode(
222            "304402202e1f64d831e89e2b4a0dc8565cb2d0a4d6061a89f9b48f2c26d5ac0b3b9a0bb102200c8d396f8b2e9c6c623bebc015c47f1f41e8824fabe7cb028f174a0e5df3c0a0"
223        ).unwrap();
224        sigvec.push(1 as u8);
225        let result = bitcoin_vec_to_signature(&sigvec, sighash_type).unwrap();
226        sigvec.pop();
227        let parsed_signature = Signature::from_der(&sigvec).expect("valid DER signature");
228        assert_eq!(result, parsed_signature);
229    }
230
231    #[test]
232    fn test_maybe_generate_seed() {
233        let known_seed: [u8; 32] = [1; 32];
234        let result = maybe_generate_seed(Some(known_seed));
235        assert_eq!(result, known_seed);
236
237        let result = maybe_generate_seed(None);
238        assert_eq!(result.len(), 32);
239    }
240
241    #[test]
242    fn test_taproot_sign() {
243        let secp = Secp256k1::new();
244        let privkey_bytes =
245            hex::decode("d8d3a3140ba89f14144b0dfe40e04220e02ed68736a5773e050a3c4116b1e31c")
246                .unwrap();
247        let secret_key =
248            SecretKey::from_slice(&privkey_bytes).expect("32 bytes, within curve order");
249        let privkey = PrivateKey::new(secret_key, Network::Bitcoin);
250        let sighash = TapSighash::hash(&[0]);
251        let aux_rand: [u8; 32] = [0u8; 32];
252        let signature = taproot_sign(&secp, &privkey, sighash, &aux_rand);
253        let expected_signature_hex =
254            "14262eb13409cd8928536ab60f431b95193d2d9c7cc476e9f43e8b8f98a8d5a8c38d3edc7bf43c389a12c9e5fad9485ee5d59df2d35f46c3f77ca07197ee1db2";
255        assert_eq!(expected_signature_hex, signature.to_string());
256    }
257
258    #[test]
259    fn test_derive_public_key() {
260        let secp = Secp256k1::new();
261        let per_commitment_secret = SecretKey::from_slice(&[2; 32]).unwrap();
262        let base_secret = SecretKey::from_slice(&[3; 32]).unwrap();
263        let per_commitment_point = PublicKey::from_secret_key(&secp, &per_commitment_secret);
264        let base_point = PublicKey::from_secret_key(&secp, &base_secret);
265
266        let result = derive_public_key(&secp, &per_commitment_point, &base_point).unwrap();
267        let expected = PublicKey::from_slice(
268            &hex::decode("038f363030fd6822d5b3cfaa650fe3c37ed218e3761bbd5e7585779aeb5ac191f3")
269                .unwrap(),
270        )
271        .unwrap();
272        assert_eq!(result, expected);
273    }
274
275    #[test]
276    fn test_signature_to_bitcoin_vec() {
277        let secp = Secp256k1::new();
278        let secret_key = SecretKey::from_slice(&[1; 32]).unwrap();
279        let message = Message::from_digest([2; 32]);
280        let sig = secp.sign_ecdsa(&message, &secret_key);
281        let result = signature_to_bitcoin_vec(sig);
282        let expected = vec![
283            48, 69, 2, 33, 0, 151, 239, 48, 35, 62, 173, 37, 209, 15, 123, 178, 191, 158, 175, 87,
284            26, 22, 242, 222, 179, 58, 117, 242, 8, 25, 40, 79, 12, 184, 255, 60, 193, 2, 32, 72,
285            112, 202, 5, 148, 1, 153, 193, 19, 180, 220, 119, 134, 111, 0, 23, 2, 105, 28, 222, 38,
286            159, 104, 53, 88, 30, 122, 234, 30, 173, 38, 96, 1,
287        ];
288        assert_eq!(result, expected);
289    }
290
291    #[test]
292    fn test_generate_seed() {
293        #[cfg(feature = "std")]
294        {
295            let seed = generate_seed();
296            assert_eq!(seed.len(), 32);
297            assert_ne!(seed, [0; 32]);
298        }
299        #[cfg(not(feature = "std"))]
300        {
301            let result = std::panic::catch_unwind(|| generate_seed());
302            assert!(result.is_err());
303        }
304    }
305
306    #[test]
307    fn test_sighash_from_heartbeat() {
308        let ser_heartbeat = [1, 2, 3];
309        let result = sighash_from_heartbeat(&ser_heartbeat);
310        let expected_hash = BitcoinSha256::hash(
311            &["vls".as_bytes(), "heartbeat".as_bytes(), &ser_heartbeat].concat(),
312        );
313        let expected = Message::from_digest(expected_hash.to_byte_array());
314        assert_eq!(result, expected);
315    }
316
317    #[test]
318    fn test_ecdsa_sign() {
319        let secp = Secp256k1::new();
320        let secret_key = SecretKey::from_slice(&[1; 32]).unwrap();
321        let privkey = PrivateKey::new(secret_key, Network::Bitcoin);
322        let sighash = sha256d::Hash::hash(&[2; 32]);
323        let sig = ecdsa_sign(&secp, &privkey, sighash);
324        let message = Message::from_digest(sighash.to_byte_array());
325        let pubkey = PublicKey::from_secret_key(&secp, &secret_key);
326        secp.verify_ecdsa(&message, &sig, &pubkey).unwrap();
327    }
328
329    #[test]
330    fn test_derive_public_revocation_key() {
331        let secp = Secp256k1::new();
332        let per_commitment_secret = SecretKey::from_slice(&[2; 32]).unwrap();
333        let base_secret = SecretKey::from_slice(&[3; 32]).unwrap();
334        let per_commitment_point = PublicKey::from_secret_key(&secp, &per_commitment_secret);
335        let base_point = RevocationBasepoint::from(PublicKey::from_secret_key(&secp, &base_secret));
336
337        let result =
338            derive_public_revocation_key(&secp, &per_commitment_point, &base_point).unwrap();
339        let expected = RevocationKey::from_basepoint(&secp, &base_point, &per_commitment_point);
340        assert_eq!(result, expected);
341    }
342}