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.
- package/README.md +3 -3
- package/index.js +186 -129
- package/lib/util.js +16 -0
- 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
|
-
|
|
70
|
+
requestCore(
|
|
70
71
|
publicKeys,
|
|
71
72
|
namespace,
|
|
72
73
|
srcCore,
|
|
73
74
|
length,
|
|
74
75
|
{ force = false, quorum, peerUpdateTimeout } = {}
|
|
75
76
|
) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
+
if (!force) await MultisigUtil.verifyCoreRequestable(srcCore, length, { peerUpdateTimeout })
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
90
|
+
requestDrive(
|
|
88
91
|
publicKeys,
|
|
89
92
|
namespace,
|
|
90
93
|
srcDrive,
|
|
91
94
|
length,
|
|
92
95
|
{ force = false, quorum, peerUpdateTimeout } = {}
|
|
93
96
|
) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
121
|
+
commitCore(
|
|
117
122
|
publicKeys,
|
|
118
123
|
namespace,
|
|
119
124
|
srcCore,
|
|
120
125
|
request,
|
|
121
126
|
responses,
|
|
122
|
-
{
|
|
127
|
+
{
|
|
128
|
+
quorum,
|
|
129
|
+
dryRun,
|
|
130
|
+
force = false,
|
|
131
|
+
skipTargetChecks = false,
|
|
132
|
+
peerUpdateTimeout,
|
|
133
|
+
minFullCopies = 2
|
|
134
|
+
} = {}
|
|
123
135
|
) {
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
128
|
-
this.swarm.join(core.discoveryKey)
|
|
143
|
+
const { length } = SignRequest.decode(z32.decode(request))
|
|
129
144
|
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
192
|
+
commitDrive(
|
|
170
193
|
publicKeys,
|
|
171
194
|
namespace,
|
|
172
195
|
srcDrive,
|
|
173
196
|
request,
|
|
174
197
|
responses,
|
|
175
|
-
{
|
|
198
|
+
{ quorum, dryRun, force = false, skipTargetChecks = false, peerUpdateTimeout } = {}
|
|
176
199
|
) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
182
|
-
|
|
205
|
+
const { manifest, core, blobsCore } = await this.createDrive(publicKeys, namespace, {
|
|
206
|
+
quorum
|
|
207
|
+
})
|
|
208
|
+
this.swarm.join(core.discoveryKey)
|
|
183
209
|
|
|
184
|
-
|
|
185
|
-
|
|
210
|
+
const { length, content } = SignRequest.decode(z32.decode(request))
|
|
211
|
+
const blobsLength = content.length
|
|
186
212
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
}
|