-
Notifications
You must be signed in to change notification settings - Fork 2
/
asymmetric-noble.mjs
345 lines (247 loc) · 10.5 KB
/
asymmetric-noble.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
import { schnorr } from '@noble/curves/secp256k1'
import { ed25519 } from '@noble/curves/ed25519'
import { bls12_381 } from '@noble/curves/bls12-381'
import { generateShares } from './secret-sharing.mjs'
import { lagrangeProjectivePoints, lagrangeProjectivePointsAtZero } from './polynomials.mjs'
export function demoSignature(message) {
// let module = schnorr
let module = ed25519
const privateKey = module.utils.randomPrivateKey()
const publicKey = module.getPublicKey(privateKey)
const encodedMessage = new TextEncoder().encode(message)
const signature = module.sign(encodedMessage, privateKey)
console.log("Signature:", Buffer.from(signature).toString('hex'))
console.log("Signature valid:", module.verify(signature, encodedMessage, publicKey))
}
export function demoBlsSignatures(message) {
const privateKey = bls12_381.utils.randomPrivateKey()
const publicKey = bls12_381.getPublicKey(privateKey)
const encodedMessage = new TextEncoder().encode(message)
const signature = bls12_381.sign(encodedMessage, privateKey)
console.log("Signature:", Buffer.from(signature).toString('hex'))
console.log("Signature valid:", bls12_381.verify(signature, encodedMessage, publicKey))
}
export function demoBlsAggregateSignatures(message) {
const privateKeys = [
bls12_381.utils.randomPrivateKey(),
bls12_381.utils.randomPrivateKey(),
bls12_381.utils.randomPrivateKey()
]
const publicKeys = privateKeys.map(bls12_381.getPublicKey)
const aggregatePublicKey = bls12_381.aggregatePublicKeys(publicKeys)
const encodedMessage = new TextEncoder().encode(message)
const signatures = privateKeys.map((p) => bls12_381.sign(encodedMessage, p))
const aggregatedSignature = bls12_381.aggregateSignatures(signatures)
console.log("Signature valid:", bls12_381.verify(aggregatedSignature, encodedMessage, aggregatePublicKey))
}
export function demoVerifyBatch() {
const privateKeys = [
bls12_381.utils.randomPrivateKey(),
bls12_381.utils.randomPrivateKey(),
bls12_381.utils.randomPrivateKey()
]
const publicKeys = privateKeys.map(bls12_381.getPublicKey)
const messages = ['Lover', 'Evermore', 'Midnights']
const encodedMessages = messages.map((m) => new TextEncoder().encode(m))
const signatures = privateKeys.map((privateKey, i) => bls12_381.sign(encodedMessages[i], privateKey))
const aggregatedSignature = bls12_381.aggregateSignatures(signatures)
console.log("Signature valid:", bls12_381.verifyBatch(aggregatedSignature, encodedMessages, publicKeys))
}
// Threshold Signatures
export function getSharesTS(numberThreshold, numberShares) {
const masterPrivateKey = bls12_381.utils.randomPrivateKey()
const masterPublicKey = bls12_381.getPublicKey(masterPrivateKey)
let numericShares = generateShares(bls12_381.G1.normPrivateKeyToScalar(masterPrivateKey), numberThreshold, numberShares)
return {
shares: numericShares.map(({ number, subSecret }) => {
let sharePrivateKey = bls12_381.fields.Fr.toBytes(subSecret)
return {
number,
privateKey: sharePrivateKey,
publicKey: bls12_381.getPublicKey(sharePrivateKey)
}
}),
masterPublicKey
}
}
export function signTS(message, share) {
const encodedMessage = new TextEncoder().encode(message)
const signature = bls12_381.sign(encodedMessage, share.privateKey)
return { number: share.number, signature }
}
export function aggregateSignaturesTS(partialSignatures) {
let xs = []
let ys = []
for (const partialSignature of partialSignatures) {
xs.push(partialSignature.number)
ys.push(bls12_381.G2.ProjectivePoint.fromHex(partialSignature.signature))
}
// Option 1:
// let interpolated = lagrangeProjectivePoints(xs, ys, bls12_381.fields.Fr, bls12_381.G2.ProjectivePoint.ZERO)
// return interpolated[0]
// Option 2:
let interpolated = lagrangeProjectivePointsAtZero(xs, ys, bls12_381.fields.Fr, bls12_381.G2.ProjectivePoint.ZERO)
return interpolated
}
// ECIES
// Documentation in https://nodejs.org/api/crypto.htm
import * as crypto from 'crypto'
import { x25519 } from '@noble/curves/ed25519'
import { pbkdf2 } from '@noble/hashes/pbkdf2'
import { sha256 } from '@noble/hashes/sha256'
import { encryptAuthenticated, decryptAuthenticated } from './symmetric.mjs'
import { getRandomBytes, getRandomElement } from './random.mjs'
export function encryptEcies(message, receiverPublicKey) {
const privateKey = x25519.utils.randomPrivateKey()
const publicKey = x25519.getPublicKey(privateKey)
const sharedKey = x25519.getSharedSecret(privateKey, receiverPublicKey)
const hashSalt = getRandomBytes(32)
const key = pbkdf2(sha256, sharedKey, hashSalt, { c: 131072, dkLen: 32 })
return { publicKey, salt: hashSalt, encrypted: encryptAuthenticated(key, message) }
}
export function decryptEcies(privateKey, cryptogram) {
const { publicKey: senderPublicKey, salt: hashSalt, encrypted } = cryptogram
const sharedKey = x25519.getSharedSecret(privateKey, senderPublicKey)
const key = pbkdf2(sha256, sharedKey, hashSalt, { c: 131072, dkLen: 32 })
return decryptAuthenticated(key, encrypted)
}
// Blind signatures
// From https://eprint.iacr.org/2002/118
// Discussion about G1 and G2, and cofactors in:
// https://hackmd.io/@benjaminion/bls12-381#Cofactor
// https://eprint.iacr.org/2015/247
// https://eprint.iacr.org/2021/1130 (the isTorsionFree() function accounts for it at the time of writing)
export function blindSignaturesSetup(encodedMessage) {
const privateKey = bls12_381.utils.randomPrivateKey()
const publicKey1 = bls12_381.getPublicKey(privateKey)
const publicKey2 = bls12_381.G2.ProjectivePoint.BASE.multiply(
bls12_381.G1.normPrivateKeyToScalar(privateKey)
).toRawBytes()
return { privateKey, publicKey1, publicKey2 }
}
export function blindSignaturesPack(encodedMessage) {
// Includes cofactor clearing; see https://hackmd.io/@benjaminion/bls12-381#Cofactor and https://eprint.iacr.org/2015/247
const hashedPoint = bls12_381.G2.hashToCurve(encodedMessage)
const randomScalar = bls12_381.G1.normPrivateKeyToScalar(
bls12_381.utils.randomPrivateKey()
)
const blindPoint = hashedPoint.add(
bls12_381.G2.ProjectivePoint.BASE.multiply(randomScalar)
)
return { blindPoint, randomScalar }
}
export function blindSignaturesSign(privateKey, blindPoint) {
// Includes a subgroup check for the blind point
// https://eprint.iacr.org/2021/1130
blindPoint.assertValidity()
return blindPoint.multiply(bls12_381.G1.normPrivateKeyToScalar(privateKey)).toRawBytes()
}
export function blindSignaturesUnpack(publicKey, randomScalar, blindSignature) {
const blindSignaturePoint = bls12_381.G2.ProjectivePoint.fromAffine(
bls12_381.G2.CURVE.fromBytes(blindSignature)
)
const publicKeyPoint = bls12_381.G2.ProjectivePoint.fromAffine(
bls12_381.G2.CURVE.fromBytes(publicKey)
)
const signaturePoint = blindSignaturePoint.subtract(
publicKeyPoint.multiply(randomScalar)
)
return signaturePoint.toRawBytes()
}
// Ring Signatures (on BN254)
// Main reference: https://web.getmonero.org/library/
import { bn254 } from '@noble/curves/bn254'
import { sha512 } from '@noble/hashes/sha512'
import * as modular from '@noble/curves/abstract/modular'
import { bytesToNumberBE } from '@noble/curves/abstract/utils'
let ringField = modular.Field(bn254.CURVE.n)
function generateHash(publicKeys, message, point) {
// Normalize point from Projective coordinates to Affine coordinates
point = bn254.ProjectivePoint.fromAffine(point.toAffine())
let hasher = sha512.create()
publicKeys.map(publicKey => hasher.update(publicKey))
let bytes = hasher.update(message)
.update(point.toRawBytes())
.digest()
return bytesToNumberBE(modular.mapHashToField(bytes, ringField.ORDER))
}
export function signRing(signerIndex, privateKey, publicKeys, message) {
// Place to store all c_x's
let c = new Array(publicKeys.length).fill(0n)
// Place to store all s_x's
let s = new Array(publicKeys.length).fill(0n)
// Initialize s_x's
for(let curr = 0; curr < publicKeys.length; curr++) {
s[curr] = getRandomElement(ringField)
}
// Calculate c_{i + 1}
let curr = signerIndex
let next = (signerIndex + 1) % publicKeys.length
let k = getRandomElement(ringField)
let hashedPoint = bn254.ProjectivePoint.BASE.multiply(k)
c[next] = generateHash(publicKeys, message, hashedPoint)
// Calculate c_j != c_i
for(var offset = 0; offset < publicKeys.length - 1; offset++) {
let curr = (signerIndex + 1 + offset) % publicKeys.length
let next = (signerIndex + 1 + offset + 1) % publicKeys.length
let pkCurr = bn254.ProjectivePoint.fromHex(publicKeys[curr])
let hashedPoint1 = bn254.ProjectivePoint.BASE.multiply(s[curr])
let hashedPoint2 = pkCurr.multiply(c[curr])
let hashedPoint = hashedPoint1.add(hashedPoint2)
c[next] = generateHash(publicKeys, message, hashedPoint)
}
// Redefine privateKey to be a scalar (not a byte sequence)
privateKey = bn254.utils.normPrivateKeyToScalar(privateKey)
s[curr] = ringField.sub(k , ringField.mul(privateKey, c[curr]))
return {
c_0: c[0],
s
}
}
export function verifyRing(publicKeys, message, ringSignature) {
// Place to store all c_x's
let c = new Array(publicKeys.length).fill(0n)
// Initialize the first c_x
c[0] = ringSignature.c_0
for (var offset = 0; offset < publicKeys.length; offset++) {
let curr = offset % publicKeys.length
let next = (offset + 1) % publicKeys.length
let pkCurr = bn254.ProjectivePoint.fromHex(publicKeys[curr])
let hashedPoint1 = bn254.ProjectivePoint.BASE.multiply(ringSignature.s[curr])
let hashedPoint2 = pkCurr.multiply(c[curr])
let hashedPoint = hashedPoint1.add(hashedPoint2)
c[next] = generateHash(publicKeys, message, hashedPoint)
}
return (c[0] == ringSignature.c_0)
}
// Own BLS implementation
// Not exported
function signBLS(message, privateKey) {
const encodedMessage = new TextEncoder().encode(message)
const hashedPoint = bls12_381.G2.hashToCurve(encodedMessage)
hashedPoint.assertValidity()
const signature = hashedPoint.multiply(
bls12_381.G2.normPrivateKeyToScalar(privateKey)
).toRawBytes()
return signature
}
function verifyBLS(signature, message, publicKey) {
// Generator of G1
const G = bls12_381.G1.ProjectivePoint.BASE
// Signature
const x_Hm = bls12_381.G2.ProjectivePoint.fromAffine(
bls12_381.G2.CURVE.fromBytes(signature)
)
// Public key
const G_x = bls12_381.G1.ProjectivePoint.fromAffine(
bls12_381.G1.CURVE.fromBytes(publicKey)
)
// MessageHash
const encodedMessage = new TextEncoder().encode(message)
const Hm = bls12_381.G2.hashToCurve(encodedMessage)
Hm.assertValidity()
// Naive, but pedagogical
const pairingA = bls12_381.pairing(G, x_Hm)
const pairingB = bls12_381.pairing(G_x, Hm)
return ( bls12_381.fields.Fp12.eql(pairingA, pairingB) )
}