voidnote 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.
- voidnote-0.1.0/.gitignore +26 -0
- voidnote-0.1.0/PKG-INFO +67 -0
- voidnote-0.1.0/README.md +46 -0
- voidnote-0.1.0/pyproject.toml +33 -0
- voidnote-0.1.0/voidnote/__init__.py +166 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Claude
|
|
5
|
+
.claude/
|
|
6
|
+
# Build output
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Astro
|
|
10
|
+
.astro/
|
|
11
|
+
|
|
12
|
+
# Wrangler
|
|
13
|
+
.wrangler/
|
|
14
|
+
|
|
15
|
+
# Environment
|
|
16
|
+
.env
|
|
17
|
+
.env.*
|
|
18
|
+
.dev.vars
|
|
19
|
+
|
|
20
|
+
# OS
|
|
21
|
+
.DS_Store
|
|
22
|
+
|
|
23
|
+
# IDE
|
|
24
|
+
.vscode/
|
|
25
|
+
.idea/
|
|
26
|
+
node_modules
|
voidnote-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: voidnote
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official SDK for VoidNote — zero-knowledge self-destructing notes
|
|
5
|
+
Project-URL: Homepage, https://voidnote.net
|
|
6
|
+
Project-URL: Repository, https://github.com/quantum-encoding/voidnote-python
|
|
7
|
+
Project-URL: Documentation, https://voidnote.net/docs
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: encryption,one-time,secrets,voidnote,zero-knowledge
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Security :: Cryptography
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Requires-Dist: cryptography>=41.0.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# voidnote
|
|
23
|
+
|
|
24
|
+
Official Python SDK for [VoidNote](https://voidnote.net) — zero-knowledge self-destructing notes.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install voidnote
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Read a note
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import voidnote
|
|
38
|
+
|
|
39
|
+
result = voidnote.read("https://voidnote.net/note/abc123...")
|
|
40
|
+
print(result.content) # decrypted content
|
|
41
|
+
print(result.destroyed) # True if view limit was reached
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Create a note (requires API key)
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import voidnote
|
|
48
|
+
|
|
49
|
+
result = voidnote.create(
|
|
50
|
+
"my secret value",
|
|
51
|
+
api_key="vn_...",
|
|
52
|
+
max_views=1,
|
|
53
|
+
title="Deploy key",
|
|
54
|
+
)
|
|
55
|
+
print(result.url) # share this link
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- Python 3.9+
|
|
61
|
+
- `cryptography >= 41.0.0`
|
|
62
|
+
|
|
63
|
+
## Links
|
|
64
|
+
|
|
65
|
+
- [voidnote.net](https://voidnote.net)
|
|
66
|
+
- [How it works](https://voidnote.net/how-it-works)
|
|
67
|
+
- [GitHub](https://github.com/quantum-encoding/voidnote-python)
|
voidnote-0.1.0/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# voidnote
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [VoidNote](https://voidnote.net) — zero-knowledge self-destructing notes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install voidnote
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Read a note
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import voidnote
|
|
17
|
+
|
|
18
|
+
result = voidnote.read("https://voidnote.net/note/abc123...")
|
|
19
|
+
print(result.content) # decrypted content
|
|
20
|
+
print(result.destroyed) # True if view limit was reached
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Create a note (requires API key)
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import voidnote
|
|
27
|
+
|
|
28
|
+
result = voidnote.create(
|
|
29
|
+
"my secret value",
|
|
30
|
+
api_key="vn_...",
|
|
31
|
+
max_views=1,
|
|
32
|
+
title="Deploy key",
|
|
33
|
+
)
|
|
34
|
+
print(result.url) # share this link
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
- Python 3.9+
|
|
40
|
+
- `cryptography >= 41.0.0`
|
|
41
|
+
|
|
42
|
+
## Links
|
|
43
|
+
|
|
44
|
+
- [voidnote.net](https://voidnote.net)
|
|
45
|
+
- [How it works](https://voidnote.net/how-it-works)
|
|
46
|
+
- [GitHub](https://github.com/quantum-encoding/voidnote-python)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "voidnote"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official SDK for VoidNote — zero-knowledge self-destructing notes"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
keywords = ["voidnote", "secrets", "encryption", "zero-knowledge", "one-time"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Topic :: Security :: Cryptography",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"cryptography>=41.0.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
dev = ["pytest", "mypy"]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://voidnote.net"
|
|
29
|
+
Repository = "https://github.com/quantum-encoding/voidnote-python"
|
|
30
|
+
Documentation = "https://voidnote.net/docs"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["voidnote"]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VoidNote SDK — Python
|
|
3
|
+
|
|
4
|
+
pip install voidnote
|
|
5
|
+
|
|
6
|
+
Works with Python 3.9+. Requires: cryptography
|
|
7
|
+
|
|
8
|
+
pip install voidnote cryptography
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import urllib.request
|
|
17
|
+
import json
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
23
|
+
_HAS_CRYPTO = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
_HAS_CRYPTO = False
|
|
26
|
+
|
|
27
|
+
DEFAULT_BASE = "https://voidnote.net"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ReadResult:
|
|
32
|
+
content: str
|
|
33
|
+
title: Optional[str]
|
|
34
|
+
view_count: int
|
|
35
|
+
max_views: int
|
|
36
|
+
destroyed: bool
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class CreateResult:
|
|
41
|
+
url: str
|
|
42
|
+
expires_at: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# --- internal helpers ---
|
|
46
|
+
|
|
47
|
+
def _extract_token(url_or_token: str) -> str:
|
|
48
|
+
match = re.search(r"[0-9a-f]{64}", url_or_token, re.IGNORECASE)
|
|
49
|
+
if not match:
|
|
50
|
+
raise ValueError("No valid 64-char token found in input")
|
|
51
|
+
return match.group(0).lower()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _generate_token() -> tuple[str, str, str]:
|
|
55
|
+
token_bytes = os.urandom(32)
|
|
56
|
+
full_token = token_bytes.hex()
|
|
57
|
+
return full_token, full_token[:32], full_token[32:]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _encrypt(content: str, secret: str) -> tuple[str, str]:
|
|
61
|
+
if not _HAS_CRYPTO:
|
|
62
|
+
raise ImportError(
|
|
63
|
+
"The 'cryptography' package is required to create notes: "
|
|
64
|
+
"pip install cryptography"
|
|
65
|
+
)
|
|
66
|
+
secret_bytes = bytes.fromhex(secret)
|
|
67
|
+
key = hashlib.sha256(secret_bytes).digest()
|
|
68
|
+
iv = os.urandom(12)
|
|
69
|
+
aesgcm = AESGCM(key)
|
|
70
|
+
ciphertext = aesgcm.encrypt(iv, content.encode("utf-8"), None)
|
|
71
|
+
return ciphertext.hex(), iv.hex()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _fetch(url: str, *, method: str = "GET", headers: dict | None = None, body: bytes | None = None):
|
|
75
|
+
req = urllib.request.Request(url, data=body, method=method, headers=headers or {})
|
|
76
|
+
with urllib.request.urlopen(req) as resp:
|
|
77
|
+
return json.loads(resp.read().decode("utf-8")), resp.status
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# --- public API ---
|
|
81
|
+
|
|
82
|
+
def read(url_or_token: str, base: str = DEFAULT_BASE) -> ReadResult:
|
|
83
|
+
"""
|
|
84
|
+
Read a VoidNote. Consumes one view.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
url_or_token: Full VoidNote URL or raw 64-char hex token
|
|
88
|
+
base: Override base URL (default: https://voidnote.net)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
ReadResult with .content, .title, .view_count, .max_views, .destroyed
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
result = voidnote.read("https://voidnote.net/note/abc123...")
|
|
95
|
+
print(result.content)
|
|
96
|
+
"""
|
|
97
|
+
token = _extract_token(url_or_token)
|
|
98
|
+
data, status = _fetch(f"{base}/api/note/{token}")
|
|
99
|
+
if status >= 400:
|
|
100
|
+
raise RuntimeError(data.get("error", f"HTTP {status}"))
|
|
101
|
+
return ReadResult(
|
|
102
|
+
content=data["content"],
|
|
103
|
+
title=data.get("title"),
|
|
104
|
+
view_count=data["viewCount"],
|
|
105
|
+
max_views=data["maxViews"],
|
|
106
|
+
destroyed=data["destroyed"],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def create(
|
|
111
|
+
content: str,
|
|
112
|
+
*,
|
|
113
|
+
api_key: str,
|
|
114
|
+
title: Optional[str] = None,
|
|
115
|
+
max_views: int = 1,
|
|
116
|
+
base: str = DEFAULT_BASE,
|
|
117
|
+
) -> CreateResult:
|
|
118
|
+
"""
|
|
119
|
+
Create a VoidNote. Encrypts content client-side — the server never sees plaintext.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
content: The secret text to encrypt and store
|
|
123
|
+
api_key: Your API key from dashboard (vn_...)
|
|
124
|
+
title: Optional plaintext title (keep non-sensitive)
|
|
125
|
+
max_views: How many times the note can be read (1–100)
|
|
126
|
+
base: Override base URL (default: https://voidnote.net)
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
CreateResult with .url (shareable link) and .expires_at
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
result = voidnote.create("my-secret-api-key", api_key="vn_...")
|
|
133
|
+
print(result.url)
|
|
134
|
+
"""
|
|
135
|
+
if not 1 <= max_views <= 100:
|
|
136
|
+
raise ValueError("max_views must be between 1 and 100")
|
|
137
|
+
|
|
138
|
+
full_token, token_id, secret = _generate_token()
|
|
139
|
+
encrypted_content, iv = _encrypt(content, secret)
|
|
140
|
+
|
|
141
|
+
payload = {
|
|
142
|
+
"tokenId": token_id,
|
|
143
|
+
"encryptedContent": encrypted_content,
|
|
144
|
+
"iv": iv,
|
|
145
|
+
"maxViews": max_views,
|
|
146
|
+
}
|
|
147
|
+
if title:
|
|
148
|
+
payload["title"] = title
|
|
149
|
+
|
|
150
|
+
body = json.dumps(payload).encode("utf-8")
|
|
151
|
+
headers = {
|
|
152
|
+
"Content-Type": "application/json",
|
|
153
|
+
"Authorization": f"Bearer {api_key}",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
data, status = _fetch(f"{base}/api/notes", method="POST", headers=headers, body=body)
|
|
157
|
+
if status >= 400 or not data.get("success"):
|
|
158
|
+
raise RuntimeError(data.get("error", f"HTTP {status}"))
|
|
159
|
+
|
|
160
|
+
return CreateResult(
|
|
161
|
+
url=f"{data['siteUrl']}/note/{full_token}",
|
|
162
|
+
expires_at=data["expiresAt"],
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
__all__ = ["read", "create", "ReadResult", "CreateResult"]
|