|
| 1 | +import os |
| 2 | +import sys |
| 3 | +import string |
| 4 | +from pathlib import Path |
1 | 5 | import bip39 # type: ignore |
2 | 6 | from glclient import Credentials, Signer, Scheduler # type: ignore |
3 | 7 | from pathlib import Path |
4 | 8 | from pyln import grpc as clnpb # type: ignore |
5 | 9 | import secrets # Make sure to use cryptographically sound randomness |
6 | 10 |
|
7 | | - |
8 | | -# ---8<--- [start: upgrade_device_certs_to_creds] |
9 | | -def upgrade_device_certs_to_creds( |
10 | | - scheduler: Scheduler, signer: Signer, device_cert: bytes, device_key: bytes |
11 | | -): |
12 | | - device_creds = Credentials.from_parts(device_cert, device_key, "") |
13 | | - return device_creds.upgrade(scheduler.inner, signer.inner) |
14 | | -# ---8<--- [end: upgrade_device_certs_to_creds] |
| 11 | +GL_NOBODY_CRT = os.environ.get('GL_NOBODY_CRT') |
| 12 | +GL_NOBODY_KEY = os.environ.get('GL_NOBODY_KEY') |
| 13 | +NETWORK="regtest" |
| 14 | +TEST_NODE_DATA_DIR_1="/tmp/gltests/node1" |
15 | 15 |
|
16 | 16 |
|
17 | 17 | def save_to_file(file_name: str, data: bytes) -> None: |
| 18 | + file_name = Path(TEST_NODE_DATA_DIR_1) / file_name |
| 19 | + os.makedirs(file_name.parent, exist_ok=True) |
18 | 20 | with open(file_name, "wb") as file: |
19 | 21 | file.write(data) |
20 | 22 |
|
21 | 23 |
|
22 | 24 | def read_file(file_name: str) -> bytes: |
| 25 | + file_name = Path(TEST_NODE_DATA_DIR_1) / file_name |
23 | 26 | with open(file_name, "rb") as file: |
24 | 27 | return file.read() |
25 | 28 |
|
26 | 29 |
|
| 30 | +def upgrade_device_certs_to_creds(scheduler: Scheduler, signer: Signer, device_creds_file_path: str): |
| 31 | + # ---8<--- [start: upgrade_device_certs_to_creds] |
| 32 | + device_creds = Credentials.from_path(device_creds_file_path) |
| 33 | + upgraded_device_creds = device_creds.upgrade(scheduler.inner, signer.inner) |
| 34 | + # ---8<--- [end: upgrade_device_certs_to_creds] |
| 35 | + return upgraded_device_creds |
| 36 | + |
| 37 | + |
27 | 38 | def create_seed() -> bytes: |
28 | 39 | # ---8<--- [start: create_seed] |
29 | 40 | rand = secrets.randbits(256).to_bytes(32, "big") # 32 bytes of randomness |
30 | 41 |
|
31 | | - # Show seed phrase to user |
| 42 | + # Seed phrase for user |
32 | 43 | phrase = bip39.encode_bytes(rand) |
33 | 44 |
|
34 | 45 | seed = bip39.phrase_to_seed(phrase)[:32] # Only need the first 32 bytes |
35 | 46 |
|
36 | 47 | # Store the seed on the filesystem, or secure configuration system |
37 | | - save_to_file("seed", seed) |
| 48 | + save_to_file("hsm_secret", seed) |
38 | 49 |
|
39 | 50 | # ---8<--- [end: create_seed] |
40 | 51 | return seed |
41 | 52 |
|
42 | | -def register_node(seed: bytes, developer_cert_path: str, developer_key_path: str) -> None: |
| 53 | + |
| 54 | +def load_developer_creds(): |
43 | 55 | # ---8<--- [start: dev_creds] |
| 56 | + developer_cert_path = os.environ.get('GL_NOBODY_CRT') |
| 57 | + developer_key_path = os.environ.get('GL_NOBODY_KEY') |
44 | 58 | developer_cert = Path(developer_cert_path).open(mode="rb").read() |
45 | 59 | developer_key = Path(developer_key_path).open(mode="rb").read() |
46 | 60 |
|
47 | 61 | developer_creds = Credentials.nobody_with(developer_cert, developer_key) |
48 | 62 | # ---8<--- [end: dev_creds] |
| 63 | + return developer_creds |
| 64 | + |
49 | 65 |
|
| 66 | +def register_node(seed: bytes, developer_creds: Credentials): |
50 | 67 | # ---8<--- [start: init_signer] |
51 | | - network = "bitcoin" |
52 | | - signer = Signer(seed, network, developer_creds) |
| 68 | + signer = Signer(seed, NETWORK, developer_creds) |
53 | 69 | # ---8<--- [end: init_signer] |
54 | 70 |
|
55 | 71 | # ---8<--- [start: register_node] |
56 | | - scheduler = Scheduler(network, developer_creds) |
| 72 | + scheduler = Scheduler(NETWORK, developer_creds) |
57 | 73 |
|
58 | 74 | # Passing in the signer is required because the client needs to prove |
59 | 75 | # ownership of the `node_id` |
60 | 76 | registration_response = scheduler.register(signer, invite_code=None) |
61 | 77 |
|
62 | 78 | # ---8<--- [start: device_creds] |
63 | 79 | device_creds = Credentials.from_bytes(registration_response.creds) |
64 | | - save_to_file("creds", device_creds.to_bytes()) |
| 80 | + save_to_file("credentials.gfs", device_creds.to_bytes()) |
65 | 81 | # ---8<--- [end: device_creds] |
66 | | - |
67 | 82 | # ---8<--- [end: register_node] |
| 83 | + return scheduler, device_creds, signer |
68 | 84 |
|
| 85 | + |
| 86 | +def get_node(scheduler: Scheduler, device_creds: Credentials) -> dict: |
69 | 87 | # ---8<--- [start: get_node] |
70 | 88 | scheduler = scheduler.authenticate(device_creds) |
71 | 89 | node = scheduler.node() |
72 | 90 | # ---8<--- [end: get_node] |
| 91 | + return node |
73 | 92 |
|
74 | 93 |
|
75 | | -# TODO: Remove node_id from signature and add node_id to credentials |
76 | | -def start_node(device_creds_path: str, node_id: bytes) -> None: |
| 94 | +def start_node(device_creds_file_path: str) -> None: |
77 | 95 | # ---8<--- [start: start_node] |
78 | | - network = "bitcoin" |
79 | | - device_creds = Credentials.from_path(device_creds_path) |
80 | | - scheduler = Scheduler(network, device_creds) |
| 96 | + device_creds = Credentials.from_path(device_creds_file_path) |
| 97 | + scheduler = Scheduler(NETWORK, device_creds) |
81 | 98 |
|
82 | 99 | node = scheduler.node() |
83 | 100 | # ---8<--- [end: start_node] |
84 | 101 |
|
85 | 102 | # ---8<--- [start: list_peers] |
86 | | - info = node.get_info() |
87 | | - peers = node.list_peers() |
| 103 | + getinfo_response = node.get_info() |
| 104 | + listpeers_response = node.list_peers() |
88 | 105 | # ---8<--- [end: list_peers] |
89 | 106 |
|
90 | 107 | # ---8<--- [start: start_signer] |
91 | | - seed = read_file("seed") |
92 | | - signer = Signer(seed, network, device_creds) |
| 108 | + seed = read_file("hsm_secret") |
| 109 | + signer = Signer(seed, NETWORK, device_creds) |
93 | 110 |
|
94 | 111 | signer.run_in_thread() |
95 | 112 | # ---8<--- [end: start_signer] |
96 | 113 |
|
97 | 114 | # ---8<--- [start: create_invoice] |
98 | | - node.invoice( |
| 115 | + invoice_response = node.invoice( |
99 | 116 | amount_msat=clnpb.AmountOrAny(amount=clnpb.Amount(msat=10000)), |
100 | | - description="description", |
101 | | - label="label", |
| 117 | + description="description".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(10)), |
| 118 | + label="label".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(10)), |
102 | 119 | ) |
103 | 120 | # ---8<--- [end: create_invoice] |
| 121 | + return getinfo_response, listpeers_response, invoice_response |
104 | 122 |
|
105 | 123 |
|
106 | 124 | def recover_node(developer_cert: bytes, developer_key: bytes) -> None: |
107 | 125 | # ---8<--- [start: recover_node] |
108 | | - seed = read_file("seed") |
109 | | - network = "bitcoin" |
| 126 | + seed = read_file("hsm_secret") |
110 | 127 | signer_creds = Credentials.nobody_with(developer_cert, developer_key) |
111 | | - signer = Signer(seed, network, signer_creds) |
112 | | - |
113 | | - scheduler = Scheduler( |
114 | | - network, |
115 | | - signer_creds, |
116 | | - ) |
117 | | - |
118 | | - scheduler_creds = signer_creds.upgrade(scheduler.inner, signer.inner) |
119 | | - |
120 | | - scheduler = Scheduler( |
121 | | - network, |
122 | | - scheduler_creds, |
123 | | - ) |
124 | | - |
125 | | - scheduler.recover(signer) |
| 128 | + signer = Signer(seed, NETWORK, signer_creds) |
| 129 | + scheduler = Scheduler(NETWORK, signer_creds) |
| 130 | + recover_response = scheduler.recover(signer) |
126 | 131 | # ---8<--- [end: recover_node] |
| 132 | + device_creds = Credentials.from_bytes(recover_response.creds) |
| 133 | + save_to_file("credentials.gfs", device_creds.to_bytes()) |
| 134 | + return scheduler, device_creds, signer |
| 135 | + |
| 136 | + |
| 137 | +def main(): |
| 138 | + # Verify files exist |
| 139 | + if not GL_NOBODY_CRT or not os.path.exists(GL_NOBODY_CRT): |
| 140 | + print(f"Error: Developer certificate not found at {GL_NOBODY_CRT}") |
| 141 | + sys.exit(1) |
| 142 | + |
| 143 | + if not GL_NOBODY_KEY or not os.path.exists(GL_NOBODY_KEY): |
| 144 | + print(f"Error: Developer key not found at {GL_NOBODY_KEY}") |
| 145 | + sys.exit(1) |
| 146 | + |
| 147 | + #Create seed |
| 148 | + print("Creating seed...") |
| 149 | + seed = create_seed() |
| 150 | + |
| 151 | + print("Loading developer credentials...") |
| 152 | + developer_creds = load_developer_creds() |
| 153 | + |
| 154 | + # Register node |
| 155 | + print("Registering node...") |
| 156 | + scheduler, device_creds, signer = register_node(seed, developer_creds) |
| 157 | + print("Node Registered!") |
| 158 | + |
| 159 | + # Get GL node |
| 160 | + print("Getting node information...") |
| 161 | + get_node(scheduler, device_creds) |
| 162 | + |
| 163 | + # Print node's information to check |
| 164 | + getinfo_response, listpeers_response, invoice_response = start_node(TEST_NODE_DATA_DIR_1 + "/credentials.gfs") |
| 165 | + print("Node pubkey:", getinfo_response.id.hex()) |
| 166 | + print("Peers list:", listpeers_response.peers) |
| 167 | + print("Invoice created:", invoice_response.bolt11) |
| 168 | + |
| 169 | + # Upgrade Certificates |
| 170 | + print("Upgrading certs...") |
| 171 | + upgrade_device_certs_to_creds(scheduler, signer, TEST_NODE_DATA_DIR_1 + "/credentials.gfs") |
| 172 | + |
| 173 | + # Recover the node |
| 174 | + print("Recovering node...") |
| 175 | + scheduler, device_creds, signer = recover_node(Path(GL_NOBODY_CRT).open(mode="rb").read(), Path(GL_NOBODY_KEY).open(mode="rb").read()) |
| 176 | + print("Node Recovered!") |
| 177 | + |
| 178 | + # Print node's information to check |
| 179 | + getinfo_response, listpeers_response, invoice_response = start_node(TEST_NODE_DATA_DIR_1 + "/credentials.gfs") |
| 180 | + print("Node pubkey:", getinfo_response.id.hex()) |
| 181 | + print("All steps completed successfully!") |
| 182 | + |
| 183 | + |
| 184 | +if __name__ == "__main__": |
| 185 | + main() |
0 commit comments