+3
-44
README.md
+3
-44
README.md
···
1
-
# Bog5 Protocol
2
-
3
-
The 5th Bogbook protocol implementation.
4
-
5
-
Try it at https://bog5.deno.dev/
6
-
7
-
Please note the above link is not networked. For a fuller and networked version be sure to check out https://wiredove.net/
8
-
9
-
### The Protocol
10
-
11
-
Bogbook has been thru a few iterations. For the fans, thanks for sticking with me. For the people who want standards, I solicit your feedback. And now, the protocol...
12
-
13
-
We send around sha256 hashes since we're using content-addressable storage for everything. First we request the hash that finds us a **protocol message**:
14
-
15
-
```
16
-
<ed25519 pubkey><ed25519 sig>
17
-
```
18
19
-
This opens to:
20
21
-
```
22
-
<unix timestamp><sha256 hash>
23
-
```
24
-
25
-
The timestamp is required since this is a social network. The sha256 hashlinks either locally or over the gossip network to a **content message**, which is Yaml file that contains everything else we need to make a post render while not duplicating too much of what we might have already such as avatar photos.
26
-
27
-
Everything in the **protocol message** is we need to sort and authenticate a feed. Everything in the **content message** is what we need to render a message.
28
-
29
-
```
30
-
Text content
31
-
```
32
-
33
-
34
-
A full post might look like this:
35
-
36
-
```
37
-
---
38
-
previous: sha256 hash previous protocol message
39
-
name: Ev
40
-
image: sha256 of image blob
41
-
edit: sha256 hash of edited protocol message
42
-
---
43
-
Content
44
-
```
45
-
46
-
This means you could in theory edit your name and/or image on a post! But we also want a record of what your name and image was on the original post, since it is a security issue with some actors on decentralized social networks if you allow publishers to change their name over their entire feed. Or, for example, in a hack it would be harder to rewrite history.
47
48
---
49
MIT
+50
-64
bogbot.js
+50
-64
bogbot.js
···
1
-
import nacl from './lib/nacl-fast-es.js'
2
-
import { decode, encode } from './lib/base64.js'
3
import { cachekv } from './lib/cachekv.js'
4
import { human } from './lib/human.js'
5
import { vb } from './lib/vb.js'
6
7
let db
8
let hashLog = []
···
10
let newMessages = false
11
let sort = true
12
13
-
export const bogbot = {}
14
15
-
bogbot.start = async (appId) => {
16
db = await cachekv(appId)
17
18
setInterval(async () => {
···
40
try {
41
const obj = {
42
hash,
43
-
sig: await bogbot.get(hash)
44
}
45
obj.author = obj.sig.substring(0, 44)
46
-
obj.opened = await bogbot.open(obj.sig)
47
-
obj.text = await bogbot.get(obj.opened.substring(13))
48
obj.ts = obj.opened.substring(0, 13)
49
newArray.push(obj)
50
} catch (err) { console.log(err) }
···
65
}, 20000)
66
}
67
68
-
bogbot.generate = async () => {
69
-
const genkey = nacl.sign.keyPair()
70
-
const keygen = encode(genkey.publicKey) + encode(genkey.secretKey)
71
-
return keygen
72
}
73
74
-
bogbot.keypair = async () => {
75
const keypair = await db.get('keypair')
76
if (keypair) {
77
return keypair
78
}
79
}
80
81
-
bogbot.pubkey = async () => {
82
-
const keypair = await bogbot.keypair()
83
if (keypair) {
84
return keypair.substring(0, 44)
85
}
86
}
87
88
-
bogbot.privkey = async () => {
89
-
const keypair = await bogbot.keypair()
90
if (keypair) {
91
return keypair.substring(44)
92
}
93
}
94
95
-
bogbot.deletekey = async () => {
96
db.rm('keypair')
97
}
98
99
-
bogbot.clear = async () => {
100
db.clear()
101
}
102
103
-
bogbot.hash = async (data) => {
104
-
return encode(
105
-
Array.from(
106
-
new Uint8Array(
107
-
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(data))
108
-
)
109
-
)
110
-
)
111
-
}
112
113
-
bogbot.sign = async (data) => {
114
-
const timestamp = Date.now()
115
-
116
-
const hash = await bogbot.make(data)
117
118
-
const sig = encode(nacl.sign(new TextEncoder().encode(timestamp + hash), decode(await bogbot.privkey())))
119
-
await bogbot.add(await bogbot.pubkey() + sig)
120
-
const protocolMsg = await bogbot.make(await bogbot.pubkey() + sig)
121
db.put('previous', protocolMsg)
122
return protocolMsg
123
}
124
125
-
bogbot.open = async (msg) => {
126
try {
127
-
const pubkey = msg.substring(0, 44)
128
-
const sig = msg.substring(44)
129
-
130
-
const opened = new TextDecoder().decode(nacl.sign.open(decode(sig), decode(pubkey)))
131
-
132
-
return opened
133
} catch (err) {
134
//console.log('Not a valid Bog5 protocol message')
135
}
···
137
138
import { yaml } from './lib/yaml.js'
139
140
-
bogbot.parseYaml = async (doc) => {
141
return await yaml.parse(doc)
142
}
143
144
-
bogbot.createYaml = async (obj, content) => {
145
return await yaml.create(obj, content)
146
}
147
148
-
bogbot.compose = async (content, prev) => {
149
let obj = {}
150
if (prev) { obj = prev }
151
···
158
if (previous) { obj.previous = previous}
159
160
if (Object.keys(obj).length > 0) {
161
-
const yaml = await bogbot.createYaml(obj, content)
162
-
return await bogbot.sign(yaml)
163
} else {
164
-
return await bogbot.sign(content)
165
}
166
}
167
168
-
bogbot.make = async (data) => {
169
-
const hash = await bogbot.hash(data)
170
171
await db.put(hash, data)
172
173
return hash
174
}
175
176
-
bogbot.get = async (hash) => {
177
const blob = await db.get(hash)
178
179
return blob
180
}
181
182
-
bogbot.put = async (key, value) => {
183
await db.put(key, value)
184
}
185
186
-
bogbot.rm = async (key) => {
187
await db.rm(key)
188
}
189
190
-
bogbot.add = async (msg) => {
191
-
const opened = await bogbot.open(msg)
192
if (opened) {
193
-
const hash = await bogbot.make(msg)
194
if (!hashLog.includes(hash)) {
195
hashLog.push(hash)
196
const obj = {
···
199
}
200
obj.author = obj.sig.substring(0, 44)
201
obj.opened = opened
202
-
obj.text = await bogbot.get(obj.opened.substring(13))
203
obj.ts = obj.opened.substring(0, 13)
204
openedLog.push(obj)
205
newMessages = true
···
208
}
209
}
210
211
-
bogbot.getHashLog = async () => { return hashLog }
212
213
-
bogbot.getOpenedLog = async () => { return openedLog }
214
215
-
bogbot.query = async (query) => {
216
if (openedLog[0] && !query) { return openedLog }
217
if (openedLog[0] && query.startsWith('?')) {
218
const search = query.substring(1).replace(/%20/g, ' ').toUpperCase()
···
224
}
225
}
226
227
-
bogbot.getPubkeys = async () => {
228
-
const arr = await bogbot.query()
229
const newSet = new Set()
230
for (const msg of arr) {
231
newSet.add(msg.author)
···
234
return newArr
235
}
236
237
-
bogbot.getLatest = async (pubkey) => {
238
const q = openedLog.filter(msg => msg.author === pubkey)
239
return q[q.length -1]
240
}
241
242
-
bogbot.human = async (ts) => {
243
return await human(new Date(parseInt(ts)))
244
}
245
246
-
bogbot.visual = async (pubkey) => {
247
return vb(decode(pubkey), 256)
248
}
249
···
1
+
import { decode } from './lib/base64.js'
2
import { cachekv } from './lib/cachekv.js'
3
import { human } from './lib/human.js'
4
import { vb } from './lib/vb.js'
5
+
import { an } from 'https://esm.sh/gh/evbogue/anproto@f7cd761/an.js'
6
7
let db
8
let hashLog = []
···
10
let newMessages = false
11
let sort = true
12
13
+
export const apds = {}
14
15
+
apds.start = async (appId) => {
16
db = await cachekv(appId)
17
18
setInterval(async () => {
···
40
try {
41
const obj = {
42
hash,
43
+
sig: await apds.get(hash)
44
}
45
obj.author = obj.sig.substring(0, 44)
46
+
obj.opened = await apds.open(obj.sig)
47
+
obj.text = await apds.get(obj.opened.substring(13))
48
obj.ts = obj.opened.substring(0, 13)
49
newArray.push(obj)
50
} catch (err) { console.log(err) }
···
65
}, 20000)
66
}
67
68
+
apds.generate = async () => {
69
+
const genkey = await an.gen()
70
+
return genkey
71
}
72
73
+
apds.keypair = async () => {
74
const keypair = await db.get('keypair')
75
if (keypair) {
76
return keypair
77
}
78
}
79
80
+
apds.pubkey = async () => {
81
+
const keypair = await apds.keypair()
82
+
console.log(keypair)
83
if (keypair) {
84
return keypair.substring(0, 44)
85
}
86
}
87
88
+
apds.privkey = async () => {
89
+
const keypair = await apds.keypair()
90
if (keypair) {
91
return keypair.substring(44)
92
}
93
}
94
95
+
apds.deletekey = async () => {
96
db.rm('keypair')
97
}
98
99
+
apds.clear = async () => {
100
db.clear()
101
}
102
103
+
apds.hash = async (data) => { return await an.hash(data) }
104
105
+
apds.sign = async (data) => {
106
+
const hash = await apds.make(data)
107
+
const sig = await an.sign(hash, await apds.keypair())
108
+
console.log(sig)
109
+
await apds.add(sig)
110
+
const protocolMsg = await apds.make(sig)
111
112
db.put('previous', protocolMsg)
113
return protocolMsg
114
}
115
116
+
apds.open = async (msg) => {
117
try {
118
+
return await an.open(msg)
119
} catch (err) {
120
//console.log('Not a valid Bog5 protocol message')
121
}
···
123
124
import { yaml } from './lib/yaml.js'
125
126
+
apds.parseYaml = async (doc) => {
127
return await yaml.parse(doc)
128
}
129
130
+
apds.createYaml = async (obj, content) => {
131
return await yaml.create(obj, content)
132
}
133
134
+
apds.compose = async (content, prev) => {
135
let obj = {}
136
if (prev) { obj = prev }
137
···
144
if (previous) { obj.previous = previous}
145
146
if (Object.keys(obj).length > 0) {
147
+
const yaml = await apds.createYaml(obj, content)
148
+
return await apds.sign(yaml)
149
} else {
150
+
return await apds.sign(content)
151
}
152
}
153
154
+
apds.make = async (data) => {
155
+
const hash = await apds.hash(data)
156
157
await db.put(hash, data)
158
159
return hash
160
}
161
162
+
apds.get = async (hash) => {
163
const blob = await db.get(hash)
164
165
return blob
166
}
167
168
+
apds.put = async (key, value) => {
169
await db.put(key, value)
170
}
171
172
+
apds.rm = async (key) => {
173
await db.rm(key)
174
}
175
176
+
apds.add = async (msg) => {
177
+
const opened = await apds.open(msg)
178
if (opened) {
179
+
const hash = await apds.make(msg)
180
if (!hashLog.includes(hash)) {
181
hashLog.push(hash)
182
const obj = {
···
185
}
186
obj.author = obj.sig.substring(0, 44)
187
obj.opened = opened
188
+
obj.text = await apds.get(obj.opened.substring(13))
189
obj.ts = obj.opened.substring(0, 13)
190
openedLog.push(obj)
191
newMessages = true
···
194
}
195
}
196
197
+
apds.getHashLog = async () => { return hashLog }
198
199
+
apds.getOpenedLog = async () => { return openedLog }
200
201
+
apds.query = async (query) => {
202
if (openedLog[0] && !query) { return openedLog }
203
if (openedLog[0] && query.startsWith('?')) {
204
const search = query.substring(1).replace(/%20/g, ' ').toUpperCase()
···
210
}
211
}
212
213
+
apds.getPubkeys = async () => {
214
+
const arr = await apds.query()
215
const newSet = new Set()
216
for (const msg of arr) {
217
newSet.add(msg.author)
···
220
return newArr
221
}
222
223
+
apds.getLatest = async (pubkey) => {
224
const q = openedLog.filter(msg => msg.author === pubkey)
225
return q[q.length -1]
226
}
227
228
+
apds.human = async (ts) => {
229
return await human(new Date(parseInt(ts)))
230
}
231
232
+
apds.visual = async (pubkey) => {
233
return vb(decode(pubkey), 256)
234
}
235
+2
-2
composer.js
+2
-2
composer.js
···
1
-
import { bogbot } from './bogbot.js'
2
import { render } from './render.js'
3
4
export const composer = async () => {
···
14
b.textContent = 'Sign'
15
16
b.onclick = async () => {
17
-
const published = await bogbot.compose(ta.value)
18
ta.value = ''
19
const scroller = document.getElementById('scroller')
20
await render.hash(published, scroller)
···
1
+
import { apds } from './apds.js'
2
import { render } from './render.js'
3
4
export const composer = async () => {
···
14
b.textContent = 'Sign'
15
16
b.onclick = async () => {
17
+
const published = await apds.compose(ta.value)
18
ta.value = ''
19
const scroller = document.getElementById('scroller')
20
await render.hash(published, scroller)
+7
-6
example.js
+7
-6
example.js
···
1
-
import { bogbot } from './bogbot.js'
2
import { profile } from './profile.js'
3
import { composer } from './composer.js'
4
import { render } from './render.js'
5
6
-
await bogbot.start('bog5example')
7
8
-
if (!await bogbot.pubkey()) {
9
-
const keypair = await bogbot.generate()
10
-
await bogbot.put('keypair', keypair)
11
}
12
13
document.body.appendChild(await profile())
···
18
19
document.body.appendChild(scroller)
20
21
-
const log = await bogbot.query()
22
23
log.forEach(async (obj) => {
24
await render.hash(obj.hash, scroller)
···
1
+
import { apds } from './apds.js'
2
import { profile } from './profile.js'
3
import { composer } from './composer.js'
4
import { render } from './render.js'
5
6
+
await apds.start('apds1')
7
8
+
if (!await apds.pubkey()) {
9
+
const keypair = await apds.generate()
10
+
console.log(keypair)
11
+
await apds.put('keypair', keypair)
12
}
13
14
document.body.appendChild(await profile())
···
19
20
document.body.appendChild(scroller)
21
22
+
const log = await apds.query()
23
24
log.forEach(async (obj) => {
25
await render.hash(obj.hash, scroller)
+11
-11
profile.js
+11
-11
profile.js
···
1
import { h } from './lib/h.js'
2
-
import { bogbot } from './bogbot.js'
3
4
export const profile = async () => {
5
const div = h('div')
6
7
-
const avatarImg = await bogbot.visual(await bogbot.pubkey())
8
9
-
const existingImage = await bogbot.get('image')
10
11
-
if (existingImage) { avatarImg.src = await bogbot.get(existingImage)}
12
13
avatarImg.style = 'height: 30px; width: 30px; float: left; margin-right: 5px; object-fit: cover;'
14
···
46
ctx.drawImage(img, 0, 0, width, height, 0, 0, cropWidth, cropHeight)
47
const croppedImage = canvas.toDataURL()
48
avatarImg.src = croppedImage
49
-
const hash = await bogbot.make(croppedImage)
50
-
await bogbot.put('image', hash)
51
} else {
52
const croppedImage = canvas.toDataURL()
53
avatarImg.src = img.src
54
-
const hash = await bogbot.make(img.src)
55
-
await bogbot.put('image', hash)
56
}
57
}
58
img.src = e.target.result
···
65
66
div.appendChild(avatarImg)
67
68
-
div.appendChild(h('div', [await bogbot.pubkey()]))
69
70
-
const name = await bogbot.get('name')
71
72
const namer = h('input', {
73
placeholder: name || 'Name yourself'
···
79
h('button', {onclick: async () => {
80
if (namer.value) {
81
namer.placeholder = namer.value
82
-
await bogbot.put('name', namer.value)
83
namer.value = ''
84
}
85
}}, ['Save'])
···
1
import { h } from './lib/h.js'
2
+
import { apds } from './apds.js'
3
4
export const profile = async () => {
5
const div = h('div')
6
7
+
const avatarImg = await apds.visual(await apds.pubkey())
8
9
+
const existingImage = await apds.get('image')
10
11
+
if (existingImage) { avatarImg.src = await apds.get(existingImage)}
12
13
avatarImg.style = 'height: 30px; width: 30px; float: left; margin-right: 5px; object-fit: cover;'
14
···
46
ctx.drawImage(img, 0, 0, width, height, 0, 0, cropWidth, cropHeight)
47
const croppedImage = canvas.toDataURL()
48
avatarImg.src = croppedImage
49
+
const hash = await apds.make(croppedImage)
50
+
await apds.put('image', hash)
51
} else {
52
const croppedImage = canvas.toDataURL()
53
avatarImg.src = img.src
54
+
const hash = await apds.make(img.src)
55
+
await apds.put('image', hash)
56
}
57
}
58
img.src = e.target.result
···
65
66
div.appendChild(avatarImg)
67
68
+
div.appendChild(h('div', [await apds.pubkey()]))
69
70
+
const name = await apds.get('name')
71
72
const namer = h('input', {
73
placeholder: name || 'Name yourself'
···
79
h('button', {onclick: async () => {
80
if (namer.value) {
81
namer.placeholder = namer.value
82
+
await apds.put('name', namer.value)
83
namer.value = ''
84
}
85
}}, ['Save'])
+10
-10
render.js
+10
-10
render.js
···
1
-
import { bogbot } from './bogbot.js'
2
import { h } from './lib/h.js'
3
4
export const render = {}
5
6
render.blob = async (blob) => {
7
-
const hash = await bogbot.hash(blob)
8
9
const div = await document.getElementById(hash)
10
11
try {
12
-
const opened = await bogbot.open(blob)
13
-
const ts = h('span', [await bogbot.human(opened.substring(0, 13))])
14
setInterval(async () => {
15
-
ts.textContent = await bogbot.human(opened.substring(0, 13))
16
}, 1000)
17
if (div) {
18
-
const img = await bogbot.visual(blob.substring(0, 44))
19
img.id = 'image'
20
img.style = 'width: 30px; height: 30px; float: left; margin-right: 5px; object-fit: cover;'
21
div.appendChild(img)
22
div.appendChild(h('a', {href: '#' + blob.substring(0, 44), id: 'name'}, [blob.substring(0, 10)]))
23
div.appendChild(h('a', {href: '#' + hash, style: 'float: right;'}, [ts]))
24
div.appendChild(h('div', {id: opened.substring(13)}))
25
-
const content = await bogbot.get(opened.substring(13))
26
if (content) {
27
await render.blob(content)
28
}
···
31
}
32
} catch (err) {
33
console.log('Not a valid protocol message')
34
-
const yaml = await bogbot.parseYaml(blob)
35
if (div) {
36
div.textContent = yaml.body
37
div.parentNode.childNodes.forEach(async (node) => {
···
39
node.textContent = yaml.name
40
}
41
if (yaml.image && node.id === 'image') {
42
-
const image = await bogbot.get(yaml.image)
43
node.src = image
44
}
45
})
···
53
54
scroller.insertBefore(div, scroller.firstChild)
55
56
-
const sig = await bogbot.get(hash)
57
58
if (sig) {
59
await render.blob(sig)
···
1
+
import { apds } from './apds.js'
2
import { h } from './lib/h.js'
3
4
export const render = {}
5
6
render.blob = async (blob) => {
7
+
const hash = await apds.hash(blob)
8
9
const div = await document.getElementById(hash)
10
11
try {
12
+
const opened = await apds.open(blob)
13
+
const ts = h('span', [await apds.human(opened.substring(0, 13))])
14
setInterval(async () => {
15
+
ts.textContent = await apds.human(opened.substring(0, 13))
16
}, 1000)
17
if (div) {
18
+
const img = await apds.visual(blob.substring(0, 44))
19
img.id = 'image'
20
img.style = 'width: 30px; height: 30px; float: left; margin-right: 5px; object-fit: cover;'
21
div.appendChild(img)
22
div.appendChild(h('a', {href: '#' + blob.substring(0, 44), id: 'name'}, [blob.substring(0, 10)]))
23
div.appendChild(h('a', {href: '#' + hash, style: 'float: right;'}, [ts]))
24
div.appendChild(h('div', {id: opened.substring(13)}))
25
+
const content = await apds.get(opened.substring(13))
26
if (content) {
27
await render.blob(content)
28
}
···
31
}
32
} catch (err) {
33
console.log('Not a valid protocol message')
34
+
const yaml = await apds.parseYaml(blob)
35
if (div) {
36
div.textContent = yaml.body
37
div.parentNode.childNodes.forEach(async (node) => {
···
39
node.textContent = yaml.name
40
}
41
if (yaml.image && node.id === 'image') {
42
+
const image = await apds.get(yaml.image)
43
node.src = image
44
}
45
})
···
53
54
scroller.insertBefore(div, scroller.firstChild)
55
56
+
const sig = await apds.get(hash)
57
58
if (sig) {
59
await render.blob(sig)