hyper-multisig 0.0.1 → 0.1.1

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.
Files changed (4) hide show
  1. package/README.md +3 -3
  2. package/index.js +186 -129
  3. package/lib/util.js +16 -0
  4. package/package.json +9 -2
package/README.md CHANGED
@@ -42,7 +42,7 @@ End users most likely want to use [hyper-multisig-cli](https://github.com/holepu
42
42
  await store.ready()
43
43
 
44
44
  const multisig = new HyperMultisig(store, swarm)
45
- const { manifest, core } = await multisig.createCore(publicKeys, namespace)
45
+ const { manifest, core } = await multisig.createCore(publicKeys, namespace).done()
46
46
  ```
47
47
 
48
48
  - Step 3: Generate a signing request based on a normal core (non-multisig) with the manifest from the previous step.
@@ -56,7 +56,7 @@ End users most likely want to use [hyper-multisig-cli](https://github.com/holepu
56
56
  srcCore.append('hello world 3')
57
57
 
58
58
  const multisig = new HyperMultisig(store)
59
- const { request } = await multisig.requestCore(publicKeys, namespace, srcCore, 3)
59
+ const { request } = await multisig.requestCore(publicKeys, namespace, srcCore, 3).done()
60
60
  ```
61
61
 
62
62
  - Step 4: Use [hypercore-sign](https://github.com/holepunchto/hypercore-sign) to sign the signing request to generate a signature. Later, signatures from all signers will be collected to sign the multisig core.
@@ -72,5 +72,5 @@ End users most likely want to use [hyper-multisig-cli](https://github.com/holepu
72
72
  const signatures = [signature1, signature2, ...]
73
73
 
74
74
  const multisig = new HyperMultisig(store, swarm)
75
- await multisig.commitCore(publicKeys, namespace, srcCore, request, signatures)
75
+ await multisig.commitCore(publicKeys, namespace, srcCore, request, signatures).done()
76
76
  ```
package/index.js CHANGED
@@ -20,6 +20,7 @@
20
20
  * }} CoreInfo
21
21
  */
22
22
 
23
+ const { EventEmitter } = require('events')
23
24
  const cenc = require('compact-encoding')
24
25
  const CoreSign = require('hypercore-sign')
25
26
  const SignRequest = require('hypercore-signing-request')
@@ -66,179 +67,235 @@ class HyperMultisig {
66
67
  return { manifest, key, core, blobsManifest, blobsKey, blobsCore }
67
68
  }
68
69
 
69
- async requestCore(
70
+ requestCore(
70
71
  publicKeys,
71
72
  namespace,
72
73
  srcCore,
73
74
  length,
74
75
  { force = false, quorum, peerUpdateTimeout } = {}
75
76
  ) {
76
- await srcCore.ready()
77
- this.swarm.join(srcCore.discoveryKey, { client: true, server: false })
78
- await srcCore.download({ start: 0, end: length }).done()
77
+ return new HyperMultisigRunner(async (runner) => {
78
+ await srcCore.ready()
79
+ this.swarm.join(srcCore.discoveryKey, { client: true, server: false })
80
+ await srcCore.download({ start: 0, end: length }).done()
79
81
 
80
- if (!force) await MultisigUtil.verifyCoreRequestable(srcCore, length, { peerUpdateTimeout })
82
+ if (!force) await MultisigUtil.verifyCoreRequestable(srcCore, length, { peerUpdateTimeout })
81
83
 
82
- const manifest = MultisigUtil.getManifest(publicKeys, namespace, { quorum })
83
- const request = await SignRequest.generate(srcCore, { manifest, length })
84
- return { manifest, request }
84
+ const manifest = MultisigUtil.getManifest(publicKeys, namespace, { quorum })
85
+ const request = await SignRequest.generate(srcCore, { manifest, length })
86
+ return { manifest, request }
87
+ })
85
88
  }
86
89
 
87
- async requestDrive(
90
+ requestDrive(
88
91
  publicKeys,
89
92
  namespace,
90
93
  srcDrive,
91
94
  length,
92
95
  { force = false, quorum, peerUpdateTimeout } = {}
93
96
  ) {
94
- await srcDrive.ready()
95
- this.swarm.join(srcDrive.discoveryKey, { client: true, server: false })
96
- await srcDrive.getBlobs()
97
- length = length || srcDrive.core.length
98
-
99
- if (!force) {
100
- await MultisigUtil.verifyCoreRequestable(srcDrive.core, length, {
101
- peerUpdateTimeout,
102
- coreId: 'db'
103
- })
104
- const contentLength = await srcDrive.getBlobsLength(length)
105
- await MultisigUtil.verifyCoreRequestable(srcDrive.blobs.core, contentLength, {
106
- peerUpdateTimeout,
107
- coreId: 'blobs'
108
- })
109
- }
97
+ return new HyperMultisigRunner(async (runner) => {
98
+ await srcDrive.ready()
99
+ this.swarm.join(srcDrive.discoveryKey, { client: true, server: false })
100
+ await srcDrive.getBlobs()
101
+ length = length || srcDrive.core.length
110
102
 
111
- const manifest = MultisigUtil.getManifest(publicKeys, namespace, { quorum })
112
- const request = await SignRequest.generateDrive(srcDrive, { manifest, length })
113
- return { manifest, request }
103
+ if (!force) {
104
+ await MultisigUtil.verifyCoreRequestable(srcDrive.core, length, {
105
+ peerUpdateTimeout,
106
+ coreId: 'db'
107
+ })
108
+ const contentLength = await srcDrive.getBlobsLength(length)
109
+ await MultisigUtil.verifyCoreRequestable(srcDrive.blobs.core, contentLength, {
110
+ peerUpdateTimeout,
111
+ coreId: 'blobs'
112
+ })
113
+ }
114
+
115
+ const manifest = MultisigUtil.getManifest(publicKeys, namespace, { quorum })
116
+ const request = await SignRequest.generateDrive(srcDrive, { manifest, length })
117
+ return { manifest, request }
118
+ })
114
119
  }
115
120
 
116
- async commitCore(
121
+ commitCore(
117
122
  publicKeys,
118
123
  namespace,
119
124
  srcCore,
120
125
  request,
121
126
  responses,
122
- { force = false, dryRun, quorum, skipTargetChecks = false, peerUpdateTimeout } = {}
127
+ {
128
+ quorum,
129
+ dryRun,
130
+ force = false,
131
+ skipTargetChecks = false,
132
+ peerUpdateTimeout,
133
+ minFullCopies = 2
134
+ } = {}
123
135
  ) {
124
- await srcCore.ready()
125
- this.swarm.join(srcCore.discoveryKey, { client: true, server: false })
136
+ return new HyperMultisigRunner(async (runner) => {
137
+ await srcCore.ready()
138
+ this.swarm.join(srcCore.discoveryKey, { client: true, server: false })
139
+
140
+ const { manifest, core } = await this.createCore(publicKeys, namespace, { quorum })
141
+ this.swarm.join(core.discoveryKey)
126
142
 
127
- const { manifest, core } = await this.createCore(publicKeys, namespace, { quorum })
128
- this.swarm.join(core.discoveryKey)
143
+ const { length } = SignRequest.decode(z32.decode(request))
129
144
 
130
- const { length } = SignRequest.decode(z32.decode(request))
145
+ if (!force) {
146
+ runner.emit('verify-committable-start')
147
+ await MultisigUtil.verifyCoreCommittable(srcCore, core, length, {
148
+ skipTargetChecks,
149
+ peerUpdateTimeout
150
+ })
151
+ }
131
152
 
132
- if (!force) {
133
- await MultisigUtil.verifyCoreCommittable(srcCore, core, length, {
134
- skipTargetChecks,
135
- peerUpdateTimeout
153
+ const signResponses = []
154
+ for (const response of responses) {
155
+ const res = cenc.decode(CoreSign.messages.Response, z32.decode(response))
156
+ await CoreSign.verify(response, request, z32.encode(res.publicKey))
157
+ const publicKeyHex = res.publicKey.toString('hex')
158
+ signResponses[publicKeyHex] = res
159
+ }
160
+ const obtainedQuorum = Object.keys(signResponses).length
161
+ if (!dryRun && obtainedQuorum < manifest.quorum) {
162
+ throw new Error(`Insufficient quorum: ${obtainedQuorum} / ${manifest.quorum}`)
163
+ }
164
+
165
+ // NOTE: the ordering is important here, must map to signers ordering
166
+ const signatures = manifest.signers.map((signer) => {
167
+ const publicKeyHex = signer.publicKey.toString('hex')
168
+ return signResponses[publicKeyHex]?.signatures[0]
136
169
  })
137
- }
138
-
139
- const signResponses = []
140
- for (const response of responses) {
141
- const res = cenc.decode(CoreSign.messages.Response, z32.decode(response))
142
- await CoreSign.verify(response, request, z32.encode(res.publicKey))
143
- const publicKeyHex = res.publicKey.toString('hex')
144
- signResponses[publicKeyHex] = res
145
- }
146
- const obtainedQuorum = Object.keys(signResponses).length
147
- if (!dryRun && obtainedQuorum < manifest.quorum) {
148
- throw new Error(`Insufficient quorum: ${obtainedQuorum} / ${manifest.quorum}`)
149
- }
150
-
151
- // NOTE: the ordering is important here, must map to signers ordering
152
- const signatures = manifest.signers.map((signer) => {
153
- const publicKeyHex = signer.publicKey.toString('hex')
154
- return signResponses[publicKeyHex]?.signatures[0]
155
- })
156
170
 
157
- const batch = await MultisigUtil.signCore(core, srcCore, signatures, {
158
- end: length,
159
- commit: !dryRun
171
+ runner.emit('commit-start')
172
+ const batch = await MultisigUtil.signCore(core, srcCore, signatures, {
173
+ end: length,
174
+ commit: !dryRun
175
+ })
176
+
177
+ if (!force && !dryRun) {
178
+ runner.emit('verify-committed-start', core.key)
179
+ await MultisigUtil.verifyCoreCommitted(core, { minPeers: minFullCopies })
180
+ }
181
+
182
+ const result = {
183
+ destCore: await MultisigUtil.getCoreInfo(core),
184
+ srcCore: await MultisigUtil.getCoreInfo(srcCore),
185
+ batch
186
+ }
187
+
188
+ return { manifest, core, quorum: obtainedQuorum, result }
160
189
  })
161
- const result = {
162
- destCore: await MultisigUtil.getCoreInfo(core),
163
- srcCore: await MultisigUtil.getCoreInfo(srcCore),
164
- batch
165
- }
166
- return { manifest, quorum: obtainedQuorum, result }
167
190
  }
168
191
 
169
- async commitDrive(
192
+ commitDrive(
170
193
  publicKeys,
171
194
  namespace,
172
195
  srcDrive,
173
196
  request,
174
197
  responses,
175
- { dryRun, quorum, force = false, skipTargetChecks = false, peerUpdateTimeout } = {}
198
+ { quorum, dryRun, force = false, skipTargetChecks = false, peerUpdateTimeout } = {}
176
199
  ) {
177
- await srcDrive.ready()
178
- this.swarm.join(srcDrive.discoveryKey, { client: true, server: false })
179
- await srcDrive.getBlobs()
200
+ return new HyperMultisigRunner(async (runner) => {
201
+ await srcDrive.ready()
202
+ this.swarm.join(srcDrive.discoveryKey, { client: true, server: false })
203
+ await srcDrive.getBlobs()
180
204
 
181
- const { manifest, core, blobsCore } = await this.createDrive(publicKeys, namespace, { quorum })
182
- this.swarm.join(core.discoveryKey)
205
+ const { manifest, core, blobsCore } = await this.createDrive(publicKeys, namespace, {
206
+ quorum
207
+ })
208
+ this.swarm.join(core.discoveryKey)
183
209
 
184
- const { length, content } = SignRequest.decode(z32.decode(request))
185
- const blobsLength = content.length
210
+ const { length, content } = SignRequest.decode(z32.decode(request))
211
+ const blobsLength = content.length
186
212
 
187
- if (!force) {
188
- await MultisigUtil.verifyCoreCommittable(srcDrive.db.core, core, length, {
189
- skipTargetChecks,
190
- peerUpdateTimeout,
191
- coreId: 'db'
192
- })
193
- await MultisigUtil.verifyCoreCommittable(srcDrive.blobs.core, blobsCore, blobsLength, {
194
- skipTargetChecks,
195
- peerUpdateTimeout,
196
- coreId: 'blobs'
213
+ if (!force) {
214
+ runner.emit('verify-committable-start')
215
+ await MultisigUtil.verifyCoreCommittable(srcDrive.db.core, core, length, {
216
+ skipTargetChecks,
217
+ peerUpdateTimeout,
218
+ coreId: 'db'
219
+ })
220
+ await MultisigUtil.verifyCoreCommittable(srcDrive.blobs.core, blobsCore, blobsLength, {
221
+ skipTargetChecks,
222
+ peerUpdateTimeout,
223
+ coreId: 'blobs'
224
+ })
225
+ }
226
+
227
+ const signResponses = []
228
+ for (const response of responses) {
229
+ const res = cenc.decode(CoreSign.messages.Response, z32.decode(response))
230
+ await CoreSign.verify(response, request, z32.encode(res.publicKey))
231
+ const publicKeyHex = res.publicKey.toString('hex')
232
+ signResponses[publicKeyHex] = res
233
+ }
234
+ const obtainedQuorum = Object.keys(signResponses).length
235
+ if (!dryRun && obtainedQuorum < manifest.quorum) {
236
+ throw new Error(`Insufficient quorum: ${obtainedQuorum} / ${manifest.quorum}`)
237
+ }
238
+
239
+ const allSignatures = manifest.signers.map((signer) => {
240
+ const publicKeyHex = signer.publicKey.toString('hex')
241
+ return signResponses[publicKeyHex]?.signatures
197
242
  })
198
- }
199
-
200
- const signResponses = []
201
- for (const response of responses) {
202
- const res = cenc.decode(CoreSign.messages.Response, z32.decode(response))
203
- await CoreSign.verify(response, request, z32.encode(res.publicKey))
204
- const publicKeyHex = res.publicKey.toString('hex')
205
- signResponses[publicKeyHex] = res
206
- }
207
- const obtainedQuorum = Object.keys(signResponses).length
208
- if (!dryRun && obtainedQuorum < manifest.quorum) {
209
- throw new Error(`Insufficient quorum: ${obtainedQuorum} / ${manifest.quorum}`)
210
- }
211
-
212
- const allSignatures = manifest.signers.map((signer) => {
213
- const publicKeyHex = signer.publicKey.toString('hex')
214
- return signResponses[publicKeyHex]?.signatures
215
- })
216
- // NOTE: the ordering is important here, must map to signers ordering
217
- const signatures = allSignatures.map((item) => item?.[0])
218
- const blobsSignatures = allSignatures.map((item) => item?.[1])
219
-
220
- const { batch, blobsBatch } = await MultisigUtil.signDrive(
221
- core,
222
- srcDrive.core,
223
- signatures,
224
- blobsCore,
225
- srcDrive.blobs.core,
226
- blobsSignatures,
227
- { end: length, blobsEnd: blobsLength, commit: !dryRun }
228
- )
229
- const result = {
230
- db: {
231
- destCore: await MultisigUtil.getCoreInfo(core),
232
- srcCore: await MultisigUtil.getCoreInfo(srcDrive.core),
233
- batch
234
- },
235
- blobs: {
236
- destCore: await MultisigUtil.getCoreInfo(blobsCore),
237
- srcCore: await MultisigUtil.getCoreInfo(srcDrive.blobs.core),
238
- batch: blobsBatch
243
+ // NOTE: the ordering is important here, must map to signers ordering
244
+ const signatures = allSignatures.map((item) => item?.[0])
245
+ const blobsSignatures = allSignatures.map((item) => item?.[1])
246
+
247
+ runner.emit('commit-start')
248
+ const { batch, blobsBatch } = await MultisigUtil.signDrive(
249
+ core,
250
+ srcDrive.core,
251
+ signatures,
252
+ blobsCore,
253
+ srcDrive.blobs.core,
254
+ blobsSignatures,
255
+ { end: length, blobsEnd: blobsLength, commit: !dryRun }
256
+ )
257
+
258
+ if (!force && !dryRun) {
259
+ runner.emit('verify-committed-start', core.key)
260
+ await MultisigUtil.verifyCoreCommitted(core)
261
+ await MultisigUtil.verifyCoreCommitted(blobsCore)
239
262
  }
240
- }
241
- return { manifest, quorum: obtainedQuorum, result }
263
+
264
+ const result = {
265
+ db: {
266
+ destCore: await MultisigUtil.getCoreInfo(core),
267
+ srcCore: await MultisigUtil.getCoreInfo(srcDrive.core),
268
+ batch
269
+ },
270
+ blobs: {
271
+ destCore: await MultisigUtil.getCoreInfo(blobsCore),
272
+ srcCore: await MultisigUtil.getCoreInfo(srcDrive.blobs.core),
273
+ batch: blobsBatch
274
+ }
275
+ }
276
+ return { manifest, core, blobsCore, quorum: obtainedQuorum, result }
277
+ })
278
+ }
279
+ }
280
+
281
+ class HyperMultisigRunner extends EventEmitter {
282
+ constructor(handler) {
283
+ super()
284
+ this.handler = handler
285
+
286
+ this._running = this._run()
287
+ // This will always be awaited, but to avoid uncaughts in case it's not awaited in the same tick
288
+ this._running.catch(() => {})
289
+ }
290
+
291
+ async _run() {
292
+ // Tick so the user can register event listeners
293
+ await new Promise((resolve) => queueMicrotask(resolve))
294
+ return await this.handler(this)
295
+ }
296
+
297
+ async done() {
298
+ return await this._running
242
299
  }
243
300
  }
244
301
 
package/lib/util.js CHANGED
@@ -272,11 +272,15 @@ async function verifyCoreCommittable(
272
272
  return
273
273
  }
274
274
 
275
+ await waitUntilSufficientPeers(tgtCore, { minPeers, timeout: peerUpdateTimeout })
276
+
275
277
  const tgtPeers = tgtCore.peers.length
276
278
  if (tgtPeers < minPeers) {
277
279
  throw MultisigError.TARGET_CORE_INSUFFICIENT_PEERS(tgtPeers, minPeers, { coreId })
278
280
  }
279
281
 
282
+ await waitUntilFullySeeded(tgtCore, { minPeers, timeout: peerUpdateTimeout })
283
+
280
284
  let tgtFullCopies = 0
281
285
  for (const p of tgtCore.peers) {
282
286
  if (p.remoteContiguousLength === tgtCore.length) tgtFullCopies++
@@ -290,6 +294,17 @@ async function verifyCoreCommittable(
290
294
  }
291
295
  }
292
296
 
297
+ async function verifyCoreCommitted(tgtCore, { minPeers = 2 } = {}) {
298
+ let tgtFullCopies = 0
299
+ for (const p of tgtCore.peers) {
300
+ if (p.remoteContiguousLength === tgtCore.length) tgtFullCopies++
301
+ }
302
+ if (tgtFullCopies < minPeers) {
303
+ await new Promise((resolve) => setTimeout(resolve, 100))
304
+ await verifyCoreCommitted(tgtCore, { minPeers })
305
+ }
306
+ }
307
+
293
308
  async function waitUntilCoreLength(core, length, { timeout = 5000 } = {}) {
294
309
  return await waitUntil(() => core.length >= length, { timeout })
295
310
  }
@@ -324,6 +339,7 @@ module.exports = {
324
339
  getCoreInfo,
325
340
  verifyCoreRequestable,
326
341
  verifyCoreCommittable,
342
+ verifyCoreCommitted,
327
343
  waitUntilCoreLength,
328
344
  waitUntilSufficientPeers,
329
345
  waitUntilFullySeeded
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyper-multisig",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "Multisig hypercore and hyperdrive",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "b4a": "^1.7.4",
18
+ "bare-events": "^2.8.2",
18
19
  "compact-encoding": "^2.18.0",
19
20
  "hypercore": "^11.24.0",
20
21
  "hypercore-crypto": "^3.6.1",
@@ -43,5 +44,11 @@
43
44
  "bugs": {
44
45
  "url": "https://github.com/holepunchto/hyper-multisig/issues"
45
46
  },
46
- "homepage": "https://github.com/holepunchto/hyper-multisig#readme"
47
+ "homepage": "https://github.com/holepunchto/hyper-multisig#readme",
48
+ "imports": {
49
+ "events": {
50
+ "bare": "bare-events",
51
+ "default": "events"
52
+ }
53
+ }
47
54
  }