know-js
Version:
JavaScript library for sending Know transactions from the client or server
524 lines (456 loc) • 12.8 kB
JavaScript
/** @module crypto */
var crypto = require("crypto");
var crypto_utils = require("../crypto.js");
var ECPair = require("../ecpair.js");
var ECSignature = require("../ecsignature.js");
/** @type {Object.<string, Network>} */
var networks = require("../networks.js")
var bs58check = require('bs58check')
if (typeof Buffer === "undefined") {
Buffer = require("buffer/").Buffer;
}
var ByteBuffer = require("bytebuffer");
var bignum = require("browserify-bignum");
var fixedPoint = Math.pow(10, 8);
// default is know mainnet
var networkVersion = 0x2d;
/**
* @static
* @param {*} obj
* @returns {boolean}
*/
function isECPair(obj) {
return obj instanceof ECPair;
}
/**
* @static
* @param {ECSignature} signature
* @returns {Uint8Array}
*/
function getSignatureBytes(signature) {
var bb = new ByteBuffer(33, true);
var publicKeyBuffer = new Buffer(signature.publicKey, "hex");
for (var i = 0; i < publicKeyBuffer.length; i++) {
bb.writeByte(publicKeyBuffer[i]);
}
bb.flip();
return new Uint8Array(bb.toArrayBuffer());
}
/**
* @static
* @param {Transaction} transaction
* @param {boolean} [skipSignature=false]
* @param {boolean} [skipSecondSignature=false]
* @returns {Buffer}
*/
function getBytes(transaction, skipSignature, skipSecondSignature) {
var assetSize = 0,
assetBytes = null;
switch (transaction.type) {
case 1: // Signature
assetBytes = getSignatureBytes(transaction.asset.signature);
assetSize = assetBytes.length;
break;
case 2: // Delegate
assetBytes = new Buffer(transaction.asset.delegate.username, "utf8");
assetSize = assetBytes.length;
break;
case 3: // Vote
if (transaction.asset.votes !== null) {
assetBytes = new Buffer(transaction.asset.votes.join(""), "utf8");
assetSize = assetBytes.length;
}
break;
case 4: // Multi-Signature
var keysgroupBuffer = new Buffer(transaction.asset.multisignature.keysgroup.join(""), "utf8");
var bb = new ByteBuffer(1 + 1 + keysgroupBuffer.length, true);
bb.writeByte(transaction.asset.multisignature.min);
bb.writeByte(transaction.asset.multisignature.lifetime);
for (var i = 0; i < keysgroupBuffer.length; i++) {
bb.writeByte(keysgroupBuffer[i]);
}
bb.flip();
assetBytes = bb.toBuffer();
assetSize = assetBytes.length;
break;
}
var bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 21 + 64 + 64 + 64 + assetSize, true);
bb.writeByte(transaction.type);
bb.writeInt(transaction.timestamp);
var senderPublicKeyBuffer = new Buffer(transaction.senderPublicKey, "hex");
for (var i = 0; i < senderPublicKeyBuffer.length; i++) {
bb.writeByte(senderPublicKeyBuffer[i]);
}
if (transaction.recipientId) {
var recipient = bs58check.decode(transaction.recipientId);
for (var i = 0; i < recipient.length; i++) {
bb.writeByte(recipient[i]);
}
} else {
for (var i = 0; i < 21; i++) {
bb.writeByte(0);
}
}
if(transaction.vendorFieldHex){
var vf = new Buffer(transaction.vendorFieldHex,"hex");
var fillstart=vf.length;
for (i = 0; i < fillstart; i++) {
bb.writeByte(vf[i]);
}
for (i = fillstart; i < 64; i++) {
bb.writeByte(0);
}
}
else if (transaction.vendorField) {
var vf = new Buffer(transaction.vendorField);
var fillstart=vf.length;
for (i = 0; i < fillstart; i++) {
bb.writeByte(vf[i]);
}
for (i = fillstart; i < 64; i++) {
bb.writeByte(0);
}
} else {
for (i = 0; i < 64; i++) {
bb.writeByte(0);
}
}
bb.writeLong(transaction.amount);
bb.writeLong(transaction.fee);
if (assetSize > 0) {
for (var i = 0; i < assetSize; i++) {
bb.writeByte(assetBytes[i]);
}
}
if (!skipSignature && transaction.signature) {
var signatureBuffer = new Buffer(transaction.signature, "hex");
for (var i = 0; i < signatureBuffer.length; i++) {
bb.writeByte(signatureBuffer[i]);
}
}
if (!skipSecondSignature && transaction.signSignature) {
var signSignatureBuffer = new Buffer(transaction.signSignature, "hex");
for (var i = 0; i < signSignatureBuffer.length; i++) {
bb.writeByte(signSignatureBuffer[i]);
}
}
bb.flip();
var arrayBuffer = new Uint8Array(bb.toArrayBuffer());
var buffer = [];
for (var i = 0; i < arrayBuffer.length; i++) {
buffer[i] = arrayBuffer[i];
}
return new Buffer(buffer);
}
/**
* @static
* @param {string} hexString
* @returns {Transaction}
*/
function fromBytes(hexString){
var tx={};
var buf = new Buffer(hexString, "hex");
tx.type = buf.readInt8(0) & 0xff;
tx.timestamp = buf.readUInt32LE(1);
tx.senderPublicKey = hexString.substring(10,10+33*2);
tx.amount = buf.readUInt32LE(38+21+64);
tx.fee = buf.readUInt32LE(38+21+64+8);
tx.vendorFieldHex = hexString.substring(76+42,76+42+128);
tx.recipientId = bs58check.encode(buf.slice(38,38+21));
if(tx.type == 0){ // transfer
parseSignatures(hexString, tx, 76+42+128+32);
}
else if(tx.type == 1){ // second signature registration
delete tx.recipientId;
tx.asset = {
signature : {
publicKey : hexString.substring(76+42+128+32,76+42+128+32+66)
}
}
parseSignatures(hexString, tx, 76+42+128+32+66);
}
else if(tx.type == 2){ // delegate registration
delete tx.recipientId;
// Impossible to assess size of delegate asset, trying to grab signature and derive delegate asset
var offset = findAndParseSignatures(hexString, tx);
tx.asset = {
delegate: {
username: new Buffer(hexString.substring(76+42+128+32,hexString.length-offset),"hex").toString("utf8")
}
};
}
else if(tx.type == 3){ // vote
// Impossible to assess size of vote asset, trying to grab signature and derive vote asset
var offset = findAndParseSignatures(hexString, tx);
tx.asset = {
votes: new Buffer(hexString.substring(76+42+128+32,hexString.length-offset),"hex").toString("utf8").split(",")
};
}
else if(tx.type == 4){ // multisignature creation
delete tx.recipientId;
var offset = findAndParseSignatures(hexString, tx);
var buffer = new Buffer(hexString.substring(76+42+128+32,hexString.length-offset),"hex")
tx.asset = {
multisignature: {}
}
tx.asset.multisignature.min = buffer.readInt8(0) & 0xff;
tx.asset.multisignature.lifetime = buffer.readInt8(1) & 0xff;
tx.asset.multisignature.keysgroup = [];
var index = 0;
while(index + 2 < buffer.length){
var key = buffer.slice(index+2,index+67+2).toString("utf8");
tx.asset.multisignature.keysgroup.push(key);
index = index + 67;
}
}
else if(tx.type == 5){ // ipfs
delete tx.recipientId;
parseSignatures(hexString, tx, 76+42+128+32);
}
console.log(tx);
return tx;
}
/**
* @static
* @param {string} hexString
* @param {Transaction} tx
* @returns {number}
*/
function findAndParseSignatures(hexString, tx){
var signature1 = new Buffer(hexString.substring(hexString.length-146), "hex");
var signature2 = null;
var found = false;
var offset = 0;
while(!found && signature1.length > 8){
if(signature1[0] != 0x30){
signature1 = signature1.slice(1);
}
else try {
ECSignature.fromDER(signature1,"hex");
found = true;
} catch(error){
signature1 = signature1.slice(1);
}
}
if(!found){
offset = 0;
signature1 = null;
}
else {
found = false;
offset = signature1.length*2;
var signature2 = new Buffer(hexString.substring(hexString.length-offset-146, hexString.length-offset), "hex");
while(!found && signature2.length > 8){
if(signature2[0] != 0x30){
signature2 = signature2.slice(1);
}
else try {
ECSignature.fromDER(signature2,"hex");
found = true;
} catch(error){
signature2 = signature2.slice(1);
}
}
if(!found){
signature2 = null;
tx.signature = signature1.toString("hex");
offset = tx.signature.length;
}
else if(signature2){
tx.signSignature = signature1.toString("hex");
tx.signature = signature2.toString("hex");
offset = tx.signature.length+tx.signSignature.length;
}
}
return offset;
}
/**
* @static
* @param {string} hexString
* @param {Transaction} tx
* @param {number} startOffset
*/
function parseSignatures(hexString, tx, startOffset){
tx.signature = hexString.substring(startOffset);
if(tx.signature.length == 0) delete tx.signature;
else {
var length = parseInt("0x" + tx.signature.substring(2,4), 16) + 2;
tx.signature = hexString.substring(startOffset, startOffset + length*2);
tx.signSignature = hexString.substring(startOffset + length*2);
if(tx.signSignature.length == 0) delete tx.signSignature;
}
}
/**
* @static
* @param {Transaction} transaction
* @returns {string}
*/
function getId(transaction) {
return crypto.createHash("sha256").update(getBytes(transaction)).digest().toString("hex");
}
/**
* @static
* @param {Transaction} transaction
* @param {boolean} [skipSignature=false]
* @param {boolean} [skipSecondSignature=false]
* @returns {Buffer}
*/
function getHash(transaction, skipSignature, skipSecondSignature) {
return crypto.createHash("sha256").update(getBytes(transaction, skipSignature, skipSecondSignature)).digest();
}
/**
* @static
* @param {Transaction} transaction
* @returns {number}
*/
function getFee(transaction) {
switch (transaction.type) {
case 0: // Normal
return 0.1 * fixedPoint;
break;
case 1: // Signature
return 100 * fixedPoint;
break;
case 2: // Delegate
return 10000 * fixedPoint;
break;
case 3: // Vote
return 1 * fixedPoint;
break;
}
}
/**
* @static
* @param {Transaction} transaction
* @param {ECPair} keys
* @returns {ECSignature}
*/
function sign(transaction, keys) {
var hash = getHash(transaction, true, true);
var signature = keys.sign(hash).toDER().toString("hex");
if (!transaction.signature) {
transaction.signature = signature;
}
return signature;
}
/**
* @static
* @param {Transaction} transaction
* @param {ECPair} keys
*/
function secondSign(transaction, keys) {
var hash = getHash(transaction, false, true);
var signature = keys.sign(hash).toDER().toString("hex");
if (!transaction.signSignature) {
transaction.signSignature = signature;
}
return signature;
}
/**
* @static
* @param {Transaction} transaction
* @param {Network} [network=networks.know]
*/
function verify(transaction, network) {
network = network || networks.know;
var hash = getHash(transaction, true, true);
var signatureBuffer = new Buffer(transaction.signature, "hex");
var senderPublicKeyBuffer = new Buffer(transaction.senderPublicKey, "hex");
var ecpair = ECPair.fromPublicKeyBuffer(senderPublicKeyBuffer, network);
var ecsignature = ECSignature.fromDER(signatureBuffer);
var res = ecpair.verify(hash, ecsignature);
return res;
}
/**
* @static
* @param {Transaction} transaction
* @param {string} publicKey
* @param {Network} [network]
*/
function verifySecondSignature(transaction, publicKey, network) {
network = network || networks.know;
var hash = getHash(transaction, false, true);
var signSignatureBuffer = new Buffer(transaction.signSignature, "hex");
var publicKeyBuffer = new Buffer(publicKey, "hex");
var ecpair = ECPair.fromPublicKeyBuffer(publicKeyBuffer, network);
var ecsignature = ECSignature.fromDER(signSignatureBuffer);
var res = ecpair.verify(hash, ecsignature);
return res;
}
/**
* @static
* @param {string} secret
* @param {Network} [network]
* @returns {ECPair}
*/
function getKeys(secret, network) {
var ecpair = ECPair.fromSeed(secret, network || networks.know);
ecpair.publicKey = ecpair.getPublicKeyBuffer().toString("hex");
ecpair.privateKey = '';
return ecpair;
}
/**
* @static
* @param {string} publicKey
* @param {number} [version]
* @returns {string}
*/
function getAddress(publicKey, version){
if(!version){
version = networkVersion;
}
var buffer = crypto_utils.ripemd160(new Buffer(publicKey,'hex'));
var payload = new Buffer(21);
payload.writeUInt8(version, 0);
buffer.copy(payload, 1);
return bs58check.encode(payload);
}
/**
* @static
* @param {number} version
*/
function setNetworkVersion(version){
networkVersion = version;
}
/**
* @static
* @returns {number}
*/
function getNetworkVersion(){
return networkVersion;
}
/**
* @static
* @param {string} address
* @param {number} [version]
* @returns {boolean}
*/
function validateAddress(address, version){
if(!version){
version = networkVersion;
}
try {
var decode = bs58check.decode(address);
return decode[0] == version;
} catch(e){
return false;
}
}
module.exports = {
getBytes: getBytes,
fromBytes: fromBytes,
getHash: getHash,
getId: getId,
getFee: getFee,
sign: sign,
secondSign: secondSign,
getKeys: getKeys,
getAddress: getAddress,
validateAddress: validateAddress,
verify: verify,
verifySecondSignature: verifySecondSignature,
fixedPoint: fixedPoint,
setNetworkVersion: setNetworkVersion,
getNetworkVersion: getNetworkVersion,
isECPair: isECPair
}