|
@@ -2,8 +2,13 @@ const sodium = require('sodium-native')
|
2
|
2
|
const crypto = require('hypercore-crypto')
|
3
|
3
|
const assert = require('assert')
|
4
|
4
|
const bip39 = require('bip39')
|
|
5
|
+const zero = sodium.sodium_memzero
|
5
|
6
|
|
6
|
7
|
class Crypto {
|
|
8
|
+ constructor () {
|
|
9
|
+ this.PACKEDLENGTH = sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_secretbox_KEYBYTES
|
|
10
|
+ }
|
|
11
|
+
|
7
|
12
|
randomBytes (length) {
|
8
|
13
|
return crypto.randomBytes(length)
|
9
|
14
|
}
|
|
@@ -21,16 +26,17 @@ class Crypto {
|
21
|
26
|
encryptionKey (encryptionKey) {
|
22
|
27
|
var key = sodium.sodium_malloc(sodium.crypto_secretbox_KEYBYTES)
|
23
|
28
|
if (encryptionKey && Buffer.isBuffer(encryptionKey)) key.copy(encryptionKey)
|
24
|
|
- else if (encryptionKey && typeof encryptionKey === 'string') key.write(encryptionKey)
|
|
29
|
+ else if (encryptionKey && typeof encryptionKey === 'string') key.write(encryptionKey, 'hex')
|
25
|
30
|
else sodium.randombytes_buf(key)
|
26
|
31
|
return key
|
27
|
32
|
}
|
28
|
33
|
|
29
|
|
- keyPair (masterKey, id, ctxt = 'cobox') {
|
|
34
|
+ keyPair (masterKey, id, ctxt = 'coboxcobox') {
|
30
|
35
|
const context = sodium.sodium_malloc(sodium.crypto_hash_sha256_BYTES)
|
31
|
36
|
sodium.crypto_hash_sha256(context, Buffer.from(ctxt))
|
32
|
37
|
const seed = sodium.sodium_malloc(sodium.crypto_kdf_KEYBYTES)
|
33
|
38
|
sodium.crypto_kdf_derive_from_key(seed, id, context, masterKey)
|
|
39
|
+ zero(context)
|
34
|
40
|
return crypto.keyPair(seed)
|
35
|
41
|
}
|
36
|
42
|
|
|
@@ -48,14 +54,14 @@ class Crypto {
|
48
|
54
|
pack (address, encryptionKey) {
|
49
|
55
|
address = this.toBuffer(address, sodium.crypto_sign_PUBLICKEYBYTES)
|
50
|
56
|
encryptionKey = this.toBuffer(encryptionKey, sodium.crypto_secretbox_KEYBYTES)
|
51
|
|
- const accessKey = sodium.sodium_malloc(sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_secretbox_KEYBYTES)
|
|
57
|
+ const accessKey = sodium.sodium_malloc(this.PACKEDLENGTH)
|
52
|
58
|
address.copy(accessKey)
|
53
|
59
|
encryptionKey.copy(accessKey, sodium.crypto_secretbox_KEYBYTES)
|
54
|
60
|
return accessKey
|
55
|
61
|
}
|
56
|
62
|
|
57
|
63
|
unpack (key) {
|
58
|
|
- key = this.toBuffer(key, [sodium.crypto_sign_PUBLICKEYBYTES, sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_secretbox_KEYBYTES])
|
|
64
|
+ key = this.toBuffer(key, [sodium.crypto_sign_PUBLICKEYBYTES, this.PACKEDLENGTH])
|
59
|
65
|
|
60
|
66
|
if (key.length === sodium.crypto_sign_PUBLICKEYBYTES) return { address: key }
|
61
|
67
|
|
|
@@ -112,6 +118,7 @@ class Crypto {
|
112
|
118
|
var nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
|
113
|
119
|
sodium.randombytes_buf(nonce)
|
114
|
120
|
sodium.crypto_secretbox_easy(ciphertext, message, nonce, encryptionKey)
|
|
121
|
+ zero(message)
|
115
|
122
|
return Buffer.concat([nonce, ciphertext])
|
116
|
123
|
},
|
117
|
124
|
|
|
@@ -133,6 +140,74 @@ class Crypto {
|
133
|
140
|
}
|
134
|
141
|
}
|
135
|
142
|
|
|
143
|
+ boxKeypair (seed) {
|
|
144
|
+ const publicKey = sodium.sodium_malloc(sodium.crypto_box_PUBLICKEYBYTES)
|
|
145
|
+ const secretKey = sodium.sodium_malloc(sodium.crypto_box_SECRETKEYBYTES)
|
|
146
|
+ if (seed) {
|
|
147
|
+ seed = this.toBuffer(seed, sodium.crypto_box_SEEDBYTES)
|
|
148
|
+ sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
|
|
149
|
+ zero(seed)
|
|
150
|
+ } else {
|
|
151
|
+ sodium.crypto_box_keypair(publicKey, secretKey)
|
|
152
|
+ }
|
|
153
|
+ return { publicKey, secretKey }
|
|
154
|
+ }
|
|
155
|
+
|
|
156
|
+ box (pubKey, messageBuffer, contextMessage = Buffer.from('cobox')) {
|
|
157
|
+ pubKey = this.toBuffer(pubKey, sodium.crypto_box_PUBLICKEYBYTES)
|
|
158
|
+ if (typeof messageBuffer === 'string') messageBuffer = Buffer.from(messageBuffer)
|
|
159
|
+ if (typeof contextMessage === 'string') contextMessage = Buffer.from(contextMessage)
|
|
160
|
+ var boxed = Buffer.alloc(messageBuffer.length + sodium.crypto_secretbox_MACBYTES)
|
|
161
|
+ const ephKeypair = this.boxKeypair()
|
|
162
|
+ const nonce = this.randomBytes(sodium.crypto_secretbox_NONCEBYTES)
|
|
163
|
+ var sharedSecret = this.genericHash(
|
|
164
|
+ Buffer.concat([ephKeypair.publicKey, pubKey, contextMessage]),
|
|
165
|
+ this.genericHash(this.scalarMult(ephKeypair.secretKey, pubKey)))
|
|
166
|
+
|
|
167
|
+ sodium.crypto_secretbox_easy(boxed, messageBuffer, nonce, sharedSecret)
|
|
168
|
+
|
|
169
|
+ zero(sharedSecret)
|
|
170
|
+ zero(ephKeypair.secretKey)
|
|
171
|
+ return Buffer.concat([nonce, ephKeypair.publicKey, boxed])
|
|
172
|
+ }
|
|
173
|
+
|
|
174
|
+ unbox (cipherText, keypair, contextMessage = Buffer.from('cobox')) {
|
|
175
|
+ keypair.publicKey = this.toBuffer(keypair.publicKey, sodium.crypto_box_PUBLICKEYBYTES)
|
|
176
|
+ keypair.secretKey = this.toBuffer(keypair.secretKey, sodium.crypto_box_SECRETKEYBYTES)
|
|
177
|
+ if (typeof contextMessage === 'string') contextMessage = Buffer.from(contextMessage)
|
|
178
|
+ const NONCEBYTES = sodium.crypto_secretbox_NONCEBYTES
|
|
179
|
+ const KEYBYTES = sodium.crypto_secretbox_KEYBYTES
|
|
180
|
+ try {
|
|
181
|
+ var nonce = cipherText.slice(0, NONCEBYTES)
|
|
182
|
+ var pubKey = cipherText.slice(NONCEBYTES, NONCEBYTES + KEYBYTES)
|
|
183
|
+ var box = cipherText.slice(NONCEBYTES + KEYBYTES, cipherText.length)
|
|
184
|
+ var unboxed = Buffer.alloc(box.length - sodium.crypto_secretbox_MACBYTES)
|
|
185
|
+ } catch (err) {
|
|
186
|
+ return false
|
|
187
|
+ }
|
|
188
|
+ const sharedSecret = this.genericHash(
|
|
189
|
+ Buffer.concat([pubKey, keypair.publicKey, contextMessage]),
|
|
190
|
+ this.genericHash(this.scalarMult(keypair.secretKey, pubKey)))
|
|
191
|
+
|
|
192
|
+ const success = sodium.crypto_secretbox_open_easy(unboxed, box, nonce, sharedSecret)
|
|
193
|
+ zero(sharedSecret)
|
|
194
|
+ zero(keypair.secretKey)
|
|
195
|
+ zero(keypair.publicKey)
|
|
196
|
+ return success ? unboxed : false
|
|
197
|
+ }
|
|
198
|
+
|
|
199
|
+ genericHash (msg, key) {
|
|
200
|
+ var hash = sodium.sodium_malloc(sodium.crypto_generichash_BYTES_MAX)
|
|
201
|
+ sodium.crypto_generichash(hash, msg, key)
|
|
202
|
+ return hash
|
|
203
|
+ }
|
|
204
|
+
|
|
205
|
+ scalarMult (sk, pk) {
|
|
206
|
+ var result = sodium.sodium_malloc(sodium.crypto_scalarmult_BYTES)
|
|
207
|
+ sodium.crypto_scalarmult(result, sk, pk)
|
|
208
|
+ return result
|
|
209
|
+ }
|
|
210
|
+
|
136
|
211
|
_resolveStringEncoder (encoder) {
|
137
|
212
|
if (encoder === 'json') return {
|
138
|
213
|
encode: (msg) => Buffer.from(JSON.stringify(msg)),
|
|
@@ -148,4 +223,3 @@ class Crypto {
|
148
|
223
|
}
|
149
|
224
|
|
150
|
225
|
module.exports = new Crypto()
|
151
|
|
-
|