neuronum 9.0.0__tar.gz → 10.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of neuronum might be problematic. Click here for more details.
- neuronum-10.0.0/PKG-INFO +157 -0
- neuronum-10.0.0/README.md +122 -0
- neuronum-10.0.0/cli/main.py +319 -0
- neuronum-10.0.0/neuronum/__init__.py +1 -0
- neuronum-10.0.0/neuronum/neuronum.py +745 -0
- neuronum-10.0.0/neuronum.egg-info/PKG-INFO +157 -0
- {neuronum-9.0.0 → neuronum-10.0.0}/neuronum.egg-info/requires.txt +2 -2
- {neuronum-9.0.0 → neuronum-10.0.0}/setup.py +4 -4
- neuronum-9.0.0/PKG-INFO +0 -124
- neuronum-9.0.0/README.md +0 -89
- neuronum-9.0.0/cli/main.py +0 -1035
- neuronum-9.0.0/neuronum/__init__.py +0 -1
- neuronum-9.0.0/neuronum/neuronum.py +0 -364
- neuronum-9.0.0/neuronum.egg-info/PKG-INFO +0 -124
- {neuronum-9.0.0 → neuronum-10.0.0}/LICENSE.md +0 -0
- {neuronum-9.0.0 → neuronum-10.0.0}/cli/__init__.py +0 -0
- {neuronum-9.0.0 → neuronum-10.0.0}/neuronum.egg-info/SOURCES.txt +0 -0
- {neuronum-9.0.0 → neuronum-10.0.0}/neuronum.egg-info/dependency_links.txt +0 -0
- {neuronum-9.0.0 → neuronum-10.0.0}/neuronum.egg-info/entry_points.txt +0 -0
- {neuronum-9.0.0 → neuronum-10.0.0}/neuronum.egg-info/top_level.txt +0 -0
- {neuronum-9.0.0 → neuronum-10.0.0}/setup.cfg +0 -0
neuronum-10.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neuronum
|
|
3
|
+
Version: 10.0.0
|
|
4
|
+
Summary: An E2EE Data Engine
|
|
5
|
+
Home-page: https://neuronum.net
|
|
6
|
+
Author: Neuronum Cybernetics
|
|
7
|
+
Author-email: welcome@neuronum.net
|
|
8
|
+
Project-URL: GitHub, https://github.com/neuronumcybernetics/neuronum
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.md
|
|
15
|
+
Requires-Dist: aiohttp
|
|
16
|
+
Requires-Dist: aiofiles
|
|
17
|
+
Requires-Dist: websockets
|
|
18
|
+
Requires-Dist: click
|
|
19
|
+
Requires-Dist: questionary
|
|
20
|
+
Requires-Dist: python-dotenv
|
|
21
|
+
Requires-Dist: requests
|
|
22
|
+
Requires-Dist: cryptography
|
|
23
|
+
Requires-Dist: bip_utils
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: author-email
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
Dynamic: project-url
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
Dynamic: summary
|
|
35
|
+
|
|
36
|
+
<h1 align="center">
|
|
37
|
+
<img src="https://neuronum.net/static/neuronum.svg" alt="Neuronum" width="80">
|
|
38
|
+
</h1>
|
|
39
|
+
<h4 align="center">Neuronum: An E2EE Data Engine</h4>
|
|
40
|
+
|
|
41
|
+
<p align="center">
|
|
42
|
+
<a href="https://neuronum.net">
|
|
43
|
+
<img src="https://img.shields.io/badge/Website-Neuronum-blue" alt="Website">
|
|
44
|
+
</a>
|
|
45
|
+
<a href="https://github.com/neuronumcybernetics/neuronum">
|
|
46
|
+
<img src="https://img.shields.io/badge/Docs-Read%20now-green" alt="Documentation">
|
|
47
|
+
</a>
|
|
48
|
+
<a href="https://pypi.org/project/neuronum/">
|
|
49
|
+
<img src="https://img.shields.io/pypi/v/neuronum.svg" alt="PyPI Version">
|
|
50
|
+
</a><br>
|
|
51
|
+
<img src="https://img.shields.io/badge/Python-3.8%2B-yellow" alt="Python Version">
|
|
52
|
+
<a href="https://github.com/neuronumcybernetics/neuronum/blob/main/LICENSE.md">
|
|
53
|
+
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License">
|
|
54
|
+
</a>
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
------------------
|
|
58
|
+
|
|
59
|
+
### **Getting Started into the Neuronum Network**
|
|
60
|
+
In this brief getting started guide, you will:
|
|
61
|
+
- [Learn about Neuronum](#about-neuronum)
|
|
62
|
+
- [Connect to the Network](#connect-to-neuronum)
|
|
63
|
+
- [Transmit Data Securely](#transmit-data-securely)
|
|
64
|
+
- [Receive Data Securely](#receive-data-securely)
|
|
65
|
+
|
|
66
|
+
------------------
|
|
67
|
+
|
|
68
|
+
### **About Neuronum**
|
|
69
|
+
Neuronum is a real-time, end-to-end encrypted data engine built in Python. It enables secure communication between devices and services by encrypting data client-side using the recipient's public key. Encrypted messages are transmitted through a passive relay server and decrypted on the recipient’s device using its private key. The relay server facilitates connectivity but cannot access or alter the content of messages.
|
|
70
|
+
|
|
71
|
+
### Requirements
|
|
72
|
+
- Python >= 3.8
|
|
73
|
+
|
|
74
|
+
------------------
|
|
75
|
+
|
|
76
|
+
### **Connect To Neuronum**
|
|
77
|
+
Installation (optional but recommended: create a virtual environment)
|
|
78
|
+
```sh
|
|
79
|
+
pip install neuronum
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Create your Cell (your secure identity):
|
|
83
|
+
```sh
|
|
84
|
+
neuronum create-cell
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
or
|
|
88
|
+
|
|
89
|
+
Connect an existing Cell (your secure identity):
|
|
90
|
+
```sh
|
|
91
|
+
neuronum connect-cell
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
------------------
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
### **Transmit Data Securely**
|
|
98
|
+
```python
|
|
99
|
+
import asyncio
|
|
100
|
+
from neuronum import Cell
|
|
101
|
+
|
|
102
|
+
async def main():
|
|
103
|
+
|
|
104
|
+
async with Cell() as cell:
|
|
105
|
+
|
|
106
|
+
data = {
|
|
107
|
+
"some": "data" # Replace with your actual payload
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Use activate_tx() if you expect a response from the other cell
|
|
111
|
+
tx_response = await cell.activate_tx("id::cell", data)
|
|
112
|
+
print(tx_response)
|
|
113
|
+
|
|
114
|
+
# Stream data to another cell (no response expected)
|
|
115
|
+
await cell.stream("id::cell", data)
|
|
116
|
+
|
|
117
|
+
if __name__ == '__main__':
|
|
118
|
+
asyncio.run(main())
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
### **Receive Data Securely**
|
|
123
|
+
```python
|
|
124
|
+
import asyncio
|
|
125
|
+
from neuronum import Cell
|
|
126
|
+
|
|
127
|
+
async def main():
|
|
128
|
+
async with Cell() as cell:
|
|
129
|
+
|
|
130
|
+
async for transmitter in cell.sync():
|
|
131
|
+
ts_str = transmitter.get("time")
|
|
132
|
+
data = transmitter.get("data")
|
|
133
|
+
transmitter_id = transmitter.get("transmitter_id")
|
|
134
|
+
client_public_key = data.get("public_key", {})
|
|
135
|
+
|
|
136
|
+
response_data = {
|
|
137
|
+
"json": "Data Received Securely - Your request was processed successfully",
|
|
138
|
+
"html": """
|
|
139
|
+
<!DOCTYPE html>
|
|
140
|
+
<html lang="en">
|
|
141
|
+
<head>
|
|
142
|
+
<meta charset="UTF-8">
|
|
143
|
+
<title>Secure Data Response</title>
|
|
144
|
+
</head>
|
|
145
|
+
<body>
|
|
146
|
+
<h3>Data Received Securely</h3>
|
|
147
|
+
<p>Your request was processed successfully.</p>
|
|
148
|
+
</body>
|
|
149
|
+
</html>
|
|
150
|
+
"""
|
|
151
|
+
}
|
|
152
|
+
await cell.tx_response(transmitter_id, response_data, client_public_key)
|
|
153
|
+
|
|
154
|
+
if __name__ == '__main__':
|
|
155
|
+
asyncio.run(main())
|
|
156
|
+
```
|
|
157
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<img src="https://neuronum.net/static/neuronum.svg" alt="Neuronum" width="80">
|
|
3
|
+
</h1>
|
|
4
|
+
<h4 align="center">Neuronum: An E2EE Data Engine</h4>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://neuronum.net">
|
|
8
|
+
<img src="https://img.shields.io/badge/Website-Neuronum-blue" alt="Website">
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://github.com/neuronumcybernetics/neuronum">
|
|
11
|
+
<img src="https://img.shields.io/badge/Docs-Read%20now-green" alt="Documentation">
|
|
12
|
+
</a>
|
|
13
|
+
<a href="https://pypi.org/project/neuronum/">
|
|
14
|
+
<img src="https://img.shields.io/pypi/v/neuronum.svg" alt="PyPI Version">
|
|
15
|
+
</a><br>
|
|
16
|
+
<img src="https://img.shields.io/badge/Python-3.8%2B-yellow" alt="Python Version">
|
|
17
|
+
<a href="https://github.com/neuronumcybernetics/neuronum/blob/main/LICENSE.md">
|
|
18
|
+
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License">
|
|
19
|
+
</a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
------------------
|
|
23
|
+
|
|
24
|
+
### **Getting Started into the Neuronum Network**
|
|
25
|
+
In this brief getting started guide, you will:
|
|
26
|
+
- [Learn about Neuronum](#about-neuronum)
|
|
27
|
+
- [Connect to the Network](#connect-to-neuronum)
|
|
28
|
+
- [Transmit Data Securely](#transmit-data-securely)
|
|
29
|
+
- [Receive Data Securely](#receive-data-securely)
|
|
30
|
+
|
|
31
|
+
------------------
|
|
32
|
+
|
|
33
|
+
### **About Neuronum**
|
|
34
|
+
Neuronum is a real-time, end-to-end encrypted data engine built in Python. It enables secure communication between devices and services by encrypting data client-side using the recipient's public key. Encrypted messages are transmitted through a passive relay server and decrypted on the recipient’s device using its private key. The relay server facilitates connectivity but cannot access or alter the content of messages.
|
|
35
|
+
|
|
36
|
+
### Requirements
|
|
37
|
+
- Python >= 3.8
|
|
38
|
+
|
|
39
|
+
------------------
|
|
40
|
+
|
|
41
|
+
### **Connect To Neuronum**
|
|
42
|
+
Installation (optional but recommended: create a virtual environment)
|
|
43
|
+
```sh
|
|
44
|
+
pip install neuronum
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Create your Cell (your secure identity):
|
|
48
|
+
```sh
|
|
49
|
+
neuronum create-cell
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
or
|
|
53
|
+
|
|
54
|
+
Connect an existing Cell (your secure identity):
|
|
55
|
+
```sh
|
|
56
|
+
neuronum connect-cell
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
------------------
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### **Transmit Data Securely**
|
|
63
|
+
```python
|
|
64
|
+
import asyncio
|
|
65
|
+
from neuronum import Cell
|
|
66
|
+
|
|
67
|
+
async def main():
|
|
68
|
+
|
|
69
|
+
async with Cell() as cell:
|
|
70
|
+
|
|
71
|
+
data = {
|
|
72
|
+
"some": "data" # Replace with your actual payload
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Use activate_tx() if you expect a response from the other cell
|
|
76
|
+
tx_response = await cell.activate_tx("id::cell", data)
|
|
77
|
+
print(tx_response)
|
|
78
|
+
|
|
79
|
+
# Stream data to another cell (no response expected)
|
|
80
|
+
await cell.stream("id::cell", data)
|
|
81
|
+
|
|
82
|
+
if __name__ == '__main__':
|
|
83
|
+
asyncio.run(main())
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
### **Receive Data Securely**
|
|
88
|
+
```python
|
|
89
|
+
import asyncio
|
|
90
|
+
from neuronum import Cell
|
|
91
|
+
|
|
92
|
+
async def main():
|
|
93
|
+
async with Cell() as cell:
|
|
94
|
+
|
|
95
|
+
async for transmitter in cell.sync():
|
|
96
|
+
ts_str = transmitter.get("time")
|
|
97
|
+
data = transmitter.get("data")
|
|
98
|
+
transmitter_id = transmitter.get("transmitter_id")
|
|
99
|
+
client_public_key = data.get("public_key", {})
|
|
100
|
+
|
|
101
|
+
response_data = {
|
|
102
|
+
"json": "Data Received Securely - Your request was processed successfully",
|
|
103
|
+
"html": """
|
|
104
|
+
<!DOCTYPE html>
|
|
105
|
+
<html lang="en">
|
|
106
|
+
<head>
|
|
107
|
+
<meta charset="UTF-8">
|
|
108
|
+
<title>Secure Data Response</title>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
<h3>Data Received Securely</h3>
|
|
112
|
+
<p>Your request was processed successfully.</p>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
|
115
|
+
"""
|
|
116
|
+
}
|
|
117
|
+
await cell.tx_response(transmitter_id, response_data, client_public_key)
|
|
118
|
+
|
|
119
|
+
if __name__ == '__main__':
|
|
120
|
+
asyncio.run(main())
|
|
121
|
+
```
|
|
122
|
+
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import questionary
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import requests
|
|
5
|
+
from cryptography.hazmat.primitives import hashes
|
|
6
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
7
|
+
from cryptography.hazmat.primitives import serialization
|
|
8
|
+
from cryptography.hazmat.backends import default_backend
|
|
9
|
+
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
|
|
10
|
+
import base64
|
|
11
|
+
import time
|
|
12
|
+
import hashlib
|
|
13
|
+
from bip_utils import Bip39MnemonicGenerator, Bip39SeedGenerator
|
|
14
|
+
from bip_utils import Bip39MnemonicValidator, Bip39Languages
|
|
15
|
+
|
|
16
|
+
# --- Configuration Constants ---
|
|
17
|
+
NEURONUM_PATH = Path.home() / ".neuronum"
|
|
18
|
+
ENV_FILE = NEURONUM_PATH / ".env"
|
|
19
|
+
PUBLIC_KEY_FILE = NEURONUM_PATH / "public_key.pem"
|
|
20
|
+
PRIVATE_KEY_FILE = NEURONUM_PATH / "private_key.pem"
|
|
21
|
+
API_BASE_URL = "https://neuronum.net/api"
|
|
22
|
+
|
|
23
|
+
# --- Utility Functions ---
|
|
24
|
+
|
|
25
|
+
def sign_message(private_key: EllipticCurvePrivateKey, message: bytes) -> str:
|
|
26
|
+
"""Signs a message using the given private key and returns a base64 encoded signature."""
|
|
27
|
+
try:
|
|
28
|
+
signature = private_key.sign(
|
|
29
|
+
message,
|
|
30
|
+
ec.ECDSA(hashes.SHA256())
|
|
31
|
+
)
|
|
32
|
+
return base64.b64encode(signature).decode()
|
|
33
|
+
except Exception as e:
|
|
34
|
+
click.echo(f"❌ Error signing message: {e}")
|
|
35
|
+
return ""
|
|
36
|
+
|
|
37
|
+
def derive_keys_from_mnemonic(mnemonic: str):
|
|
38
|
+
"""Derives EC-SECP256R1 keys from a BIP-39 mnemonic's seed."""
|
|
39
|
+
try:
|
|
40
|
+
seed = Bip39SeedGenerator(mnemonic).Generate()
|
|
41
|
+
# Hash the seed to get a deterministic and strong key derivation input
|
|
42
|
+
digest = hashlib.sha256(seed).digest()
|
|
43
|
+
int_key = int.from_bytes(digest, "big")
|
|
44
|
+
|
|
45
|
+
# Derive the private key
|
|
46
|
+
private_key = ec.derive_private_key(int_key, ec.SECP256R1(), default_backend())
|
|
47
|
+
public_key = private_key.public_key()
|
|
48
|
+
|
|
49
|
+
# Serialize keys to PEM format
|
|
50
|
+
pem_private = private_key.private_bytes(
|
|
51
|
+
encoding=serialization.Encoding.PEM,
|
|
52
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
53
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
pem_public = public_key.public_bytes(
|
|
57
|
+
encoding=serialization.Encoding.PEM,
|
|
58
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return private_key, public_key, pem_private, pem_public
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
click.echo(f"❌ Error generating keys from mnemonic: {e}")
|
|
65
|
+
return None, None, None, None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def save_credentials(host: str, mnemonic: str, pem_public: bytes, pem_private: bytes):
|
|
69
|
+
"""Saves host, mnemonic, and keys to the .neuronum directory."""
|
|
70
|
+
try:
|
|
71
|
+
NEURONUM_PATH.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
|
|
73
|
+
# Save .env with host and mnemonic (Sensitive data)
|
|
74
|
+
env_content = f"HOST={host}\nMNEMONIC=\"{mnemonic}\""
|
|
75
|
+
ENV_FILE.write_text(env_content)
|
|
76
|
+
|
|
77
|
+
# Save PEM files
|
|
78
|
+
PUBLIC_KEY_FILE.write_bytes(pem_public)
|
|
79
|
+
PRIVATE_KEY_FILE.write_bytes(pem_private)
|
|
80
|
+
|
|
81
|
+
return True
|
|
82
|
+
except Exception as e:
|
|
83
|
+
click.echo(f"❌ Error saving credentials: {e}")
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
def load_credentials():
|
|
87
|
+
"""Loads host, mnemonic, and private key from local files."""
|
|
88
|
+
credentials = {}
|
|
89
|
+
try:
|
|
90
|
+
# Load .env data (Host and Mnemonic)
|
|
91
|
+
if not ENV_FILE.exists():
|
|
92
|
+
click.echo("Error: No credentials found. Please create or connect a cell first.")
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
with open(ENV_FILE, "r") as f:
|
|
96
|
+
for line in f:
|
|
97
|
+
line = line.strip()
|
|
98
|
+
if "=" in line:
|
|
99
|
+
key, value = line.split("=", 1)
|
|
100
|
+
# Clean up quotes from mnemonic
|
|
101
|
+
credentials[key] = value.strip().strip('"')
|
|
102
|
+
|
|
103
|
+
credentials['host'] = credentials.get("HOST")
|
|
104
|
+
credentials['mnemonic'] = credentials.get("MNEMONIC")
|
|
105
|
+
|
|
106
|
+
# Load Private Key
|
|
107
|
+
with open(PRIVATE_KEY_FILE, "rb") as key_file:
|
|
108
|
+
private_key = serialization.load_pem_private_key(
|
|
109
|
+
key_file.read(),
|
|
110
|
+
password=None,
|
|
111
|
+
backend=default_backend()
|
|
112
|
+
)
|
|
113
|
+
credentials['private_key'] = private_key
|
|
114
|
+
credentials['public_key'] = private_key.public_key()
|
|
115
|
+
|
|
116
|
+
return credentials
|
|
117
|
+
|
|
118
|
+
except FileNotFoundError:
|
|
119
|
+
click.echo("Error: Credentials files are incomplete. Try deleting the '.neuronum' folder or reconnecting.")
|
|
120
|
+
return None
|
|
121
|
+
except Exception as e:
|
|
122
|
+
click.echo(f"Error loading credentials: {e}")
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
# --- CLI Group ---
|
|
126
|
+
|
|
127
|
+
@click.group()
|
|
128
|
+
def cli():
|
|
129
|
+
"""Neuronum CLI Tool for Community Cell management."""
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
# --- CLI Commands ---
|
|
133
|
+
|
|
134
|
+
@click.command()
|
|
135
|
+
def create_cell():
|
|
136
|
+
"""Creates a new Community Cell with a randomly generated key pair."""
|
|
137
|
+
|
|
138
|
+
# 1. Generate Mnemonic and Keys
|
|
139
|
+
mnemonic = Bip39MnemonicGenerator().FromWordsNumber(12)
|
|
140
|
+
private_key, public_key, pem_private, pem_public = derive_keys_from_mnemonic(mnemonic)
|
|
141
|
+
|
|
142
|
+
if not private_key:
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
# 2. Call API to Create Cell
|
|
146
|
+
click.echo("🔗 Requesting new cell creation from server...")
|
|
147
|
+
url = f"{API_BASE_URL}/create_cell"
|
|
148
|
+
create_data = {"public_key": pem_public.decode("utf-8")}
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
response = requests.post(url, json=create_data, timeout=10)
|
|
152
|
+
response.raise_for_status()
|
|
153
|
+
host = response.json().get("host")
|
|
154
|
+
|
|
155
|
+
except requests.exceptions.RequestException as e:
|
|
156
|
+
click.echo(f"❌ Error communicating with the server: {e}")
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
# 3. Save Credentials
|
|
160
|
+
if host:
|
|
161
|
+
if save_credentials(host, mnemonic, pem_public, pem_private):
|
|
162
|
+
click.echo("\n" + "=" * 50)
|
|
163
|
+
click.echo(" ✅ WELCOME TO NEURONUM! Cell Created Successfully.")
|
|
164
|
+
click.echo("=" * 50)
|
|
165
|
+
click.echo(f" Host: {host}")
|
|
166
|
+
click.echo(f" Mnemonic (CRITICAL! Back this up!):")
|
|
167
|
+
click.echo(f" {mnemonic}")
|
|
168
|
+
click.echo("-" * 50)
|
|
169
|
+
click.echo(f"Credentials saved to: {NEURONUM_PATH}")
|
|
170
|
+
else:
|
|
171
|
+
# Error saving credentials already echoed in helper
|
|
172
|
+
pass
|
|
173
|
+
else:
|
|
174
|
+
click.echo("❌ Error: Server did not return a host. Cell creation failed.")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@click.command()
|
|
178
|
+
def connect_cell():
|
|
179
|
+
"""Connects to an existing Community Cell using a 12-word mnemonic."""
|
|
180
|
+
|
|
181
|
+
# 1. Get and Validate Mnemonic
|
|
182
|
+
mnemonic = questionary.text("Enter your 12-word BIP-39 mnemonic (space separated):").ask()
|
|
183
|
+
|
|
184
|
+
if not mnemonic:
|
|
185
|
+
click.echo("Connection canceled.")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
mnemonic = " ".join(mnemonic.strip().split())
|
|
189
|
+
words = mnemonic.split()
|
|
190
|
+
|
|
191
|
+
if len(words) != 12:
|
|
192
|
+
click.echo("❌ Mnemonic must be exactly 12 words.")
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
if not Bip39MnemonicValidator(Bip39Languages.ENGLISH).IsValid(mnemonic):
|
|
196
|
+
click.echo("❌ Invalid mnemonic. Please ensure all words are valid BIP-39 words.")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# 2. Derive Keys
|
|
200
|
+
private_key, public_key, pem_private, pem_public = derive_keys_from_mnemonic(mnemonic)
|
|
201
|
+
if not private_key:
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
# 3. Prepare Signed Message
|
|
205
|
+
timestamp = str(int(time.time()))
|
|
206
|
+
public_key_pem_str = pem_public.decode('utf-8')
|
|
207
|
+
message = f"public_key={public_key_pem_str};timestamp={timestamp}"
|
|
208
|
+
signature_b64 = sign_message(private_key, message.encode())
|
|
209
|
+
|
|
210
|
+
if not signature_b64:
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
# 4. Call API to Connect
|
|
214
|
+
click.echo("🔗 Attempting to connect to cell...")
|
|
215
|
+
url = f"{API_BASE_URL}/connect_cell"
|
|
216
|
+
connect_data = {
|
|
217
|
+
"public_key": public_key_pem_str,
|
|
218
|
+
"signed_message": signature_b64,
|
|
219
|
+
"message": message
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
response = requests.post(url, json=connect_data, timeout=10)
|
|
224
|
+
response.raise_for_status()
|
|
225
|
+
host = response.json().get("host")
|
|
226
|
+
except requests.exceptions.RequestException as e:
|
|
227
|
+
click.echo(f"❌ Error connecting to cell: {e}")
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
# 5. Save Credentials
|
|
231
|
+
if host:
|
|
232
|
+
if save_credentials(host, mnemonic, pem_public, pem_private):
|
|
233
|
+
click.echo(f"🔗 Successfully connected to Community Cell '{host}'.")
|
|
234
|
+
# Error saving credentials already echoed in helper
|
|
235
|
+
else:
|
|
236
|
+
click.echo("❌ Failed to retrieve host from server. Connection failed.")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@click.command()
|
|
240
|
+
def view_cell():
|
|
241
|
+
"""Displays the connection status and host name of the current cell."""
|
|
242
|
+
|
|
243
|
+
credentials = load_credentials()
|
|
244
|
+
|
|
245
|
+
if credentials:
|
|
246
|
+
click.echo("\n--- Neuronum Cell Status ---")
|
|
247
|
+
click.echo(f"Status: ✅ Connected")
|
|
248
|
+
click.echo(f"Host: {credentials['host']}")
|
|
249
|
+
click.echo(f"Path: {NEURONUM_PATH}")
|
|
250
|
+
click.echo(f"Key Type: {credentials['private_key'].curve.name} (SECP256R1)")
|
|
251
|
+
click.echo("----------------------------")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@click.command()
|
|
255
|
+
def delete_cell():
|
|
256
|
+
"""Deletes the locally stored credentials and requests cell deletion from the server."""
|
|
257
|
+
|
|
258
|
+
# 1. Load Credentials
|
|
259
|
+
credentials = load_credentials()
|
|
260
|
+
if not credentials:
|
|
261
|
+
# Error already echoed in helper
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
host = credentials['host']
|
|
265
|
+
private_key = credentials['private_key']
|
|
266
|
+
|
|
267
|
+
# 2. Confirmation
|
|
268
|
+
confirm = click.confirm(f"Are you sure you want to permanently delete connection to '{host}'?", default=False)
|
|
269
|
+
if not confirm:
|
|
270
|
+
click.echo("Deletion canceled.")
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
# 3. Prepare Signed Message
|
|
274
|
+
timestamp = str(int(time.time()))
|
|
275
|
+
message = f"host={host};timestamp={timestamp}"
|
|
276
|
+
signature_b64 = sign_message(private_key, message.encode())
|
|
277
|
+
|
|
278
|
+
if not signature_b64:
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
# 4. Call API to Delete
|
|
282
|
+
click.echo(f"🗑️ Requesting deletion of cell '{host}'...")
|
|
283
|
+
url = f"{API_BASE_URL}/delete_cell"
|
|
284
|
+
payload = {
|
|
285
|
+
"host": host,
|
|
286
|
+
"signed_message": signature_b64,
|
|
287
|
+
"message": message
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try:
|
|
291
|
+
response = requests.delete(url, json=payload, timeout=10)
|
|
292
|
+
response.raise_for_status()
|
|
293
|
+
status = response.json().get("status", False)
|
|
294
|
+
except requests.exceptions.RequestException as e:
|
|
295
|
+
click.echo(f"❌ Error communicating with the server during deletion: {e}")
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
# 5. Cleanup Local Files
|
|
299
|
+
if status:
|
|
300
|
+
try:
|
|
301
|
+
ENV_FILE.unlink(missing_ok=True)
|
|
302
|
+
PRIVATE_KEY_FILE.unlink(missing_ok=True)
|
|
303
|
+
PUBLIC_KEY_FILE.unlink(missing_ok=True)
|
|
304
|
+
|
|
305
|
+
click.echo(f"✅ Neuronum Cell '{host}' has been deleted and local credentials removed.")
|
|
306
|
+
except Exception as e:
|
|
307
|
+
click.echo(f"⚠️ Warning: Successfully deleted cell on server, but failed to clean up all local files: {e}")
|
|
308
|
+
else:
|
|
309
|
+
click.echo(f"❌ Neuronum Cell '{host}' deletion failed on server.")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# --- CLI Registration ---
|
|
313
|
+
cli.add_command(create_cell)
|
|
314
|
+
cli.add_command(view_cell)
|
|
315
|
+
cli.add_command(connect_cell)
|
|
316
|
+
cli.add_command(delete_cell)
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
cli()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from neuronum import Cell
|