hypercore 10.22.2 → 10.23.0

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/core.js CHANGED
@@ -7,25 +7,24 @@ const MerkleTree = require('./merkle-tree')
7
7
  const BlockStore = require('./block-store')
8
8
  const Bitfield = require('./bitfield')
9
9
  const Info = require('./info')
10
- const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = require('hypercore-errors')
11
- const c = require('compact-encoding')
10
+ const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE, INVALID_CHECKSUM } = require('hypercore-errors')
12
11
  const m = require('./messages')
13
- const caps = require('./caps')
12
+ const { manifestHash, createVerifier, createManifest, defaultSignerManifest, isCompat } = require('./manifest')
14
13
 
15
14
  module.exports = class Core {
16
- constructor (header, crypto, oplog, bigHeader, tree, blocks, bitfield, auth, legacy, onupdate, onconflict) {
15
+ constructor (header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, legacy, onupdate, onconflict) {
17
16
  this.onupdate = onupdate
18
17
  this.onconflict = onconflict
19
18
  this.preupdate = null
20
19
  this.header = header
21
- this.compat = !!(header.manifest && header.manifest.signer && b4a.equals(header.key, header.manifest.signer.publicKey))
20
+ this.compat = compat
22
21
  this.crypto = crypto
23
22
  this.oplog = oplog
24
23
  this.bigHeader = bigHeader
25
24
  this.tree = tree
26
25
  this.blocks = blocks
27
26
  this.bitfield = bitfield
28
- this.defaultAuth = auth
27
+ this.verifier = verifier
29
28
  this.truncating = 0
30
29
  this.updating = false
31
30
  this.closed = false
@@ -54,26 +53,14 @@ module.exports = class Core {
54
53
  }
55
54
  }
56
55
 
57
- static createAuth (crypto, header) {
58
- const manifest = header.manifest || defaultSignerManifest(header.keyPair.publicKey)
59
- const secretKey = header.keyPair && header.keyPair.secretKey
60
- const publicKey = manifest.signer.publicKey
61
- const sign = signable => crypto.sign(signable, secretKey)
62
-
63
- return {
64
- sign: secretKey ? sign : null,
65
- verify (signable, signature) {
66
- return crypto.verify(signable, signature, publicKey)
67
- }
68
- }
69
- }
70
-
71
56
  static async resume (oplogFile, treeFile, bitfieldFile, dataFile, headerFile, opts) {
72
57
  let overwrite = opts.overwrite === true
73
58
 
74
59
  const force = opts.force === true
75
60
  const createIfMissing = opts.createIfMissing !== false
76
61
  const crypto = opts.crypto || hypercoreCrypto
62
+ // kill this flag soon
63
+ const legacy = !!opts.legacy
77
64
 
78
65
  const oplog = new Oplog(oplogFile, {
79
66
  headerEncoding: m.oplog.header,
@@ -81,6 +68,9 @@ module.exports = class Core {
81
68
  readonly: opts.readonly
82
69
  })
83
70
 
71
+ // default to true for now if no manifest is provided
72
+ let compat = opts.compat === true || (opts.compat !== false && !opts.manifest)
73
+
84
74
  let { header, entries } = await oplog.open()
85
75
 
86
76
  if (force && opts.key && header && !b4a.equals(header.key, opts.key)) {
@@ -94,18 +84,19 @@ module.exports = class Core {
94
84
  throw STORAGE_EMPTY('No Hypercore is stored here')
95
85
  }
96
86
 
97
- if (opts.compat) {
87
+ if (compat) {
98
88
  if (opts.key && opts.keyPair && !b4a.equals(opts.key, opts.keyPair.publicKey)) {
99
- throw BAD_ARGUMENT('Key must match publicKey when in compat mode.')
89
+ throw BAD_ARGUMENT('Key must match publicKey when in compat mode')
100
90
  }
101
91
  }
102
92
 
103
93
  const keyPair = opts.keyPair || (opts.key ? null : crypto.keyPair())
104
- const manifest = opts.manifest || (opts.key && !opts.compat) ? null : defaultSignerManifest(opts.key || keyPair.publicKey)
94
+ const defaultManifest = !opts.manifest && (!!opts.compat || !opts.key || !!(keyPair && b4a.equals(opts.key, keyPair.publicKey)))
95
+ const manifest = defaultManifest ? defaultSignerManifest(opts.key || keyPair.publicKey) : createManifest(opts.manifest)
105
96
 
106
97
  header = {
107
98
  external: null,
108
- key: opts.key || (opts.compat ? manifest.signer.publicKey : hashManifest(manifest)),
99
+ key: opts.key || (compat ? manifest.signer.publicKey : manifestHash(manifest)),
109
100
  manifest,
110
101
  keyPair,
111
102
  userData: [],
@@ -126,10 +117,22 @@ module.exports = class Core {
126
117
  header = await bigHeader.load(header.external)
127
118
  }
128
119
 
120
+ if (opts.manifest) {
121
+ // if we provide a manifest and no key, verify that the stored key is the same
122
+ if (!opts.key && !isValidManifest(header.key, createManifest(opts.manifest))) {
123
+ throw STORAGE_CONFLICT('Manifest does not hash to provided key')
124
+ }
125
+ }
126
+
129
127
  if (opts.key && !b4a.equals(header.key, opts.key)) {
130
128
  throw STORAGE_CONFLICT('Another Hypercore is stored here')
131
129
  }
132
130
 
131
+ // if we signalled compat, but already now this core isn't disable it
132
+ if (compat && header.manifest && !isCompat(header.key, header.manifest)) {
133
+ compat = false
134
+ }
135
+
133
136
  const tree = await MerkleTree.open(treeFile, { crypto, ...header.tree })
134
137
  const bitfield = await Bitfield.open(bitfieldFile, tree)
135
138
  const blocks = new BlockStore(dataFile, tree)
@@ -149,7 +152,7 @@ module.exports = class Core {
149
152
  while (bitfield.get(header.hints.contiguousLength)) header.hints.contiguousLength++
150
153
  }
151
154
 
152
- const auth = opts.auth || (header.manifest ? this.createAuth(crypto, header) : null)
155
+ const verifier = header.manifest ? createVerifier(header.manifest, { compat: isCompat(header.key, header.manifest), crypto, legacy }) : null
153
156
 
154
157
  for (const e of entries) {
155
158
  if (e.userData) {
@@ -181,7 +184,22 @@ module.exports = class Core {
181
184
  }
182
185
  }
183
186
 
184
- return new this(header, crypto, oplog, bigHeader, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop, opts.onconflict || noop)
187
+ return new this(header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, legacy, opts.onupdate || noop, opts.onconflict || noop)
188
+ }
189
+
190
+ setManifest (manifest, keyPair) {
191
+ if (!manifest && b4a.equals(keyPair.publicKey, this.header.key)) manifest = defaultSignerManifest(this.header.key)
192
+ if (!manifest) return
193
+
194
+ const compat = isCompat(this.header.key, manifest)
195
+ const verifier = createVerifier(manifest, { compat, crypto: this.crypto, legacy: this._legacy })
196
+
197
+ this.compat = compat
198
+ this.header.manifest = manifest
199
+ this.verifier = verifier
200
+ this._manifestFlushed = false
201
+
202
+ this.onupdate(0b10000, null, null, null)
185
203
  }
186
204
 
187
205
  _shouldFlush () {
@@ -199,7 +217,7 @@ module.exports = class Core {
199
217
  return false
200
218
  }
201
219
 
202
- async copyFrom (src, signature, auth = this.defaultAuth) {
220
+ async copyFrom (src, signature) {
203
221
  this._mutex.lock()
204
222
 
205
223
  try {
@@ -242,14 +260,16 @@ module.exports = class Core {
242
260
  this.tree.roots = [...src.tree.roots]
243
261
  this.tree.length = src.tree.length
244
262
  this.tree.byteLength = src.tree.byteLength
245
- this.tree.signature = null // must provide signature
246
-
247
- this.tree.signature = signature || auth.sign(this.tree.signable())
248
263
 
249
- if (signature && !this._verifyBatchUpgrade(this.tree, null)) {
250
- // TODO: how to handle signature failure?
264
+ try {
265
+ const batch = this.tree.batch()
266
+ batch.signature = signature
267
+ this._verifyBatchUpgrade(batch, this.header.manifest)
268
+ this.tree.signature = signature
269
+ } catch (err) {
251
270
  this.tree.signature = null
252
- throw INVALID_SIGNATURE('Clone was provided with an invalid signature')
271
+ // TODO: how to handle signature failure?
272
+ throw err
253
273
  }
254
274
 
255
275
  this.header.tree.length = this.tree.length
@@ -263,6 +283,17 @@ module.exports = class Core {
263
283
  }
264
284
  }
265
285
 
286
+ async flush () {
287
+ await this._mutex.lock()
288
+ try {
289
+ this._manifestFlushed = true
290
+ this._autoFlush = 4
291
+ await this._flushOplog()
292
+ } finally {
293
+ this._mutex.unlock()
294
+ }
295
+ }
296
+
266
297
  async _flushOplog () {
267
298
  // TODO: the apis using this, actually do not need to wait for the bitfields, tree etc to flush
268
299
  // as their mutations are already stored in the oplog. We could potentially just run this in the
@@ -316,13 +347,16 @@ module.exports = class Core {
316
347
  }
317
348
  }
318
349
 
319
- async truncate (length, fork, auth = this.defaultAuth) {
350
+ async truncate (length, fork, { signature, keyPair = this.header.keyPair } = {}) {
320
351
  this.truncating++
321
352
  await this._mutex.lock()
322
353
 
354
+ // upsert compat manifest
355
+ if (this.verifier === null && keyPair) this.setManifest(null, keyPair)
356
+
323
357
  try {
324
358
  const batch = await this.tree.truncate(length, fork)
325
- batch.signature = await auth.sign(batch.signable(), batch)
359
+ batch.signature = signature || this.verifier.sign(batch, keyPair)
326
360
  await this._truncate(batch, null)
327
361
  } finally {
328
362
  this.truncating--
@@ -398,11 +432,14 @@ module.exports = class Core {
398
432
  })
399
433
  }
400
434
 
401
- async append (values, auth = this.defaultAuth, hooks = {}) {
435
+ async append (values, { signature, keyPair = this.header.keyPair, preappend } = {}) {
402
436
  await this._mutex.lock()
403
437
 
438
+ // upsert compat manifest
439
+ if (this.verifier === null && keyPair) this.setManifest(null, keyPair)
440
+
404
441
  try {
405
- if (hooks.preappend) await hooks.preappend(values)
442
+ if (preappend) await preappend(values)
406
443
 
407
444
  if (!values.length) {
408
445
  return { length: this.tree.length, byteLength: this.tree.byteLength }
@@ -411,8 +448,7 @@ module.exports = class Core {
411
448
  const batch = this.tree.batch()
412
449
  for (const val of values) batch.append(val)
413
450
 
414
- const hash = batch.hash()
415
- batch.signature = await auth.sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash), batch)
451
+ batch.signature = signature || this.verifier.sign(batch, keyPair)
416
452
 
417
453
  const entry = {
418
454
  userData: null,
@@ -433,7 +469,7 @@ module.exports = class Core {
433
469
  batch.commit()
434
470
 
435
471
  this.header.tree.length = batch.length
436
- this.header.tree.rootHash = hash
472
+ this.header.tree.rootHash = batch.hash()
437
473
  this.header.tree.signature = batch.signature
438
474
 
439
475
  const status = 0b0001 | updateContig(this.header, entry.bitfield, this.bitfield)
@@ -448,27 +484,25 @@ module.exports = class Core {
448
484
  }
449
485
 
450
486
  _verifyBatchUpgrade (batch, manifest) {
451
- const hash = batch.hash()
452
- const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
453
-
454
487
  if (!this.header.manifest) {
455
- if (!manifest) { // compat mode, remove in future version
456
- manifest = defaultSignerManifest(this.header.key)
457
- } else if (!manifest || !b4a.equals(this.header.key, hashManifest(manifest))) {
488
+ if (!manifest && this.compat) manifest = defaultSignerManifest(this.header.key)
489
+
490
+ if (!manifest || !(isValidManifest(this.header.key, manifest) || (this.compat && isCompat(this.header.key, manifest)))) {
458
491
  throw INVALID_SIGNATURE('Proof contains an invalid manifest') // TODO: proper error type
459
492
  }
460
493
  }
461
494
 
462
- const auth = this.defaultAuth || Core.createAuth(this.crypto, { ...this.header, manifest })
495
+ const verifier = this.verifier || createVerifier(manifest, { compat: isCompat(this.header.key, manifest), crypto: this.crypto, legacy: this._legacy })
463
496
 
464
- if (!batch.signature || !auth.verify(signable, batch.signature, batch)) {
497
+ if (!batch.signature || !verifier.verify(batch, batch.signature)) {
465
498
  throw INVALID_SIGNATURE('Proof contains an invalid signature')
466
499
  }
467
500
 
468
- this.defaultAuth = auth
469
501
  if (!this.header.manifest) {
470
- this.compat = !!manifest.signer && b4a.equals(this.header.key, manifest.signer.publicKey)
502
+ this.compat = isCompat(this.header.key, manifest)
471
503
  this.header.manifest = manifest
504
+ this.verifier = verifier
505
+ this.onupdate(0b10000, null, null, null)
472
506
  }
473
507
  }
474
508
 
@@ -548,7 +582,7 @@ module.exports = class Core {
548
582
  await this.oplog.append(entries, false)
549
583
 
550
584
  for (let i = 0; i < verifies.length; i++) {
551
- const { batch, bitfield, value, from } = verifies[i]
585
+ const { batch, bitfield, value, manifest, from } = verifies[i]
552
586
 
553
587
  if (!batch.commitable()) {
554
588
  verifies[i] = null // signal that we cannot commit this one
@@ -562,6 +596,12 @@ module.exports = class Core {
562
596
  status = updateContig(this.header, bitfield, this.bitfield)
563
597
  }
564
598
 
599
+ // if we got a manifest AND its strictly a non compat one, lets store it
600
+ if (manifest && this.header.manifest === null) {
601
+ if (!isValidManifest(this.header.key, manifest)) throw INVALID_CHECKSUM('Manifest hash does not match')
602
+ this.setManifest(manifest, null)
603
+ }
604
+
565
605
  batch.commit()
566
606
 
567
607
  this.onupdate(status, bitfield, value, from)
@@ -730,6 +770,10 @@ function updateContig (header, upd, bitfield) {
730
770
  return 0b1000
731
771
  }
732
772
 
773
+ function isValidManifest (key, manifest) {
774
+ return b4a.equals(key, manifestHash(manifest))
775
+ }
776
+
733
777
  function addReorgHint (list, tree, batch) {
734
778
  if (tree.length === 0 || tree.fork === batch.fork) return
735
779
 
@@ -788,21 +832,4 @@ async function flushHeader (oplog, bigHeader, header) {
788
832
  }
789
833
  }
790
834
 
791
- function defaultSignerManifest (publicKey) {
792
- return {
793
- hash: 'blake2b',
794
- static: null,
795
- signer: {
796
- signature: 'ed25519',
797
- namespace: caps.DEFAULT_NAMESPACE,
798
- publicKey
799
- },
800
- multipleSigners: null
801
- }
802
- }
803
-
804
- function hashManifest (manifest) {
805
- return caps.manifestHash(c.encode(m.manifest, manifest))
806
- }
807
-
808
835
  function noop () {}
@@ -0,0 +1,218 @@
1
+ const defaultCrypto = require('hypercore-crypto')
2
+ const b4a = require('b4a')
3
+ const c = require('compact-encoding')
4
+ const { BAD_ARGUMENT } = require('hypercore-errors')
5
+
6
+ const m = require('./messages')
7
+ const multisig = require('./multisig')
8
+ const caps = require('./caps')
9
+
10
+ module.exports = {
11
+ manifestHash,
12
+ isCompat,
13
+ defaultSignerManifest,
14
+ createManifest,
15
+ createVerifier
16
+ }
17
+
18
+ class StaticVerifier {
19
+ constructor (treeHash) {
20
+ this.treeHash = treeHash
21
+ }
22
+
23
+ sign () {
24
+ return null
25
+ }
26
+
27
+ verify (batch, signature) {
28
+ return b4a.equals(batch.hash(), this.treeHash)
29
+ }
30
+ }
31
+
32
+ class CompatVerifier {
33
+ constructor (crypto, signer, legacy) {
34
+ validateSigner(signer)
35
+
36
+ this.legacy = legacy
37
+ this.crypto = crypto
38
+ this.publicKey = signer.publicKey
39
+ }
40
+
41
+ sign (batch, keyPair) {
42
+ if (!keyPair || !keyPair.secretKey) throw BAD_ARGUMENT('No signer was passed')
43
+ return this.crypto.sign(batch.signableCompat(this.legacy), keyPair.secretKey)
44
+ }
45
+
46
+ verify (batch, signature) {
47
+ return this.crypto.verify(batch.signableCompat(this.legacy), signature, this.publicKey)
48
+ }
49
+ }
50
+
51
+ class SingleVerifier {
52
+ constructor (crypto, signer) {
53
+ validateSigner(signer)
54
+
55
+ this.crypto = crypto
56
+ this.publicKey = signer.publicKey
57
+ this.namespace = signer.namespace
58
+ }
59
+
60
+ sign (batch, keyPair) {
61
+ if (!keyPair || !keyPair.secretKey) throw BAD_ARGUMENT('No signer was passed')
62
+ return this.crypto.sign(batch.signable(this.namespace), keyPair.secretKey)
63
+ }
64
+
65
+ verify (batch, signature) {
66
+ return this.crypto.verify(batch.signable(this.namespace), signature, this.publicKey)
67
+ }
68
+ }
69
+
70
+ class MultiVerifier {
71
+ constructor (crypto, multipleSigners) {
72
+ this.signers = multipleSigners.signers
73
+ this.quorum = multipleSigners.quorum
74
+ this.allowPatched = multipleSigners.allowPatched
75
+ this.verifiers = this.signers.map(s => new SingleVerifier(crypto, s))
76
+
77
+ if (this.verifiers.length < this.quorum || (this.quorum === 0)) throw BAD_ARGUMENT('Invalid quorum')
78
+ }
79
+
80
+ sign () {
81
+ throw BAD_ARGUMENT('Multi signature must be provided')
82
+ }
83
+
84
+ verify (batch, signature) {
85
+ const inputs = multisig.inflate(signature)
86
+
87
+ if (inputs.length < this.quorum) return false
88
+
89
+ const tried = new Uint8Array(this.verifiers.length)
90
+
91
+ for (let i = 0; i < this.quorum; i++) {
92
+ const inp = inputs[i]
93
+
94
+ let tree = batch
95
+
96
+ if (inp.patch) {
97
+ if (!this.allowPatched) return false
98
+
99
+ tree = batch.clone()
100
+ const proof = { fork: tree.fork, block: null, hash: null, seek: null, upgrade: inp.patch, manifest: null }
101
+
102
+ try {
103
+ if (!tree.verifyUpgrade(proof)) return false
104
+ } catch {
105
+ return false
106
+ }
107
+ }
108
+
109
+ if (inp.signer >= this.verifiers.length || tried[inp.signer]) return false
110
+ tried[inp.signer] = 1
111
+
112
+ if (!this.verifiers[inp.signer].verify(tree, inp.signature)) return false
113
+ }
114
+
115
+ return true
116
+ }
117
+ }
118
+
119
+ function createVerifier (manifest, { compat = false, crypto = defaultCrypto, legacy = false } = {}) {
120
+ if (compat && manifest.signer) {
121
+ return new CompatVerifier(crypto, manifest.signer, legacy)
122
+ }
123
+
124
+ if (manifest.static) {
125
+ return new StaticVerifier(manifest.static)
126
+ }
127
+
128
+ if (manifest.signer) {
129
+ return new SingleVerifier(crypto, manifest.signer)
130
+ }
131
+
132
+ if (manifest.multipleSigners) {
133
+ return new MultiVerifier(crypto, manifest.multipleSigners)
134
+ }
135
+
136
+ throw BAD_ARGUMENT('No signer was provided')
137
+ }
138
+
139
+ function createManifest (inp) {
140
+ if (!inp) return null
141
+
142
+ const manifest = {
143
+ hash: 'blake2b',
144
+ static: null,
145
+ signer: null,
146
+ multipleSigners: null
147
+ }
148
+
149
+ if (inp.hash && inp.hash !== 'blake2b') throw BAD_ARGUMENT('Only Blake2b hashes are supported')
150
+
151
+ if (inp.static) {
152
+ if (!(b4a.isBuffer(inp.static) && inp.static.byteLength === 32)) throw BAD_ARGUMENT('Invalid static manifest')
153
+ manifest.static = inp.static
154
+ return manifest
155
+ }
156
+
157
+ if (inp.signer) {
158
+ manifest.signer = parseSigner(inp.signer)
159
+ return manifest
160
+ }
161
+
162
+ if (inp.multipleSigners) {
163
+ manifest.multipleSigners = parseMultipleSigners(inp.multipleSigners)
164
+ return manifest
165
+ }
166
+
167
+ throw BAD_ARGUMENT('No signer was provided')
168
+ }
169
+
170
+ function parseMultipleSigners (m) {
171
+ if (m.signers.length < m.quorum || !(m.quorum > 0)) throw BAD_ARGUMENT('Invalid quorum')
172
+
173
+ return {
174
+ allowPatched: !!m.allowPatched,
175
+ quorum: m.quorum,
176
+ signers: m.signers.map(parseSigner)
177
+ }
178
+ }
179
+
180
+ function parseSigner (signer) {
181
+ validateSigner(signer)
182
+ return {
183
+ signature: 'ed25519',
184
+ namespace: signer.namespace || caps.DEFAULT_NAMESPACE,
185
+ publicKey: signer.publicKey
186
+ }
187
+ }
188
+
189
+ function validateSigner (signer) {
190
+ if (!signer || !signer.publicKey) throw BAD_ARGUMENT('Signer missing public key')
191
+ if (signer.signature !== 'ed25519') throw BAD_ARGUMENT('Only Ed25519 signatures are supported')
192
+ }
193
+
194
+ function defaultSignerManifest (publicKey) {
195
+ return {
196
+ hash: 'blake2b',
197
+ static: null,
198
+ signer: {
199
+ signature: 'ed25519',
200
+ namespace: caps.DEFAULT_NAMESPACE,
201
+ publicKey
202
+ },
203
+ multipleSigners: null
204
+ }
205
+ }
206
+
207
+ function manifestHash (manifest) {
208
+ const state = { start: 0, end: 32, buffer: null }
209
+ m.manifest.preencode(state, manifest)
210
+ state.buffer = b4a.allocUnsafe(state.end)
211
+ c.raw.encode(state, caps.MANIFEST)
212
+ m.manifest.encode(state, manifest)
213
+ return defaultCrypto.hash(state.buffer)
214
+ }
215
+
216
+ function isCompat (key, manifest) {
217
+ return !!(manifest && manifest.signer && b4a.equals(key, manifest.signer.publicKey))
218
+ }
@@ -79,12 +79,12 @@ class MerkleTreeBatch {
79
79
  return this.hashCached
80
80
  }
81
81
 
82
- signable (hash = this.hash()) {
83
- return caps.treeSignable(hash, this.length, this.fork)
82
+ signable (namespace) {
83
+ return caps.treeSignable(namespace, this.hash(), this.length, this.fork)
84
84
  }
85
85
 
86
- signableLegacy (hash = this.hash()) {
87
- return caps.treeSignableLegacy(hash, this.length, this.fork)
86
+ signableCompat (noHeader) {
87
+ return caps.treeSignableCompat(this.hash(), this.length, this.fork, noHeader)
88
88
  }
89
89
 
90
90
  get (index) {
@@ -425,8 +425,8 @@ module.exports = class MerkleTree {
425
425
  return this.crypto.tree(this.roots)
426
426
  }
427
427
 
428
- signable (hash = this.hash()) {
429
- return caps.treeSignable(hash, this.length, this.fork)
428
+ signable (namespace) {
429
+ return caps.treeSignable(namespace, this.hash(), this.length, this.fork)
430
430
  }
431
431
 
432
432
  getRoots (length) {