Implement note encryption

This commit is contained in:
Jack Grigg 2017-06-02 14:38:46 +12:00
parent 27692c26ea
commit e823108984
No known key found for this signature in database
GPG Key ID: 665DBCD284F7DAFF
6 changed files with 195 additions and 0 deletions

View File

@ -27,6 +27,7 @@ var ZC_NOTECIPHERTEXT_SIZE = (
module.exports = {
ZC_MEMO_SIZE: ZC_MEMO_SIZE,
ZC_NOTECIPHERTEXT_SIZE: ZC_NOTECIPHERTEXT_SIZE,
ZC_NOTEPLAINTEXT_SIZE: ZC_NOTEPLAINTEXT_SIZE,
ZC_NUM_JS_INPUTS: ZC_NUM_JS_INPUTS,
ZC_NUM_JS_OUTPUTS: ZC_NUM_JS_OUTPUTS
}

29
src/kdf.js Normal file
View File

@ -0,0 +1,29 @@
'use strict'
var blake2b = require('./blake2b')
var libsodium = require('libsodium-sumo')
function KDF (dhsecret, epk, pkEnc, hSig, nonce) {
if (nonce === 0xff) {
throw new Error('no additional nonce space for KDF')
}
var block = new Uint8Array(128)
hSig.copy(block, 0)
dhsecret.copy(block, 32)
epk.copy(block, 64)
pkEnc.copy(block, 96)
var personalization = new Uint8Array(libsodium._crypto_generichash_blake2b_personalbytes()).fill(0)
Buffer.from('ZcashKDF').copy(personalization, 0)
personalization[8] = nonce
return Buffer.from(blake2b.crypto_generichash_blake2b_salt_personal(
32,
block,
undefined, // No key.
undefined, // No salt.
personalization))
}
module.exports = KDF

38
src/note_decryption.js Normal file
View File

@ -0,0 +1,38 @@
'use strict'
var sodium = require('libsodium-wrappers-sumo')
var typeforce = require('typeforce')
var types = require('./types')
var zutil = require('./util')
var KDF = require('./kdf')
function ZCNoteDecryption (skEnc) {
typeforce(types.Buffer256bit, skEnc)
this.sk_enc = skEnc
this.pk_enc = zutil.generate_pubkey(skEnc)
}
ZCNoteDecryption.prototype.decrypt = function (ciphertext, epk, hSig, nonce) {
typeforce(types.tuple(
types.Buffer,
types.Buffer256bit,
types.Buffer256bit,
types.Number
), arguments)
var dhsecret = Buffer.from(sodium.crypto_scalarmult(this.sk_enc, epk))
// Construct the symmetric key
var K = KDF(dhsecret, epk, this.pk_enc, hSig, nonce)
// The nonce is zero because we never reuse keys
var cipherNonce = new Uint8Array(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
sodium.memzero(cipherNonce)
return Buffer.from(sodium.crypto_aead_chacha20poly1305_ietf_decrypt(
null, ciphertext, null, cipherNonce, K))
}
module.exports = ZCNoteDecryption

41
src/note_encryption.js Normal file
View File

@ -0,0 +1,41 @@
'use strict'
var sodium = require('libsodium-wrappers-sumo')
var typeforce = require('typeforce')
var types = require('./types')
var zutil = require('./util')
var KDF = require('./kdf')
function ZCNoteEncryption (hSig) {
typeforce(types.Buffer256bit, hSig)
this.nonce = 0
this.hSig = hSig
this.esk = zutil.random_uint256()
this.epk = zutil.generate_pubkey(this.esk)
}
ZCNoteEncryption.prototype.encrypt = function (pkEnc, message) {
typeforce(types.tuple(
types.Buffer256bit,
types.Buffer
), arguments)
var dhsecret = Buffer.from(sodium.crypto_scalarmult(this.esk, pkEnc))
// Construct the symmetric key
var K = KDF(dhsecret, this.epk, pkEnc, this.hSig, this.nonce)
// Increment the number of encryptions we've performed
this.nonce++
// The nonce is zero because we never reuse keys
var cipherNonce = new Uint8Array(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
sodium.memzero(cipherNonce)
return Buffer.from(sodium.crypto_aead_chacha20poly1305_ietf_encrypt(
message, null, null, cipherNonce, K))
}
module.exports = ZCNoteEncryption

View File

@ -83,4 +83,8 @@ NotePlaintext.prototype.note = function (aPk) {
return new Note(aPk, this.value, this.rho, this.r)
}
NotePlaintext.prototype.encrypt = function (encryptor, pkEnc) {
return encryptor.encrypt(pkEnc, this.toBuffer())
}
module.exports = NotePlaintext

82
test/note_encryption.js Normal file
View File

@ -0,0 +1,82 @@
/* global describe, it */
'use strict'
var assert = require('assert')
var ZCNoteDecryption = require('../src/note_decryption')
var ZCNoteEncryption = require('../src/note_encryption')
var util = require('../src/util')
var zconst = require('../src/const')
describe('Note Encryption', function () {
var skEnc = util.generate_privkey([].reverse.call(Buffer.from('21035d60bc1983e37950ce4803418a8fb33ea68d5b937ca382ecbae7564d6a07', 'hex')))
var pkEnc = util.generate_pubkey(skEnc)
var hSig = util.random_uint256()
var b = new ZCNoteEncryption(hSig)
var message = Buffer.alloc(zconst.ZC_NOTEPLAINTEXT_SIZE)
for (let i = 0; i < zconst.ZC_NOTEPLAINTEXT_SIZE; ++i) {
message[i] = i
}
for (let i = 0; i < 255; ++i) {
var ciphertext
var decrypter = new ZCNoteDecryption(skEnc)
it('correctly encrypts and decrypts nonce ' + i, function () {
ciphertext = b.encrypt(pkEnc, message)
var plaintext = decrypter.decrypt(ciphertext, b.epk, hSig, i)
assert.strictEqual(plaintext.toString('hex'), message.toString('hex'))
})
it('fails to decrypt ' + i + ' with wrong nonce', function () {
assert.throws(function () {
decrypter.decrypt(ciphertext, b.epk, hSig, (i === 0) ? 1 : (i - 1))
})
})
it('fails to decrypt ' + i + ' with wrong oneTimePubKey', function () {
var c = new ZCNoteEncryption(hSig)
assert.throws(function () {
decrypter.decrypt(ciphertext, c.epk, hSig, i)
})
})
it('fails to decrypt ' + i + ' with wrong seed', function () {
assert.throws(function () {
decrypter.decrypt(ciphertext, b.epk, [].reverse.call(Buffer.from('11035d60bc1983e37950ce4803418a8fb33ea68d5b937ca382ecbae7564d6a77', 'hex')), i)
})
})
it('fails to decrypt ' + i + ' with corrupted ciphertext', function () {
ciphertext[10] ^= 0xff
assert.throws(function () {
decrypter.decrypt(ciphertext, b.epk, hSig, i)
})
ciphertext[10] ^= 0xff
})
it('fails to decrypt ' + i + ' with wrong private key', function () {
var skEnc2 = util.generate_privkey(util.random_uint252())
var decrypter2 = new ZCNoteDecryption(skEnc2)
assert.throws(function () {
decrypter2.decrypt(ciphertext, b.epk, hSig, i)
})
})
it('fails to decrypt ' + i + ' with wrong public key (test of KDF)', function () {
var decrypter2 = new ZCNoteDecryption(skEnc)
decrypter2.pk_enc = util.generate_pubkey(util.random_uint256())
assert.throws(function () {
decrypter2.decrypt(ciphertext, b.epk, hSig, i)
})
})
}
it('runs out of nonce space with nonce 255', function () {
assert.throws(function () {
b.encrypt(pkEnc, message)
}, new RegExp('no additional nonce space for KDF'))
})
})