@smartledger/bsv 4.6.0 → 5.0.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/CHANGELOG.md +111 -0
- package/README.md +91 -71
- package/bsv-covenant.min.js +8 -5
- package/bsv-ecies.min.js +1 -1
- package/bsv-gdaf.min.js +9 -6
- package/bsv-ltp.min.js +9 -6
- package/bsv-shamir.min.js +5 -2
- package/bsv-smartcontract.min.js +9 -6
- package/bsv-statuslist.min.js +1 -1
- package/bsv-vcjwt.min.js +3 -3
- package/bsv.bundle.js +13 -10
- package/bsv.min.js +13 -10
- package/build/webpack.bundle.config.js +3 -2
- package/build/webpack.config.js +3 -1
- package/build/webpack.security.config.js +2 -1
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/getting-started/INSTALLATION.md +23 -23
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/index.js +2 -2
- package/lib/crypto/elliptic-fixed.js +30 -37
- package/lib/crypto/shamir.js +239 -238
- package/lib/ecies/bitcore-ecies.js +16 -5
- package/lib/vcjwt/index.js +83 -22
- package/package.json +3 -9
package/lib/crypto/shamir.js
CHANGED
|
@@ -3,119 +3,261 @@
|
|
|
3
3
|
var BN = require('./bn')
|
|
4
4
|
var Random = require('./random')
|
|
5
5
|
var Hash = require('./hash')
|
|
6
|
-
var _ = require('../util/_')
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
// secrets.js-grempe is loaded lazily and wired to use the library's own vetted
|
|
8
|
+
// Random (Node CSPRNG / window.crypto), so it never depends on the bundler's
|
|
9
|
+
// `crypto` resolution for randomness. Lazy loading also means simply requiring
|
|
10
|
+
// this module (e.g. loading the full bsv bundle) never runs secrets.js's
|
|
11
|
+
// auto-init, which would otherwise throw in a bundle that mocks node `crypto`.
|
|
12
|
+
var _secrets = null
|
|
13
|
+
function secretsLib () {
|
|
14
|
+
if (_secrets) return _secrets
|
|
15
|
+
var s = require('secrets.js-grempe')
|
|
16
|
+
s.setRNG(function (bits) {
|
|
17
|
+
var bytes = Random.getRandomBuffer(Math.ceil(bits / 8))
|
|
18
|
+
var str = ''
|
|
19
|
+
for (var i = 0; i < bytes.length; i++) {
|
|
20
|
+
str += ('00000000' + bytes[i].toString(2)).slice(-8)
|
|
21
|
+
}
|
|
22
|
+
str = str.slice(0, bits)
|
|
23
|
+
// secrets.js rejects an all-zero draw; with a CSPRNG this is astronomically
|
|
24
|
+
// unlikely, but honour the contract by signalling a re-draw.
|
|
25
|
+
return /1/.test(str) ? str : null
|
|
26
|
+
})
|
|
27
|
+
_secrets = s
|
|
28
|
+
return _secrets
|
|
29
|
+
}
|
|
19
30
|
|
|
20
31
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
32
|
+
* Shamir Secret Sharing.
|
|
33
|
+
*
|
|
34
|
+
* The split/combine math is provided by `secrets.js-grempe`, a vetted GF(2^8)
|
|
35
|
+
* implementation (the same field/approach used by hardware wallets), rather
|
|
36
|
+
* than a hand-rolled finite field. On top of it this module adds:
|
|
37
|
+
*
|
|
38
|
+
* - a per-split nonce (`splitId`) so shares from different splits can't be
|
|
39
|
+
* silently mixed into a wrong-but-plausible reconstruction;
|
|
40
|
+
* - an integrity `checksum` (truncated SHA-256 of the secret) so a tampered
|
|
41
|
+
* share set is detected at combine time instead of returning garbage.
|
|
42
|
+
*
|
|
43
|
+
* Share objects produced here are format v2. Shares produced by <= 4.x (which
|
|
44
|
+
* carried a `bytes` array of {x, y} points over a 31-bit prime field) are still
|
|
45
|
+
* accepted by `combine()` for recovery only — see `_combineLegacy`.
|
|
23
46
|
*/
|
|
24
|
-
|
|
47
|
+
|
|
48
|
+
// Marker byte prepended to the secret before sharing. Guarantees a non-zero
|
|
49
|
+
// leading byte so leading zero bytes of the secret survive the hex round-trip,
|
|
50
|
+
// and lets combine() recover the exact original length.
|
|
51
|
+
var MARKER = 'ff'
|
|
52
|
+
|
|
53
|
+
var SHARE_VERSION = 2
|
|
25
54
|
|
|
26
55
|
/**
|
|
27
|
-
* Shamir Secret Sharing constructor
|
|
28
|
-
* @param {Object} options
|
|
56
|
+
* Shamir Secret Sharing constructor (kept for API compatibility).
|
|
57
|
+
* @param {Object} options
|
|
29
58
|
*/
|
|
30
|
-
function Shamir(options) {
|
|
59
|
+
function Shamir (options) {
|
|
31
60
|
if (!(this instanceof Shamir)) {
|
|
32
61
|
return new Shamir(options)
|
|
33
62
|
}
|
|
34
|
-
|
|
35
63
|
this.options = options || {}
|
|
36
64
|
return this
|
|
37
65
|
}
|
|
38
66
|
|
|
39
67
|
/**
|
|
40
|
-
* Split a secret into shares
|
|
68
|
+
* Split a secret into shares.
|
|
41
69
|
* @param {Buffer|String} secret - The secret to split
|
|
42
|
-
* @param {Number} threshold - Minimum
|
|
43
|
-
* @param {Number} shares - Total
|
|
44
|
-
* @param {Object} options
|
|
45
|
-
* @
|
|
70
|
+
* @param {Number} threshold - Minimum shares needed to reconstruct (>= 2)
|
|
71
|
+
* @param {Number} shares - Total shares to generate (>= threshold, <= 255)
|
|
72
|
+
* @param {Object} [options]
|
|
73
|
+
* @param {Boolean} [options.checksum=true] - embed a truncated SHA-256 of the
|
|
74
|
+
* secret in each share so combine() can detect a wrong/tampered share set.
|
|
75
|
+
* Set false to avoid disclosing any hash of the secret.
|
|
76
|
+
* @returns {Array} Array of v2 share objects
|
|
46
77
|
*/
|
|
47
|
-
Shamir.split = function(secret, threshold, shares, options) {
|
|
78
|
+
Shamir.split = function (secret, threshold, shares, options) {
|
|
48
79
|
options = options || {}
|
|
49
|
-
|
|
50
|
-
if (
|
|
80
|
+
|
|
81
|
+
if (secret === undefined || secret === null) {
|
|
51
82
|
throw new Error('Secret is required')
|
|
52
83
|
}
|
|
53
|
-
|
|
54
84
|
if (threshold < 2) {
|
|
55
85
|
throw new Error('Threshold must be at least 2')
|
|
56
86
|
}
|
|
57
|
-
|
|
58
87
|
if (shares < threshold) {
|
|
59
88
|
throw new Error('Number of shares must be at least threshold')
|
|
60
89
|
}
|
|
61
|
-
|
|
62
90
|
if (threshold > 255 || shares > 255) {
|
|
63
91
|
throw new Error('Threshold and shares must be <= 255')
|
|
64
92
|
}
|
|
65
|
-
|
|
66
|
-
// Convert secret to buffer if string
|
|
93
|
+
|
|
67
94
|
var secretBuffer = Buffer.isBuffer(secret) ? secret : Buffer.from(secret, 'utf8')
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
var
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Combine shares across bytes
|
|
95
|
+
|
|
96
|
+
var markedHex = MARKER + secretBuffer.toString('hex')
|
|
97
|
+
var rawShares = secretsLib().share(markedHex, shares, threshold)
|
|
98
|
+
|
|
99
|
+
var splitId = Random.getRandomBuffer(8).toString('hex')
|
|
100
|
+
var useChecksum = options.checksum !== false
|
|
101
|
+
var checksum = useChecksum
|
|
102
|
+
? Hash.sha256(secretBuffer).slice(0, 4).toString('hex')
|
|
103
|
+
: null
|
|
104
|
+
|
|
79
105
|
var result = []
|
|
80
|
-
for (var
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
for (var i = 0; i < rawShares.length; i++) {
|
|
107
|
+
result.push({
|
|
108
|
+
v: SHARE_VERSION,
|
|
109
|
+
id: i + 1,
|
|
83
110
|
threshold: threshold,
|
|
84
111
|
shares: shares,
|
|
85
112
|
length: secretBuffer.length,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
shareData.bytes.push(allShares[k][j])
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
result.push(shareData)
|
|
113
|
+
splitId: splitId,
|
|
114
|
+
share: rawShares[i],
|
|
115
|
+
checksum: checksum
|
|
116
|
+
})
|
|
94
117
|
}
|
|
95
|
-
|
|
96
118
|
return result
|
|
97
119
|
}
|
|
98
120
|
|
|
99
121
|
/**
|
|
100
|
-
* Combine shares to reconstruct the original secret
|
|
101
|
-
* @param {Array} shares - Array of share objects
|
|
122
|
+
* Combine shares to reconstruct the original secret.
|
|
123
|
+
* @param {Array} shares - Array of share objects (v2 or legacy)
|
|
102
124
|
* @returns {Buffer} The reconstructed secret
|
|
103
125
|
*/
|
|
104
|
-
Shamir.combine = function(shares) {
|
|
126
|
+
Shamir.combine = function (shares) {
|
|
105
127
|
if (!shares || shares.length === 0) {
|
|
106
128
|
throw new Error('Shares array is required')
|
|
107
129
|
}
|
|
108
|
-
|
|
109
|
-
//
|
|
130
|
+
|
|
131
|
+
// Route legacy (<= 4.x) shares to the retained recovery-only combiner.
|
|
132
|
+
if (shares[0] && Array.isArray(shares[0].bytes) && shares[0].share === undefined) {
|
|
133
|
+
return Shamir._combineLegacy(shares)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var threshold = shares[0].threshold
|
|
137
|
+
var splitId = shares[0].splitId
|
|
138
|
+
|
|
139
|
+
if (shares.length < threshold) {
|
|
140
|
+
throw new Error('Insufficient shares: need ' + threshold + ', got ' + shares.length)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
var rawShares = []
|
|
144
|
+
for (var i = 0; i < shares.length; i++) {
|
|
145
|
+
var s = shares[i]
|
|
146
|
+
if (!s || typeof s.share !== 'string') {
|
|
147
|
+
throw new Error('Invalid share at index ' + i)
|
|
148
|
+
}
|
|
149
|
+
// All shares must come from the same split.
|
|
150
|
+
if (splitId !== undefined && s.splitId !== splitId) {
|
|
151
|
+
throw new Error('Shares are from different splits (splitId mismatch)')
|
|
152
|
+
}
|
|
153
|
+
if (s.threshold !== threshold) {
|
|
154
|
+
throw new Error('Shares have inconsistent threshold')
|
|
155
|
+
}
|
|
156
|
+
rawShares.push(s.share)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
var markedHex = secretsLib().combine(rawShares)
|
|
160
|
+
if (markedHex.slice(0, MARKER.length) !== MARKER) {
|
|
161
|
+
// Wrong/insufficient/corrupt shares produced an unmarked result.
|
|
162
|
+
throw new Error('Reconstruction failed: invalid or insufficient shares')
|
|
163
|
+
}
|
|
164
|
+
var secretBuffer = Buffer.from(markedHex.slice(MARKER.length), 'hex')
|
|
165
|
+
|
|
166
|
+
// Integrity check: a tampered share or a mismatched set reconstructs to the
|
|
167
|
+
// wrong secret; the embedded checksum catches it instead of silently
|
|
168
|
+
// returning garbage.
|
|
169
|
+
var expected = shares[0].checksum
|
|
170
|
+
if (expected) {
|
|
171
|
+
var actual = Hash.sha256(secretBuffer).slice(0, 4).toString('hex')
|
|
172
|
+
if (actual !== expected) {
|
|
173
|
+
throw new Error('Integrity check failed: shares are inconsistent or tampered')
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return secretBuffer
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Verify a share is structurally valid.
|
|
182
|
+
* @param {Object} share
|
|
183
|
+
* @returns {Boolean}
|
|
184
|
+
*/
|
|
185
|
+
Shamir.verifyShare = function (share) {
|
|
186
|
+
try {
|
|
187
|
+
if (!share || typeof share !== 'object') {
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Legacy share shape (recovery only).
|
|
192
|
+
if (Array.isArray(share.bytes) && share.share === undefined) {
|
|
193
|
+
return Shamir._verifyLegacyShare(share)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (share.v !== SHARE_VERSION) {
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
if (typeof share.share !== 'string' || !/^[0-9a-fA-F]+$/.test(share.share)) {
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
if (typeof share.threshold !== 'number' || typeof share.shares !== 'number') {
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
if (share.threshold < 2 || share.shares < share.threshold || share.shares > 255) {
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
if (share.id < 1 || share.id > share.shares) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
if (share.checksum !== null && share.checksum !== undefined &&
|
|
212
|
+
!/^[0-9a-fA-F]{8}$/.test(share.checksum)) {
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
// secrets.js validates the internal share structure (throws on garbage).
|
|
216
|
+
secretsLib().extractShareComponents(share.share)
|
|
217
|
+
return true
|
|
218
|
+
} catch (e) {
|
|
219
|
+
return false
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Generate test vectors for validation.
|
|
225
|
+
* @returns {Object}
|
|
226
|
+
*/
|
|
227
|
+
Shamir.generateTestVectors = function () {
|
|
228
|
+
var secret = 'Hello, Bitcoin SV!'
|
|
229
|
+
var threshold = 3
|
|
230
|
+
var shares = 5
|
|
231
|
+
|
|
232
|
+
var splitShares = Shamir.split(secret, threshold, shares)
|
|
233
|
+
var reconstructed = Shamir.combine(splitShares.slice(0, threshold))
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
secret: secret,
|
|
237
|
+
threshold: threshold,
|
|
238
|
+
totalShares: shares,
|
|
239
|
+
shares: splitShares,
|
|
240
|
+
reconstructed: reconstructed.toString('utf8'),
|
|
241
|
+
valid: reconstructed.toString('utf8') === secret
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Legacy recovery (shares produced by <= 4.x). Read-only; do NOT use to split.
|
|
247
|
+
// These reconstruct over the old 31-bit prime field. Retained so secrets split
|
|
248
|
+
// with older versions remain recoverable.
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
var LEGACY_PRIME = new BN(2147483647) // 2^31 - 1
|
|
252
|
+
|
|
253
|
+
Shamir._combineLegacy = function (shares) {
|
|
110
254
|
var threshold = shares[0].threshold
|
|
111
255
|
var totalShares = shares[0].shares
|
|
112
256
|
var secretLength = shares[0].length
|
|
113
|
-
|
|
257
|
+
|
|
114
258
|
if (shares.length < threshold) {
|
|
115
259
|
throw new Error('Insufficient shares: need ' + threshold + ', got ' + shares.length)
|
|
116
260
|
}
|
|
117
|
-
|
|
118
|
-
// Verify all shares have same parameters
|
|
119
261
|
for (var i = 0; i < shares.length; i++) {
|
|
120
262
|
if (shares[i].threshold !== threshold || shares[i].shares !== totalShares) {
|
|
121
263
|
throw new Error('Shares have inconsistent parameters')
|
|
@@ -124,158 +266,66 @@ Shamir.combine = function(shares) {
|
|
|
124
266
|
throw new Error('Shares have different secret lengths')
|
|
125
267
|
}
|
|
126
268
|
}
|
|
127
|
-
|
|
269
|
+
|
|
128
270
|
var reconstructedBytes = []
|
|
129
|
-
|
|
130
|
-
// Reconstruct each byte
|
|
131
271
|
for (var j = 0; j < secretLength; j++) {
|
|
132
272
|
var byteShares = []
|
|
133
273
|
for (var k = 0; k < Math.min(shares.length, threshold); k++) {
|
|
134
274
|
byteShares.push(shares[k].bytes[j])
|
|
135
275
|
}
|
|
136
|
-
|
|
137
|
-
var byte = Shamir._combineByte(byteShares)
|
|
138
|
-
reconstructedBytes.push(byte)
|
|
276
|
+
reconstructedBytes.push(Shamir._combineByteLegacy(byteShares))
|
|
139
277
|
}
|
|
140
|
-
|
|
141
|
-
// Create buffer from reconstructed bytes
|
|
142
278
|
return Buffer.from(reconstructedBytes)
|
|
143
279
|
}
|
|
144
280
|
|
|
145
|
-
|
|
146
|
-
* Split a single byte using polynomial interpolation
|
|
147
|
-
* @private
|
|
148
|
-
*/
|
|
149
|
-
Shamir._splitByte = function(secretByte, threshold, shares) {
|
|
150
|
-
// Convert byte to big number
|
|
151
|
-
var secret = new BN(secretByte)
|
|
152
|
-
|
|
153
|
-
// Generate random polynomial coefficients
|
|
154
|
-
var coefficients = [secret] // a0 = secret
|
|
155
|
-
|
|
156
|
-
for (var i = 1; i < threshold; i++) {
|
|
157
|
-
var coeff = Shamir._randomFieldElement()
|
|
158
|
-
coefficients.push(coeff)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Generate shares by evaluating polynomial at different points
|
|
162
|
-
var result = []
|
|
163
|
-
for (var x = 1; x <= shares; x++) {
|
|
164
|
-
var y = Shamir._evaluatePolynomial(coefficients, new BN(x))
|
|
165
|
-
result.push({
|
|
166
|
-
x: x,
|
|
167
|
-
y: y.toString(16)
|
|
168
|
-
})
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return result
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Combine shares for a single byte using Lagrange interpolation
|
|
176
|
-
* @private
|
|
177
|
-
*/
|
|
178
|
-
Shamir._combineByte = function(shares) {
|
|
179
|
-
if (shares.length === 0) {
|
|
180
|
-
throw new Error('No shares provided')
|
|
181
|
-
}
|
|
182
|
-
|
|
281
|
+
Shamir._combineByteLegacy = function (shares) {
|
|
183
282
|
var result = new BN(0)
|
|
184
|
-
|
|
185
283
|
for (var i = 0; i < shares.length; i++) {
|
|
186
284
|
var xi = new BN(shares[i].x)
|
|
187
285
|
var yi = new BN(shares[i].y, 16)
|
|
188
|
-
|
|
189
|
-
// Calculate Lagrange basis polynomial
|
|
190
286
|
var numerator = new BN(1)
|
|
191
287
|
var denominator = new BN(1)
|
|
192
|
-
|
|
288
|
+
|
|
193
289
|
for (var j = 0; j < shares.length; j++) {
|
|
194
290
|
if (i !== j) {
|
|
195
291
|
var xj = new BN(shares[j].x)
|
|
196
|
-
// For Lagrange interpolation at x=0 (to get the constant term)
|
|
197
292
|
var numFactor = new BN(0).sub(xj)
|
|
198
293
|
if (numFactor.lt(new BN(0))) {
|
|
199
|
-
numFactor = numFactor.add(
|
|
294
|
+
numFactor = numFactor.add(LEGACY_PRIME)
|
|
200
295
|
}
|
|
201
|
-
numerator = numerator.mul(numFactor).mod(
|
|
202
|
-
|
|
296
|
+
numerator = numerator.mul(numFactor).mod(LEGACY_PRIME)
|
|
297
|
+
|
|
203
298
|
var denFactor = xi.sub(xj)
|
|
204
299
|
if (denFactor.lt(new BN(0))) {
|
|
205
|
-
denFactor = denFactor.add(
|
|
300
|
+
denFactor = denFactor.add(LEGACY_PRIME)
|
|
206
301
|
}
|
|
207
|
-
denominator = denominator.mul(denFactor).mod(
|
|
302
|
+
denominator = denominator.mul(denFactor).mod(LEGACY_PRIME)
|
|
208
303
|
}
|
|
209
304
|
}
|
|
210
|
-
|
|
211
|
-
// Calculate modular inverse of denominator
|
|
212
|
-
var inverse = Shamir._modInverse(denominator, PRIME)
|
|
213
|
-
var lagrange = numerator.mul(inverse).mod(PRIME)
|
|
214
|
-
|
|
215
|
-
// Add to result
|
|
216
|
-
result = result.add(yi.mul(lagrange)).mod(PRIME)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Convert back to byte (0-255) - ensure positive result
|
|
220
|
-
var finalResult = result.mod(PRIME).mod(new BN(256))
|
|
221
|
-
return finalResult.toNumber()
|
|
222
|
-
}
|
|
223
305
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
*/
|
|
228
|
-
Shamir._evaluatePolynomial = function(coefficients, x) {
|
|
229
|
-
var result = new BN(0)
|
|
230
|
-
var xPower = new BN(1)
|
|
231
|
-
|
|
232
|
-
for (var i = 0; i < coefficients.length; i++) {
|
|
233
|
-
result = result.add(coefficients[i].mul(xPower)).mod(PRIME)
|
|
234
|
-
xPower = xPower.mul(x).mod(PRIME)
|
|
306
|
+
var inverse = Shamir._modInverseLegacy(denominator, LEGACY_PRIME)
|
|
307
|
+
var lagrange = numerator.mul(inverse).mod(LEGACY_PRIME)
|
|
308
|
+
result = result.add(yi.mul(lagrange)).mod(LEGACY_PRIME)
|
|
235
309
|
}
|
|
236
|
-
|
|
237
|
-
return result
|
|
310
|
+
return result.mod(LEGACY_PRIME).mod(new BN(256)).toNumber()
|
|
238
311
|
}
|
|
239
312
|
|
|
240
|
-
|
|
241
|
-
* Generate random field element
|
|
242
|
-
* @private
|
|
243
|
-
*/
|
|
244
|
-
Shamir._randomFieldElement = function() {
|
|
245
|
-
var bytes = Random.getRandomBuffer(32)
|
|
246
|
-
var num = new BN(bytes)
|
|
247
|
-
return num.mod(PRIME.sub(new BN(1))).add(new BN(1))
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Calculate modular inverse using extended Euclidean algorithm
|
|
252
|
-
* @private
|
|
253
|
-
*/
|
|
254
|
-
Shamir._modInverse = function(a, m) {
|
|
313
|
+
Shamir._modInverseLegacy = function (a, m) {
|
|
255
314
|
if (a.lt(new BN(0))) {
|
|
256
315
|
a = a.mod(m).add(m)
|
|
257
316
|
}
|
|
258
|
-
|
|
259
|
-
var g = Shamir._extendedGCD(a, m)
|
|
260
|
-
|
|
317
|
+
var g = Shamir._extendedGCDLegacy(a, m)
|
|
261
318
|
if (!g.gcd.eq(new BN(1))) {
|
|
262
319
|
throw new Error('Modular inverse does not exist')
|
|
263
320
|
}
|
|
264
|
-
|
|
265
321
|
return g.x.mod(m).add(m).mod(m)
|
|
266
322
|
}
|
|
267
323
|
|
|
268
|
-
|
|
269
|
-
* Extended Euclidean algorithm
|
|
270
|
-
* @private
|
|
271
|
-
*/
|
|
272
|
-
Shamir._extendedGCD = function(a, b) {
|
|
324
|
+
Shamir._extendedGCDLegacy = function (a, b) {
|
|
273
325
|
if (a.eq(new BN(0))) {
|
|
274
326
|
return { gcd: b, x: new BN(0), y: new BN(1) }
|
|
275
327
|
}
|
|
276
|
-
|
|
277
|
-
var g = Shamir._extendedGCD(b.mod(a), a)
|
|
278
|
-
|
|
328
|
+
var g = Shamir._extendedGCDLegacy(b.mod(a), a)
|
|
279
329
|
return {
|
|
280
330
|
gcd: g.gcd,
|
|
281
331
|
x: g.y.sub(b.div(a).mul(g.x)),
|
|
@@ -283,78 +333,29 @@ Shamir._extendedGCD = function(a, b) {
|
|
|
283
333
|
}
|
|
284
334
|
}
|
|
285
335
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
336
|
+
Shamir._verifyLegacyShare = function (share) {
|
|
337
|
+
if (!share.id || !share.threshold || !share.shares || !share.bytes || typeof share.length !== 'number') {
|
|
338
|
+
return false
|
|
339
|
+
}
|
|
340
|
+
if (share.threshold < 2 || share.shares < share.threshold) {
|
|
341
|
+
return false
|
|
342
|
+
}
|
|
343
|
+
if (!Array.isArray(share.bytes) || share.bytes.length !== share.length) {
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
346
|
+
for (var i = 0; i < share.bytes.length; i++) {
|
|
347
|
+
var byteShare = share.bytes[i]
|
|
348
|
+
if (!byteShare || !byteShare.x || !byteShare.y) {
|
|
298
349
|
return false
|
|
299
350
|
}
|
|
300
|
-
|
|
301
|
-
if (share.threshold < 2 || share.shares < share.threshold) {
|
|
351
|
+
if (byteShare.x < 1 || byteShare.x > share.shares) {
|
|
302
352
|
return false
|
|
303
353
|
}
|
|
304
|
-
|
|
305
|
-
if (!Array.isArray(share.bytes) || share.bytes.length !== share.length) {
|
|
354
|
+
if (!/^[0-9a-fA-F]+$/.test(byteShare.y)) {
|
|
306
355
|
return false
|
|
307
356
|
}
|
|
308
|
-
|
|
309
|
-
// Verify each byte share
|
|
310
|
-
for (var i = 0; i < share.bytes.length; i++) {
|
|
311
|
-
var byteShare = share.bytes[i]
|
|
312
|
-
if (!byteShare.x || !byteShare.y) {
|
|
313
|
-
return false
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (byteShare.x < 1 || byteShare.x > share.shares) {
|
|
317
|
-
return false
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Verify y is valid hex
|
|
321
|
-
try {
|
|
322
|
-
var testHex = byteShare.y
|
|
323
|
-
if (!/^[0-9a-fA-F]+$/.test(testHex)) {
|
|
324
|
-
return false
|
|
325
|
-
}
|
|
326
|
-
new BN(testHex, 16)
|
|
327
|
-
} catch (e) {
|
|
328
|
-
return false
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return true
|
|
333
|
-
} catch (e) {
|
|
334
|
-
return false
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Generate test vectors for validation
|
|
340
|
-
* @returns {Object} Test data
|
|
341
|
-
*/
|
|
342
|
-
Shamir.generateTestVectors = function() {
|
|
343
|
-
var secret = 'Hello, Bitcoin SV!'
|
|
344
|
-
var threshold = 3
|
|
345
|
-
var shares = 5
|
|
346
|
-
|
|
347
|
-
var splitShares = Shamir.split(secret, threshold, shares)
|
|
348
|
-
var reconstructed = Shamir.combine(splitShares.slice(0, threshold))
|
|
349
|
-
|
|
350
|
-
return {
|
|
351
|
-
secret: secret,
|
|
352
|
-
threshold: threshold,
|
|
353
|
-
totalShares: shares,
|
|
354
|
-
shares: splitShares,
|
|
355
|
-
reconstructed: reconstructed.toString('utf8'),
|
|
356
|
-
valid: reconstructed.toString('utf8') === secret
|
|
357
357
|
}
|
|
358
|
+
return true
|
|
358
359
|
}
|
|
359
360
|
|
|
360
|
-
module.exports = Shamir
|
|
361
|
+
module.exports = Shamir
|
|
@@ -9,6 +9,20 @@ var aesjs = require('aes-js')
|
|
|
9
9
|
var CBC = aesjs.ModeOfOperation.cbc
|
|
10
10
|
var Random = bsv.crypto.Random
|
|
11
11
|
|
|
12
|
+
// Constant-time buffer comparison. Works in Node and browser bundles alike
|
|
13
|
+
// (crypto.timingSafeEqual is not provided by crypto-browserify). Compares the
|
|
14
|
+
// full length unconditionally so timing does not leak how many bytes matched.
|
|
15
|
+
function constantTimeEqual (a, b) {
|
|
16
|
+
if (a.length !== b.length) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
var diff = 0
|
|
20
|
+
for (var i = 0; i < a.length; i++) {
|
|
21
|
+
diff |= a[i] ^ b[i]
|
|
22
|
+
}
|
|
23
|
+
return diff === 0
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
var AESCBC = function AESCBC () {}
|
|
13
27
|
|
|
14
28
|
AESCBC.encrypt = function (messagebuf, cipherkeybuf, ivbuf) {
|
|
@@ -149,11 +163,8 @@ ECIES.prototype.decrypt = function (encbuf) {
|
|
|
149
163
|
var d2 = Hash.sha256hmac(c, this.kM)
|
|
150
164
|
if (this.opts.shortTag) d2 = d2.slice(0, 4)
|
|
151
165
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
equal &= (d[i] === d2[i])
|
|
155
|
-
}
|
|
156
|
-
if (!equal) {
|
|
166
|
+
// Constant-time MAC comparison (encrypt-then-MAC verification).
|
|
167
|
+
if (!constantTimeEqual(d, d2)) {
|
|
157
168
|
throw new Error('Invalid checksum')
|
|
158
169
|
}
|
|
159
170
|
|