606 lines
20 KiB
JavaScript
606 lines
20 KiB
JavaScript
|
/********************************************************************************
|
||
|
* Ledger Communication toolkit
|
||
|
* (c) 2016 Ledger
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
********************************************************************************/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var Ledger3 = require('./ledger3q');
|
||
|
|
||
|
var LedgerBtc = function(timeout) {
|
||
|
this.comm = new Ledger3("BTC", timeout);
|
||
|
}
|
||
|
|
||
|
LedgerBtc.splitPath = function(path) {
|
||
|
var result = [];
|
||
|
var components = path.split('/');
|
||
|
components.forEach(function (element, index) {
|
||
|
var number = parseInt(element, 10);
|
||
|
if (isNaN(number)) {
|
||
|
return;
|
||
|
}
|
||
|
if ((element.length > 1) && (element[element.length - 1] == "'")) {
|
||
|
number += 0x80000000;
|
||
|
}
|
||
|
result.push(number);
|
||
|
});
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.getWalletPublicKey_async = function(path) {
|
||
|
var splitPath = LedgerBtc.splitPath(path);
|
||
|
var buffer = Buffer.alloc(5 + 1 + splitPath.length * 4);
|
||
|
buffer[0] = 0xe0;
|
||
|
buffer[1] = 0x40;
|
||
|
buffer[2] = 0x00;
|
||
|
buffer[3] = 0x00;
|
||
|
buffer[4] = 1 + splitPath.length * 4;
|
||
|
buffer[5] = splitPath.length;
|
||
|
splitPath.forEach(function (element, index) {
|
||
|
buffer.writeUInt32BE(element, 6 + 4 * index);
|
||
|
});
|
||
|
var self = this;
|
||
|
return this.comm.exchange(buffer.toString('hex'), [0x9000]).then(function(response) {
|
||
|
var result = {};
|
||
|
response = Buffer.from(response, 'hex');
|
||
|
var publicKeyLength = response[0];
|
||
|
var addressLength = response[1 + publicKeyLength];
|
||
|
result['publicKey'] = response.slice(1, 1 + publicKeyLength).toString('hex');
|
||
|
result['bitcoinAddress'] = response.slice(1 + publicKeyLength + 1, 1 + publicKeyLength + 1 + addressLength).toString('ascii');
|
||
|
result['chainCode'] = response.slice(1 + publicKeyLength + 1 + addressLength, 1 + publicKeyLength + 1 + addressLength + 32).toString('hex');
|
||
|
return result;
|
||
|
|
||
|
});
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.getTrustedInputRaw_async = function(firstRound, indexLookup, transactionData) {
|
||
|
var data;
|
||
|
if (firstRound) {
|
||
|
var prefix = Buffer.alloc(4);
|
||
|
prefix.writeUInt32BE(indexLookup, 0);
|
||
|
data = Buffer.concat([prefix, transactionData], transactionData.length + 4);
|
||
|
}
|
||
|
else {
|
||
|
data = transactionData;
|
||
|
}
|
||
|
var buffer = Buffer.alloc(5);
|
||
|
buffer[0] = 0xe0;
|
||
|
buffer[1] = 0x42;
|
||
|
buffer[2] = (firstRound ? 0x00 : 0x80);
|
||
|
buffer[3] = 0x00;
|
||
|
buffer[4] = data.length;
|
||
|
buffer = Buffer.concat([buffer, data], 5 + data.length);
|
||
|
return this.comm.exchange(buffer.toString('hex'), [0x9000]).then(function(trustedInput) {
|
||
|
return trustedInput.substring(0, trustedInput.length - 4);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.getTrustedInput_async = function(indexLookup, transaction) {
|
||
|
var currentObject = this;
|
||
|
var deferred = Q.defer();
|
||
|
var processScriptBlocks = function(script, sequence) {
|
||
|
var internalPromise = Q.defer();
|
||
|
var scriptBlocks = [];
|
||
|
var offset = 0;
|
||
|
while (offset != script.length) {
|
||
|
var blockSize = (script.length - offset > LedgerBtc.MAX_SCRIPT_BLOCK ?
|
||
|
LedgerBtc.MAX_SCRIPT_BLOCK : script.length - offset);
|
||
|
if ((offset + blockSize) != script.length) {
|
||
|
scriptBlocks.push(script.slice(offset, offset + blockSize));
|
||
|
}
|
||
|
else {
|
||
|
scriptBlocks.push(Buffer.concat([script.slice(offset, offset + blockSize), sequence]));
|
||
|
}
|
||
|
offset += blockSize;
|
||
|
}
|
||
|
async.eachSeries(
|
||
|
scriptBlocks,
|
||
|
function(scriptBlock, finishedCallback) {
|
||
|
currentObject.getTrustedInputRaw_async(false, undefined, scriptBlock).then(function (result) {
|
||
|
finishedCallback();
|
||
|
}).fail(function (err) { internalPromise.reject(err); });
|
||
|
},
|
||
|
function(finished) {
|
||
|
internalPromise.resolve();
|
||
|
}
|
||
|
);
|
||
|
return internalPromise.promise;
|
||
|
}
|
||
|
var processInputs = function() {
|
||
|
async.eachSeries(
|
||
|
transaction['inputs'],
|
||
|
function (input, finishedCallback) {
|
||
|
data = Buffer.concat([input['prevout'], currentObject.createVarint(input['script'].length)]);
|
||
|
currentObject.getTrustedInputRaw_async(false, undefined, data).then(function (result) {
|
||
|
// iteration (eachSeries) ended
|
||
|
// TODO notify progress
|
||
|
// deferred.notify("input");
|
||
|
processScriptBlocks(input['script'], input['sequence']).then(function (result) {
|
||
|
finishedCallback();
|
||
|
}).fail(function(err) { deferred.reject(err); });
|
||
|
}).fail(function (err) { deferred.reject(err); });
|
||
|
},
|
||
|
function(finished) {
|
||
|
data = currentObject.createVarint(transaction['outputs'].length);
|
||
|
currentObject.getTrustedInputRaw_async(false, undefined, data).then(function (result) {
|
||
|
processOutputs();
|
||
|
}).fail(function (err) { deferred.reject(err); });
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
var processOutputs = function() {
|
||
|
async.eachSeries(
|
||
|
transaction['outputs'],
|
||
|
function(output, finishedCallback) {
|
||
|
data = output['amount'];
|
||
|
data = Buffer.concat([data, currentObject.createVarint(output['script'].length), output['script']]);
|
||
|
currentObject.getTrustedInputRaw_async(false, undefined, data).then(function(result) {
|
||
|
// iteration (eachSeries) ended
|
||
|
// TODO notify progress
|
||
|
// deferred.notify("output");
|
||
|
finishedCallback();
|
||
|
}).fail(function (err) { deferred.reject(err); });
|
||
|
},
|
||
|
function(finished) {
|
||
|
data = transaction['locktime'];
|
||
|
currentObject.getTrustedInputRaw_async(false, undefined, data).then (function(result) {
|
||
|
deferred.resolve(result);
|
||
|
}).fail(function (err) { deferred.reject(err); });
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
var data = Buffer.concat([transaction['version'], currentObject.createVarint(transaction['inputs'].length)]);
|
||
|
currentObject.getTrustedInputRaw_async(true, indexLookup, data).then(function (result) {
|
||
|
processInputs();
|
||
|
}).fail(function (err) { deferred.reject(err); });
|
||
|
// return the promise to be resolve when the trusted input has been processed completely
|
||
|
return deferred.promise;
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.getVarint = function(data, offset) {
|
||
|
if (data[offset] < 0xfd) {
|
||
|
return [ data[offset], 1 ];
|
||
|
}
|
||
|
if (data[offset] == 0xfd) {
|
||
|
return [ ((data[offset + 2] << 8) + data[offset + 1]), 3 ];
|
||
|
}
|
||
|
if (data[offset] == 0xfe) {
|
||
|
return [ ((data[offset + 4] << 24) + (data[offset + 3] << 16) +
|
||
|
(data[offset + 2] << 8) + data[offset + 1]), 5 ];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.startUntrustedHashTransactionInputRaw_async = function (newTransaction, firstRound, transactionData) {
|
||
|
var buffer = Buffer.alloc(5);
|
||
|
buffer[0] = 0xe0;
|
||
|
buffer[1] = 0x44;
|
||
|
buffer[2] = (firstRound ? 0x00 : 0x80);
|
||
|
buffer[3] = (newTransaction ? 0x00 : 0x80);
|
||
|
buffer[4] = transactionData.length;
|
||
|
buffer = Buffer.concat([buffer, transactionData], 5 + transactionData.length);
|
||
|
return this.comm.exchange(buffer.toString('hex'), [0x9000]);
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.startUntrustedHashTransactionInput_async = function (newTransaction, transaction, trustedInputs) {
|
||
|
var currentObject = this;
|
||
|
var data = Buffer.concat([transaction['version'],
|
||
|
currentObject.createVarint(transaction['inputs'].length)]);
|
||
|
var deferred = Q.defer();
|
||
|
currentObject.startUntrustedHashTransactionInputRaw_async(newTransaction, true, data).then(function (result) {
|
||
|
var i = 0;
|
||
|
async.eachSeries(
|
||
|
transaction['inputs'],
|
||
|
function (input, finishedCallback) {
|
||
|
var inputKey;
|
||
|
// TODO : segwit
|
||
|
var prefix = Buffer.alloc(2);
|
||
|
prefix[0] = 0x01;
|
||
|
prefix[1] = trustedInputs[i].length;
|
||
|
data = Buffer.concat([prefix, trustedInputs[i], currentObject.createVarint(input['script'].length)]);
|
||
|
currentObject.startUntrustedHashTransactionInputRaw_async(newTransaction, false, data).then(function (result) {
|
||
|
data = Buffer.concat([input['script'], input['sequence']]);
|
||
|
currentObject.startUntrustedHashTransactionInputRaw_async(newTransaction, false, data).then(function (result) {
|
||
|
// TODO notify progress
|
||
|
i++;
|
||
|
finishedCallback();
|
||
|
}).fail(function (err) {
|
||
|
deferred.reject(err);
|
||
|
});
|
||
|
}).fail(function (err) {
|
||
|
deferred.reject(err);
|
||
|
});
|
||
|
},
|
||
|
function (finished) {
|
||
|
deferred.resolve(finished);
|
||
|
}
|
||
|
)
|
||
|
}).fail(function (err) {
|
||
|
deferred.reject(err);
|
||
|
});
|
||
|
// return the notified object at end of the loop
|
||
|
return deferred.promise;
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.provideOutputFullChangePath_async = function(path) {
|
||
|
var splitPath = LedgerBtc.splitPath(path);
|
||
|
var buffer = Buffer.alloc(5 + 1 + splitPath.length * 4);
|
||
|
buffer[0] = 0xe0;
|
||
|
buffer[1] = 0x4a;
|
||
|
buffer[2] = 0xff;
|
||
|
buffer[3] = 0x00;
|
||
|
buffer[4] = 1 + splitPath.length * 4;
|
||
|
buffer[5] = splitPath.length;
|
||
|
splitPath.forEach(function (element, index) {
|
||
|
buffer.writeUInt32BE(element, 6 + 4 * index);
|
||
|
});
|
||
|
var self = this;
|
||
|
return this.comm.exchange(buffer.toString('hex'), [0x9000]);
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.hashOutputFull_async = function(outputScript) {
|
||
|
var offset = 0;
|
||
|
var self = this;
|
||
|
|
||
|
return asyncWhile(function () {return offset < outputScript.length;}, function () {
|
||
|
var blockSize = ((offset + LedgerBtc.MAX_SCRIPT_BLOCK) >= outputScript.length ? outputScript.length - offset : LedgerBtc.MAX_SCRIPT_BLOCK);
|
||
|
var p1 = ((offset + blockSize) == outputScript.length ? 0x80 : 0x00);
|
||
|
var prefix = Buffer.alloc(5);
|
||
|
prefix[0] = 0xe0;
|
||
|
prefix[1] = 0x4a;
|
||
|
prefix[2] = p1;
|
||
|
prefix[3] = 0x00;
|
||
|
prefix[4] = blockSize;
|
||
|
var data = Buffer.concat([prefix, outputScript.slice(offset, offset + blockSize)]);
|
||
|
return self.comm.exchange(data.toString('hex'), [0x9000]).then(function(data) {
|
||
|
offset += blockSize;
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Library
|
||
|
function asyncWhile(condition, callback) {
|
||
|
var deferred = Q.defer();
|
||
|
var iterate = function (result) {
|
||
|
if (!condition()) {
|
||
|
deferred.resolve(result);
|
||
|
return ;
|
||
|
}
|
||
|
callback().then(function (res) {
|
||
|
result.push(res);
|
||
|
iterate(result);
|
||
|
}).fail(function (ex) {
|
||
|
deferred.reject(ex);
|
||
|
}).done();
|
||
|
};
|
||
|
iterate([]);
|
||
|
return deferred.promise;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
LedgerBtc.prototype.signTransaction_async = function (path, lockTime, sigHashType) {
|
||
|
if (typeof lockTime == "undefined") {
|
||
|
lockTime = LedgerBtc.DEFAULT_LOCKTIME;
|
||
|
}
|
||
|
if (typeof sigHashType == "undefined") {
|
||
|
sigHashType = LedgerBtc.SIGHASH_ALL;
|
||
|
}
|
||
|
var splitPath = LedgerBtc.splitPath(path);
|
||
|
var buffer = Buffer.alloc(5 + 1 + splitPath.length * 4 + 1 + 4 + 1);
|
||
|
var offset = 0;
|
||
|
buffer[offset++] = 0xe0;
|
||
|
buffer[offset++] = 0x48;
|
||
|
buffer[offset++] = 0x00;
|
||
|
buffer[offset++] = 0x00;
|
||
|
buffer[offset++] = 1 + splitPath.length * 4 + 1 + 4 + 1;
|
||
|
buffer[offset++] = splitPath.length;
|
||
|
splitPath.forEach(function (element) {
|
||
|
buffer.writeUInt32BE(element, offset);
|
||
|
offset += 4;
|
||
|
});
|
||
|
buffer[offset++] = 0x00; // authorization length
|
||
|
buffer.writeUInt32LE(lockTime, offset);
|
||
|
offset += 4;
|
||
|
buffer[offset++] = sigHashType;
|
||
|
var self = this;
|
||
|
return self.comm.exchange(buffer.toString('hex'), [0x9000]).then(function(signature) {
|
||
|
var result = Buffer.from(signature, 'hex');
|
||
|
result[0] = 0x30;
|
||
|
return result.slice(0, result.length - 2);
|
||
|
})
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associatedKeysets, changePath, outputScript, lockTime, sigHashType) {
|
||
|
// Inputs are provided as arrays of [transaction, output_index, optional redeem script, optional sequence]
|
||
|
// associatedKeysets are provided as arrays of [path]
|
||
|
var nullScript = Buffer.alloc(0);
|
||
|
var defaultVersion = Buffer.alloc(4);
|
||
|
defaultVersion.writeUInt32LE(1, 0);
|
||
|
var trustedInputs = [];
|
||
|
var regularOutputs = [];
|
||
|
var signatures = [];
|
||
|
var publicKeys = [];
|
||
|
var firstRun = true;
|
||
|
var resuming = false;
|
||
|
var self = this;
|
||
|
var targetTransaction = {};
|
||
|
|
||
|
outputScript = Buffer.from(outputScript, 'hex');
|
||
|
|
||
|
if (typeof lockTime == "undefined") {
|
||
|
lockTime = LedgerBtc.DEFAULT_LOCKTIME;
|
||
|
}
|
||
|
if (typeof sigHashType == "undefined") {
|
||
|
sigHashType = LedgerBtc.SIGHASH_ALL;
|
||
|
}
|
||
|
|
||
|
var deferred = Q.defer();
|
||
|
|
||
|
foreach(inputs, function (input, i) {
|
||
|
return doIf(!resuming, function () {
|
||
|
return self.getTrustedInput_async(input[1], input[0])
|
||
|
.then(function (trustedInput) {
|
||
|
trustedInputs.push(Buffer.from(trustedInput, 'hex'));
|
||
|
});
|
||
|
}).then(function () {
|
||
|
regularOutputs.push(input[0].outputs[input[1]]);
|
||
|
});
|
||
|
}).then(function () {
|
||
|
// Pre-build the target transaction
|
||
|
targetTransaction['version'] = defaultVersion;
|
||
|
targetTransaction['inputs'] = [];
|
||
|
|
||
|
for (var i = 0; i < inputs.length; i++) {
|
||
|
var tmpInput = {};
|
||
|
var tmp = Buffer.alloc(4);
|
||
|
var sequence;
|
||
|
if ((inputs[i].length >= 4) && (typeof inputs[i][3] != "undefined")) {
|
||
|
sequence = inputs[i][3];
|
||
|
}
|
||
|
else {
|
||
|
sequence = LedgerBtc.DEFAULT_SEQUENCE;
|
||
|
}
|
||
|
tmp.writeUInt32LE(sequence, 0);
|
||
|
tmpInput['script'] = nullScript;
|
||
|
tmpInput['sequence'] = tmp;
|
||
|
targetTransaction['inputs'].push(tmpInput);
|
||
|
}
|
||
|
}).then(function () {
|
||
|
return doIf(!resuming, function () {
|
||
|
// Collect public keys
|
||
|
return foreach(inputs, function (input, i) {
|
||
|
return self.getWalletPublicKey_async(associatedKeysets[i]).then(function (p) {
|
||
|
return p;
|
||
|
});
|
||
|
}).then(function (result) {
|
||
|
for (var index = 0; index < result.length; index++) {
|
||
|
publicKeys.push(self.compressPublicKey(Buffer.from(result[index]['publicKey'], 'hex')));
|
||
|
}
|
||
|
});
|
||
|
})
|
||
|
}).then(function () {
|
||
|
return foreach(inputs, function (input, i) {
|
||
|
var usedScript;
|
||
|
if ((inputs[i].length >= 3) && (typeof inputs[i][2] != "undefined")) {
|
||
|
usedScript = inputs[i][2];
|
||
|
}
|
||
|
else {
|
||
|
usedScript = regularOutputs[i]['script'];
|
||
|
}
|
||
|
targetTransaction['inputs'][i]['script'] = usedScript;
|
||
|
return self.startUntrustedHashTransactionInput_async(firstRun, targetTransaction, trustedInputs).then(function () {
|
||
|
return doIf(!resuming && (typeof changePath != "undefined"), function () {
|
||
|
return self.provideOutputFullChangePath_async(changePath);
|
||
|
}).then (function () {
|
||
|
return self.hashOutputFull_async(outputScript);
|
||
|
}).then (function (resultHash) {
|
||
|
return self.signTransaction_async(associatedKeysets[i], lockTime, sigHashType).then(function (signature) {
|
||
|
signatures.push(signature);
|
||
|
targetTransaction['inputs'][i]['script'] = nullScript;
|
||
|
if (firstRun) {
|
||
|
firstRun = false;
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
}).then(function () {
|
||
|
// Populate the final input scripts
|
||
|
for (var i=0; i < inputs.length; i++) {
|
||
|
var signatureSize = Buffer.alloc(1);
|
||
|
var keySize = Buffer.alloc(1);
|
||
|
signatureSize[0] = signatures[i].length;
|
||
|
keySize[0] = publicKeys[i].length;
|
||
|
targetTransaction['inputs'][i]['script'] = Buffer.concat([signatureSize, signatures[i], keySize, publicKeys[i]]);
|
||
|
targetTransaction['inputs'][i]['prevout'] = trustedInputs[i].slice(4, 4 + 0x24);
|
||
|
}
|
||
|
|
||
|
var lockTimeBuffer = Buffer.alloc(4);
|
||
|
lockTimeBuffer.writeUInt32LE(lockTime, 0);
|
||
|
|
||
|
var result = Buffer.concat([
|
||
|
self.serializeTransaction(targetTransaction),
|
||
|
outputScript,
|
||
|
lockTimeBuffer]);
|
||
|
|
||
|
return result.toString('hex');
|
||
|
}).fail(function (failure) {
|
||
|
throw failure;
|
||
|
}).then(function (result) {
|
||
|
deferred.resolve(result);
|
||
|
}).fail(function (error) {
|
||
|
deferred.reject(error);
|
||
|
});
|
||
|
|
||
|
return deferred.promise;
|
||
|
|
||
|
// Library
|
||
|
function foreach(arr, callback) {
|
||
|
var deferred = Q.defer();
|
||
|
var iterate = function (index, array, result) {
|
||
|
if (index >= array.length) {
|
||
|
deferred.resolve(result);
|
||
|
return ;
|
||
|
}
|
||
|
callback(array[index], index).then(function (res) {
|
||
|
result.push(res);
|
||
|
iterate(index + 1, array, result);
|
||
|
}).fail(function (ex) {
|
||
|
deferred.reject(ex);
|
||
|
}).done();
|
||
|
};
|
||
|
iterate(0, arr, []);
|
||
|
return deferred.promise;
|
||
|
}
|
||
|
|
||
|
function doIf(condition, callback) {
|
||
|
var deferred = Q.defer();
|
||
|
if (condition) {
|
||
|
deferred.resolve(callback())
|
||
|
} else {
|
||
|
deferred.resolve();
|
||
|
}
|
||
|
return deferred.promise;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// startP2SHUntrustedHashTransactionInput_async
|
||
|
//
|
||
|
|
||
|
|
||
|
LedgerBtc.prototype.compressPublicKey = function (publicKey) {
|
||
|
var prefix = ((publicKey[64] & 1) != 0 ? 0x03 : 0x02);
|
||
|
var prefixBuffer = Buffer.alloc(1);
|
||
|
prefixBuffer[0] = prefix;
|
||
|
return Buffer.concat([prefixBuffer, publicKey.slice(1, 1 + 32)]);
|
||
|
},
|
||
|
|
||
|
LedgerBtc.prototype.createVarint = function(value) {
|
||
|
if (value < 0xfd) {
|
||
|
var buffer = Buffer.alloc(1);
|
||
|
buffer[0] = value;
|
||
|
return buffer;
|
||
|
}
|
||
|
if (value <= 0xffff) {
|
||
|
var buffer = Buffer.alloc(3);
|
||
|
buffer[0] = 0xfd;
|
||
|
buffer[1] = (value & 0xff);
|
||
|
buffer[2] = ((value >> 8) & 0xff);
|
||
|
return buffer;
|
||
|
}
|
||
|
var buffer = Buffer.alloc(4);
|
||
|
buffer[0] = 0xfe;
|
||
|
buffer[1] = (value & 0xff);
|
||
|
buffer[2] = ((value >> 8) & 0xff);
|
||
|
buffer[3] = ((value >> 16) & 0xff);
|
||
|
buffer[4] = ((value >> 24) & 0xff);
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.splitTransaction = function(transaction) {
|
||
|
var result = {};
|
||
|
var inputs = [];
|
||
|
var outputs = [];
|
||
|
var offset = 0;
|
||
|
var transaction = Buffer.from(transaction, 'hex');
|
||
|
var version = transaction.slice(offset, offset + 4);
|
||
|
offset += 4;
|
||
|
var varint = this.getVarint(transaction, offset);
|
||
|
var numberInputs = varint[0];
|
||
|
offset += varint[1];
|
||
|
for (var i=0; i<numberInputs; i++) {
|
||
|
var input = {};
|
||
|
input['prevout'] = transaction.slice(offset, offset + 36);
|
||
|
offset += 36;
|
||
|
varint = this.getVarint(transaction, offset);
|
||
|
offset += varint[1];
|
||
|
input['script'] = transaction.slice(offset, offset + varint[0]);
|
||
|
offset += varint[0];
|
||
|
input['sequence'] = transaction.slice(offset, offset + 4);
|
||
|
offset += 4;
|
||
|
inputs.push(input);
|
||
|
}
|
||
|
varint = this.getVarint(transaction, offset);
|
||
|
var numberOutputs = varint[0];
|
||
|
offset += varint[1];
|
||
|
for (var i=0; i<numberOutputs; i++) {
|
||
|
var output = {};
|
||
|
output['amount'] = transaction.slice(offset, offset + 8);
|
||
|
offset += 8;
|
||
|
varint = this.getVarint(transaction, offset);
|
||
|
offset += varint[1];
|
||
|
output['script'] = transaction.slice(offset, offset + varint[0]);
|
||
|
offset += varint[0];
|
||
|
outputs.push(output);
|
||
|
}
|
||
|
var locktime = transaction.slice(offset, offset + 4);
|
||
|
result['version'] = version;
|
||
|
result['inputs'] = inputs;
|
||
|
result['outputs'] = outputs;
|
||
|
result['locktime'] = locktime;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.serializeTransaction = function (transaction) {
|
||
|
var self = this;
|
||
|
var inputBuffer = Buffer.alloc(0);
|
||
|
transaction['inputs'].forEach(function (input) {
|
||
|
inputBuffer = Buffer.concat([inputBuffer,
|
||
|
input['prevout'],
|
||
|
self.createVarint(input['script'].length),
|
||
|
input['script'],
|
||
|
input['sequence']
|
||
|
]);
|
||
|
});
|
||
|
|
||
|
var outputBuffer = Buffer.alloc(0);
|
||
|
if (typeof transaction['outputs'] != "undefined") {
|
||
|
outputBuffer = Buffer.concat([outputBuffer, self.createVarint(transaction['outputs'].length)]);
|
||
|
transaction['outputs'].forEach(function (output) {
|
||
|
outputBuffer = Buffer.concat([outputBuffer,
|
||
|
output['amount'],
|
||
|
self.createVarint(output['script'].length),
|
||
|
output['script']]);
|
||
|
});
|
||
|
outputBuffer = Buffer.concat([outputBuffer, transaction['locktime']]);
|
||
|
}
|
||
|
|
||
|
return Buffer.concat([
|
||
|
transaction['version'],
|
||
|
self.createVarint(transaction['inputs'].length),
|
||
|
inputBuffer,
|
||
|
outputBuffer]);
|
||
|
}
|
||
|
|
||
|
LedgerBtc.prototype.displayTransactionDebug = function(transaction) {
|
||
|
console.log("version " + transaction['version'].toString('hex'));
|
||
|
for (var i=0; i<transaction['inputs'].length; i++) {
|
||
|
var input = transaction['inputs'][i];
|
||
|
console.log("input " + i + " prevout " + input['prevout'].toString('hex') + " script " + input['script'].toString('hex') + " sequence " + input['sequence'].toString('hex'));
|
||
|
}
|
||
|
for (var i=0; i<transaction['outputs'].length; i++) {
|
||
|
var output = transaction['outputs'][i];
|
||
|
console.log("output " + i + " amount " + output['amount'].toString('hex') + " script " + output['script'].toString('hex'));
|
||
|
}
|
||
|
console.log("locktime " + transaction['locktime'].toString('hex'));
|
||
|
}
|
||
|
|
||
|
|
||
|
LedgerBtc.MAX_SCRIPT_BLOCK = 50;
|
||
|
LedgerBtc.DEFAULT_LOCKTIME = 0;
|
||
|
LedgerBtc.DEFAULT_SEQUENCE = 0xffffffff;
|
||
|
LedgerBtc.SIGHASH_ALL = 1;
|
||
|
|
||
|
module.exports = LedgerBtc;
|
||
|
|