Skip to main content

lexe_common/api/
provision.rs

1use std::fmt;
2
3use lexe_serde::hexstr_or_bytes_opt;
4#[cfg(test)]
5use proptest_derive::Arbitrary;
6use serde::{Deserialize, Serialize};
7
8#[cfg(test)]
9use crate::test_utils::arbitrary;
10use crate::{env::DeployEnv, ln::network::Network, root_seed::RootSeed};
11
12/// The client sends this request to the provisioning node.
13#[derive(Serialize, Deserialize)]
14// Only impl PartialEq in tests since root seed comparison is not constant time.
15#[cfg_attr(test, derive(PartialEq, Arbitrary))]
16pub struct NodeProvisionRequest {
17    /// The secret root seed the client wants to provision into the node.
18    pub root_seed: RootSeed,
19    /// The [`DeployEnv`] that this [`RootSeed`] should be bound to.
20    pub deploy_env: DeployEnv,
21    /// The [`Network`] that this [`RootSeed`] should be bound to.
22    pub network: Network,
23    /// The auth `code` which can used to obtain a set of GDrive credentials.
24    /// - Applicable only in staging/prod.
25    /// - If provided, the provisioning node will acquire the full set of
26    ///   GDrive credentials and persist them (encrypted ofc) in Lexe's DB.
27    /// - If NOT provided, the provisioning node will attempt to use a set of
28    ///   GDrive credentials which have already been persisted to Lexe's DB.
29    #[cfg_attr(test, proptest(strategy = "arbitrary::any_option_string()"))]
30    pub google_auth_code: Option<String>,
31    /// Whether this provision instance is allowed to access the user's
32    /// `GoogleVfs`. In order to ensure that different provision instances do
33    /// not overwrite each other's updates to the `GoogleVfs`, this paramater
34    /// must only be `true` for at most one provision instance at a time.
35    ///
36    /// - The mobile app must always set this to `true`, and must ensure that
37    ///   it is only (re-)provisioning one instance at a time. Node version
38    ///   approval and revocation (which requires mutating the `GoogleVfs`) can
39    ///   only be handled if this is set to `true`.
40    /// - Running nodes, which initiate root seed replication, must always set
41    ///   this to `false`, so that replicating instances will not overwrite
42    ///   updates made by (re-)provisioning instances.
43    ///
44    /// NOTE that it is always possible that while this instance is
45    /// provisioning, the user's node is also running. Even when this parameter
46    /// is `true`, the provision instance must be careful not to mutate
47    /// `GoogleVfs` data which can also be mutated by a running user node,
48    /// unless a persistence race between the provision and run modes is
49    /// acceptable.
50    ///
51    /// See `GoogleVfs::gid_cache` for more info on GVFS consistency.
52    pub allow_gvfs_access: bool,
53    /// The password-encrypted [`RootSeed`] which can be backed up in
54    /// GDrive.
55    /// - Applicable only in staging/prod, and if GDrive is enabled.
56    /// - Requires `allow_gvfs_access=true` if `Some`; errors otherwise.
57    /// - If `Some`, and GDrive is enabled, the provision instance will back up
58    ///   this encrypted [`RootSeed`] in Google Drive. If a backup already
59    ///   exists, it is overwritten.
60    /// - If `None`, and GDrive is enabled, and we are missing a backup,
61    ///   provision will error.
62    /// - The mobile app should set this to `Some` at least on the very first
63    ///   provision. The mobile app can also pass `None` to avoid unnecessary
64    ///   work when it is known that the user already has a root seed backup.
65    /// - Replication (from running nodes) should always set this to `None`.
66    /// - We require the client to password-encrypt prior to sending the
67    ///   provision request to prevent leaking the length of the password. It
68    ///   also shifts the burden of running the 600K HMAC iterations from the
69    ///   provision instance to the mobile app.
70    #[serde(with = "hexstr_or_bytes_opt")]
71    pub encrypted_seed: Option<Vec<u8>>,
72}
73
74impl fmt::Debug for NodeProvisionRequest {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        f.write_str("NodeProvisionRequest { .. }")
77    }
78}
79
80#[cfg(test)]
81mod test {
82    use lexe_crypto::rng::FastRng;
83
84    use super::*;
85    use crate::test_utils::roundtrip;
86
87    #[test]
88    fn test_node_provision_request_sample() {
89        let mut rng = FastRng::from_u64(12345);
90        let req = NodeProvisionRequest {
91            root_seed: RootSeed::from_rng(&mut rng),
92            deploy_env: DeployEnv::Dev,
93            network: Network::Regtest,
94            google_auth_code: Some("auth_code".to_owned()),
95            allow_gvfs_access: false,
96            encrypted_seed: None,
97        };
98        let actual = serde_json::to_value(&req).unwrap();
99        let expected = serde_json::json!({
100            "root_seed": "0a7d28d375bc07250ca30e015a808a6d70d43c5a55c4d5828cdeacca640191a1",
101            "deploy_env": "dev",
102            "network": "regtest",
103            "google_auth_code": "auth_code",
104            "allow_gvfs_access": false,
105            "encrypted_seed": null,
106        });
107        assert_eq!(&actual, &expected);
108    }
109
110    #[test]
111    fn test_node_provision_request_json_canonical() {
112        roundtrip::json_value_roundtrip_proptest::<NodeProvisionRequest>();
113    }
114}