trustnet 1.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.
- trustnet-1.0.0/LICENSE.txt +20 -0
- trustnet-1.0.0/PKG-INFO +154 -0
- trustnet-1.0.0/README.md +127 -0
- trustnet-1.0.0/pyproject.toml +44 -0
- trustnet-1.0.0/setup.cfg +4 -0
- trustnet-1.0.0/trustnet/__init__.py +1 -0
- trustnet-1.0.0/trustnet/cli.py +293 -0
- trustnet-1.0.0/trustnet/core.py +376 -0
- trustnet-1.0.0/trustnet/gui.py +198 -0
- trustnet-1.0.0/trustnet/icon.py +113 -0
- trustnet-1.0.0/trustnet/network/__init__.py +2 -0
- trustnet-1.0.0/trustnet/network/client.py +73 -0
- trustnet-1.0.0/trustnet/network/discovery.py +96 -0
- trustnet-1.0.0/trustnet/network/ledger.py +135 -0
- trustnet-1.0.0/trustnet/network/node.py +360 -0
- trustnet-1.0.0/trustnet/network/protocol.py +76 -0
- trustnet-1.0.0/trustnet.egg-info/PKG-INFO +154 -0
- trustnet-1.0.0/trustnet.egg-info/SOURCES.txt +20 -0
- trustnet-1.0.0/trustnet.egg-info/dependency_links.txt +1 -0
- trustnet-1.0.0/trustnet.egg-info/entry_points.txt +2 -0
- trustnet-1.0.0/trustnet.egg-info/requires.txt +3 -0
- trustnet-1.0.0/trustnet.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thames Senneam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
trustnet-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trustnet
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Decentralized cryptographic file & package trust network
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/thamessenneam/trustnet
|
|
7
|
+
Project-URL: Issues, https://github.com/thamessenneam/trustnet/issues
|
|
8
|
+
Keywords: security,cryptography,trust,p2p,signing,verification
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Security :: Cryptography
|
|
19
|
+
Classifier: Topic :: System :: Networking
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE.txt
|
|
23
|
+
Requires-Dist: cryptography>=41.0.0
|
|
24
|
+
Requires-Dist: Pillow>=10.0.0
|
|
25
|
+
Requires-Dist: zeroconf>=0.131.0
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# TrustNet
|
|
29
|
+
|
|
30
|
+
**Decentralized cryptographic file & package trust network.**
|
|
31
|
+
|
|
32
|
+
Sign any file or folder. Share it. Anyone can verify it hasn't been tampered with — backed by a peer-to-peer network of independent witnesses.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Download
|
|
37
|
+
|
|
38
|
+
| Platform | Installer | Instructions |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| Windows | [TrustNet-Setup.exe](https://github.com/thamessenneam/TrustNet/releases/latest) | Double-click → Next → Install |
|
|
41
|
+
| macOS | [TrustNet-1.0.0.pkg](https://github.com/thamessenneam/TrustNet/releases/latest) | Double-click → Continue → Install |
|
|
42
|
+
| Linux | [trustnet_1.0.0_amd64.deb](https://github.com/thamessenneam/TrustNet/releases/latest) | `sudo dpkg -i trustnet_1.0.0_amd64.deb` |
|
|
43
|
+
|
|
44
|
+
No Python required. The installer handles everything automatically.
|
|
45
|
+
|
|
46
|
+
Or install via pip (developers):
|
|
47
|
+
```bash
|
|
48
|
+
pip install trustnet
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## What it does
|
|
54
|
+
|
|
55
|
+
- **Sign** any file or folder with your personal Ed25519 key
|
|
56
|
+
- **Verify** files are untampered — locally and across the network
|
|
57
|
+
- **Detect** exactly which files in a folder were modified
|
|
58
|
+
- **Broadcast** your signatures to a decentralized P2P network
|
|
59
|
+
- **Works everywhere** — Windows, macOS, Linux, right-click context menu
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## How to use
|
|
64
|
+
|
|
65
|
+
### Right-click (easiest)
|
|
66
|
+
After installing, right-click any file or folder in your file manager:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Any file or folder
|
|
70
|
+
└── TrustNet
|
|
71
|
+
├── Sign File ← creates a .trustsig alongside it
|
|
72
|
+
└── Verify File ← checks if it was tampered
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Command line
|
|
76
|
+
```bash
|
|
77
|
+
trustnet sign myfile.zip # sign a file
|
|
78
|
+
trustnet verify myfile.zip # verify a file
|
|
79
|
+
trustnet sign my_project/ # sign an entire folder
|
|
80
|
+
trustnet verify my_project/ # verify all files in a folder
|
|
81
|
+
trustnet pubkey # show your public key
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Run the P2P node
|
|
85
|
+
```bash
|
|
86
|
+
trustnet node start # starts the background node (port 7337)
|
|
87
|
+
trustnet node status # check if it's running
|
|
88
|
+
trustnet node peers # list connected peers
|
|
89
|
+
trustnet node add <ip> # connect to a remote peer
|
|
90
|
+
trustnet node stop # stop the node
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Once your node is running, every file you sign is automatically broadcast to all peers. When anyone verifies a file, they see how many independent nodes have attested to it.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## How it works
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
You sign a file
|
|
101
|
+
→ Ed25519 signature saved to .trustsig
|
|
102
|
+
→ attestation published to your local node
|
|
103
|
+
→ node propagates to all known peers
|
|
104
|
+
|
|
105
|
+
Someone verifies the file
|
|
106
|
+
→ local: hash re-computed, signature checked
|
|
107
|
+
→ network: node queried for independent attestations
|
|
108
|
+
→ result: VERIFIED (N signers) or TAMPERED
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Every attestation is cryptographically signed. Nodes reject any attestation with an invalid signature — it's impossible to fake one without the signer's private key.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Install
|
|
116
|
+
|
|
117
|
+
### Windows
|
|
118
|
+
1. Download `TrustNet-Windows.zip`
|
|
119
|
+
2. Extract it
|
|
120
|
+
3. Right-click `install.py` → **Run as administrator**
|
|
121
|
+
4. Done — right-click any file to see TrustNet
|
|
122
|
+
|
|
123
|
+
### macOS
|
|
124
|
+
```bash
|
|
125
|
+
unzip TrustNet-macOS.zip
|
|
126
|
+
cd TrustNet-macOS
|
|
127
|
+
./install.sh
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Linux
|
|
131
|
+
```bash
|
|
132
|
+
unzip TrustNet-Linux.zip
|
|
133
|
+
cd TrustNet-Linux
|
|
134
|
+
./install.sh
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Via pip (developers)
|
|
138
|
+
```bash
|
|
139
|
+
pip install trustnet
|
|
140
|
+
trustnet keygen # generate your key
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## First time setup
|
|
146
|
+
```bash
|
|
147
|
+
trustnet keygen
|
|
148
|
+
```
|
|
149
|
+
This creates your personal Ed25519 keypair. Your private key stays on your machine — never shared. Your public key is what others use to verify your signatures.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
MIT — free to use, modify, and distribute.
|
trustnet-1.0.0/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# TrustNet
|
|
2
|
+
|
|
3
|
+
**Decentralized cryptographic file & package trust network.**
|
|
4
|
+
|
|
5
|
+
Sign any file or folder. Share it. Anyone can verify it hasn't been tampered with — backed by a peer-to-peer network of independent witnesses.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Download
|
|
10
|
+
|
|
11
|
+
| Platform | Installer | Instructions |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Windows | [TrustNet-Setup.exe](https://github.com/thamessenneam/TrustNet/releases/latest) | Double-click → Next → Install |
|
|
14
|
+
| macOS | [TrustNet-1.0.0.pkg](https://github.com/thamessenneam/TrustNet/releases/latest) | Double-click → Continue → Install |
|
|
15
|
+
| Linux | [trustnet_1.0.0_amd64.deb](https://github.com/thamessenneam/TrustNet/releases/latest) | `sudo dpkg -i trustnet_1.0.0_amd64.deb` |
|
|
16
|
+
|
|
17
|
+
No Python required. The installer handles everything automatically.
|
|
18
|
+
|
|
19
|
+
Or install via pip (developers):
|
|
20
|
+
```bash
|
|
21
|
+
pip install trustnet
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## What it does
|
|
27
|
+
|
|
28
|
+
- **Sign** any file or folder with your personal Ed25519 key
|
|
29
|
+
- **Verify** files are untampered — locally and across the network
|
|
30
|
+
- **Detect** exactly which files in a folder were modified
|
|
31
|
+
- **Broadcast** your signatures to a decentralized P2P network
|
|
32
|
+
- **Works everywhere** — Windows, macOS, Linux, right-click context menu
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## How to use
|
|
37
|
+
|
|
38
|
+
### Right-click (easiest)
|
|
39
|
+
After installing, right-click any file or folder in your file manager:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Any file or folder
|
|
43
|
+
└── TrustNet
|
|
44
|
+
├── Sign File ← creates a .trustsig alongside it
|
|
45
|
+
└── Verify File ← checks if it was tampered
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Command line
|
|
49
|
+
```bash
|
|
50
|
+
trustnet sign myfile.zip # sign a file
|
|
51
|
+
trustnet verify myfile.zip # verify a file
|
|
52
|
+
trustnet sign my_project/ # sign an entire folder
|
|
53
|
+
trustnet verify my_project/ # verify all files in a folder
|
|
54
|
+
trustnet pubkey # show your public key
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Run the P2P node
|
|
58
|
+
```bash
|
|
59
|
+
trustnet node start # starts the background node (port 7337)
|
|
60
|
+
trustnet node status # check if it's running
|
|
61
|
+
trustnet node peers # list connected peers
|
|
62
|
+
trustnet node add <ip> # connect to a remote peer
|
|
63
|
+
trustnet node stop # stop the node
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Once your node is running, every file you sign is automatically broadcast to all peers. When anyone verifies a file, they see how many independent nodes have attested to it.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## How it works
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
You sign a file
|
|
74
|
+
→ Ed25519 signature saved to .trustsig
|
|
75
|
+
→ attestation published to your local node
|
|
76
|
+
→ node propagates to all known peers
|
|
77
|
+
|
|
78
|
+
Someone verifies the file
|
|
79
|
+
→ local: hash re-computed, signature checked
|
|
80
|
+
→ network: node queried for independent attestations
|
|
81
|
+
→ result: VERIFIED (N signers) or TAMPERED
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Every attestation is cryptographically signed. Nodes reject any attestation with an invalid signature — it's impossible to fake one without the signer's private key.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Install
|
|
89
|
+
|
|
90
|
+
### Windows
|
|
91
|
+
1. Download `TrustNet-Windows.zip`
|
|
92
|
+
2. Extract it
|
|
93
|
+
3. Right-click `install.py` → **Run as administrator**
|
|
94
|
+
4. Done — right-click any file to see TrustNet
|
|
95
|
+
|
|
96
|
+
### macOS
|
|
97
|
+
```bash
|
|
98
|
+
unzip TrustNet-macOS.zip
|
|
99
|
+
cd TrustNet-macOS
|
|
100
|
+
./install.sh
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Linux
|
|
104
|
+
```bash
|
|
105
|
+
unzip TrustNet-Linux.zip
|
|
106
|
+
cd TrustNet-Linux
|
|
107
|
+
./install.sh
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Via pip (developers)
|
|
111
|
+
```bash
|
|
112
|
+
pip install trustnet
|
|
113
|
+
trustnet keygen # generate your key
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## First time setup
|
|
119
|
+
```bash
|
|
120
|
+
trustnet keygen
|
|
121
|
+
```
|
|
122
|
+
This creates your personal Ed25519 keypair. Your private key stays on your machine — never shared. Your public key is what others use to verify your signatures.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
MIT — free to use, modify, and distribute.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "trustnet"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Decentralized cryptographic file & package trust network"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
keywords = ["security", "cryptography", "trust", "p2p", "signing", "verification"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Intended Audience :: System Administrators",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Security :: Cryptography",
|
|
24
|
+
"Topic :: System :: Networking",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"cryptography>=41.0.0",
|
|
28
|
+
"Pillow>=10.0.0",
|
|
29
|
+
"zeroconf>=0.131.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/thamessenneam/trustnet"
|
|
34
|
+
Issues = "https://github.com/thamessenneam/trustnet/issues"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
trustnet = "trustnet:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["."]
|
|
41
|
+
include = ["trustnet*"]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
"trustnet.assets" = ["*"]
|
trustnet-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "1.0.0"
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Command-line interface for TrustNet."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from trustnet.network import DEFAULT_PORT
|
|
12
|
+
from trustnet.core import (
|
|
13
|
+
fingerprint,
|
|
14
|
+
generate_keypair,
|
|
15
|
+
get_config_dir,
|
|
16
|
+
get_public_key_b64,
|
|
17
|
+
sign_directory,
|
|
18
|
+
sign_file,
|
|
19
|
+
verify_directory,
|
|
20
|
+
verify_file,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _fmt_time(ts: int) -> str:
|
|
25
|
+
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts)) if ts else "unknown"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cmd_sign(args: argparse.Namespace) -> int:
|
|
29
|
+
path = Path(args.path)
|
|
30
|
+
try:
|
|
31
|
+
if path.is_dir():
|
|
32
|
+
result = sign_directory(path)
|
|
33
|
+
print(f"[TrustNet] Directory signed.")
|
|
34
|
+
print(f" Directory : {result['directory']}")
|
|
35
|
+
print(f" Files : {result['file_count']}")
|
|
36
|
+
print(f" Root hash : {result['root_hash'][:16]}...")
|
|
37
|
+
print(f" Manifest : {result['manifest_file']}")
|
|
38
|
+
print(f" Key : {result['fingerprint']}")
|
|
39
|
+
else:
|
|
40
|
+
result = sign_file(path)
|
|
41
|
+
print(f"[TrustNet] File signed.")
|
|
42
|
+
print(f" File : {result['file']}")
|
|
43
|
+
print(f" SHA-256 : {result['hash'][:16]}...")
|
|
44
|
+
print(f" Signature : {result['sig_file']}")
|
|
45
|
+
print(f" Key : {result['fingerprint']}")
|
|
46
|
+
|
|
47
|
+
if args.json:
|
|
48
|
+
print(json.dumps(result, indent=2))
|
|
49
|
+
return 0
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"[TrustNet] Error: {e}", file=sys.stderr)
|
|
52
|
+
return 1
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def cmd_verify(args: argparse.Namespace) -> int:
|
|
56
|
+
path = Path(args.path)
|
|
57
|
+
try:
|
|
58
|
+
if path.is_dir():
|
|
59
|
+
result = verify_directory(path)
|
|
60
|
+
else:
|
|
61
|
+
result = verify_file(path)
|
|
62
|
+
|
|
63
|
+
status = result["status"]
|
|
64
|
+
ok = result["success"]
|
|
65
|
+
symbol = "[OK]" if ok else "[FAIL]"
|
|
66
|
+
print(f"[TrustNet] {symbol} {status}")
|
|
67
|
+
print(f" {result['message']}")
|
|
68
|
+
print(f" Key : {result.get('fingerprint', '-')}")
|
|
69
|
+
print(f" Signed at : {_fmt_time(result.get('timestamp', 0))}")
|
|
70
|
+
|
|
71
|
+
# Network trust info
|
|
72
|
+
n_atts = result.get("network_attestations", 0)
|
|
73
|
+
n_signers = result.get("network_signers", [])
|
|
74
|
+
if result.get("network_online"):
|
|
75
|
+
print(f" Network : {n_atts} attestation(s) from {len(n_signers)} unique signer(s)")
|
|
76
|
+
for s in n_signers:
|
|
77
|
+
print(f" - {s}")
|
|
78
|
+
else:
|
|
79
|
+
print(f" Network : offline (run: trustnet node start)")
|
|
80
|
+
|
|
81
|
+
if not ok and result.get("changed_files"):
|
|
82
|
+
print(f" Changed files ({len(result['changed_files'])}):")
|
|
83
|
+
for f in result["changed_files"][:10]:
|
|
84
|
+
print(f" - {f}")
|
|
85
|
+
|
|
86
|
+
if args.json:
|
|
87
|
+
print(json.dumps(result, indent=2))
|
|
88
|
+
|
|
89
|
+
return 0 if ok else 2
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f"[TrustNet] Error: {e}", file=sys.stderr)
|
|
92
|
+
return 1
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def cmd_keygen(args: argparse.Namespace) -> int:
|
|
96
|
+
config = get_config_dir()
|
|
97
|
+
private_path = config / "private.key"
|
|
98
|
+
if private_path.exists() and not args.force:
|
|
99
|
+
print("[TrustNet] Key already exists.")
|
|
100
|
+
print(f" Location : {config}")
|
|
101
|
+
print(f" Key : {fingerprint(get_public_key_b64())}")
|
|
102
|
+
print(" Use --force to regenerate (this invalidates all existing signatures).")
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
if private_path.exists() and args.force:
|
|
106
|
+
private_path.unlink()
|
|
107
|
+
(config / "public.key").unlink(missing_ok=True)
|
|
108
|
+
|
|
109
|
+
generate_keypair()
|
|
110
|
+
pub_b64 = get_public_key_b64()
|
|
111
|
+
print("[TrustNet] New keypair generated.")
|
|
112
|
+
print(f" Location : {config}")
|
|
113
|
+
print(f" Fingerprint: {fingerprint(pub_b64)}")
|
|
114
|
+
print(f" Public key : {pub_b64[:24]}...")
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cmd_pubkey(args: argparse.Namespace) -> int:
|
|
119
|
+
try:
|
|
120
|
+
pub_b64 = get_public_key_b64()
|
|
121
|
+
fp = fingerprint(pub_b64)
|
|
122
|
+
if args.json:
|
|
123
|
+
print(json.dumps({"public_key": pub_b64, "fingerprint": fp}))
|
|
124
|
+
else:
|
|
125
|
+
print(f"Fingerprint : {fp}")
|
|
126
|
+
print(f"Public key : {pub_b64}")
|
|
127
|
+
return 0
|
|
128
|
+
except Exception as e:
|
|
129
|
+
print(f"[TrustNet] Error: {e}", file=sys.stderr)
|
|
130
|
+
return 1
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def cmd_node(args: argparse.Namespace) -> int:
|
|
134
|
+
from trustnet.network import node as n, DEFAULT_PORT
|
|
135
|
+
|
|
136
|
+
sub = args.node_cmd
|
|
137
|
+
|
|
138
|
+
if sub == "start":
|
|
139
|
+
if n.is_running():
|
|
140
|
+
print("[TrustNet] Node is already running.")
|
|
141
|
+
info = n.get_local_info()
|
|
142
|
+
if info:
|
|
143
|
+
print(f" Port : {info.get('port', DEFAULT_PORT)}")
|
|
144
|
+
print(f" Attestations : {info.get('attestations', 0)}")
|
|
145
|
+
print(f" Peers : {info.get('peers', 0)}")
|
|
146
|
+
return 0
|
|
147
|
+
port = getattr(args, "port", DEFAULT_PORT)
|
|
148
|
+
print(f"[TrustNet] Starting node on port {port}...")
|
|
149
|
+
try:
|
|
150
|
+
n.start_daemon(port)
|
|
151
|
+
print(f"[TrustNet] Node started.")
|
|
152
|
+
print(f" Port : {port}")
|
|
153
|
+
print(f" Other TrustNet nodes on your network will be discovered automatically.")
|
|
154
|
+
except Exception as e:
|
|
155
|
+
print(f"[TrustNet] Failed to start: {e}", file=sys.stderr)
|
|
156
|
+
return 1
|
|
157
|
+
return 0
|
|
158
|
+
|
|
159
|
+
elif sub == "stop":
|
|
160
|
+
if not n.is_running():
|
|
161
|
+
print("[TrustNet] Node is not running.")
|
|
162
|
+
return 0
|
|
163
|
+
if n.stop_daemon():
|
|
164
|
+
print("[TrustNet] Node stopped.")
|
|
165
|
+
else:
|
|
166
|
+
print("[TrustNet] Could not stop node.", file=sys.stderr)
|
|
167
|
+
return 1
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
elif sub == "status":
|
|
171
|
+
if n.is_running():
|
|
172
|
+
info = n.get_local_info()
|
|
173
|
+
print("[TrustNet] Node is RUNNING")
|
|
174
|
+
if info:
|
|
175
|
+
print(f" Port : {info.get('port', DEFAULT_PORT)}")
|
|
176
|
+
print(f" Attestations : {info.get('attestations', 0)}")
|
|
177
|
+
print(f" Peers : {info.get('peers', 0)}")
|
|
178
|
+
else:
|
|
179
|
+
print("[TrustNet] Node is STOPPED")
|
|
180
|
+
print(" Run: trustnet node start")
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
elif sub == "peers":
|
|
184
|
+
if not n.is_running():
|
|
185
|
+
print("[TrustNet] Node is not running. Start it first: trustnet node start")
|
|
186
|
+
return 1
|
|
187
|
+
from trustnet.network.ledger import Ledger
|
|
188
|
+
db = Ledger()
|
|
189
|
+
peers = db.get_peers()
|
|
190
|
+
if not peers:
|
|
191
|
+
print("[TrustNet] No peers known yet.")
|
|
192
|
+
print(" Peers on your LAN are discovered automatically.")
|
|
193
|
+
print(" Add a remote peer: trustnet node add <host>")
|
|
194
|
+
else:
|
|
195
|
+
print(f"[TrustNet] {len(peers)} peer(s):")
|
|
196
|
+
for p in peers:
|
|
197
|
+
last = _fmt_time(p.get("last_seen", 0))
|
|
198
|
+
sync = _fmt_time(p.get("last_sync", 0))
|
|
199
|
+
print(f" {p['host']}:{p['port']} seen={last} sync={sync}")
|
|
200
|
+
return 0
|
|
201
|
+
|
|
202
|
+
elif sub == "add":
|
|
203
|
+
host = args.host
|
|
204
|
+
port = getattr(args, "port", DEFAULT_PORT)
|
|
205
|
+
if not n.is_running():
|
|
206
|
+
print("[TrustNet] Node is not running. Start it first: trustnet node start")
|
|
207
|
+
return 1
|
|
208
|
+
from trustnet.network import client
|
|
209
|
+
info = client.get_info(host, port)
|
|
210
|
+
if not info:
|
|
211
|
+
print(f"[TrustNet] Cannot reach {host}:{port}", file=sys.stderr)
|
|
212
|
+
return 1
|
|
213
|
+
from trustnet.network.ledger import Ledger
|
|
214
|
+
Ledger().add_peer(host, port, info.get("node_id", ""))
|
|
215
|
+
print(f"[TrustNet] Peer added: {host}:{port}")
|
|
216
|
+
print(f" Attestations on that node : {info.get('attestations', 0)}")
|
|
217
|
+
print(" Syncing in background...")
|
|
218
|
+
return 0
|
|
219
|
+
|
|
220
|
+
elif sub == "sync":
|
|
221
|
+
if not n.is_running():
|
|
222
|
+
print("[TrustNet] Node is not running.")
|
|
223
|
+
return 1
|
|
224
|
+
from trustnet.network.ledger import Ledger
|
|
225
|
+
from trustnet.network import client
|
|
226
|
+
from trustnet.network.protocol import verify_attestation
|
|
227
|
+
db = Ledger()
|
|
228
|
+
peers = db.get_peers()
|
|
229
|
+
if not peers:
|
|
230
|
+
print("[TrustNet] No peers to sync with.")
|
|
231
|
+
return 0
|
|
232
|
+
total = 0
|
|
233
|
+
for p in peers:
|
|
234
|
+
atts = client.sync_since(p["host"], p["port"], p.get("last_sync", 0))
|
|
235
|
+
new = sum(1 for a in atts if verify_attestation(a) and db.add(a))
|
|
236
|
+
total += new
|
|
237
|
+
db.update_sync(p["host"], p["port"])
|
|
238
|
+
print(f" {p['host']}:{p['port']} -> {new} new attestation(s)")
|
|
239
|
+
print(f"[TrustNet] Sync complete. {total} new attestation(s) total.")
|
|
240
|
+
return 0
|
|
241
|
+
|
|
242
|
+
print(f"[TrustNet] Unknown node command: {sub}", file=sys.stderr)
|
|
243
|
+
return 1
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
247
|
+
parser = argparse.ArgumentParser(
|
|
248
|
+
prog="trustnet",
|
|
249
|
+
description="TrustNet — cryptographic file & package signing",
|
|
250
|
+
)
|
|
251
|
+
parser.add_argument("--json", action="store_true", help="Output JSON")
|
|
252
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
253
|
+
|
|
254
|
+
p_sign = sub.add_parser("sign", help="Sign a file or directory")
|
|
255
|
+
p_sign.add_argument("path", help="File or directory to sign")
|
|
256
|
+
p_sign.set_defaults(func=cmd_sign)
|
|
257
|
+
|
|
258
|
+
p_verify = sub.add_parser("verify", help="Verify a file or directory")
|
|
259
|
+
p_verify.add_argument("path", help="File or directory to verify")
|
|
260
|
+
p_verify.set_defaults(func=cmd_verify)
|
|
261
|
+
|
|
262
|
+
p_keygen = sub.add_parser("keygen", help="Generate a new keypair")
|
|
263
|
+
p_keygen.add_argument("--force", action="store_true", help="Overwrite existing key")
|
|
264
|
+
p_keygen.set_defaults(func=cmd_keygen)
|
|
265
|
+
|
|
266
|
+
p_pub = sub.add_parser("pubkey", help="Show your public key and fingerprint")
|
|
267
|
+
p_pub.set_defaults(func=cmd_pubkey)
|
|
268
|
+
|
|
269
|
+
# node subcommand with sub-subcommands
|
|
270
|
+
p_node = sub.add_parser("node", help="Manage the TrustNet P2P node")
|
|
271
|
+
node_sub = p_node.add_subparsers(dest="node_cmd", required=True)
|
|
272
|
+
|
|
273
|
+
ns = node_sub.add_parser("start", help="Start the P2P node daemon")
|
|
274
|
+
ns.add_argument("--port", type=int, default=DEFAULT_PORT)
|
|
275
|
+
|
|
276
|
+
node_sub.add_parser("stop", help="Stop the node daemon")
|
|
277
|
+
node_sub.add_parser("status", help="Show node status")
|
|
278
|
+
node_sub.add_parser("peers", help="List known peers")
|
|
279
|
+
node_sub.add_parser("sync", help="Force sync with all peers")
|
|
280
|
+
|
|
281
|
+
na = node_sub.add_parser("add", help="Manually add a peer by host")
|
|
282
|
+
na.add_argument("host", help="Peer hostname or IP address")
|
|
283
|
+
na.add_argument("--port", type=int, default=DEFAULT_PORT)
|
|
284
|
+
|
|
285
|
+
p_node.set_defaults(func=cmd_node)
|
|
286
|
+
|
|
287
|
+
return parser
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def main(argv: list[str] | None = None) -> int:
|
|
291
|
+
parser = build_parser()
|
|
292
|
+
args = parser.parse_args(argv)
|
|
293
|
+
return args.func(args)
|