trustplane-sdk 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.
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: trustplane-sdk
3
+ Version: 0.1.0
4
+ Summary: Trustplane SDK (Python) for generating request proof headers
5
+ Project-URL: Homepage, https://trustplane.mergematter.io
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pynacl>=1.5.0
9
+
10
+ # Trustplane Python SDK (v0.1)
11
+
12
+ Minimal SDK to generate Trustplane proof headers.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install trustplane-sdk
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```python
23
+ from trustplane_sdk import sign
24
+
25
+ out = sign(
26
+ tenant_id="mergematter.io",
27
+ api_id="api_demo",
28
+ client_id="client_demo",
29
+ private_key_b64url="<private_key_b64url>",
30
+ method="GET",
31
+ path="/orders",
32
+ body=""
33
+ )
34
+
35
+ print(out["headers"])
36
+ ```
37
+
38
+ ## Config file
39
+
40
+ ```python
41
+ from trustplane_sdk import from_file
42
+
43
+ client = from_file("./trustplane.json")
44
+ out = client.sign(method="GET", path="/orders", body="", private_key_b64url="<private_key_b64url>")
45
+ ```
46
+
47
+ ## Tests
48
+
49
+ ```bash
50
+ python3 -m unittest sdk/python/tests/test_vector.py
51
+ ```
@@ -0,0 +1,42 @@
1
+ # Trustplane Python SDK (v0.1)
2
+
3
+ Minimal SDK to generate Trustplane proof headers.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install trustplane-sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from trustplane_sdk import sign
15
+
16
+ out = sign(
17
+ tenant_id="mergematter.io",
18
+ api_id="api_demo",
19
+ client_id="client_demo",
20
+ private_key_b64url="<private_key_b64url>",
21
+ method="GET",
22
+ path="/orders",
23
+ body=""
24
+ )
25
+
26
+ print(out["headers"])
27
+ ```
28
+
29
+ ## Config file
30
+
31
+ ```python
32
+ from trustplane_sdk import from_file
33
+
34
+ client = from_file("./trustplane.json")
35
+ out = client.sign(method="GET", path="/orders", body="", private_key_b64url="<private_key_b64url>")
36
+ ```
37
+
38
+ ## Tests
39
+
40
+ ```bash
41
+ python3 -m unittest sdk/python/tests/test_vector.py
42
+ ```
@@ -0,0 +1,14 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "trustplane-sdk"
7
+ version = "0.1.0"
8
+ description = "Trustplane SDK (Python) for generating request proof headers"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = ["pynacl>=1.5.0"]
12
+
13
+ [project.urls]
14
+ Homepage = "https://trustplane.mergematter.io"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ import unittest
2
+
3
+ from trustplane_sdk import sign
4
+
5
+
6
+ class TestVector(unittest.TestCase):
7
+ def test_headers_shape(self) -> None:
8
+ out = sign(
9
+ tenant_id="mergematter.io",
10
+ api_id="api_demo",
11
+ client_id="client_demo",
12
+ private_key_b64url="dyIi-Xwr2tl6NdzkT0CXWUZkb9cPRXTSbDQgz-SCcORqDZPYyFAEXEGn6Eg6hlokBxvS8avUjpmk4VwaXmg7zQ",
13
+ method="GET",
14
+ path="/orders",
15
+ body="",
16
+ bucket_seconds=20,
17
+ time_bucket_override=88498363,
18
+ nonce_override="9biCQnN2URhTCqhn_-orBQ",
19
+ )
20
+ headers = out["headers"]
21
+ self.assertEqual(headers["x-tp-tenant-id"], "mergematter.io")
22
+ self.assertEqual(headers["x-tp-api-id"], "api_demo")
23
+ self.assertEqual(headers["x-tp-client-id"], "client_demo")
24
+ self.assertEqual(headers["x-tp-time-bucket"], "88498363")
25
+ self.assertEqual(headers["x-tp-nonce"], "9biCQnN2URhTCqhn_-orBQ")
26
+ self.assertTrue(headers["x-tp-signature"])
27
+ self.assertEqual(
28
+ headers["x-tp-body-hash"],
29
+ "sha256:47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU",
30
+ )
31
+
32
+
33
+ if __name__ == "__main__":
34
+ unittest.main()
@@ -0,0 +1,155 @@
1
+ import base64
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import time
6
+ from typing import Any, Dict
7
+
8
+ from nacl.signing import SigningKey
9
+
10
+
11
+ def _b64url_encode(data: bytes) -> str:
12
+ return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")
13
+
14
+
15
+ def _b64url_decode(data: str) -> bytes:
16
+ pad = "=" * (-len(data) % 4)
17
+ return base64.urlsafe_b64decode(data + pad)
18
+
19
+
20
+ def _sha256_b64url(data: bytes) -> str:
21
+ return _b64url_encode(hashlib.sha256(data).digest())
22
+
23
+
24
+ def _random_nonce() -> str:
25
+ return _b64url_encode(os.urandom(16))
26
+
27
+
28
+ def _build_transcript(
29
+ tenant_id: str,
30
+ api_id: str,
31
+ client_id: str,
32
+ method: str,
33
+ path: str,
34
+ body_hash: str,
35
+ time_bucket: int,
36
+ nonce: str,
37
+ ) -> str:
38
+ return "\n".join(
39
+ [
40
+ "TP1",
41
+ f"tenant_id={tenant_id}",
42
+ f"api_id={api_id}",
43
+ f"client_id={client_id}",
44
+ f"method={method}",
45
+ f"path={path}",
46
+ f"body_hash={body_hash}",
47
+ f"time_bucket={time_bucket}",
48
+ f"nonce={nonce}",
49
+ ]
50
+ )
51
+
52
+
53
+ def sign(
54
+ tenant_id: str,
55
+ api_id: str,
56
+ client_id: str,
57
+ private_key_b64url: str,
58
+ method: str = "GET",
59
+ path: str = "/",
60
+ body: str = "",
61
+ bucket_seconds: int = 20,
62
+ time_bucket_override: int = None,
63
+ nonce_override: str = None,
64
+ ) -> Dict[str, Any]:
65
+ if not tenant_id or not api_id or not client_id:
66
+ raise ValueError("tenant_id, api_id, and client_id are required")
67
+ if not private_key_b64url:
68
+ raise ValueError("private_key_b64url is required")
69
+ if not path:
70
+ raise ValueError("path is required")
71
+
72
+ body_hash = f"sha256:{_sha256_b64url(body.encode('utf-8'))}"
73
+ now_seconds = int(time.time())
74
+ bucket = bucket_seconds if bucket_seconds > 0 else 20
75
+ time_bucket = time_bucket_override if time_bucket_override is not None else (now_seconds // bucket)
76
+ nonce = nonce_override if nonce_override is not None else _random_nonce()
77
+
78
+ transcript = _build_transcript(
79
+ tenant_id,
80
+ api_id,
81
+ client_id,
82
+ method.upper(),
83
+ path,
84
+ body_hash,
85
+ time_bucket,
86
+ nonce,
87
+ )
88
+
89
+ digest = hashlib.sha256(transcript.encode("utf-8")).digest()
90
+ priv = _b64url_decode(private_key_b64url)
91
+ if len(priv) != 64:
92
+ raise ValueError("private_key_b64url must be ed25519 private key (64 bytes)")
93
+ signing_key = SigningKey(priv[:32])
94
+ signature = signing_key.sign(digest).signature
95
+ signature_b64url = _b64url_encode(signature)
96
+
97
+ headers = {
98
+ "x-tp-tenant-id": tenant_id,
99
+ "x-tp-api-id": api_id,
100
+ "x-tp-client-id": client_id,
101
+ "x-tp-time-bucket": str(time_bucket),
102
+ "x-tp-nonce": nonce,
103
+ "x-tp-signature": signature_b64url,
104
+ "x-tp-body-hash": body_hash,
105
+ }
106
+
107
+ verify_payload = {
108
+ "tenant_id": tenant_id,
109
+ "api_id": api_id,
110
+ "client_id": client_id,
111
+ "method": method.upper(),
112
+ "path": path,
113
+ "body_hash": body_hash,
114
+ "time_bucket": time_bucket,
115
+ "nonce": nonce,
116
+ "signature": signature_b64url,
117
+ }
118
+
119
+ return {
120
+ "headers": headers,
121
+ "verify_payload": verify_payload,
122
+ "transcript": transcript,
123
+ "digest_b64url": _b64url_encode(digest),
124
+ }
125
+
126
+
127
+ class TrustplaneClient:
128
+ def __init__(self, tenant_id: str, api_id: str, client_id: str, bucket_seconds: int = 20):
129
+ self.tenant_id = tenant_id
130
+ self.api_id = api_id
131
+ self.client_id = client_id
132
+ self.bucket_seconds = bucket_seconds
133
+
134
+ def sign(self, method: str, path: str, body: str, private_key_b64url: str) -> Dict[str, Any]:
135
+ return sign(
136
+ tenant_id=self.tenant_id,
137
+ api_id=self.api_id,
138
+ client_id=self.client_id,
139
+ private_key_b64url=private_key_b64url,
140
+ method=method,
141
+ path=path,
142
+ body=body,
143
+ bucket_seconds=self.bucket_seconds,
144
+ )
145
+
146
+
147
+ def from_file(path: str) -> TrustplaneClient:
148
+ with open(path, "r", encoding="utf-8") as f:
149
+ cfg = json.load(f)
150
+ return TrustplaneClient(
151
+ tenant_id=cfg.get("tenant_id"),
152
+ api_id=cfg.get("api_id"),
153
+ client_id=cfg.get("client_id"),
154
+ bucket_seconds=cfg.get("bucket_seconds", 20),
155
+ )
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: trustplane-sdk
3
+ Version: 0.1.0
4
+ Summary: Trustplane SDK (Python) for generating request proof headers
5
+ Project-URL: Homepage, https://trustplane.mergematter.io
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pynacl>=1.5.0
9
+
10
+ # Trustplane Python SDK (v0.1)
11
+
12
+ Minimal SDK to generate Trustplane proof headers.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install trustplane-sdk
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```python
23
+ from trustplane_sdk import sign
24
+
25
+ out = sign(
26
+ tenant_id="mergematter.io",
27
+ api_id="api_demo",
28
+ client_id="client_demo",
29
+ private_key_b64url="<private_key_b64url>",
30
+ method="GET",
31
+ path="/orders",
32
+ body=""
33
+ )
34
+
35
+ print(out["headers"])
36
+ ```
37
+
38
+ ## Config file
39
+
40
+ ```python
41
+ from trustplane_sdk import from_file
42
+
43
+ client = from_file("./trustplane.json")
44
+ out = client.sign(method="GET", path="/orders", body="", private_key_b64url="<private_key_b64url>")
45
+ ```
46
+
47
+ ## Tests
48
+
49
+ ```bash
50
+ python3 -m unittest sdk/python/tests/test_vector.py
51
+ ```
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ tests/test_vector.py
4
+ trustplane_sdk/__init__.py
5
+ trustplane_sdk.egg-info/PKG-INFO
6
+ trustplane_sdk.egg-info/SOURCES.txt
7
+ trustplane_sdk.egg-info/dependency_links.txt
8
+ trustplane_sdk.egg-info/requires.txt
9
+ trustplane_sdk.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ pynacl>=1.5.0
@@ -0,0 +1 @@
1
+ trustplane_sdk