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}