Skip to main content

lexe_common/api/
user.rs

1use std::{collections::HashSet, fmt, str::FromStr};
2
3use anyhow::{Context, anyhow};
4use bitcoin::{secp256k1, secp256k1::Secp256k1};
5use lexe_byte_array::ByteArray;
6use lexe_crypto::ed25519::{self, Signable};
7#[cfg(any(test, feature = "test-utils"))]
8use lexe_crypto::rng::FastRng;
9use lexe_hex::hex;
10use lexe_serde::hexstr_or_bytes;
11use lexe_sha256::sha256;
12use lexe_std::array;
13use lightning::util::scid_utils;
14#[cfg(any(test, feature = "test-utils"))]
15use proptest::{
16    arbitrary::{Arbitrary, any},
17    strategy::{BoxedStrategy, Strategy},
18};
19#[cfg(any(test, feature = "test-utils"))]
20use proptest_derive::Arbitrary;
21use ref_cast::RefCast;
22use serde::{Deserialize, Serialize};
23use thiserror::Error;
24
25#[cfg(any(test, feature = "test-utils"))]
26use crate::root_seed::RootSeed;
27use crate::secp256k1_ctx::SECP256K1;
28#[cfg(any(test, feature = "test-utils"))]
29use crate::test_utils::arbitrary;
30
31/// A Lexe user, as represented in the DB.
32#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
33pub struct User {
34    pub user_pk: UserPk,
35    pub node_pk: NodePk,
36}
37
38/// An upgradeable version of [`Option<User>`].
39#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
40pub struct MaybeUser {
41    pub maybe_user: Option<User>,
42}
43
44/// A Lexe user's primary identifier, derived from the root seed.
45/// Serialized as a 64-character hex string.
46//
47// Internally an `ed25519::PublicKey`.
48#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
49#[derive(Copy, Clone, Eq, PartialEq, Hash, RefCast, Serialize, Deserialize)]
50#[repr(transparent)]
51pub struct UserPk(#[serde(with = "hexstr_or_bytes")] [u8; 32]);
52
53/// A [`UserPk`] shortened to its first four bytes (8 hex chars).
54#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
55#[derive(Copy, Clone, Hash, Eq, PartialEq, RefCast, Serialize, Deserialize)]
56#[repr(transparent)]
57pub struct ShortUserPk(#[serde(with = "hexstr_or_bytes")] [u8; 4]);
58
59/// Upgradeable API struct for a user pk.
60#[derive(Debug, PartialEq, Serialize, Deserialize)]
61#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
62pub struct UserPkStruct {
63    pub user_pk: UserPk,
64}
65
66/// Upgradeable API struct for a set of user pks
67#[derive(Debug, PartialEq, Serialize, Deserialize)]
68#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
69pub struct UserPkSet {
70    #[cfg_attr(
71        any(test, feature = "test-utils"),
72        proptest(strategy = "arbitrary::any_hashset::<UserPk>()")
73    )]
74    pub user_pks: HashSet<UserPk>,
75}
76
77/// A Lightning node's secp256k1 public key (the `node_id`). Serialized as a
78/// 66-character hex string.
79//
80// A simple wrapper around [`secp256k1::PublicKey`] which allows for
81// `Arbitrary` and other custom impls.
82//
83// # Notes
84//
85// - We do not represent the inner value as `[u8; 33]` (the output of
86//   [`secp256k1::PublicKey::serialize`]) because not all `[u8; 33]`s are valid
87//   pubkeys.
88// - We use [`PublicKey`]'s [`Serialize`] / [`Deserialize`] impls because it
89//   calls into `secp256k1` which does complicated validation to ensure that
90//   [`PublicKey`] is always valid.
91// - We use [`PublicKey`]'s [`FromStr`] / [`fmt::Display`] impls for similar
92//   reasons. Nevertheless, we still run proptests to check for correctness.
93//
94// [`PublicKey`]: secp256k1::PublicKey
95#[derive(Copy, Clone, Hash, Eq, PartialEq)]
96#[derive(RefCast, Serialize, Deserialize)]
97#[repr(transparent)]
98pub struct NodePk(pub secp256k1::PublicKey);
99
100/// A Proof-of-Key-Possession for a given [`NodePk`].
101///
102/// Used to ensure a user's signup request contains a [`NodePk`] actually owned
103/// by the user.
104///
105/// Like the outer [`UserSignupRequestWire`], this PoP is vulnerable to replay
106/// attacks in the general case.
107///
108/// [`UserSignupRequestWire`]: crate::api::auth::UserSignupRequestWire
109#[derive(Clone, Debug, Eq, PartialEq)]
110#[derive(Serialize, Deserialize)]
111pub struct NodePkProof {
112    node_pk: NodePk,
113    sig: secp256k1::ecdsa::Signature,
114}
115
116/// Upgradeable API struct for a node pk.
117#[derive(Debug, PartialEq, Serialize, Deserialize)]
118#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
119pub struct NodePkStruct {
120    pub node_pk: NodePk,
121}
122
123#[derive(Debug, Error)]
124#[error("invalid node pk proof signature")]
125pub struct InvalidNodePkProofSignature;
126
127/// A newtype for the `short_channel_id` (`scid`) used throughout LDK.
128#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
129#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
130#[derive(Serialize, Deserialize)]
131pub struct Scid(pub u64);
132
133/// Upgradeable API struct for a scid.
134#[derive(Debug, PartialEq, Serialize, Deserialize)]
135#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
136pub struct ScidStruct {
137    pub scid: Scid,
138}
139
140/// Upgradeable API struct for multiple scids.
141#[derive(Debug, PartialEq, Serialize, Deserialize)]
142#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
143pub struct Scids {
144    pub scids: Vec<Scid>,
145}
146
147/// An upgradeable version of [`Option<Scid>`].
148#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
149pub struct MaybeScid {
150    pub maybe_scid: Option<Scid>,
151}
152
153/// Represents an entry in the `user_scid` table.
154#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
155#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
156pub struct UserScid {
157    pub node_pk: NodePk,
158    pub scid: Scid,
159}
160
161/// Upgradable API struct representing multiple `user_scid` entries.
162#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
163#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
164pub struct UserScids {
165    pub user_scids: Vec<UserScid>,
166}
167
168/// A request to get at least `min_scids` [`Scid`]s from the LSP, inclusive
169/// of any existing [`Scid`]s. The node requests this from the LSP when it
170/// detects that it needs the LSP to generate a few more.
171///
172/// Example:
173/// - Node detects it has 3 [`Scid`]s, but it wants 2 new scids to make 5 total.
174/// - Node sets `min_scids` to 5.
175/// - LSP sees that the node already has 3, generates 2 new ones, and returns 5.
176#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
177#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
178pub struct GetNewScidsRequest {
179    pub node_pk: NodePk,
180    pub min_scids: usize,
181}
182
183// --- impl UserPk --- //
184
185impl UserPk {
186    pub const fn new(inner: [u8; 32]) -> Self {
187        Self(inner)
188    }
189
190    pub const fn from_ref(inner: &[u8; 32]) -> &Self {
191        lexe_std::const_utils::const_ref_cast(inner)
192    }
193
194    pub const fn as_ed25519(&self) -> &ed25519::PublicKey {
195        ed25519::PublicKey::from_ref(&self.0)
196    }
197
198    pub fn short(&self) -> ShortUserPk {
199        ShortUserPk::from(self)
200    }
201
202    /// Used to quickly construct `UserPk`s for tests.
203    pub fn from_u64(v: u64) -> Self {
204        // Convert u64 to [u8; 8]
205        let bytes = v.to_le_bytes();
206
207        // Fill the first 8 bytes with the u64 bytes
208        let mut inner = [0u8; 32];
209        inner[0..8].copy_from_slice(&bytes);
210
211        Self(inner)
212    }
213
214    /// Used to compare inner `u64` values set during tests
215    pub fn to_u64(self) -> u64 {
216        let mut bytes = [0u8; 8];
217        bytes.copy_from_slice(&self.0[0..8]);
218        u64::from_le_bytes(bytes)
219    }
220}
221
222lexe_byte_array::impl_byte_array!(UserPk, 32);
223lexe_byte_array::impl_fromstr_fromhex!(UserPk, 32);
224lexe_byte_array::impl_debug_display_as_hex!(UserPk);
225
226// --- impl ShortUserPk --- //
227
228impl ShortUserPk {
229    pub const fn new(bytes: [u8; 4]) -> Self {
230        Self(bytes)
231    }
232
233    /// Whether this [`ShortUserPk`] is a prefix of the given [`UserPk`].
234    pub fn is_prefix_of(&self, long: &UserPk) -> bool {
235        self.0 == long.0[..4]
236    }
237}
238
239lexe_byte_array::impl_byte_array!(ShortUserPk, 4);
240lexe_byte_array::impl_fromstr_fromhex!(ShortUserPk, 4);
241lexe_byte_array::impl_debug_display_as_hex!(ShortUserPk);
242
243impl From<&UserPk> for ShortUserPk {
244    fn from(long: &UserPk) -> Self {
245        (long.0)[..4].try_into().map(Self).unwrap()
246    }
247}
248
249impl From<ed25519::PublicKey> for UserPk {
250    fn from(pk: ed25519::PublicKey) -> Self {
251        Self::new(pk.to_array())
252    }
253}
254
255// --- impl NodePk --- //
256
257impl NodePk {
258    pub fn inner(self) -> secp256k1::PublicKey {
259        self.0
260    }
261
262    pub fn as_inner(&self) -> &secp256k1::PublicKey {
263        &self.0
264    }
265
266    pub fn from_slice(bytes: &[u8]) -> Result<Self, secp256k1::Error> {
267        secp256k1::PublicKey::from_slice(bytes).map(Self)
268    }
269
270    pub fn from_inner_ref(pk: &secp256k1::PublicKey) -> &Self {
271        Self::ref_cast(pk)
272    }
273
274    pub fn to_array(&self) -> [u8; 33] {
275        self.0.serialize()
276    }
277
278    pub fn to_vec(&self) -> Vec<u8> {
279        self.0.serialize().to_vec()
280    }
281}
282
283impl FromStr for NodePk {
284    type Err = bitcoin::secp256k1::Error;
285    fn from_str(s: &str) -> Result<Self, Self::Err> {
286        // Delegate the FromStr impl
287        secp256k1::PublicKey::from_str(s).map(Self)
288    }
289}
290
291impl fmt::Display for NodePk {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        // Call into secp256k1::PublicKey's Display impl
294        write!(f, "{}", self.0)
295    }
296}
297
298impl fmt::Debug for NodePk {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        write!(f, "NodePk({self})")
301    }
302}
303
304impl From<secp256k1::PublicKey> for NodePk {
305    fn from(public_key: secp256k1::PublicKey) -> Self {
306        Self(public_key)
307    }
308}
309
310impl From<NodePk> for secp256k1::PublicKey {
311    fn from(node_pk: NodePk) -> secp256k1::PublicKey {
312        node_pk.0
313    }
314}
315
316#[cfg(any(test, feature = "test-utils"))]
317impl Arbitrary for NodePk {
318    type Parameters = ();
319    type Strategy = BoxedStrategy<Self>;
320
321    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
322        any::<FastRng>()
323            .prop_map(|mut rng| RootSeed::from_rng(&mut rng).derive_node_pk())
324            .boxed()
325    }
326}
327
328// -- impl NodePkProof -- //
329
330impl NodePkProof {
331    // msg := H(H(DSV) || node_pk)
332    fn message(node_pk: &NodePk) -> secp256k1::Message {
333        let node_pk_bytes = node_pk.0.serialize();
334        let hash = sha256::digest_many(&[
335            &NodePkProof::DOMAIN_SEPARATOR,
336            &node_pk_bytes,
337        ]);
338        secp256k1::Message::from_digest(hash.to_array())
339    }
340
341    /// Given a [`secp256k1::Keypair`], sign a new [`NodePkProof`]
342    /// Proof-of-Key-Possession for your key pair.
343    pub fn sign(node_key_pair: &secp256k1::Keypair) -> Self {
344        let node_pk = NodePk::from(node_key_pair.public_key());
345        let msg = Self::message(&node_pk);
346        let sig = SECP256K1.sign_ecdsa(&msg, &node_key_pair.secret_key());
347
348        Self { node_pk, sig }
349    }
350
351    /// Verify a [`NodePkProof`], getting the verified [`NodePk`] contained
352    /// inside on success.
353    pub fn verify(&self) -> Result<&NodePk, InvalidNodePkProofSignature> {
354        let msg = Self::message(&self.node_pk);
355        Secp256k1::verification_only()
356            .verify_ecdsa(&msg, &self.sig, &self.node_pk.0)
357            .map(|()| &self.node_pk)
358            .map_err(|_| InvalidNodePkProofSignature)
359    }
360
361    /// Dump this `NodePkProof` to a hex-encoded string. Please don't use this.
362    pub fn to_hex_string(&self) -> String {
363        hex::encode(&bcs::to_bytes(self).expect("Failed to serialize"))
364    }
365}
366
367impl Signable for NodePkProof {
368    const DOMAIN_SEPARATOR: [u8; 32] = array::pad(*b"LEXE-REALM::NodePkProof");
369}
370
371#[cfg(any(test, feature = "test-utils"))]
372impl Arbitrary for NodePkProof {
373    type Parameters = ();
374    type Strategy = BoxedStrategy<Self>;
375
376    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
377        any::<FastRng>()
378            .prop_map(|mut rng| {
379                let key_pair =
380                    RootSeed::from_rng(&mut rng).derive_node_key_pair();
381                NodePkProof::sign(&key_pair)
382            })
383            .boxed()
384    }
385}
386
387// --- impl Scid --- //
388
389impl Scid {
390    /// Some platforms don't support unsigned ints, but they support [`i64`],
391    /// so this can be used to convert to the type suitable for that platform.
392    pub fn to_i64(&self) -> i64 {
393        let bytes = self.0.to_le_bytes();
394        i64::from_le_bytes(bytes)
395    }
396
397    /// Some platforms don't support unsigned ints, but they support [`i64`],
398    /// so this can be used to convert from the type suitable for that platform.
399    pub fn from_i64(bytes_i64: i64) -> Self {
400        let bytes = bytes_i64.to_le_bytes();
401        Self(u64::from_le_bytes(bytes))
402    }
403}
404
405impl FromStr for Scid {
406    type Err = anyhow::Error;
407    fn from_str(s: &str) -> Result<Self, Self::Err> {
408        let (block, rest) = s
409            .split_once('x')
410            .with_context(|| format!("Bad scid '{s}'; want BLOCKxTXxVOUT"))?;
411        let (tx, vout) = rest
412            .split_once('x')
413            .with_context(|| format!("Bad scid '{s}'"))?;
414        scid_utils::scid_from_parts(
415            block.parse().context("Bad scid block")?,
416            tx.parse().context("Bad scid tx index")?,
417            vout.parse().context("Bad scid vout")?,
418        )
419        .map(Self)
420        .map_err(|e| anyhow!("Bad scid '{s}': {e:?}"))
421    }
422}
423
424/// The `BLOCKxTXxVOUT` form in which most LN implementations display scids.
425impl fmt::Display for Scid {
426    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427        let block = scid_utils::block_from_scid(self.0);
428        let tx = scid_utils::tx_index_from_scid(self.0);
429        let vout = scid_utils::vout_from_scid(self.0);
430        write!(f, "{block}x{tx}x{vout}")
431    }
432}
433
434impl From<i64> for Scid {
435    fn from(i: i64) -> Self {
436        Self::from_i64(i)
437    }
438}
439impl From<Scid> for i64 {
440    fn from(scid: Scid) -> Self {
441        scid.to_i64()
442    }
443}
444
445#[cfg(test)]
446mod test {
447    use proptest::{prop_assume, proptest};
448
449    use super::*;
450    use crate::test_utils::roundtrip;
451
452    #[test]
453    fn user_node_pk_ser_examples() {
454        let mut rng = FastRng::from_u64(811011698);
455        let root_seed = RootSeed::from_rng(&mut rng);
456        let user_pk = root_seed.derive_user_pk();
457        let node_key_pair = root_seed.derive_node_key_pair();
458        let node_pk = NodePk(node_key_pair.public_key());
459        let node_pk_proof = NodePkProof::sign(&node_key_pair);
460
461        assert_eq!(
462            "52b999003525a3d905f9916eff26cee6625a3976fc25270ce5b3e79aa3c16f45",
463            user_pk.to_string()
464        );
465        assert_eq!(
466            "024de9a91aaf32588a7b0bb97ba7fad3db22fcfe62a52bc2b2d389c5fa9d946e1b",
467            node_pk.to_string(),
468        );
469        assert_eq!(
470            "024de9a91aaf32588a7b0bb97ba7fad3db22fcfe62a52bc2b2d389c5fa9d946e1b46304402206f762d23d206f3af2ffa452a71a11bca3df68838408851ab77931d7eb7fa1ef6022057141408428d6885d00ca6ca50e6d702aeab227c1550135be5fce4af4e726736",
471            node_pk_proof.to_hex_string(),
472        );
473    }
474
475    #[test]
476    fn user_pk_consistent() {
477        let user_pk1 = UserPk::new(hex::decode_const(
478            b"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
479        ));
480        let user_pk2 = UserPk::new(hex::decode_const(
481            b"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
482        ));
483        assert_eq!(user_pk1, user_pk2);
484    }
485
486    #[test]
487    fn user_pk_human_readable() {
488        roundtrip::fromstr_display_roundtrip_proptest::<UserPk>();
489    }
490
491    #[test]
492    fn user_pk_json() {
493        roundtrip::json_string_roundtrip_proptest::<UserPk>();
494    }
495
496    #[test]
497    fn node_pk_human_readable() {
498        roundtrip::fromstr_display_roundtrip_proptest::<NodePk>();
499    }
500
501    #[test]
502    fn node_pk_json() {
503        roundtrip::json_string_roundtrip_proptest::<NodePk>();
504    }
505
506    #[test]
507    fn node_pk_proof_bcs() {
508        roundtrip::bcs_roundtrip_proptest::<NodePkProof>();
509    }
510
511    #[test]
512    fn node_pk_proofs_verify() {
513        let arb_mutation = any::<Vec<u8>>()
514            .prop_filter("can't be empty or all zeroes", |m| {
515                !m.is_empty() && !m.iter().all(|x| x == &0u8)
516            });
517
518        proptest!(|(
519            mut rng: FastRng,
520            mut_offset in any::<usize>(),
521            mut mutation in arb_mutation,
522        )| {
523            let node_key_pair = RootSeed::from_rng(&mut rng)
524                .derive_node_key_pair();
525            let node_pk1 = NodePk::from(node_key_pair.public_key());
526
527            let proof1 = NodePkProof::sign(&node_key_pair);
528            let proof2 = NodePkProof::sign(&node_key_pair);
529
530            // signing should be deterministic
531            assert_eq!(proof1, proof2);
532
533            // valid proof should always verify
534            let node_pk2 = proof1.verify().unwrap();
535            assert_eq!(&node_pk1, node_pk2);
536
537            let mut proof_bytes = bcs::to_bytes(&proof1).unwrap();
538            // println!("{}", hex::encode(&proof_bytes));
539
540            // mutation must not be idempotent (otherwise the proof won't change
541            // and will actually verify).
542            mutation.truncate(proof_bytes.len());
543            prop_assume!(
544                !mutation.is_empty() && !mutation.iter().all(|x| x == &0)
545            );
546
547            // xor in the mutation bytes to the proof to modify it. any modified
548            // bit should cause the verification to fail.
549            for (idx_mut, m) in mutation.into_iter().enumerate() {
550                let idx_sig = idx_mut
551                    .wrapping_add(mut_offset) % proof_bytes.len();
552                proof_bytes[idx_sig] ^= m;
553            }
554
555            // mutated proof should always fail to deserialize or verify.
556            bcs::from_bytes::<NodePkProof>(&proof_bytes)
557                .map_err(anyhow::Error::new)
558                .and_then(|proof| {
559                    proof.verify()
560                        .map(|_| ())
561                        .map_err(anyhow::Error::new)
562                })
563                .unwrap_err();
564        });
565    }
566
567    #[test]
568    fn scid_basic() {
569        let scid = Scid(69);
570        assert_eq!(serde_json::to_string(&scid).unwrap(), "69");
571    }
572
573    #[test]
574    fn scid_human_roundtrip() {
575        let scid = Scid::from_str("802416x1618x1").unwrap();
576        assert_eq!(scid.to_string(), "802416x1618x1");
577    }
578
579    #[test]
580    fn scid_roundtrips() {
581        roundtrip::json_string_roundtrip_proptest::<Scid>();
582        roundtrip::fromstr_display_roundtrip_proptest::<Scid>();
583    }
584
585    #[test]
586    fn user_scid_roundtrips() {
587        roundtrip::json_value_roundtrip_proptest::<UserScid>();
588    }
589
590    #[test]
591    fn user_pk_struct_roundtrip() {
592        roundtrip::query_string_roundtrip_proptest::<UserPkStruct>();
593    }
594
595    #[test]
596    fn node_pk_struct_roundtrip() {
597        roundtrip::query_string_roundtrip_proptest::<NodePkStruct>();
598    }
599
600    #[test]
601    fn scid_qs_roundtrip() {
602        roundtrip::query_string_roundtrip_proptest::<ScidStruct>();
603        roundtrip::query_string_roundtrip_proptest::<GetNewScidsRequest>();
604    }
605}