Implement parsing of transactions with JoinSplits (version 2)
This commit is contained in:
parent
8740d9e964
commit
80f5b45034
|
@ -0,0 +1,206 @@
|
|||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
var $ = require('../util/preconditions');
|
||||
var BN = require('../crypto/bn');
|
||||
var buffer = require('buffer');
|
||||
var BufferWriter = require('../encoding/bufferwriter');
|
||||
var BufferUtil = require('../util/buffer');
|
||||
var JSUtil = require('../util/js');
|
||||
|
||||
var ZCProof = require('../zcash/proof');
|
||||
|
||||
var ZC_NUM_JS_INPUTS = 2;
|
||||
var ZC_NUM_JS_OUTPUTS = 2;
|
||||
|
||||
// leading + v + rho + r + memo + auth
|
||||
var ZC_NOTECIPHERTEXT_SIZE = 1 + 8 + 32 + 32 + 512 + 16;
|
||||
|
||||
function JSDescription(params) {
|
||||
if (!(this instanceof JSDescription)) {
|
||||
return new JSDescription(params);
|
||||
}
|
||||
this.nullifiers = [];
|
||||
this.commitments = [];
|
||||
this.ciphertexts = [];
|
||||
this.macs = [];
|
||||
if (params) {
|
||||
return this._fromObject(params);
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(JSDescription.prototype, 'vpub_old', {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._vpub_old;
|
||||
},
|
||||
set: function(num) {
|
||||
if (num instanceof BN) {
|
||||
this._vpub_oldBN = num;
|
||||
this._vpub_old = num.toNumber();
|
||||
} else if (_.isString(num)) {
|
||||
this._vpub_old = parseInt(num);
|
||||
this._vpub_oldBN = BN.fromNumber(this._vpub_old);
|
||||
} else {
|
||||
$.checkArgument(
|
||||
JSUtil.isNaturalNumber(num),
|
||||
'vpub_old is not a natural number'
|
||||
);
|
||||
this._vpub_oldBN = BN.fromNumber(num);
|
||||
this._vpub_old = num;
|
||||
}
|
||||
$.checkState(
|
||||
JSUtil.isNaturalNumber(this._vpub_old),
|
||||
'vpub_old is not a natural number'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(JSDescription.prototype, 'vpub_new', {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._vpub_new;
|
||||
},
|
||||
set: function(num) {
|
||||
if (num instanceof BN) {
|
||||
this._vpub_newBN = num;
|
||||
this._vpub_new = num.toNumber();
|
||||
} else if (_.isString(num)) {
|
||||
this._vpub_new = parseInt(num);
|
||||
this._vpub_newBN = BN.fromNumber(this._vpub_new);
|
||||
} else {
|
||||
$.checkArgument(
|
||||
JSUtil.isNaturalNumber(num),
|
||||
'vpub_new is not a natural number'
|
||||
);
|
||||
this._vpub_newBN = BN.fromNumber(num);
|
||||
this._vpub_new = num;
|
||||
}
|
||||
$.checkState(
|
||||
JSUtil.isNaturalNumber(this._vpub_new),
|
||||
'vpub_new is not a natural number'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
JSDescription.fromObject = function(obj) {
|
||||
$.checkArgument(_.isObject(obj));
|
||||
var jsdesc = new JSDescription();
|
||||
return jsdesc._fromObject(obj);
|
||||
};
|
||||
|
||||
JSDescription.prototype._fromObject = function(params) {
|
||||
var nullifiers = [];
|
||||
_.each(params.nullifiers, function(nullifier) {
|
||||
nullifiers.push(BufferUtil.reverse(new buffer.Buffer(nullifier, 'hex')));
|
||||
});
|
||||
var commitments = [];
|
||||
_.each(params.commitments, function(commitment) {
|
||||
commitments.push(BufferUtil.reverse(new buffer.Buffer(commitment, 'hex')));
|
||||
});
|
||||
var ciphertexts = [];
|
||||
_.each(params.ciphertexts, function(ciphertext) {
|
||||
ciphertexts.push(new buffer.Buffer(ciphertext, 'hex'));
|
||||
});
|
||||
var macs = [];
|
||||
_.each(params.macs, function(mac) {
|
||||
macs.push(BufferUtil.reverse(new buffer.Buffer(mac, 'hex')));
|
||||
});
|
||||
this.vpub_old = params.vpub_old;
|
||||
this.vpub_new = params.vpub_new;
|
||||
this.anchor = BufferUtil.reverse(new buffer.Buffer(params.anchor, 'hex'));
|
||||
this.nullifiers = nullifiers;
|
||||
this.commitments = commitments;
|
||||
this.ephemeralKey = BufferUtil.reverse(new buffer.Buffer(params.ephemeralKey, 'hex'));
|
||||
this.ciphertexts = ciphertexts;
|
||||
this.randomSeed = BufferUtil.reverse(new buffer.Buffer(params.randomSeed, 'hex'));
|
||||
this.macs = macs;
|
||||
this.proof = ZCProof.fromObject(params.proof);
|
||||
return this;
|
||||
};
|
||||
|
||||
JSDescription.prototype.toObject = JSDescription.prototype.toJSON = function toObject() {
|
||||
var nullifiers = [];
|
||||
_.each(this.nullifiers, function(nullifier) {
|
||||
nullifiers.push(BufferUtil.reverse(nullifier).toString('hex'));
|
||||
});
|
||||
var commitments = [];
|
||||
_.each(this.commitments, function(commitment) {
|
||||
commitments.push(BufferUtil.reverse(commitment).toString('hex'));
|
||||
});
|
||||
var ciphertexts = [];
|
||||
_.each(this.ciphertexts, function(ciphertext) {
|
||||
ciphertexts.push(ciphertext.toString('hex'));
|
||||
});
|
||||
var macs = [];
|
||||
_.each(this.macs, function(mac) {
|
||||
macs.push(BufferUtil.reverse(mac).toString('hex'));
|
||||
});
|
||||
var obj = {
|
||||
vpub_old: this.vpub_old,
|
||||
vpub_new: this.vpub_new,
|
||||
anchor: BufferUtil.reverse(this.anchor).toString('hex'),
|
||||
nullifiers: nullifiers,
|
||||
commitments: commitments,
|
||||
ephemeralKey: BufferUtil.reverse(this.ephemeralKey).toString('hex'),
|
||||
ciphertexts: ciphertexts,
|
||||
randomSeed: BufferUtil.reverse(this.randomSeed).toString('hex'),
|
||||
macs: macs,
|
||||
proof: this.proof.toObject(),
|
||||
};
|
||||
return obj;
|
||||
};
|
||||
|
||||
JSDescription.fromBufferReader = function(br) {
|
||||
var i;
|
||||
var jsdesc = new JSDescription();
|
||||
jsdesc.vpub_old = br.readUInt64LEBN();
|
||||
jsdesc.vpub_new = br.readUInt64LEBN();
|
||||
jsdesc.anchor = br.read(32);
|
||||
for (i = 0; i < ZC_NUM_JS_INPUTS; i++) {
|
||||
jsdesc.nullifiers.push(br.read(32));
|
||||
}
|
||||
for (i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
jsdesc.commitments.push(br.read(32));
|
||||
}
|
||||
jsdesc.ephemeralKey = br.read(32);
|
||||
jsdesc.randomSeed = br.read(32);
|
||||
for (i = 0; i < ZC_NUM_JS_INPUTS; i++) {
|
||||
jsdesc.macs.push(br.read(32));
|
||||
}
|
||||
jsdesc.proof = ZCProof.fromBufferReader(br);
|
||||
for (i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
jsdesc.ciphertexts.push(br.read(ZC_NOTECIPHERTEXT_SIZE));
|
||||
}
|
||||
return jsdesc;
|
||||
};
|
||||
|
||||
JSDescription.prototype.toBufferWriter = function(writer) {
|
||||
var i;
|
||||
if (!writer) {
|
||||
writer = new BufferWriter();
|
||||
}
|
||||
writer.writeUInt64LEBN(this._vpub_oldBN);
|
||||
writer.writeUInt64LEBN(this._vpub_newBN);
|
||||
writer.write(this.anchor);
|
||||
for (i = 0; i < ZC_NUM_JS_INPUTS; i++) {
|
||||
writer.write(this.nullifiers[i]);
|
||||
}
|
||||
for (i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
writer.write(this.commitments[i]);
|
||||
}
|
||||
writer.write(this.ephemeralKey);
|
||||
writer.write(this.randomSeed);
|
||||
for (i = 0; i < ZC_NUM_JS_INPUTS; i++) {
|
||||
writer.write(this.macs[i]);
|
||||
}
|
||||
this.proof.toBufferWriter(writer);
|
||||
for (i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
writer.write(this.ciphertexts[i]);
|
||||
}
|
||||
return writer;
|
||||
};
|
||||
|
||||
module.exports = JSDescription;
|
|
@ -26,6 +26,8 @@ var Script = require('../script');
|
|||
var PrivateKey = require('../privatekey');
|
||||
var BN = require('../crypto/bn');
|
||||
|
||||
var JSDescription = require('./jsdescription');
|
||||
|
||||
/**
|
||||
* Represents a transaction, a set of inputs and outputs to change ownership of tokens
|
||||
*
|
||||
|
@ -38,6 +40,7 @@ function Transaction(serialized) {
|
|||
}
|
||||
this.inputs = [];
|
||||
this.outputs = [];
|
||||
this.joinSplits = [];
|
||||
this._inputAmount = undefined;
|
||||
this._outputAmount = undefined;
|
||||
|
||||
|
@ -289,6 +292,16 @@ Transaction.prototype.toBufferWriter = function(writer) {
|
|||
output.toBufferWriter(writer);
|
||||
});
|
||||
writer.writeUInt32LE(this.nLockTime);
|
||||
if (this.version >= 2) {
|
||||
writer.writeVarintNum(this.joinSplits.length);
|
||||
_.each(this.joinSplits, function(jsdesc) {
|
||||
jsdesc.toBufferWriter(writer);
|
||||
});
|
||||
if (this.joinSplits.length > 0) {
|
||||
writer.write(this.joinSplitPubKey);
|
||||
writer.write(this.joinSplitSig);
|
||||
}
|
||||
}
|
||||
return writer;
|
||||
};
|
||||
|
||||
|
@ -299,7 +312,7 @@ Transaction.prototype.fromBuffer = function(buffer) {
|
|||
|
||||
Transaction.prototype.fromBufferReader = function(reader) {
|
||||
$.checkArgument(!reader.finished(), 'No transaction data received');
|
||||
var i, sizeTxIns, sizeTxOuts;
|
||||
var i, sizeTxIns, sizeTxOuts, sizeJSDescs;
|
||||
|
||||
this.version = reader.readUInt32LE();
|
||||
sizeTxIns = reader.readVarintNum();
|
||||
|
@ -312,6 +325,16 @@ Transaction.prototype.fromBufferReader = function(reader) {
|
|||
this.outputs.push(Output.fromBufferReader(reader));
|
||||
}
|
||||
this.nLockTime = reader.readUInt32LE();
|
||||
if (this.version >= 2) {
|
||||
sizeJSDescs = reader.readVarintNum();
|
||||
for (i = 0; i < sizeJSDescs; i++) {
|
||||
this.joinSplits.push(JSDescription.fromBufferReader(reader));
|
||||
}
|
||||
if (sizeJSDescs > 0) {
|
||||
this.joinSplitPubKey = reader.read(32);
|
||||
this.joinSplitSig = reader.read(64);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
|
@ -331,6 +354,17 @@ Transaction.prototype.toObject = Transaction.prototype.toJSON = function toObjec
|
|||
outputs: outputs,
|
||||
nLockTime: this.nLockTime
|
||||
};
|
||||
if (this.version >= 2) {
|
||||
var joinSplits = [];
|
||||
this.joinSplits.forEach(function(joinSplit) {
|
||||
joinSplits.push(joinSplit.toObject());
|
||||
});
|
||||
obj.joinSplits = joinSplits;
|
||||
if (this.joinSplits.length > 0) {
|
||||
obj.joinSplitPubKey = BufferUtil.reverse(this.joinSplitPubKey).toString('hex');
|
||||
obj.joinSplitSig = this.joinSplitSig.toString('hex');
|
||||
}
|
||||
}
|
||||
if (this._changeScript) {
|
||||
obj.changeScript = this._changeScript.toString();
|
||||
}
|
||||
|
@ -387,6 +421,15 @@ Transaction.prototype.fromObject = function fromObject(arg) {
|
|||
}
|
||||
this.nLockTime = transaction.nLockTime;
|
||||
this.version = transaction.version;
|
||||
if (this.version >= 2) {
|
||||
_.each(transaction.joinSplits, function(joinSplit) {
|
||||
self.joinSplits.push(new JSDescription(joinSplit));
|
||||
});
|
||||
if (self.joinSplits.length > 0) {
|
||||
self.joinSplitPubKey = BufferUtil.reverse(new Buffer(transaction.joinSplitPubKey, 'hex'));
|
||||
self.joinSplitSig = new Buffer(transaction.joinSplitSig, 'hex');
|
||||
}
|
||||
}
|
||||
this._checkConsistency(arg);
|
||||
return this;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
'use strict';
|
||||
|
||||
var $ = require('../util/preconditions');
|
||||
var buffer = require('buffer');
|
||||
var BufferWriter = require('../encoding/bufferwriter');
|
||||
|
||||
var G1_PREFIX_MASK = 0x02;
|
||||
var G2_PREFIX_MASK = 0x0a;
|
||||
|
||||
function CompressedG1(params) {
|
||||
if (!(this instanceof CompressedG1)) {
|
||||
return new CompressedG1(params);
|
||||
}
|
||||
if (params) {
|
||||
return this._fromObject(params);
|
||||
}
|
||||
}
|
||||
|
||||
CompressedG1.fromObject = function(obj) {
|
||||
$.checkArgument(_.isObject(obj));
|
||||
var pt = new CompressedG1();
|
||||
return pt._fromObject(obj);
|
||||
};
|
||||
|
||||
CompressedG1.prototype._fromObject = function(params) {
|
||||
this.y_lsb = params.y_lsb;
|
||||
this.x = new buffer.Buffer(params.x, 'hex');
|
||||
return this;
|
||||
};
|
||||
|
||||
CompressedG1.prototype.toObject = CompressedG1.prototype.toJSON = function toObject() {
|
||||
var obj = {
|
||||
y_lsb: this.y_lsb,
|
||||
x: this.x.toString('hex'),
|
||||
};
|
||||
return obj;
|
||||
};
|
||||
|
||||
CompressedG1.fromBufferReader = function(br) {
|
||||
var pt = new CompressedG1();
|
||||
var y_lsb = br.readUInt8();
|
||||
pt.y_lsb = y_lsb & 1;
|
||||
pt.x = br.read(32);
|
||||
return pt;
|
||||
};
|
||||
|
||||
CompressedG1.prototype.toBufferWriter = function(writer) {
|
||||
if (!writer) {
|
||||
writer = new BufferWriter();
|
||||
}
|
||||
writer.writeUInt8(G1_PREFIX_MASK | this.y_lsb);
|
||||
writer.write(this.x);
|
||||
return writer;
|
||||
};
|
||||
|
||||
function CompressedG2(params) {
|
||||
if (!(this instanceof CompressedG2)) {
|
||||
return new CompressedG2(params);
|
||||
}
|
||||
if (params) {
|
||||
return this._fromObject(params);
|
||||
}
|
||||
}
|
||||
|
||||
CompressedG2.fromObject = function(obj) {
|
||||
$.checkArgument(_.isObject(obj));
|
||||
var pt = new CompressedG2();
|
||||
return pt._fromObject(obj);
|
||||
};
|
||||
|
||||
CompressedG2.prototype._fromObject = function(params) {
|
||||
this.y_gt = params.y_gt;
|
||||
this.x = new buffer.Buffer(params.x, 'hex');
|
||||
return this;
|
||||
};
|
||||
|
||||
CompressedG2.prototype.toObject = CompressedG2.prototype.toJSON = function toObject() {
|
||||
var obj = {
|
||||
y_gt: this.y_gt,
|
||||
x: this.x.toString('hex'),
|
||||
};
|
||||
return obj;
|
||||
};
|
||||
|
||||
CompressedG2.fromBufferReader = function(br) {
|
||||
var pt = new CompressedG2();
|
||||
var y_gt = br.readUInt8();
|
||||
pt.y_gt = y_gt & 1;
|
||||
pt.x = br.read(64);
|
||||
return pt;
|
||||
};
|
||||
|
||||
CompressedG2.prototype.toBufferWriter = function(writer) {
|
||||
if (!writer) {
|
||||
writer = new BufferWriter();
|
||||
}
|
||||
writer.writeUInt8(G2_PREFIX_MASK | this.y_gt);
|
||||
writer.write(this.x);
|
||||
return writer;
|
||||
};
|
||||
|
||||
function ZCProof(params) {
|
||||
if (!(this instanceof ZCProof)) {
|
||||
return new ZCProof(params);
|
||||
}
|
||||
if (params) {
|
||||
return this._fromObject(params);
|
||||
}
|
||||
}
|
||||
|
||||
ZCProof.fromObject = function(obj) {
|
||||
$.checkArgument(_.isObject(obj));
|
||||
var proof = new ZCProof();
|
||||
return proof._fromObject(obj);
|
||||
};
|
||||
|
||||
ZCProof.prototype._fromObject = function(params) {
|
||||
this.g_A = CompressedG1.fromObject(params.g_A);
|
||||
this.g_A_prime = CompressedG1.fromObject(params.g_A_prime);
|
||||
this.g_B = CompressedG2.fromObject(params.g_B);
|
||||
this.g_B_prime = CompressedG1.fromObject(params.g_B_prime);
|
||||
this.g_C = CompressedG1.fromObject(params.g_C);
|
||||
this.g_C_prime = CompressedG1.fromObject(params.g_C_prime);
|
||||
this.g_K = CompressedG1.fromObject(params.g_K);
|
||||
this.g_H = CompressedG1.fromObject(params.g_H);
|
||||
return this;
|
||||
};
|
||||
|
||||
ZCProof.prototype.toObject = ZCProof.prototype.toJSON = function toObject() {
|
||||
var obj = {
|
||||
g_A: this.g_A.toObject(),
|
||||
g_A_prime: this.g_A_prime.toObject(),
|
||||
g_B: this.g_B.toObject(),
|
||||
g_B_prime: this.g_B_prime.toObject(),
|
||||
g_C: this.g_C.toObject(),
|
||||
g_C_prime: this.g_C_prime.toObject(),
|
||||
g_K: this.g_K.toObject(),
|
||||
g_H: this.g_H.toObject(),
|
||||
};
|
||||
return obj;
|
||||
};
|
||||
|
||||
ZCProof.fromBufferReader = function(br) {
|
||||
var proof = new ZCProof();
|
||||
proof.g_A = CompressedG1.fromBufferReader(br);
|
||||
proof.g_A_prime = CompressedG1.fromBufferReader(br);
|
||||
proof.g_B = CompressedG2.fromBufferReader(br);
|
||||
proof.g_B_prime = CompressedG1.fromBufferReader(br);
|
||||
proof.g_C = CompressedG1.fromBufferReader(br);
|
||||
proof.g_C_prime = CompressedG1.fromBufferReader(br);
|
||||
proof.g_K = CompressedG1.fromBufferReader(br);
|
||||
proof.g_H = CompressedG1.fromBufferReader(br);
|
||||
return proof;
|
||||
};
|
||||
|
||||
ZCProof.prototype.toBufferWriter = function(writer) {
|
||||
if (!writer) {
|
||||
writer = new BufferWriter();
|
||||
}
|
||||
this.g_A.toBufferWriter(writer);
|
||||
this.g_A_prime.toBufferWriter(writer);
|
||||
this.g_B.toBufferWriter(writer);
|
||||
this.g_B_prime.toBufferWriter(writer);
|
||||
this.g_C.toBufferWriter(writer);
|
||||
this.g_C_prime.toBufferWriter(writer);
|
||||
this.g_K.toBufferWriter(writer);
|
||||
this.g_H.toBufferWriter(writer);
|
||||
return writer;
|
||||
};
|
||||
|
||||
module.exports = ZCProof;
|
Loading…
Reference in New Issue