hypercore 10.0.0-alpha.3 → 10.0.0-alpha.32

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,34 @@
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, and loose the types
8
+ // from the hashes there - they are not needed since we lock the indexes in the tree
9
+ // hash and just makes alignment etc harder in other languages
10
+
11
+ const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER] = crypto.namespace('hypercore', 3)
12
+
13
+ exports.replicate = function (isInitiator, key, handshakeHash) {
14
+ const out = b4a.allocUnsafe(32)
15
+ sodium.crypto_generichash_batch(out, [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key], handshakeHash)
16
+ return out
17
+ }
18
+
19
+ exports.treeSignable = function (hash, length, fork) {
20
+ const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
21
+ c.raw.encode(state, TREE)
22
+ c.raw.encode(state, hash)
23
+ c.uint64.encode(state, length)
24
+ c.uint64.encode(state, fork)
25
+ return state.buffer
26
+ }
27
+
28
+ exports.treeSignableLegacy = function (hash, length, fork) {
29
+ const state = { start: 0, end: 48, buffer: b4a.allocUnsafe(48) }
30
+ c.raw.encode(state, hash)
31
+ c.uint64.encode(state, length)
32
+ c.uint64.encode(state, fork)
33
+ return state.buffer
34
+ }
package/lib/core.js CHANGED
@@ -1,13 +1,14 @@
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 m = require('./messages')
8
9
 
9
10
  module.exports = class Core {
10
- constructor (header, crypto, oplog, tree, blocks, bitfield, sign, onupdate) {
11
+ constructor (header, crypto, oplog, tree, blocks, bitfield, sign, legacy, onupdate) {
11
12
  this.onupdate = onupdate
12
13
  this.header = header
13
14
  this.crypto = crypto
@@ -23,6 +24,7 @@ module.exports = class Core {
23
24
  this._verifies = null
24
25
  this._verifiesFlushed = null
25
26
  this._mutex = new Mutex()
27
+ this._legacy = legacy
26
28
  }
27
29
 
28
30
  static async open (storage, opts = {}) {
@@ -56,18 +58,24 @@ module.exports = class Core {
56
58
  }
57
59
 
58
60
  static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
59
- const overwrite = opts.overwrite === true
61
+ let overwrite = opts.overwrite === true
62
+
63
+ const force = opts.force === true
60
64
  const createIfMissing = opts.createIfMissing !== false
61
65
  const crypto = opts.crypto || hypercoreCrypto
62
66
 
63
67
  const oplog = new Oplog(oplogFile, {
64
- headerEncoding: oplogHeader,
65
- entryEncoding: oplogEntry
68
+ headerEncoding: m.oplog.header,
69
+ entryEncoding: m.oplog.entry
66
70
  })
67
71
 
68
72
  let { header, entries } = await oplog.open()
69
73
 
70
- if (!header || overwrite === true) {
74
+ if (force && opts.keyPair && header && header.signer && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
75
+ overwrite = true
76
+ }
77
+
78
+ if (!header || overwrite) {
71
79
  if (!createIfMissing) {
72
80
  throw new Error('No hypercore is stored here')
73
81
  }
@@ -90,7 +98,7 @@ module.exports = class Core {
90
98
  await oplog.flush(header)
91
99
  }
92
100
 
93
- if (opts.keyPair && !header.signer.publicKey.equals(opts.keyPair.publicKey)) {
101
+ if (opts.keyPair && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
94
102
  throw new Error('Another hypercore is stored here')
95
103
  }
96
104
 
@@ -102,6 +110,10 @@ module.exports = class Core {
102
110
  await tree.clear()
103
111
  await blocks.clear()
104
112
  await bitfield.clear()
113
+ entries = []
114
+ } else if (bitfield.resumed && header.tree.length === 0) {
115
+ // If this was an old bitfield, reset it since it loads based on disk size atm (TODO: change that)
116
+ await bitfield.clear()
105
117
  }
106
118
 
107
119
  const sign = opts.sign || (header.signer.secretKey ? this.createSigner(crypto, header.signer) : null)
@@ -118,7 +130,7 @@ module.exports = class Core {
118
130
  }
119
131
 
120
132
  if (e.bitfield) {
121
- bitfield.setRange(e.bitfield.start, e.bitfield.length)
133
+ bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
122
134
  }
123
135
 
124
136
  if (e.treeUpgrade) {
@@ -135,7 +147,7 @@ module.exports = class Core {
135
147
  }
136
148
  }
137
149
 
138
- return new this(header, crypto, oplog, tree, blocks, bitfield, sign, opts.onupdate || noop)
150
+ return new this(header, crypto, oplog, tree, blocks, bitfield, sign, !!opts.legacy, opts.onupdate || noop)
139
151
  }
140
152
 
141
153
  _shouldFlush () {
@@ -176,7 +188,7 @@ module.exports = class Core {
176
188
 
177
189
  for (const u of this.header.userData) {
178
190
  if (u.key !== key) continue
179
- if (value && u.value.equals(value)) return
191
+ if (value && b4a.equals(u.value, value)) return
180
192
  empty = false
181
193
  break
182
194
  }
@@ -214,17 +226,19 @@ module.exports = class Core {
214
226
  }
215
227
  }
216
228
 
217
- async append (values, sign = this.defaultSign) {
229
+ async append (values, sign = this.defaultSign, hooks = {}) {
218
230
  await this._mutex.lock()
219
231
 
220
232
  try {
233
+ if (hooks.preappend) await hooks.preappend(values)
234
+
221
235
  if (!values.length) return this.tree.length
222
236
 
223
237
  const batch = this.tree.batch()
224
238
  for (const val of values) batch.append(val)
225
239
 
226
240
  const hash = batch.hash()
227
- batch.signature = await sign(batch.signable(hash))
241
+ batch.signature = await sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
228
242
 
229
243
  const entry = {
230
244
  userData: null,
@@ -256,10 +270,15 @@ module.exports = class Core {
256
270
  }
257
271
  }
258
272
 
273
+ _signed (batch, hash) {
274
+ const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
275
+ return this.crypto.verify(signable, batch.signature, this.header.signer.publicKey)
276
+ }
277
+
259
278
  async _verifyExclusive ({ batch, bitfield, value, from }) {
260
279
  // TODO: move this to tree.js
261
280
  const hash = batch.hash()
262
- if (!batch.signature || !this.crypto.verify(batch.signable(hash), batch.signature, this.header.signer.publicKey)) {
281
+ if (!batch.signature || !this._signed(batch, hash)) {
263
282
  throw new Error('Remote signature does not match')
264
283
  }
265
284