2017-05-30 23:12:14 -07:00
|
|
|
'use strict'
|
|
|
|
|
2017-06-01 19:45:30 -07:00
|
|
|
var blake2b = require('./blake2b')
|
2017-05-30 23:12:14 -07:00
|
|
|
var bufferutils = require('./bufferutils')
|
2017-06-01 19:45:30 -07:00
|
|
|
var prf = require('./prf')
|
|
|
|
var typeforce = require('typeforce')
|
|
|
|
var types = require('./types')
|
|
|
|
var util = require('./util')
|
2017-05-30 23:12:14 -07:00
|
|
|
var zconst = require('./const')
|
|
|
|
|
2017-06-01 19:45:30 -07:00
|
|
|
var JSProofWitness = require('./proof_witness')
|
|
|
|
var NotePlaintext = require('./note_plaintext')
|
|
|
|
var ZCNoteEncryption = require('./note_encryption')
|
2017-05-30 23:12:14 -07:00
|
|
|
var ZCProof = require('./proof')
|
|
|
|
|
2017-06-01 19:45:30 -07:00
|
|
|
function hSig (randomSeed, nullifiers, pubKeyHash) {
|
|
|
|
typeforce(types.tuple(
|
|
|
|
types.Buffer256bit,
|
|
|
|
types.arrayOf(types.Buffer256bit),
|
|
|
|
types.Buffer256bit
|
|
|
|
), arguments)
|
|
|
|
|
|
|
|
return Buffer.from(blake2b.crypto_generichash_blake2b_salt_personal(
|
|
|
|
32,
|
|
|
|
Buffer.concat([randomSeed].concat(nullifiers).concat([pubKeyHash])),
|
|
|
|
undefined, // No key.
|
|
|
|
undefined, // No salt.
|
|
|
|
'ZcashComputehSig'))
|
|
|
|
}
|
|
|
|
|
2017-05-30 23:12:14 -07:00
|
|
|
function JSDescription () {
|
|
|
|
this.nullifiers = []
|
|
|
|
this.commitments = []
|
|
|
|
this.ciphertexts = []
|
|
|
|
this.macs = []
|
|
|
|
}
|
|
|
|
|
|
|
|
JSDescription.fromBuffer = function (buffer, __noStrict) {
|
|
|
|
var offset = 0
|
|
|
|
function readSlice (n) {
|
|
|
|
offset += n
|
|
|
|
return buffer.slice(offset - n, offset)
|
|
|
|
}
|
|
|
|
|
|
|
|
function readUInt64 () {
|
|
|
|
var i = bufferutils.readUInt64LE(buffer, offset)
|
|
|
|
offset += 8
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
function readZCProof () {
|
|
|
|
var proof = ZCProof.fromBuffer(buffer.slice(offset), true)
|
|
|
|
offset += proof.byteLength()
|
|
|
|
return proof
|
|
|
|
}
|
|
|
|
|
|
|
|
var jsdesc = new JSDescription()
|
|
|
|
jsdesc.vpub_old = readUInt64()
|
|
|
|
jsdesc.vpub_new = readUInt64()
|
|
|
|
jsdesc.anchor = readSlice(32)
|
|
|
|
|
|
|
|
for (var i = 0; i < zconst.ZC_NUM_JS_INPUTS; ++i) {
|
|
|
|
jsdesc.nullifiers.push(readSlice(32))
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < zconst.ZC_NUM_JS_OUTPUTS; ++i) {
|
|
|
|
jsdesc.commitments.push(readSlice(32))
|
|
|
|
}
|
|
|
|
|
|
|
|
jsdesc.onetimePubKey = readSlice(32)
|
|
|
|
jsdesc.randomSeed = readSlice(32)
|
|
|
|
|
|
|
|
for (i = 0; i < zconst.ZC_NUM_JS_INPUTS; ++i) {
|
|
|
|
jsdesc.macs.push(readSlice(32))
|
|
|
|
}
|
|
|
|
|
|
|
|
jsdesc.proof = readZCProof()
|
|
|
|
|
|
|
|
for (i = 0; i < zconst.ZC_NUM_JS_OUTPUTS; ++i) {
|
|
|
|
jsdesc.ciphertexts.push(readSlice(zconst.ZC_NOTECIPHERTEXT_SIZE))
|
|
|
|
}
|
|
|
|
|
|
|
|
if (__noStrict) return jsdesc
|
|
|
|
if (offset !== buffer.length) throw new Error('JSDescription has unexpected data')
|
|
|
|
|
|
|
|
return jsdesc
|
|
|
|
}
|
|
|
|
|
|
|
|
JSDescription.fromHex = function (hex) {
|
|
|
|
return JSDescription.fromBuffer(Buffer.from(hex, 'hex'))
|
|
|
|
}
|
|
|
|
|
|
|
|
JSDescription.prototype.byteLength = function () {
|
|
|
|
return (
|
|
|
|
112 +
|
|
|
|
zconst.ZC_NUM_JS_INPUTS * 64 +
|
|
|
|
zconst.ZC_NUM_JS_OUTPUTS * (32 + zconst.ZC_NOTECIPHERTEXT_SIZE) +
|
|
|
|
this.proof.byteLength()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
JSDescription.prototype.clone = function () {
|
|
|
|
var newJSDesc = new JSDescription()
|
|
|
|
newJSDesc.vpub_old = this.vpub_old
|
|
|
|
newJSDesc.vpub_new = this.vpub_new
|
|
|
|
newJSDesc.anchor = this.anchor
|
|
|
|
|
|
|
|
newJSDesc.nullifiers = this.nullifiers.map(function (nullifier) {
|
|
|
|
return nullifier
|
|
|
|
})
|
|
|
|
|
|
|
|
newJSDesc.commitments = this.commitments.map(function (commitment) {
|
|
|
|
return commitment
|
|
|
|
})
|
|
|
|
|
|
|
|
newJSDesc.onetimePubKey = this.onetimePubKey
|
|
|
|
newJSDesc.randomSeed = this.randomSeed
|
|
|
|
|
|
|
|
newJSDesc.macs = this.macs.map(function (mac) {
|
|
|
|
return mac
|
|
|
|
})
|
|
|
|
|
|
|
|
newJSDesc.proof = this.proof.clone()
|
|
|
|
|
|
|
|
newJSDesc.ciphertexts = this.ciphertexts.map(function (ciphertext) {
|
|
|
|
return ciphertext
|
|
|
|
})
|
|
|
|
|
|
|
|
return newJSDesc
|
|
|
|
}
|
|
|
|
|
|
|
|
JSDescription.prototype.toBuffer = function () {
|
|
|
|
var buffer = Buffer.alloc(this.byteLength())
|
|
|
|
|
|
|
|
var offset = 0
|
|
|
|
function writeSlice (slice) {
|
|
|
|
slice.copy(buffer, offset)
|
|
|
|
offset += slice.length
|
|
|
|
}
|
|
|
|
|
|
|
|
function writeUInt64 (i) {
|
|
|
|
bufferutils.writeUInt64LE(buffer, i, offset)
|
|
|
|
offset += 8
|
|
|
|
}
|
|
|
|
|
|
|
|
writeUInt64(this.vpub_old)
|
|
|
|
writeUInt64(this.vpub_new)
|
|
|
|
writeSlice(this.anchor)
|
|
|
|
|
|
|
|
this.nullifiers.forEach(function (nullifier) {
|
|
|
|
writeSlice(nullifier)
|
|
|
|
})
|
|
|
|
|
|
|
|
this.commitments.forEach(function (commitment) {
|
|
|
|
writeSlice(commitment)
|
|
|
|
})
|
|
|
|
|
|
|
|
writeSlice(this.onetimePubKey)
|
|
|
|
writeSlice(this.randomSeed)
|
|
|
|
|
|
|
|
this.macs.forEach(function (mac) {
|
|
|
|
writeSlice(mac)
|
|
|
|
})
|
|
|
|
|
|
|
|
writeSlice(this.proof.toBuffer())
|
|
|
|
|
|
|
|
this.ciphertexts.forEach(function (ciphertext) {
|
|
|
|
writeSlice(ciphertext)
|
|
|
|
})
|
|
|
|
|
|
|
|
return buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
JSDescription.prototype.toHex = function () {
|
|
|
|
return this.toBuffer().toString('hex')
|
|
|
|
}
|
|
|
|
|
2017-06-01 19:45:30 -07:00
|
|
|
JSDescription.prototype.h_sig = function (joinSplitPubKey) {
|
|
|
|
return hSig(this.randomSeed, this.nullifiers, joinSplitPubKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
JSDescription.withWitness = function (inputs, outputs, pubKeyHash, vpubOld, vpubNew, rt) {
|
|
|
|
typeforce(types.tuple(
|
|
|
|
types.arrayOf(types.JSInput),
|
|
|
|
types.arrayOf(types.JSOutput),
|
|
|
|
types.Buffer256bit,
|
|
|
|
types.Zatoshi,
|
|
|
|
types.Zatoshi,
|
|
|
|
types.Buffer256bit
|
|
|
|
), arguments)
|
|
|
|
|
|
|
|
if (inputs.length !== zconst.ZC_NUM_JS_INPUTS) {
|
|
|
|
throw new Error(`invalid number of inputs (found ${inputs.length}, expected ${zconst.ZC_NUM_JS_INPUTS}`)
|
|
|
|
}
|
|
|
|
if (outputs.length !== zconst.ZC_NUM_JS_OUTPUTS) {
|
|
|
|
throw new Error(`invalid number of inputs (found ${outputs.length}, expected ${zconst.ZC_NUM_JS_OUTPUTS}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
var jsdesc = new JSDescription()
|
|
|
|
jsdesc.vpub_old = vpubOld
|
|
|
|
jsdesc.vpub_new = vpubNew
|
|
|
|
jsdesc.anchor = rt
|
|
|
|
|
|
|
|
var lhsValue = vpubOld
|
|
|
|
var rhsValue = vpubNew
|
|
|
|
|
|
|
|
inputs.forEach(function (input) {
|
|
|
|
// Sanity checks of input
|
|
|
|
|
|
|
|
// If note has nonzero value
|
|
|
|
if (input.note.value !== 0) {
|
|
|
|
// The witness root must equal the input root.
|
|
|
|
if (input.witness.root() !== rt) {
|
|
|
|
throw new Error('joinsplit not anchored to the correct root')
|
|
|
|
}
|
|
|
|
|
|
|
|
// The tree must witness the correct element
|
|
|
|
if (input.note.cm() !== input.witness.element()) {
|
|
|
|
throw new Error('witness of wrong element for joinsplit input')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure we have the key to this note.
|
|
|
|
if (input.note.a_pk.toString('hex') !== input.key.address().a_pk.toString('hex')) {
|
|
|
|
throw new Error('input note not authorized to spend with given key')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Balance must be sensical
|
|
|
|
typeforce(types.Zatoshi, input.note.value)
|
|
|
|
lhsValue += input.note.value
|
|
|
|
typeforce(types.Zatoshi, lhsValue)
|
|
|
|
|
|
|
|
// Compute nullifier of input
|
|
|
|
jsdesc.nullifiers.push(input.nullifier())
|
|
|
|
})
|
|
|
|
|
|
|
|
// Sample randomSeed
|
|
|
|
jsdesc.randomSeed = util.random_uint256()
|
|
|
|
|
|
|
|
// Compute h_sig
|
|
|
|
var hSig = jsdesc.h_sig(pubKeyHash)
|
|
|
|
|
|
|
|
// Sample phi
|
|
|
|
var phi = util.random_uint252()
|
|
|
|
|
|
|
|
// Compute notes for outputs
|
|
|
|
var notes = []
|
|
|
|
outputs.forEach(function (output, i) {
|
|
|
|
// Sanity checks of output
|
|
|
|
typeforce(types.Zatoshi, output.value)
|
|
|
|
rhsValue += output.value
|
|
|
|
typeforce(types.Zatoshi, rhsValue)
|
|
|
|
|
|
|
|
// Sample r
|
|
|
|
var r = util.random_uint256()
|
|
|
|
|
|
|
|
notes.push(output.note(phi, r, i, hSig))
|
|
|
|
})
|
|
|
|
|
|
|
|
if (lhsValue !== rhsValue) {
|
|
|
|
throw new Error('invalid joinsplit balance')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute the output commitments
|
|
|
|
notes.forEach(function (note) {
|
|
|
|
jsdesc.commitments.push(note.cm())
|
|
|
|
})
|
|
|
|
|
|
|
|
// Encrypt the ciphertexts containing the note
|
|
|
|
// plaintexts to the recipients of the value.
|
|
|
|
var encryptor = new ZCNoteEncryption(hSig)
|
|
|
|
|
|
|
|
notes.forEach(function (note, i) {
|
|
|
|
var pt = new NotePlaintext(note, outputs[i].memo)
|
|
|
|
|
|
|
|
jsdesc.ciphertexts.push(pt.encrypt(encryptor, outputs[i].addr.pk_enc))
|
|
|
|
})
|
|
|
|
|
2017-06-09 01:22:18 -07:00
|
|
|
jsdesc.onetimePubKey = encryptor.epk
|
2017-06-01 19:45:30 -07:00
|
|
|
|
|
|
|
// Authenticate hSig with each of the input
|
|
|
|
// spending keys, producing macs which protect
|
|
|
|
// against malleability.
|
|
|
|
inputs.forEach(function (input, i) {
|
|
|
|
jsdesc.macs.push(prf.PRF_pk(inputs[i].key.a_sk, i, hSig))
|
|
|
|
})
|
|
|
|
|
|
|
|
jsdesc.witness = new JSProofWitness(phi, rt, hSig, inputs, notes, vpubOld, vpubNew)
|
|
|
|
|
|
|
|
return jsdesc
|
|
|
|
}
|
|
|
|
|
2017-05-30 23:12:14 -07:00
|
|
|
module.exports = JSDescription
|