proofchain-cli 0.1.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.
- proofchain_cli-0.1.0/PKG-INFO +7 -0
- proofchain_cli-0.1.0/README.md +44 -0
- proofchain_cli-0.1.0/proofchain/__init__.py +0 -0
- proofchain_cli-0.1.0/proofchain/cli.py +163 -0
- proofchain_cli-0.1.0/proofchain_cli.egg-info/PKG-INFO +7 -0
- proofchain_cli-0.1.0/proofchain_cli.egg-info/SOURCES.txt +10 -0
- proofchain_cli-0.1.0/proofchain_cli.egg-info/dependency_links.txt +1 -0
- proofchain_cli-0.1.0/proofchain_cli.egg-info/entry_points.txt +2 -0
- proofchain_cli-0.1.0/proofchain_cli.egg-info/requires.txt +2 -0
- proofchain_cli-0.1.0/proofchain_cli.egg-info/top_level.txt +1 -0
- proofchain_cli-0.1.0/pyproject.toml +13 -0
- proofchain_cli-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# proofchain
|
|
2
|
+
|
|
3
|
+
Point it at any Postgres table of real events. Get a hash-chained,
|
|
4
|
+
Ed25519-signed, append-only proof log. Anyone can independently verify
|
|
5
|
+
the chain — no trust in the operator required.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Most "proof of X" claims in finance and crypto are just numbers an
|
|
10
|
+
operator generated internally, with no way for an outside party to
|
|
11
|
+
check them. proofchain fixes that generically: point it at any table,
|
|
12
|
+
any columns, and get a cryptographic chain that's tamper-evident and
|
|
13
|
+
independently re-verifiable by anyone, using only the public key.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
pip install proofchain
|
|
18
|
+
|
|
19
|
+
## Example: a small business proving payout history to a lender
|
|
20
|
+
|
|
21
|
+
export PROOFCHAIN_DSN="postgresql://user@host:port/business_db"
|
|
22
|
+
export PROOFCHAIN_KEYFILE="/path/to/signing_key.json"
|
|
23
|
+
|
|
24
|
+
proofchain build \
|
|
25
|
+
--table vendor_payouts \
|
|
26
|
+
--id-col id \
|
|
27
|
+
--columns vendor_name,amount_usd,paid_at \
|
|
28
|
+
--where "status = 'completed'" \
|
|
29
|
+
--order-by paid_at \
|
|
30
|
+
--log ./payout_proof.jsonl
|
|
31
|
+
|
|
32
|
+
proofchain verify --log ./payout_proof.jsonl
|
|
33
|
+
|
|
34
|
+
The lender (or anyone) can independently re-run `verify` against the
|
|
35
|
+
same log file and confirm nothing was altered — without ever needing
|
|
36
|
+
API access to the business's database or having to trust the business
|
|
37
|
+
at all.
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+
Each `build` run snapshots the specified rows, signs them with your
|
|
42
|
+
Ed25519 key, and chains the entry to the previous one via SHA-256.
|
|
43
|
+
Any edit to historical data breaks the hash chain on the next verify.
|
|
44
|
+
No table or column names are hardcoded — this works on any schema.
|
|
File without changes
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
proofchain.py — generic hash-chained, signed audit-proof CLI.
|
|
4
|
+
|
|
5
|
+
Point it at ANY Postgres table of real events (trades, sales, burns,
|
|
6
|
+
whatever) and get a tamper-evident, independently-verifiable proof log.
|
|
7
|
+
Generalized from omega_revenue_proof.py — same mechanism, config-driven
|
|
8
|
+
instead of hardcoded to one schema.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
export PROOFCHAIN_DSN="postgresql://user@host:port/db"
|
|
12
|
+
export PROOFCHAIN_KEYFILE="/path/to/key.json" # {"secret":[...]} or [...]
|
|
13
|
+
|
|
14
|
+
python3 proofchain.py build \
|
|
15
|
+
--table my_trades \
|
|
16
|
+
--id-col id \
|
|
17
|
+
--columns symbol,pnl_usdt,closed_at \
|
|
18
|
+
--where "status IN ('closed_profit','closed_stop') AND closed_at IS NOT NULL" \
|
|
19
|
+
--order-by closed_at \
|
|
20
|
+
--log ./my_proof.jsonl
|
|
21
|
+
|
|
22
|
+
python3 proofchain.py verify --log ./my_proof.jsonl
|
|
23
|
+
|
|
24
|
+
No table/column names are hardcoded. Nothing here reads or writes
|
|
25
|
+
omega_bank, omega_ledger, or any Omega-specific table unless you pass
|
|
26
|
+
those names explicitly via --table.
|
|
27
|
+
"""
|
|
28
|
+
import os, sys, json, base64, argparse, hashlib
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
|
|
32
|
+
import psycopg2
|
|
33
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
|
34
|
+
Ed25519PrivateKey, Ed25519PublicKey
|
|
35
|
+
)
|
|
36
|
+
from cryptography.exceptions import InvalidSignature
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_signer():
|
|
40
|
+
keyfile = os.environ.get("PROOFCHAIN_KEYFILE")
|
|
41
|
+
if not keyfile:
|
|
42
|
+
sys.exit("PROOFCHAIN_KEYFILE env var not set — no placeholder key will be used.")
|
|
43
|
+
data = json.loads(Path(keyfile).read_text())
|
|
44
|
+
raw = bytes(data["secret"][:32]) if isinstance(data, dict) else bytes(data[:32])
|
|
45
|
+
priv = Ed25519PrivateKey.from_private_bytes(raw)
|
|
46
|
+
pub = priv.public_key()
|
|
47
|
+
return priv, pub
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build(args):
|
|
51
|
+
dsn = os.environ.get("PROOFCHAIN_DSN")
|
|
52
|
+
if not dsn:
|
|
53
|
+
sys.exit("PROOFCHAIN_DSN env var not set.")
|
|
54
|
+
|
|
55
|
+
priv, pub = load_signer()
|
|
56
|
+
cols = [c.strip() for c in args.columns.split(",")]
|
|
57
|
+
select_cols = ", ".join([args.id_col] + cols)
|
|
58
|
+
|
|
59
|
+
query = f"SELECT {select_cols} FROM {args.table}"
|
|
60
|
+
if args.where:
|
|
61
|
+
query += f" WHERE {args.where}"
|
|
62
|
+
if args.order_by:
|
|
63
|
+
query += f" ORDER BY {args.order_by} ASC"
|
|
64
|
+
|
|
65
|
+
conn = psycopg2.connect(dsn)
|
|
66
|
+
with conn.cursor() as cur:
|
|
67
|
+
cur.execute(query)
|
|
68
|
+
rows = cur.fetchall()
|
|
69
|
+
conn.close()
|
|
70
|
+
|
|
71
|
+
records = []
|
|
72
|
+
for row in rows:
|
|
73
|
+
rec = {"id": str(row[0])}
|
|
74
|
+
for i, col in enumerate(cols):
|
|
75
|
+
rec[col] = str(row[i + 1]) if row[i + 1] is not None else None
|
|
76
|
+
records.append(rec)
|
|
77
|
+
|
|
78
|
+
log_path = Path(args.log)
|
|
79
|
+
prev_hash = "genesis"
|
|
80
|
+
if log_path.exists() and log_path.stat().st_size > 0:
|
|
81
|
+
last_line = log_path.read_text().strip().splitlines()[-1]
|
|
82
|
+
prev_hash = json.loads(last_line)["entry_hash"]
|
|
83
|
+
|
|
84
|
+
snapshot = {
|
|
85
|
+
"type": "proofchain_snapshot",
|
|
86
|
+
"table": args.table,
|
|
87
|
+
"record_count": len(records),
|
|
88
|
+
"records": records,
|
|
89
|
+
"prev_hash": prev_hash,
|
|
90
|
+
"built_at": datetime.now(timezone.utc).isoformat(),
|
|
91
|
+
}
|
|
92
|
+
msg = json.dumps(snapshot, sort_keys=True, default=str).encode()
|
|
93
|
+
sig = base64.b64encode(priv.sign(msg)).decode()
|
|
94
|
+
entry_hash = hashlib.sha256(msg + sig.encode()).hexdigest()
|
|
95
|
+
snapshot["signature"] = sig
|
|
96
|
+
snapshot["entry_hash"] = entry_hash
|
|
97
|
+
|
|
98
|
+
with open(log_path, "a") as f:
|
|
99
|
+
f.write(json.dumps(snapshot, default=str) + "\n")
|
|
100
|
+
|
|
101
|
+
print(f"Snapshot written. {len(records)} record(s) from '{args.table}'. "
|
|
102
|
+
f"entry_hash={entry_hash[:16]}...")
|
|
103
|
+
print(f"Log: {log_path}")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def verify(args):
|
|
107
|
+
priv, pub = load_signer()
|
|
108
|
+
log_path = Path(args.log)
|
|
109
|
+
if not log_path.exists():
|
|
110
|
+
print("No log yet.")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
lines = log_path.read_text().strip().splitlines()
|
|
114
|
+
prev_hash = "genesis"
|
|
115
|
+
ok = True
|
|
116
|
+
for i, line in enumerate(lines):
|
|
117
|
+
record = json.loads(line)
|
|
118
|
+
sig = record.pop("signature")
|
|
119
|
+
claimed_hash = record.pop("entry_hash")
|
|
120
|
+
if record.get("prev_hash") != prev_hash:
|
|
121
|
+
print(f" [BROKEN CHAIN] entry {i}: expected prev_hash={prev_hash}, "
|
|
122
|
+
f"got {record.get('prev_hash')}")
|
|
123
|
+
ok = False
|
|
124
|
+
msg = json.dumps(record, sort_keys=True, default=str).encode()
|
|
125
|
+
try:
|
|
126
|
+
pub.verify(base64.b64decode(sig), msg)
|
|
127
|
+
except InvalidSignature:
|
|
128
|
+
print(f" [BAD SIGNATURE] entry {i}")
|
|
129
|
+
ok = False
|
|
130
|
+
recomputed = hashlib.sha256(msg + sig.encode()).hexdigest()
|
|
131
|
+
if recomputed != claimed_hash:
|
|
132
|
+
print(f" [HASH MISMATCH] entry {i}")
|
|
133
|
+
ok = False
|
|
134
|
+
prev_hash = claimed_hash
|
|
135
|
+
|
|
136
|
+
print("VERIFIED: chain intact, all signatures valid" if ok
|
|
137
|
+
else "FAILED: tampering or corruption detected")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main():
|
|
141
|
+
parser = argparse.ArgumentParser()
|
|
142
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
143
|
+
|
|
144
|
+
b = sub.add_parser("build")
|
|
145
|
+
b.add_argument("--table", required=True)
|
|
146
|
+
b.add_argument("--id-col", default="id")
|
|
147
|
+
b.add_argument("--columns", required=True, help="comma-separated column list")
|
|
148
|
+
b.add_argument("--where", default="")
|
|
149
|
+
b.add_argument("--order-by", default="")
|
|
150
|
+
b.add_argument("--log", required=True)
|
|
151
|
+
|
|
152
|
+
v = sub.add_parser("verify")
|
|
153
|
+
v.add_argument("--log", required=True)
|
|
154
|
+
|
|
155
|
+
args = parser.parse_args()
|
|
156
|
+
if args.cmd == "build":
|
|
157
|
+
build(args)
|
|
158
|
+
elif args.cmd == "verify":
|
|
159
|
+
verify(args)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
proofchain/__init__.py
|
|
4
|
+
proofchain/cli.py
|
|
5
|
+
proofchain_cli.egg-info/PKG-INFO
|
|
6
|
+
proofchain_cli.egg-info/SOURCES.txt
|
|
7
|
+
proofchain_cli.egg-info/dependency_links.txt
|
|
8
|
+
proofchain_cli.egg-info/entry_points.txt
|
|
9
|
+
proofchain_cli.egg-info/requires.txt
|
|
10
|
+
proofchain_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
proofchain
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "proofchain-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Hash-chained, signed, independently-verifiable audit proofs for any Postgres table of real events."
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
dependencies = ["psycopg2-binary", "cryptography"]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
proofchain = "proofchain.cli:main"
|