hypercore 10.0.0-alpha.5 → 10.0.0-alpha.52

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.
package/lib/bitfield.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // TODO: needs massive improvements obvs
2
2
 
3
3
  const BigSparseArray = require('big-sparse-array')
4
+ const b4a = require('b4a')
4
5
 
5
6
  class FixedBitfield {
6
7
  constructor (index, bitfield) {
@@ -40,8 +41,9 @@ module.exports = class Bitfield {
40
41
  this.pages = new BigSparseArray()
41
42
  this.unflushed = []
42
43
  this.storage = storage
44
+ this.resumed = !!(buf && buf.byteLength >= 4)
43
45
 
44
- const all = (buf && buf.byteLength >= 4)
46
+ const all = this.resumed
45
47
  ? new Uint32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4))
46
48
  : new Uint32Array(1024)
47
49
 
@@ -92,8 +94,10 @@ module.exports = class Bitfield {
92
94
  clear () {
93
95
  return new Promise((resolve, reject) => {
94
96
  this.storage.del(0, Infinity, (err) => {
95
- if (err) reject(err)
96
- else resolve()
97
+ if (err) return reject(err)
98
+ this.pages = new BigSparseArray()
99
+ this.unflushed = []
100
+ resolve()
97
101
  })
98
102
  })
99
103
  }
@@ -116,9 +120,9 @@ module.exports = class Bitfield {
116
120
  let error = null
117
121
 
118
122
  for (const page of this.unflushed) {
119
- const b = Buffer.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
123
+ const buf = b4a.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
120
124
  page.dirty = false
121
- this.storage.write(page.index * 4096, b, done)
125
+ this.storage.write(page.index * 4096, buf, done)
122
126
  }
123
127
 
124
128
  function done (err) {
@@ -0,0 +1,68 @@
1
+ const sodium = require('sodium-universal')
2
+ const c = require('compact-encoding')
3
+ const b4a = require('b4a')
4
+
5
+ const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
6
+
7
+ module.exports = class BlockEncryption {
8
+ constructor (encryptionKey, hypercoreKey) {
9
+ const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)
10
+
11
+ this.key = encryptionKey
12
+ this.blockKey = subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
13
+ this.blindingKey = subKeys.subarray(sodium.crypto_stream_KEYBYTES)
14
+ this.padding = 8
15
+
16
+ sodium.crypto_generichash(this.blockKey, encryptionKey, hypercoreKey)
17
+ sodium.crypto_generichash(this.blindingKey, this.blockKey)
18
+ }
19
+
20
+ encrypt (index, block, fork) {
21
+ const padding = block.subarray(0, this.padding)
22
+ block = block.subarray(this.padding)
23
+
24
+ c.uint64.encode({ start: 0, end: 8, buffer: padding }, fork)
25
+ c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
26
+
27
+ // Zero out any previous padding.
28
+ nonce.fill(0, 8, 8 + padding.byteLength)
29
+
30
+ // Blind the fork ID, possibly risking reusing the nonce on a reorg of the
31
+ // Hypercore. This is fine as the blinding is best-effort and the latest
32
+ // fork ID shared on replication anyway.
33
+ sodium.crypto_stream_xor(
34
+ padding,
35
+ padding,
36
+ nonce,
37
+ this.blindingKey
38
+ )
39
+
40
+ nonce.set(padding, 8)
41
+
42
+ // The combination of a (blinded) fork ID and a block index is unique for a
43
+ // given Hypercore and is therefore a valid nonce for encrypting the block.
44
+ sodium.crypto_stream_xor(
45
+ block,
46
+ block,
47
+ nonce,
48
+ this.blockKey
49
+ )
50
+ }
51
+
52
+ decrypt (index, block) {
53
+ const padding = block.subarray(0, this.padding)
54
+ block = block.subarray(this.padding)
55
+
56
+ c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
57
+
58
+ nonce.set(padding, 8)
59
+
60
+ // Decrypt the block using the blinded fork ID.
61
+ sodium.crypto_stream_xor(
62
+ block,
63
+ block,
64
+ nonce,
65
+ this.blockKey
66
+ )
67
+ }
68
+ }
@@ -1,3 +1,5 @@
1
+ const b4a = require('b4a')
2
+
1
3
  module.exports = class BlockStore {
2
4
  constructor (storage, tree) {
3
5
  this.storage = storage
@@ -15,7 +17,7 @@ module.exports = class BlockStore {
15
17
 
16
18
  putBatch (i, batch, offset) {
17
19
  if (batch.length === 0) return Promise.resolve()
18
- return this.put(i, batch.length === 1 ? batch[0] : Buffer.concat(batch), offset)
20
+ return this.put(i, batch.length === 1 ? batch[0] : b4a.concat(batch), offset)
19
21
  }
20
22
 
21
23
  clear () {
package/lib/caps.js ADDED
@@ -0,0 +1,32 @@
1
+ const crypto = require('hypercore-crypto')
2
+ const sodium = require('sodium-universal')
3
+ const b4a = require('b4a')
4
+ const c = require('compact-encoding')
5
+
6
+ // TODO: rename this to "crypto" and move everything hashing related etc in here
7
+ // Also lets move the tree stuff from hypercore-crypto here
8
+
9
+ const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER] = crypto.namespace('hypercore', 3)
10
+
11
+ exports.replicate = function (isInitiator, key, handshakeHash) {
12
+ const out = b4a.allocUnsafe(32)
13
+ sodium.crypto_generichash_batch(out, [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key], handshakeHash)
14
+ return out
15
+ }
16
+
17
+ exports.treeSignable = function (hash, length, fork) {
18
+ const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
19
+ c.raw.encode(state, TREE)
20
+ c.raw.encode(state, hash)
21
+ c.uint64.encode(state, length)
22
+ c.uint64.encode(state, fork)
23
+ return state.buffer
24
+ }
25
+
26
+ exports.treeSignableLegacy = function (hash, length, fork) {
27
+ const state = { start: 0, end: 48, buffer: b4a.allocUnsafe(48) }
28
+ c.raw.encode(state, hash)
29
+ c.uint64.encode(state, length)
30
+ c.uint64.encode(state, fork)
31
+ return state.buffer
32
+ }
package/lib/core.js CHANGED
@@ -1,21 +1,24 @@
1
1
  const hypercoreCrypto = require('hypercore-crypto')
2
+ const b4a = require('b4a')
2
3
  const Oplog = require('./oplog')
3
4
  const Mutex = require('./mutex')
4
5
  const MerkleTree = require('./merkle-tree')
5
6
  const BlockStore = require('./block-store')
6
7
  const Bitfield = require('./bitfield')
7
- const { oplogHeader, oplogEntry } = require('./messages')
8
+ const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = require('./errors')
9
+ const m = require('./messages')
8
10
 
9
11
  module.exports = class Core {
10
- constructor (header, crypto, oplog, tree, blocks, bitfield, sign, onupdate) {
12
+ constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate, oncontigupdate) {
11
13
  this.onupdate = onupdate
14
+ this.oncontigupdate = oncontigupdate
12
15
  this.header = header
13
16
  this.crypto = crypto
14
17
  this.oplog = oplog
15
18
  this.tree = tree
16
19
  this.blocks = blocks
17
20
  this.bitfield = bitfield
18
- this.defaultSign = sign
21
+ this.defaultAuth = auth
19
22
  this.truncating = 0
20
23
 
21
24
  this._maxOplogSize = 65536
@@ -23,6 +26,9 @@ module.exports = class Core {
23
26
  this._verifies = null
24
27
  this._verifiesFlushed = null
25
28
  this._mutex = new Mutex()
29
+ this._legacy = legacy
30
+
31
+ this._updateContiguousLength(header.contiguousLength)
26
32
  }
27
33
 
28
34
  static async open (storage, opts = {}) {
@@ -49,27 +55,46 @@ module.exports = class Core {
49
55
  }
50
56
  }
51
57
 
52
- // TODO: we should prob have a general "auth" abstraction instead somewhere?
53
- static createSigner (crypto, { publicKey, secretKey }) {
54
- if (!crypto.validateKeyPair({ publicKey, secretKey })) throw new Error('Invalid key pair')
55
- return signable => crypto.sign(signable, secretKey)
58
+ static createAuth (crypto, { publicKey, secretKey }, opts = {}) {
59
+ if (secretKey && !crypto.validateKeyPair({ publicKey, secretKey })) {
60
+ throw BAD_ARGUMENT('Invalid key pair')
61
+ }
62
+
63
+ const sign = opts.sign
64
+ ? opts.sign
65
+ : secretKey
66
+ ? (signable) => crypto.sign(signable, secretKey)
67
+ : undefined
68
+
69
+ return {
70
+ sign,
71
+ verify (signable, signature) {
72
+ return crypto.verify(signable, signature, publicKey)
73
+ }
74
+ }
56
75
  }
57
76
 
58
77
  static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
59
- const overwrite = opts.overwrite === true
78
+ let overwrite = opts.overwrite === true
79
+
80
+ const force = opts.force === true
60
81
  const createIfMissing = opts.createIfMissing !== false
61
82
  const crypto = opts.crypto || hypercoreCrypto
62
83
 
63
84
  const oplog = new Oplog(oplogFile, {
64
- headerEncoding: oplogHeader,
65
- entryEncoding: oplogEntry
85
+ headerEncoding: m.oplog.header,
86
+ entryEncoding: m.oplog.entry
66
87
  })
67
88
 
68
89
  let { header, entries } = await oplog.open()
69
90
 
70
- if (!header || overwrite === true) {
91
+ if (force && opts.keyPair && header && header.signer && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
92
+ overwrite = true
93
+ }
94
+
95
+ if (!header || overwrite) {
71
96
  if (!createIfMissing) {
72
- throw new Error('No hypercore is stored here')
97
+ throw STORAGE_EMPTY('No Hypercore is stored here')
73
98
  }
74
99
 
75
100
  header = {
@@ -84,14 +109,15 @@ module.exports = class Core {
84
109
  signer: opts.keyPair || crypto.keyPair(),
85
110
  hints: {
86
111
  reorgs: []
87
- }
112
+ },
113
+ contiguousLength: 0
88
114
  }
89
115
 
90
116
  await oplog.flush(header)
91
117
  }
92
118
 
93
- if (opts.keyPair && !header.signer.publicKey.equals(opts.keyPair.publicKey)) {
94
- throw new Error('Another hypercore is stored here')
119
+ if (opts.keyPair && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
120
+ throw STORAGE_CONFLICT('Another Hypercore is stored here')
95
121
  }
96
122
 
97
123
  const tree = await MerkleTree.open(treeFile, { crypto, ...header.tree })
@@ -102,9 +128,13 @@ module.exports = class Core {
102
128
  await tree.clear()
103
129
  await blocks.clear()
104
130
  await bitfield.clear()
131
+ entries = []
132
+ } else if (bitfield.resumed && header.tree.length === 0) {
133
+ // If this was an old bitfield, reset it since it loads based on disk size atm (TODO: change that)
134
+ await bitfield.clear()
105
135
  }
106
136
 
107
- const sign = opts.sign || (header.signer.secretKey ? this.createSigner(crypto, header.signer) : null)
137
+ const auth = opts.auth || this.createAuth(crypto, header.signer)
108
138
 
109
139
  for (const e of entries) {
110
140
  if (e.userData) {
@@ -118,7 +148,7 @@ module.exports = class Core {
118
148
  }
119
149
 
120
150
  if (e.bitfield) {
121
- bitfield.setRange(e.bitfield.start, e.bitfield.length)
151
+ bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
122
152
  }
123
153
 
124
154
  if (e.treeUpgrade) {
@@ -135,7 +165,7 @@ module.exports = class Core {
135
165
  }
136
166
  }
137
167
 
138
- return new this(header, crypto, oplog, tree, blocks, bitfield, sign, opts.onupdate || noop)
168
+ return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop, opts.oncontigupdate || noop)
139
169
  }
140
170
 
141
171
  _shouldFlush () {
@@ -166,6 +196,17 @@ module.exports = class Core {
166
196
  await this.blocks.put(index, value, byteOffset)
167
197
  }
168
198
 
199
+ _updateContiguousLength (index, length = 0) {
200
+ if (index === this.header.contiguousLength) {
201
+ let i = this.header.contiguousLength + length
202
+
203
+ while (this.bitfield.get(i)) i++
204
+
205
+ this.header.contiguousLength = i
206
+ this.oncontigupdate()
207
+ }
208
+ }
209
+
169
210
  async userData (key, value) {
170
211
  // TODO: each oplog append can set user data, so we should have a way
171
212
  // to just hitch a ride on one of the other ongoing appends?
@@ -176,7 +217,7 @@ module.exports = class Core {
176
217
 
177
218
  for (const u of this.header.userData) {
178
219
  if (u.key !== key) continue
179
- if (value && u.value.equals(value)) return
220
+ if (value && b4a.equals(u.value, value)) return
180
221
  empty = false
181
222
  break
182
223
  }
@@ -200,13 +241,13 @@ module.exports = class Core {
200
241
  }
201
242
  }
202
243
 
203
- async truncate (length, fork, sign = this.defaultSign) {
244
+ async truncate (length, fork, auth = this.defaultAuth) {
204
245
  this.truncating++
205
246
  await this._mutex.lock()
206
247
 
207
248
  try {
208
249
  const batch = await this.tree.truncate(length, fork)
209
- batch.signature = await sign(batch.signable())
250
+ batch.signature = await auth.sign(batch.signable())
210
251
  await this._truncate(batch, null)
211
252
  } finally {
212
253
  this.truncating--
@@ -214,17 +255,19 @@ module.exports = class Core {
214
255
  }
215
256
  }
216
257
 
217
- async append (values, sign = this.defaultSign) {
258
+ async append (values, auth = this.defaultAuth, hooks = {}) {
218
259
  await this._mutex.lock()
219
260
 
220
261
  try {
262
+ if (hooks.preappend) await hooks.preappend(values)
263
+
221
264
  if (!values.length) return this.tree.length
222
265
 
223
266
  const batch = this.tree.batch()
224
267
  for (const val of values) batch.append(val)
225
268
 
226
269
  const hash = batch.hash()
227
- batch.signature = await sign(batch.signable(hash))
270
+ batch.signature = await auth.sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
228
271
 
229
272
  const entry = {
230
273
  userData: null,
@@ -243,6 +286,8 @@ module.exports = class Core {
243
286
  this.bitfield.setRange(batch.ancestors, batch.length - batch.ancestors, true)
244
287
  batch.commit()
245
288
 
289
+ this.header.contiguousLength = batch.length
290
+ this.oncontigupdate()
246
291
  this.header.tree.length = batch.length
247
292
  this.header.tree.rootHash = hash
248
293
  this.header.tree.signature = batch.signature
@@ -256,11 +301,16 @@ module.exports = class Core {
256
301
  }
257
302
  }
258
303
 
304
+ _signed (batch, hash, auth = this.defaultAuth) {
305
+ const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
306
+ return auth.verify(signable, batch.signature)
307
+ }
308
+
259
309
  async _verifyExclusive ({ batch, bitfield, value, from }) {
260
310
  // TODO: move this to tree.js
261
311
  const hash = batch.hash()
262
- if (!batch.signature || !this.crypto.verify(batch.signable(hash), batch.signature, this.header.signer.publicKey)) {
263
- throw new Error('Remote signature does not match')
312
+ if (!batch.signature || !this._signed(batch, hash)) {
313
+ throw INVALID_SIGNATURE('Proof contains an invalid signature')
264
314
  }
265
315
 
266
316
  await this._mutex.lock()
@@ -279,7 +329,11 @@ module.exports = class Core {
279
329
 
280
330
  await this.oplog.append([entry], false)
281
331
 
282
- if (bitfield) this.bitfield.set(bitfield.start, true)
332
+ if (bitfield) {
333
+ this.bitfield.set(bitfield.start, true)
334
+ this._updateContiguousLength(bitfield.start, bitfield.length)
335
+ }
336
+
283
337
  batch.commit()
284
338
 
285
339
  this.header.tree.fork = batch.fork
@@ -333,8 +387,13 @@ module.exports = class Core {
333
387
  continue
334
388
  }
335
389
 
336
- if (bitfield) this.bitfield.set(bitfield.start, true)
390
+ if (bitfield) {
391
+ this.bitfield.set(bitfield.start, true)
392
+ this._updateContiguousLength(bitfield.start, bitfield.length)
393
+ }
394
+
337
395
  batch.commit()
396
+
338
397
  this.onupdate(0, bitfield, value, from)
339
398
  }
340
399
 
@@ -415,6 +474,8 @@ module.exports = class Core {
415
474
 
416
475
  const appended = batch.length > batch.ancestors
417
476
 
477
+ this.header.contiguousLength = Math.min(batch.ancestors, this.header.contiguousLength)
478
+ this.oncontigupdate()
418
479
  this.header.tree.fork = batch.fork
419
480
  this.header.tree.length = batch.length
420
481
  this.header.tree.rootHash = batch.hash()
package/lib/errors.js ADDED
@@ -0,0 +1,50 @@
1
+ module.exports = class HypercoreError extends Error {
2
+ constructor (msg, code, fn = HypercoreError) {
3
+ super(`${code}: ${msg}`)
4
+ this.code = code
5
+
6
+ if (Error.captureStackTrace) {
7
+ Error.captureStackTrace(this, fn)
8
+ }
9
+ }
10
+
11
+ get name () {
12
+ return 'HypercoreError'
13
+ }
14
+
15
+ static BAD_ARGUMENT (msg) {
16
+ return new HypercoreError(msg, 'BAD_ARGUMENT', HypercoreError.BAD_ARGUMENT)
17
+ }
18
+
19
+ static STORAGE_EMPTY (msg) {
20
+ return new HypercoreError(msg, 'STORAGE_EMPTY', HypercoreError.STORAGE_EMPTY)
21
+ }
22
+
23
+ static STORAGE_CONFLICT (msg) {
24
+ return new HypercoreError(msg, 'STORAGE_CONFLICT', HypercoreError.STORAGE_CONFLICT)
25
+ }
26
+
27
+ static INVALID_SIGNATURE (msg) {
28
+ return new HypercoreError(msg, 'INVALID_SIGNATURE', HypercoreError.INVALID_SIGNATURE)
29
+ }
30
+
31
+ static INVALID_CAPABILITY (msg) {
32
+ return new HypercoreError(msg, 'INVALID_CAPABILITY', HypercoreError.INVALID_CAPABILITY)
33
+ }
34
+
35
+ static SNAPSHOT_NOT_AVAILABLE (msg = 'Snapshot is not available') {
36
+ return new HypercoreError(msg, 'SNAPSHOT_NOT_AVAILABLE', HypercoreError.SNAPSHOT_NOT_AVAILABLE)
37
+ }
38
+
39
+ static REQUEST_CANCELLED (msg = 'Request was cancelled') {
40
+ return new HypercoreError(msg, 'REQUEST_CANCELLED', HypercoreError.REQUEST_CANCELLED)
41
+ }
42
+
43
+ static SESSION_NOT_WRITABLE (msg = 'Session is not writable') {
44
+ return new HypercoreError(msg, 'SESSION_NOT_WRITABLE', HypercoreError.SESSION_NOT_WRITABLE)
45
+ }
46
+
47
+ static SESSION_CLOSED (msg = 'Session is closed') {
48
+ return new HypercoreError(msg, 'SESSION_CLOSED', HypercoreError.SESSION_CLOSED)
49
+ }
50
+ }