Add U2F proxy client API for P2SH inputs
This commit is contained in:
parent
4114fbccec
commit
f9d02783d5
27
README.md
27
README.md
|
@ -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); });
|
||||
);
|
||||
```
|
||||
|
|
|
@ -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) {
|
||||
|
|
304
js/ledger-btc.js
304
js/ledger-btc.js
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue