+15
README.md
+15
README.md
···
1
+
# Cistern
2
+
3
+
Cistern is an attempt at implementing a private, personal quick-capture system
4
+
on AT Protocol. Cistern "items" are encrypted, so that they are only readable
5
+
by the holder of the correct secret key—stored off-protocol. These items are
6
+
intended to be ephemeral, and to be deleted after they've been read.
7
+
8
+
The intention is for Cistern to bridge the gap between where ideas are had and
9
+
where they can be stored long-term. For example, let's say you have an idea
10
+
while at a restaurant. You create an item using your phone, and once you're
11
+
back at your desk and you open Obsidian, that item is automatically pulled from
12
+
your PDS, decrypted, and deleted from your PDS. If your notebook was open at
13
+
the time you created your item, the Cistern Obsidian plugin would have been
14
+
notified of the new item via the Jetstream, and so you would find your memo
15
+
waiting for you once you got home.
+57
deno.lock
+57
deno.lock
···
1
+
{
2
+
"version": "5",
3
+
"specifiers": {
4
+
"npm:@atproto/lexicon@~0.5.1": "0.5.1"
5
+
},
6
+
"npm": {
7
+
"@atproto/common-web@0.4.3": {
8
+
"integrity": "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg==",
9
+
"dependencies": [
10
+
"graphemer",
11
+
"multiformats",
12
+
"uint8arrays",
13
+
"zod"
14
+
]
15
+
},
16
+
"@atproto/lexicon@0.5.1": {
17
+
"integrity": "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A==",
18
+
"dependencies": [
19
+
"@atproto/common-web",
20
+
"@atproto/syntax",
21
+
"iso-datestring-validator",
22
+
"multiformats",
23
+
"zod"
24
+
]
25
+
},
26
+
"@atproto/syntax@0.4.1": {
27
+
"integrity": "sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw=="
28
+
},
29
+
"graphemer@1.4.0": {
30
+
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="
31
+
},
32
+
"iso-datestring-validator@2.2.2": {
33
+
"integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="
34
+
},
35
+
"multiformats@9.9.0": {
36
+
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="
37
+
},
38
+
"uint8arrays@3.0.0": {
39
+
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
40
+
"dependencies": [
41
+
"multiformats"
42
+
]
43
+
},
44
+
"zod@3.25.76": {
45
+
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
46
+
}
47
+
},
48
+
"workspace": {
49
+
"members": {
50
+
"packages/lexicon": {
51
+
"dependencies": [
52
+
"npm:@atproto/lexicon@~0.5.1"
53
+
]
54
+
}
55
+
}
56
+
}
57
+
}
+9
packages/lexicon/deno.jsonc
+9
packages/lexicon/deno.jsonc
+11
packages/lexicon/mod.ts
+11
packages/lexicon/mod.ts
···
1
+
import item from "./schemas/item.json" with { type: "json" };
2
+
import pubkey from "./schemas/pubkey.json" with { type: "json" };
3
+
4
+
import { Lexicons } from "@atproto/lexicon";
5
+
6
+
const lexicons = new Lexicons();
7
+
8
+
lexicons.add(item);
9
+
lexicons.add(pubkey);
10
+
11
+
export { item, lexicons, pubkey };
+101
packages/lexicon/schemas/item.json
+101
packages/lexicon/schemas/item.json
···
1
+
{
2
+
"version": 1,
3
+
"id": "app.cistern.lexicon.item",
4
+
"description": "An encrypted value meant to be accessed and deleted later.",
5
+
"defs": {
6
+
"encryptedField": {
7
+
"type": "object",
8
+
"description": "An inline-encrypted property",
9
+
"required": ["value", "nonce"],
10
+
"properties": {
11
+
"value": {
12
+
"type": "string",
13
+
"description": "Encrypted field value"
14
+
},
15
+
"nonce": {
16
+
"type": "string",
17
+
"description": "Nonce used to encrypt field value"
18
+
}
19
+
}
20
+
},
21
+
"main": {
22
+
"type": "record",
23
+
"description": "A manifest representing an encrypted note or binary file",
24
+
"record": {
25
+
"type": "object",
26
+
"required": [
27
+
"tid",
28
+
"ciphertext",
29
+
"nonce",
30
+
"algorithm",
31
+
"pubkey",
32
+
"chunks",
33
+
"metadata"
34
+
],
35
+
"properties": {
36
+
"tid": {
37
+
"type": "string",
38
+
"description": "TID representing when this item was created",
39
+
"format": "tid"
40
+
}
41
+
"ciphertext": {
42
+
"type": "string",
43
+
"description": "Encapsulated shared ciphertext"
44
+
},
45
+
"nonce": {
46
+
"type": "string",
47
+
"description": "Nonce used for content encryption",
48
+
"maxLength": 64,
49
+
},
50
+
"algorithm": {
51
+
"type": "string",
52
+
"description": "Algorithm used for encryption, in <kem>-<cipher>-<hash> format.",
53
+
"knownValues": ["x_wing-xchacha20_poly1305-sha3_512"]
54
+
},
55
+
"pubkey": {
56
+
"type": "string",
57
+
"description": "URI to the public key used to encrypt this item",
58
+
"format": "at-uri"
59
+
},
60
+
"chunks": {
61
+
"type": "array",
62
+
"description": "Encrypted blobs that compose the encrypted contents of the item"
63
+
"items": {
64
+
"type": "blob",
65
+
"accept": "application/octet-stream",
66
+
"maxSize": 50000000
67
+
},
68
+
"minLength": 1
69
+
},
70
+
"metadata": {
71
+
"type": "object",
72
+
"description": "Information about the encrypted content.",
73
+
"required": ["format", "length", "hash"],
74
+
"properties": {
75
+
"format": {
76
+
"type": "string",
77
+
"description": "Original contents format",
78
+
"knownValues": ["text", "file"]
79
+
},
80
+
"length": {
81
+
"type": "ref",
82
+
"description": "Original content length in bytes",
83
+
"ref": "app.cistern.lexicon.item#encryptedField"
84
+
},
85
+
"hash": {
86
+
"type": "string",
87
+
"description": "SHA-256 hash of the original file",
88
+
"maxLength": 64,
89
+
},
90
+
"mimetype": {
91
+
"type": "ref",
92
+
"description": "Mimetype of original contents, if a file",
93
+
"ref": "app.cistern.lexicon.item#encryptedField"
94
+
}
95
+
}
96
+
}
97
+
}
98
+
}
99
+
}
100
+
}
101
+
}
+35
packages/lexicon/schemas/pubkey.json
+35
packages/lexicon/schemas/pubkey.json
···
1
+
{
2
+
"version": 1,
3
+
"id": "app.cistern.lexicon.pubkey",
4
+
"description": "A public key used to encrypt Cistern items",
5
+
"defs": {
6
+
"main": {
7
+
"type": "record",
8
+
"description": "A public key used to encrypt Cistern items",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["name", "algorithm", "content", "createdAt"],
12
+
"properties": {
13
+
"name": {
14
+
"type": "string",
15
+
"minGraphemes": 1,
16
+
"description": "A memorable name for this public key. Avoid using revealing names, such as \"Graham's Macbook\""
17
+
},
18
+
"algorithm": {
19
+
"type": "string",
20
+
"knownValues": ["x_wing"],
21
+
"description": "KEM algorithm used to generate this key"
22
+
},
23
+
"content": {
24
+
"type": "string",
25
+
"description": "Contents of the public key, encoded in base64"
26
+
},
27
+
"createdAt": {
28
+
"type": "string",
29
+
"format": "datetime"
30
+
}
31
+
}
32
+
}
33
+
}
34
+
}
35
+
}