Add U2F proxy client API for P2SH inputs

This commit is contained in:
BTChip 2016-12-13 18:39:23 +01:00
parent 4114fbccec
commit f9d02783d5
3 changed files with 453 additions and 182 deletions

View File

@ -40,12 +40,12 @@ var tx1 = dongle.splitTransaction("01000000014ea60aeac5252c14291d428915bd7ccd1bf
var tx2 = dongle.splitTransaction("...")
```
Call createPaymentTransactionNew_async with the folowing parameters
To sign a transaction involving standard (P2PKH) inputs, call createPaymentTransactionNew_async with the folowing parameters
- `inputs` is an array of [ transaction, output_index, optional redeem script, optional sequence ] where
- transaction is the previously computed transaction object for this UTXO
- output_index is the output in the transaction used as input for this UTXO (counting from 0)
- redeem script is the redeem script to use when consuming a P2SH input, or non present when consuming a P2PKH input
- redeem script is the optional redeem script to use when consuming a Segregated Witness input
- sequence is the sequence number to use for this input (when using RBF), or non present
- `associatedKeysets` is an array of BIP 32 paths pointing to the path to the private key used for each UTXO
- `changePath` is an optional BIP 32 path pointing to the path to the public key used to compute the change address
@ -66,3 +66,26 @@ dongle.createPaymentTransactionNew_async(
);
```
To obtain the signature of multisignature (P2SH) inputs, call signP2SHTransaction_async with the folowing parameters
- `inputs` is an array of [ transaction, output_index, redeem script, optional sequence ] where
- transaction is the previously computed transaction object for this UTXO
- output_index is the output in the transaction used as input for this UTXO (counting from 0)
- redeem script is the mandatory redeem script associated to the current P2SH input
- sequence is the sequence number to use for this input (when using RBF), or non present
- `associatedKeysets` is an array of BIP 32 paths pointing to the path to the private key used for each UTXO
- `outputScript` is the hexadecimal serialized outputs of the transaction to sign
- `lockTime` is the optional lockTime of the transaction to sign, or default (0)
- `sigHashType` is the hash type of the transaction to sign, or default (all)
This method returns the signed transaction ready to be broadcast
```javascript
dongle.signP2SHTransaction_async(
[ [tx, 1, "52210289b4a3ad52a919abd2bdd6920d8a6879b1e788c38aa76f0440a6f32a9f1996d02103a3393b1439d1693b063482c04bd40142db97bdf139eedd1b51ffb7070a37eac321030b9a409a1e476b0d5d17b804fcdb81cf30f9b99c6f3ae1178206e08bc500639853ae"] ],
["0'/0/0"],
"01905f0100000000001976a91472a5d75c8d2d0565b656a5232703b167d50d5a2b88ac").then(
function(result) { console.log(result);}).fail(
function(error) { console.log(error); });
);
```

304
js/dist/ledger-btc.js vendored
View File

@ -25,6 +25,8 @@ var LedgerBtc = function(timeout) {
this.comm = new Ledger3("BTC", timeout);
}
// Libraries
LedgerBtc.splitPath = function(path) {
var result = [];
var components = path.split('/');
@ -41,6 +43,53 @@ LedgerBtc.splitPath = function(path) {
return result;
}
LedgerBtc.foreach = function (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;
}
LedgerBtc.doIf = function(condition, callback) {
var deferred = Q.defer();
if (condition) {
deferred.resolve(callback())
} else {
deferred.resolve();
}
return deferred.promise;
}
LedgerBtc.asyncWhile = function(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.getWalletPublicKey_async = function(path) {
var splitPath = LedgerBtc.splitPath(path);
var buffer = Buffer.alloc(5 + 1 + splitPath.length * 4);
@ -195,10 +244,10 @@ LedgerBtc.prototype.startUntrustedHashTransactionInputRaw_async = function (newT
return this.comm.exchange(buffer.toString('hex'), [0x9000]);
}
LedgerBtc.prototype.startUntrustedHashTransactionInput_async = function (newTransaction, transaction, trustedInputs) {
LedgerBtc.prototype.startUntrustedHashTransactionInput_async = function (newTransaction, transaction, inputs) {
var currentObject = this;
var data = Buffer.concat([transaction['version'],
currentObject.createVarint(transaction['inputs'].length)]);
currentObject.createVarint(transaction['inputs'].length)]);
var deferred = Q.defer();
currentObject.startUntrustedHashTransactionInputRaw_async(newTransaction, true, data).then(function (result) {
var i = 0;
@ -207,19 +256,48 @@ LedgerBtc.prototype.startUntrustedHashTransactionInput_async = function (newTran
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)]);
var prefix;
if (inputs[i]['trustedInput']) {
prefix = Buffer.alloc(2);
prefix[0] = 0x01;
prefix[1] = inputs[i]['value'].length;
}
else {
prefix = Buffer.alloc(1);
prefix[0] = 0x00;
}
data = Buffer.concat([prefix, inputs[i]['value'], 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);
});
var scriptBlocks = [];
var offset = 0;
if (input['script'].length == 0) {
scriptBlocks.push(input['sequence']);
}
else {
while (offset != input['script'].length) {
var blockSize = (input['script'].length - offset > LedgerBtc.MAX_SCRIPT_BLOCK ?
LedgerBtc.MAX_SCRIPT_BLOCK : input['script'].length - offset);
if ((offset + blockSize) != input['script'].length) {
scriptBlocks.push(input['script'].slice(offset, offset + blockSize));
}
else {
scriptBlocks.push(Buffer.concat([input['script'].slice(offset, offset + blockSize), input['sequence']]));
}
offset += blockSize;
}
}
async.eachSeries(
scriptBlocks,
function(scriptBlock, blockFinishedCallback) {
currentObject.startUntrustedHashTransactionInputRaw_async(newTransaction, false, scriptBlock).then(function (result) {
blockFinishedCallback();
}).fail(function (err) { deferred.reject(err); });
},
function(finished) {
finishedCallback();
}
);
}).fail(function (err) {
deferred.reject(err);
});
@ -252,43 +330,24 @@ LedgerBtc.prototype.provideOutputFullChangePath_async = function(path) {
}
LedgerBtc.prototype.hashOutputFull_async = function(outputScript) {
var offset = 0;
var self = this;
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;
}
},
return LedgerBtc.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;
});
});
}
LedgerBtc.prototype.signTransaction_async = function (path, lockTime, sigHashType) {
if (typeof lockTime == "undefined") {
@ -348,11 +407,14 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
var deferred = Q.defer();
foreach(inputs, function (input, i) {
return doIf(!resuming, function () {
LedgerBtc.foreach(inputs, function (input, i) {
return LedgerBtc.doIf(!resuming, function () {
return self.getTrustedInput_async(input[1], input[0])
.then(function (trustedInput) {
trustedInputs.push(Buffer.from(trustedInput, 'hex'));
var inputItem = {};
inputItem['trustedInput'] = true;
inputItem['value'] = Buffer.from(trustedInput, 'hex');
trustedInputs.push(inputItem);
});
}).then(function () {
regularOutputs.push(input[0].outputs[input[1]]);
@ -378,9 +440,9 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
targetTransaction['inputs'].push(tmpInput);
}
}).then(function () {
return doIf(!resuming, function () {
return LedgerBtc.doIf(!resuming, function () {
// Collect public keys
return foreach(inputs, function (input, i) {
return LedgerBtc.foreach(inputs, function (input, i) {
return self.getWalletPublicKey_async(associatedKeysets[i]).then(function (p) {
return p;
});
@ -391,17 +453,17 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
});
})
}).then(function () {
return foreach(inputs, function (input, i) {
return LedgerBtc.foreach(inputs, function (input, i) {
var usedScript;
if ((inputs[i].length >= 3) && (typeof inputs[i][2] != "undefined")) {
usedScript = inputs[i][2];
usedScript = Buffer.from(inputs[i][2], 'hex');
}
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 LedgerBtc.doIf(!resuming && (typeof changePath != "undefined"), function () {
return self.provideOutputFullChangePath_async(changePath);
}).then (function () {
return self.hashOutputFull_async(outputScript);
@ -424,7 +486,7 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
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);
targetTransaction['inputs'][i]['prevout'] = trustedInputs[i]['value'].slice(4, 4 + 0x24);
}
var lockTimeBuffer = Buffer.alloc(4);
@ -445,39 +507,101 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
});
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.signP2SHTransaction_async = function(inputs, associatedKeysets, outputScript, lockTime, sigHashType) {
// Inputs are provided as arrays of [transaction, output_index, 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();
LedgerBtc.foreach(inputs, function (input, i) {
return LedgerBtc.doIf(!resuming, function () {
return self.getTrustedInput_async(input[1], input[0])
.then(function (trustedInput) {
var inputItem = {};
inputItem['trustedInput'] = false;
inputItem['value'] = Buffer.from(trustedInput, 'hex').slice(4, 4 + 0x24);
trustedInputs.push(inputItem);
});
}).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 LedgerBtc.foreach(inputs, function (input, i) {
var usedScript;
if ((inputs[i].length >= 3) && (typeof inputs[i][2] != "undefined")) {
usedScript = Buffer.from(inputs[i][2], 'hex');
}
else {
usedScript = regularOutputs[i]['script'];
}
targetTransaction['inputs'][i]['script'] = usedScript;
return self.startUntrustedHashTransactionInput_async(firstRun, targetTransaction, trustedInputs).then(function () {
return self.hashOutputFull_async(outputScript);
}).then (function (resultHash) {
return self.signTransaction_async(associatedKeysets[i], lockTime, sigHashType).then(function (signature) {
signatures.push(signature.slice(0, signature.length - 1).toString('hex'));
targetTransaction['inputs'][i]['script'] = nullScript;
if (firstRun) {
firstRun = false;
}
});
});
});
}).then(function () {
// Return the signatures
return signatures;
}).fail(function (failure) {
throw failure;
}).then(function (result) {
deferred.resolve(result);
}).fail(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
LedgerBtc.prototype.compressPublicKey = function (publicKey) {

View File

@ -23,6 +23,8 @@ var LedgerBtc = function(timeout) {
this.comm = new Ledger3("BTC", timeout);
}
// Libraries
LedgerBtc.splitPath = function(path) {
var result = [];
var components = path.split('/');
@ -39,6 +41,53 @@ LedgerBtc.splitPath = function(path) {
return result;
}
LedgerBtc.foreach = function (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;
}
LedgerBtc.doIf = function(condition, callback) {
var deferred = Q.defer();
if (condition) {
deferred.resolve(callback())
} else {
deferred.resolve();
}
return deferred.promise;
}
LedgerBtc.asyncWhile = function(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.getWalletPublicKey_async = function(path) {
var splitPath = LedgerBtc.splitPath(path);
var buffer = Buffer.alloc(5 + 1 + splitPath.length * 4);
@ -193,10 +242,10 @@ LedgerBtc.prototype.startUntrustedHashTransactionInputRaw_async = function (newT
return this.comm.exchange(buffer.toString('hex'), [0x9000]);
}
LedgerBtc.prototype.startUntrustedHashTransactionInput_async = function (newTransaction, transaction, trustedInputs) {
LedgerBtc.prototype.startUntrustedHashTransactionInput_async = function (newTransaction, transaction, inputs) {
var currentObject = this;
var data = Buffer.concat([transaction['version'],
currentObject.createVarint(transaction['inputs'].length)]);
currentObject.createVarint(transaction['inputs'].length)]);
var deferred = Q.defer();
currentObject.startUntrustedHashTransactionInputRaw_async(newTransaction, true, data).then(function (result) {
var i = 0;
@ -205,19 +254,48 @@ LedgerBtc.prototype.startUntrustedHashTransactionInput_async = function (newTran
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)]);
var prefix;
if (inputs[i]['trustedInput']) {
prefix = Buffer.alloc(2);
prefix[0] = 0x01;
prefix[1] = inputs[i]['value'].length;
}
else {
prefix = Buffer.alloc(1);
prefix[0] = 0x00;
}
data = Buffer.concat([prefix, inputs[i]['value'], 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);
});
var scriptBlocks = [];
var offset = 0;
if (input['script'].length == 0) {
scriptBlocks.push(input['sequence']);
}
else {
while (offset != input['script'].length) {
var blockSize = (input['script'].length - offset > LedgerBtc.MAX_SCRIPT_BLOCK ?
LedgerBtc.MAX_SCRIPT_BLOCK : input['script'].length - offset);
if ((offset + blockSize) != input['script'].length) {
scriptBlocks.push(input['script'].slice(offset, offset + blockSize));
}
else {
scriptBlocks.push(Buffer.concat([input['script'].slice(offset, offset + blockSize), input['sequence']]));
}
offset += blockSize;
}
}
async.eachSeries(
scriptBlocks,
function(scriptBlock, blockFinishedCallback) {
currentObject.startUntrustedHashTransactionInputRaw_async(newTransaction, false, scriptBlock).then(function (result) {
blockFinishedCallback();
}).fail(function (err) { deferred.reject(err); });
},
function(finished) {
finishedCallback();
}
);
}).fail(function (err) {
deferred.reject(err);
});
@ -250,43 +328,24 @@ LedgerBtc.prototype.provideOutputFullChangePath_async = function(path) {
}
LedgerBtc.prototype.hashOutputFull_async = function(outputScript) {
var offset = 0;
var self = this;
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;
}
},
return LedgerBtc.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;
});
});
}
LedgerBtc.prototype.signTransaction_async = function (path, lockTime, sigHashType) {
if (typeof lockTime == "undefined") {
@ -346,11 +405,14 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
var deferred = Q.defer();
foreach(inputs, function (input, i) {
return doIf(!resuming, function () {
LedgerBtc.foreach(inputs, function (input, i) {
return LedgerBtc.doIf(!resuming, function () {
return self.getTrustedInput_async(input[1], input[0])
.then(function (trustedInput) {
trustedInputs.push(Buffer.from(trustedInput, 'hex'));
var inputItem = {};
inputItem['trustedInput'] = true;
inputItem['value'] = Buffer.from(trustedInput, 'hex');
trustedInputs.push(inputItem);
});
}).then(function () {
regularOutputs.push(input[0].outputs[input[1]]);
@ -376,9 +438,9 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
targetTransaction['inputs'].push(tmpInput);
}
}).then(function () {
return doIf(!resuming, function () {
return LedgerBtc.doIf(!resuming, function () {
// Collect public keys
return foreach(inputs, function (input, i) {
return LedgerBtc.foreach(inputs, function (input, i) {
return self.getWalletPublicKey_async(associatedKeysets[i]).then(function (p) {
return p;
});
@ -389,17 +451,17 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
});
})
}).then(function () {
return foreach(inputs, function (input, i) {
return LedgerBtc.foreach(inputs, function (input, i) {
var usedScript;
if ((inputs[i].length >= 3) && (typeof inputs[i][2] != "undefined")) {
usedScript = inputs[i][2];
usedScript = Buffer.from(inputs[i][2], 'hex');
}
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 LedgerBtc.doIf(!resuming && (typeof changePath != "undefined"), function () {
return self.provideOutputFullChangePath_async(changePath);
}).then (function () {
return self.hashOutputFull_async(outputScript);
@ -422,7 +484,7 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
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);
targetTransaction['inputs'][i]['prevout'] = trustedInputs[i]['value'].slice(4, 4 + 0x24);
}
var lockTimeBuffer = Buffer.alloc(4);
@ -443,39 +505,101 @@ LedgerBtc.prototype.createPaymentTransactionNew_async = function(inputs, associa
});
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.signP2SHTransaction_async = function(inputs, associatedKeysets, outputScript, lockTime, sigHashType) {
// Inputs are provided as arrays of [transaction, output_index, 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();
LedgerBtc.foreach(inputs, function (input, i) {
return LedgerBtc.doIf(!resuming, function () {
return self.getTrustedInput_async(input[1], input[0])
.then(function (trustedInput) {
var inputItem = {};
inputItem['trustedInput'] = false;
inputItem['value'] = Buffer.from(trustedInput, 'hex').slice(4, 4 + 0x24);
trustedInputs.push(inputItem);
});
}).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 LedgerBtc.foreach(inputs, function (input, i) {
var usedScript;
if ((inputs[i].length >= 3) && (typeof inputs[i][2] != "undefined")) {
usedScript = Buffer.from(inputs[i][2], 'hex');
}
else {
usedScript = regularOutputs[i]['script'];
}
targetTransaction['inputs'][i]['script'] = usedScript;
return self.startUntrustedHashTransactionInput_async(firstRun, targetTransaction, trustedInputs).then(function () {
return self.hashOutputFull_async(outputScript);
}).then (function (resultHash) {
return self.signTransaction_async(associatedKeysets[i], lockTime, sigHashType).then(function (signature) {
signatures.push(signature.slice(0, signature.length - 1).toString('hex'));
targetTransaction['inputs'][i]['script'] = nullScript;
if (firstRun) {
firstRun = false;
}
});
});
});
}).then(function () {
// Return the signatures
return signatures;
}).fail(function (failure) {
throw failure;
}).then(function (result) {
deferred.resolve(result);
}).fail(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
LedgerBtc.prototype.compressPublicKey = function (publicKey) {