Skip to main content

lexe_common/api/
user.rs

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