@ledgerhq/hw-app-btc
Advanced tools
Comparing version 2.1.3 to 2.2.0-beta.9d29a5c6
@@ -1,3 +0,3 @@ | ||
// flow-typed signature: 559e6d9e212eae61339873c83e1b86ad | ||
// flow-typed version: <<STUB>>/@ledgerhq/hw-transport_v^2.0.5/flow_v0.63.1 | ||
// flow-typed signature: 75d330203ebfe98a2c99295790ffe07c | ||
// flow-typed version: <<STUB>>/@ledgerhq/hw-transport_v^2.1.0/flow_v0.63.1 | ||
@@ -25,2 +25,18 @@ /** | ||
*/ | ||
declare module '@ledgerhq/hw-transport/flow-typed/npm/events_vx.x.x' { | ||
declare module.exports: any; | ||
} | ||
declare module '@ledgerhq/hw-transport/flow-typed/npm/flow-bin_v0.x.x' { | ||
declare module.exports: any; | ||
} | ||
declare module '@ledgerhq/hw-transport/flow-typed/npm/flow-typed_vx.x.x' { | ||
declare module.exports: any; | ||
} | ||
declare module '@ledgerhq/hw-transport/flow-typed/npm/invariant_v2.x.x' { | ||
declare module.exports: any; | ||
} | ||
declare module '@ledgerhq/hw-transport/src/Transport' { | ||
@@ -31,4 +47,16 @@ declare module.exports: any; | ||
// Filename aliases | ||
declare module '@ledgerhq/hw-transport/flow-typed/npm/events_vx.x.x.js' { | ||
declare module.exports: $Exports<'@ledgerhq/hw-transport/flow-typed/npm/events_vx.x.x'>; | ||
} | ||
declare module '@ledgerhq/hw-transport/flow-typed/npm/flow-bin_v0.x.x.js' { | ||
declare module.exports: $Exports<'@ledgerhq/hw-transport/flow-typed/npm/flow-bin_v0.x.x'>; | ||
} | ||
declare module '@ledgerhq/hw-transport/flow-typed/npm/flow-typed_vx.x.x.js' { | ||
declare module.exports: $Exports<'@ledgerhq/hw-transport/flow-typed/npm/flow-typed_vx.x.x'>; | ||
} | ||
declare module '@ledgerhq/hw-transport/flow-typed/npm/invariant_v2.x.x.js' { | ||
declare module.exports: $Exports<'@ledgerhq/hw-transport/flow-typed/npm/invariant_v2.x.x'>; | ||
} | ||
declare module '@ledgerhq/hw-transport/src/Transport.js' { | ||
declare module.exports: $Exports<'@ledgerhq/hw-transport/src/Transport'>; | ||
} |
289
lib/Btc.js
@@ -7,6 +7,10 @@ "use strict"; | ||
var _promise = require("babel-runtime/core-js/promise"); | ||
var _regenerator = require("babel-runtime/regenerator"); | ||
var _promise2 = _interopRequireDefault(_promise); | ||
var _regenerator2 = _interopRequireDefault(_regenerator); | ||
var _asyncToGenerator2 = require("babel-runtime/helpers/asyncToGenerator"); | ||
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); | ||
var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); | ||
@@ -22,6 +26,8 @@ | ||
var _createHash = require("create-hash"); | ||
var _createHash2 = _interopRequireDefault(_createHash); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var MAX_SCRIPT_BLOCK = 50; | ||
// TODO future refactoring | ||
@@ -31,7 +37,11 @@ // - drop utils.js & refactoring with async/await style | ||
// - there are redundant code across apps (see Eth vs Btc). we might want to factorize it somewhere. also each app apdu call should be abstracted it out as an api | ||
var MAX_SCRIPT_BLOCK = 50; | ||
var DEFAULT_LOCKTIME = 0; | ||
var DEFAULT_SEQUENCE = 0xffffffff; | ||
var SIGHASH_ALL = 1; | ||
var OP_PUSHDATA1 = 0x76; | ||
var OP_HASH160 = 0xa9; | ||
var HASH_SIZE = 0x14; | ||
var OP_EQUALVERIFY = 0x88; | ||
var OP_CHECKSIG = 0xac; | ||
/** | ||
@@ -53,13 +63,30 @@ * Bitcoin API. | ||
/** | ||
* @param path a BIP 32 path | ||
* @example | ||
* btc.getWalletPublicKey("44'/0'/0'/0").then(o => o.bitcoinAddress) | ||
*/ | ||
(0, _createClass3.default)(Btc, [{ | ||
key: "hashPublicKey", | ||
value: function hashPublicKey(buffer) { | ||
return (0, _createHash2.default)("rmd160").update((0, _createHash2.default)("sha256").update(buffer).digest()).digest(); | ||
} | ||
/** | ||
* @param path a BIP 32 path | ||
* @param segwit use segwit | ||
* @example | ||
* btc.getWalletPublicKey("44'/0'/0'/0").then(o => o.bitcoinAddress) | ||
*/ | ||
(0, _createClass3.default)(Btc, [{ | ||
}, { | ||
key: "getWalletPublicKey", | ||
value: function getWalletPublicKey(path) { | ||
var verify = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; | ||
var segwit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; | ||
var paths = (0, _utils.splitPath)(path); | ||
var p1 = 0x00; | ||
var p2 = 0x00; | ||
if (verify === true) { | ||
p1 = 0x01; | ||
} | ||
if (segwit == true) { | ||
p2 = 0x01; | ||
} | ||
var buffer = Buffer.alloc(1 + paths.length * 4); | ||
@@ -70,3 +97,3 @@ buffer[0] = paths.length; | ||
}); | ||
return this.transport.send(0xe0, 0x40, 0x00, 0x00, buffer).then(function (response) { | ||
return this.transport.send(0xe0, 0x40, p1, p2, buffer).then(function (response) { | ||
var publicKeyLength = response[0]; | ||
@@ -162,2 +189,69 @@ var addressLength = response[1 + publicKeyLength]; | ||
}, { | ||
key: "getTrustedInputBIP143", | ||
value: function () { | ||
var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(indexLookup, transaction) { | ||
var sha, hash, data, outputs, locktime; | ||
return _regenerator2.default.wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
if (transaction) { | ||
_context.next = 2; | ||
break; | ||
} | ||
throw new Error("getTrustedInputBIP143: missing tx"); | ||
case 2: | ||
sha = (0, _createHash2.default)("sha256"); | ||
sha.update(this.serializeTransaction(transaction, true)); | ||
hash = sha.digest(); | ||
sha = (0, _createHash2.default)("sha256"); | ||
sha.update(hash); | ||
hash = sha.digest(); | ||
data = Buffer.alloc(4); | ||
data.writeUInt32LE(indexLookup, 0); | ||
outputs = transaction.outputs, locktime = transaction.locktime; | ||
if (!(!outputs || !locktime)) { | ||
_context.next = 13; | ||
break; | ||
} | ||
throw new Error("getTrustedInputBIP143: locktime & outputs is expected"); | ||
case 13: | ||
if (outputs[indexLookup]) { | ||
_context.next = 15; | ||
break; | ||
} | ||
throw new Error("getTrustedInputBIP143: wrong index"); | ||
case 15: | ||
hash = Buffer.concat([hash, data, outputs[indexLookup].amount]); | ||
_context.next = 18; | ||
return hash.toString("hex"); | ||
case 18: | ||
return _context.abrupt("return", _context.sent); | ||
case 19: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee, this); | ||
})); | ||
function getTrustedInputBIP143(_x3, _x4) { | ||
return _ref.apply(this, arguments); | ||
} | ||
return getTrustedInputBIP143; | ||
}() | ||
}, { | ||
key: "getVarint", | ||
@@ -180,3 +274,5 @@ value: function getVarint(data, offset) { | ||
value: function startUntrustedHashTransactionInputRaw(newTransaction, firstRound, transactionData) { | ||
return this.transport.send(0xe0, 0x44, firstRound ? 0x00 : 0x80, newTransaction ? 0x00 : 0x80, transactionData); | ||
var segwit = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; | ||
return this.transport.send(0xe0, 0x44, firstRound ? 0x00 : 0x80, newTransaction ? segwit ? 0x02 : 0x00 : 0x80, transactionData); | ||
} | ||
@@ -188,18 +284,20 @@ }, { | ||
var segwit = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; | ||
var data = Buffer.concat([transaction.version, this.createVarint(transaction.inputs.length)]); | ||
return this.startUntrustedHashTransactionInputRaw(newTransaction, true, data).then(function () { | ||
return this.startUntrustedHashTransactionInputRaw(newTransaction, true, data, segwit).then(function () { | ||
var i = 0; | ||
return (0, _utils.eachSeries)(transaction.inputs, function (input) { | ||
// TODO : segwit | ||
var prefix = void 0; | ||
if (inputs[i].trustedInput) { | ||
prefix = Buffer.alloc(2); | ||
prefix[0] = 0x01; | ||
prefix[1] = inputs[i].value.length; | ||
if (segwit) { | ||
prefix = Buffer.from([0x02]); | ||
} else { | ||
prefix = Buffer.from([0x01, inputs[i].value.length]); | ||
} | ||
} else { | ||
prefix = Buffer.alloc(1); | ||
prefix[0] = 0x00; | ||
prefix = Buffer.from([0x00]); | ||
} | ||
data = Buffer.concat([prefix, inputs[i].value, _this2.createVarint(input.script.length)]); | ||
return _this2.startUntrustedHashTransactionInputRaw(newTransaction, false, data).then(function () { | ||
return _this2.startUntrustedHashTransactionInputRaw(newTransaction, false, data, segwit).then(function () { | ||
var scriptBlocks = []; | ||
@@ -221,3 +319,3 @@ var offset = 0; | ||
return (0, _utils.eachSeries)(scriptBlocks, function (scriptBlock) { | ||
return _this2.startUntrustedHashTransactionInputRaw(newTransaction, false, scriptBlock); | ||
return _this2.startUntrustedHashTransactionInputRaw(newTransaction, false, scriptBlock, segwit); | ||
}).then(function () { | ||
@@ -356,2 +454,3 @@ i++; | ||
* * sequence is the sequence number to use for this input (when using RBF), or non present | ||
* @param segwit is a boolean indicating wether to use segwit or not | ||
* @param associatedKeysets is an array of BIP 32 paths pointing to the path to the private key used for each UTXO | ||
@@ -375,6 +474,8 @@ * @param changePath is an optional BIP 32 path pointing to the path to the public key used to compute the change address | ||
value: function createPaymentTransactionNew(inputs, associatedKeysets, changePath, outputScriptHex) { | ||
var lockTime = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : DEFAULT_LOCKTIME; | ||
var _this5 = this; | ||
var lockTime = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : DEFAULT_LOCKTIME; | ||
var sigHashType = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : SIGHASH_ALL; | ||
var segwit = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false; | ||
@@ -397,3 +498,3 @@ // Inputs are provided as arrays of [transaction, output_index, optional redeem script, optional sequence] | ||
}; | ||
var getTrustedInputCall = segwit ? this.getTrustedInputBIP143.bind(this) : this.getTrustedInput.bind(this); | ||
var outputScript = Buffer.from(outputScriptHex, "hex"); | ||
@@ -403,3 +504,3 @@ | ||
return (0, _utils.doIf)(!resuming, function () { | ||
return _this5.getTrustedInput(input[1], input[0]).then(function (trustedInput) { | ||
return getTrustedInputCall(input[1], input[0]).then(function (trustedInput) { | ||
trustedInputs.push({ | ||
@@ -417,14 +518,14 @@ trustedInput: true, | ||
} | ||
}).then(function () { | ||
for (var i = 0; i < inputs.length; i++) { | ||
var _sequence = Buffer.alloc(4); | ||
_sequence.writeUInt32LE(inputs[i].length >= 4 && typeof inputs[i][3] === "number" ? inputs[i][3] : DEFAULT_SEQUENCE, 0); | ||
targetTransaction.inputs.push({ | ||
script: nullScript, | ||
prevout: nullPrevout, | ||
sequence: _sequence | ||
}); | ||
} | ||
}); | ||
}).then(function () { | ||
for (var i = 0; i < inputs.length; i++) { | ||
var _sequence = Buffer.alloc(4); | ||
_sequence.writeUInt32LE(inputs[i].length >= 4 && typeof inputs[i][3] === "number" ? inputs[i][3] : DEFAULT_SEQUENCE, 0); | ||
targetTransaction.inputs.push({ | ||
script: nullScript, | ||
prevout: nullPrevout, | ||
sequence: _sequence | ||
}); | ||
} | ||
}).then(function () { | ||
return (0, _utils.doIf)(!resuming, function () { | ||
@@ -443,31 +544,52 @@ return ( | ||
}).then(function () { | ||
return (0, _utils.foreach)(inputs, function (input, i) { | ||
targetTransaction.inputs[i].script = inputs[i].length >= 3 && typeof inputs[i][2] === "string" ? Buffer.from(inputs[i][2], "hex") : regularOutputs[i].script; | ||
return _this5.startUntrustedHashTransactionInput(firstRun, targetTransaction, trustedInputs).then(function () { | ||
return _promise2.default.resolve().then(function () { | ||
if (!resuming && typeof changePath !== "undefined") { | ||
return _this5.provideOutputFullChangePath(changePath); | ||
} | ||
}).then(function () { | ||
return _this5.hashOutputFull(outputScript); | ||
}).then(function () { | ||
return _this5.signTransaction(associatedKeysets[i], lockTime, sigHashType).then(function (signature) { | ||
signatures.push(signature); | ||
targetTransaction.inputs[i].script = nullScript; | ||
if (firstRun) { | ||
firstRun = false; | ||
} | ||
return (0, _utils.doIf)(segwit, function () { | ||
return ( | ||
// Do the first run with all inputs | ||
_this5.startUntrustedHashTransactionInput(true, targetTransaction, trustedInputs, segwit).then(function () { | ||
return (0, _utils.doIf)(!resuming && typeof changePath != "undefined", function () { | ||
return this.provideOutputFullChangePath(changePath); | ||
}).then(function () { | ||
return _this5.hashOutputFull(outputScript); | ||
}); | ||
}); | ||
}); | ||
}) | ||
); | ||
}); | ||
}).then(function () { | ||
return ( | ||
// Do the second run with the individual transaction | ||
(0, _utils.foreach)(inputs, function (input, i) { | ||
targetTransaction.inputs[i].script = inputs[i].length >= 3 && typeof inputs[i][2] === "string" ? Buffer.from(inputs[i][2], "hex") : !segwit ? regularOutputs[i].script : Buffer.concat([Buffer.from([OP_PUSHDATA1, OP_HASH160, HASH_SIZE]), _this5.hashPublicKey(publicKeys[i]), Buffer.from([OP_EQUALVERIFY, OP_CHECKSIG])]); | ||
return _this5.startUntrustedHashTransactionInput(!segwit && firstRun, targetTransaction, trustedInputs, segwit).then(function () { | ||
return (0, _utils.doIf)(!segwit, function () { | ||
return (0, _utils.doIf)(!resuming && typeof changePath != "undefined", function () { | ||
return this.provideOutputFullChangePath(changePath); | ||
}).then(function () { | ||
return _this5.hashOutputFull(outputScript); | ||
}); | ||
}); | ||
}).then(function () { | ||
return _this5.signTransaction(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].value.slice(4, 4 + 0x24); | ||
for (var _i = 0; _i < inputs.length; _i++) { | ||
if (segwit) { | ||
targetTransaction.inputs[_i].script = Buffer.concat([Buffer.from("160014", "hex"), _this5.hashPublicKey(publicKeys[_i])]); | ||
} else { | ||
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].value.slice(4, 4 + 0x24); | ||
} | ||
@@ -478,4 +600,15 @@ | ||
var result = Buffer.concat([_this5.serializeTransaction(targetTransaction), outputScript, lockTimeBuffer]); | ||
var result = Buffer.concat([_this5.serializeTransaction(targetTransaction, false), outputScript]); | ||
if (segwit) { | ||
var witness = Buffer.alloc(0); | ||
for (var i = 0; i < inputs.length; i++) { | ||
var tmpScriptData = Buffer.concat([Buffer.from("02", "hex"), Buffer.from([signatures[i].length]), signatures[i], Buffer.from([publicKeys[i].length]), publicKeys[i]]); | ||
witness = Buffer.concat([witness, tmpScriptData]); | ||
} | ||
result = Buffer.concat([result, witness]); | ||
} | ||
result = Buffer.concat([result, lockTimeBuffer]); | ||
return result.toString("hex"); | ||
@@ -567,3 +700,3 @@ }); | ||
targetTransaction.inputs[i].script = inputs[i].length >= 3 && typeof inputs[i][2] === "string" ? Buffer.from(inputs[i][2], "hex") : regularOutputs[i].script; | ||
return _this6.startUntrustedHashTransactionInput(firstRun, targetTransaction, trustedInputs).then(function () { | ||
return _this6.startUntrustedHashTransactionInput(firstRun, targetTransaction, trustedInputs, false).then(function () { | ||
return _this6.hashOutputFull(outputScript); | ||
@@ -624,5 +757,6 @@ }).then(function () { | ||
key: "splitTransaction", | ||
value: function splitTransaction(transactionHex) { | ||
value: function splitTransaction(transactionHex, isSegwitSupported) { | ||
var inputs = []; | ||
var outputs = []; | ||
var witness = false; | ||
var offset = 0; | ||
@@ -632,2 +766,6 @@ var transaction = Buffer.from(transactionHex, "hex"); | ||
offset += 4; | ||
if (isSegwitSupported && transaction[offset] === 0 && transaction[offset + 1] !== 0) { | ||
offset += 2; | ||
witness = true; | ||
} | ||
var varint = this.getVarint(transaction, offset); | ||
@@ -650,3 +788,3 @@ var numberInputs = varint[0]; | ||
offset += varint[1]; | ||
for (var _i = 0; _i < numberOutputs; _i++) { | ||
for (var _i2 = 0; _i2 < numberOutputs; _i2++) { | ||
var _amount = transaction.slice(offset, offset + 8); | ||
@@ -660,4 +798,10 @@ offset += 8; | ||
} | ||
var locktime = transaction.slice(offset, offset + 4); | ||
return { version: version, inputs: inputs, outputs: outputs, locktime: locktime }; | ||
var witnessScript, locktime; | ||
if (witness) { | ||
witnessScript = transaction.slice(offset, -4); | ||
locktime = transaction.slice(transaction.length - 4); | ||
} else { | ||
locktime = transaction.slice(offset, offset + 4); | ||
} | ||
return { version: version, inputs: inputs, outputs: outputs, locktime: locktime, witness: witnessScript }; | ||
} | ||
@@ -673,6 +817,6 @@ | ||
key: "serializeTransactionOutputs", | ||
value: function serializeTransactionOutputs(_ref) { | ||
value: function serializeTransactionOutputs(_ref2) { | ||
var _this7 = this; | ||
var outputs = _ref.outputs; | ||
var outputs = _ref2.outputs; | ||
@@ -694,6 +838,7 @@ var outputBuffer = Buffer.alloc(0); | ||
key: "serializeTransaction", | ||
value: function serializeTransaction(transaction) { | ||
value: function serializeTransaction(transaction, skipWitness) { | ||
var _this8 = this; | ||
var inputBuffer = Buffer.alloc(0); | ||
var useWitness = typeof transaction["witness"] != "undefined" && !skipWitness; | ||
transaction.inputs.forEach(function (input) { | ||
@@ -705,6 +850,6 @@ inputBuffer = Buffer.concat([inputBuffer, input.prevout, _this8.createVarint(input.script.length), input.script, input.sequence]); | ||
if (typeof transaction.outputs !== "undefined" && typeof transaction.locktime !== "undefined") { | ||
outputBuffer = Buffer.concat([outputBuffer, transaction.locktime]); | ||
outputBuffer = Buffer.concat([outputBuffer, useWitness && transaction.witness || Buffer.alloc(0), transaction.locktime]); | ||
} | ||
return Buffer.concat([transaction.version, this.createVarint(transaction.inputs.length), inputBuffer, outputBuffer]); | ||
return Buffer.concat([transaction.version, useWitness ? Buffer.from("0001", "hex") : Buffer.alloc(0), this.createVarint(transaction.inputs.length), inputBuffer, outputBuffer]); | ||
} | ||
@@ -711,0 +856,0 @@ |
{ | ||
"name": "@ledgerhq/hw-app-btc", | ||
"version": "2.1.3", | ||
"version": "2.2.0-beta.9d29a5c6", | ||
"description": "Ledger Hardware Wallet Bitcoin Application API", | ||
@@ -28,3 +28,4 @@ "keywords": [ | ||
"dependencies": { | ||
"@ledgerhq/hw-transport": "^2.1.3" | ||
"@ledgerhq/hw-transport": "^2.2.0-beta.9d29a5c6", | ||
"create-hash": "^1.1.3" | ||
}, | ||
@@ -31,0 +32,0 @@ "devDependencies": { |
347
src/Btc.js
@@ -9,2 +9,3 @@ //@flow | ||
import type Transport from "@ledgerhq/hw-transport"; | ||
import createHash from "create-hash"; | ||
@@ -15,3 +16,7 @@ const MAX_SCRIPT_BLOCK = 50; | ||
const SIGHASH_ALL = 1; | ||
const OP_PUSHDATA1 = 0x76; | ||
const OP_HASH160 = 0xa9; | ||
const HASH_SIZE = 0x14; | ||
const OP_EQUALVERIFY = 0x88; | ||
const OP_CHECKSIG = 0xac; | ||
/** | ||
@@ -32,4 +37,15 @@ * Bitcoin API. | ||
hashPublicKey(buffer: Buffer) { | ||
return createHash("rmd160") | ||
.update( | ||
createHash("sha256") | ||
.update(buffer) | ||
.digest() | ||
) | ||
.digest(); | ||
} | ||
/** | ||
* @param path a BIP 32 path | ||
* @param segwit use segwit | ||
* @example | ||
@@ -39,3 +55,5 @@ * btc.getWalletPublicKey("44'/0'/0'/0").then(o => o.bitcoinAddress) | ||
getWalletPublicKey( | ||
path: string | ||
path: string, | ||
verify?: boolean = false, | ||
segwit?: boolean = false | ||
): Promise<{ | ||
@@ -47,2 +65,10 @@ publicKey: string, | ||
const paths = splitPath(path); | ||
var p1 = 0x00; | ||
var p2 = 0x00; | ||
if (verify === true) { | ||
p1 = 0x01; | ||
} | ||
if (segwit == true) { | ||
p2 = 0x01; | ||
} | ||
const buffer = Buffer.alloc(1 + paths.length * 4); | ||
@@ -53,24 +79,17 @@ buffer[0] = paths.length; | ||
}); | ||
return this.transport | ||
.send(0xe0, 0x40, 0x00, 0x00, buffer) | ||
.then(response => { | ||
const publicKeyLength = response[0]; | ||
const addressLength = response[1 + publicKeyLength]; | ||
const publicKey = response | ||
.slice(1, 1 + publicKeyLength) | ||
.toString("hex"); | ||
const bitcoinAddress = response | ||
.slice( | ||
1 + publicKeyLength + 1, | ||
1 + publicKeyLength + 1 + addressLength | ||
) | ||
.toString("ascii"); | ||
const chainCode = response | ||
.slice( | ||
1 + publicKeyLength + 1 + addressLength, | ||
1 + publicKeyLength + 1 + addressLength + 32 | ||
) | ||
.toString("hex"); | ||
return { publicKey, bitcoinAddress, chainCode }; | ||
}); | ||
return this.transport.send(0xe0, 0x40, p1, p2, buffer).then(response => { | ||
const publicKeyLength = response[0]; | ||
const addressLength = response[1 + publicKeyLength]; | ||
const publicKey = response.slice(1, 1 + publicKeyLength).toString("hex"); | ||
const bitcoinAddress = response | ||
.slice(1 + publicKeyLength + 1, 1 + publicKeyLength + 1 + addressLength) | ||
.toString("ascii"); | ||
const chainCode = response | ||
.slice( | ||
1 + publicKeyLength + 1 + addressLength, | ||
1 + publicKeyLength + 1 + addressLength + 32 | ||
) | ||
.toString("hex"); | ||
return { publicKey, bitcoinAddress, chainCode }; | ||
}); | ||
} | ||
@@ -175,2 +194,25 @@ | ||
async getTrustedInputBIP143(indexLookup: number, transaction: Transaction) { | ||
if (!transaction) { | ||
throw new Error("getTrustedInputBIP143: missing tx"); | ||
} | ||
let sha = createHash("sha256"); | ||
sha.update(this.serializeTransaction(transaction, true)); | ||
let hash = sha.digest(); | ||
sha = createHash("sha256"); | ||
sha.update(hash); | ||
hash = sha.digest(); | ||
const data = Buffer.alloc(4); | ||
data.writeUInt32LE(indexLookup, 0); | ||
const { outputs, locktime } = transaction; | ||
if (!outputs || !locktime) { | ||
throw new Error("getTrustedInputBIP143: locktime & outputs is expected"); | ||
} | ||
if (!outputs[indexLookup]) { | ||
throw new Error("getTrustedInputBIP143: wrong index"); | ||
} | ||
hash = Buffer.concat([hash, data, outputs[indexLookup].amount]); | ||
return await hash.toString("hex"); | ||
} | ||
getVarint(data: Buffer, offset: number): [number, number] { | ||
@@ -199,3 +241,4 @@ if (data[offset] < 0xfd) { | ||
firstRound: boolean, | ||
transactionData: Buffer | ||
transactionData: Buffer, | ||
segwit?: boolean = false | ||
) { | ||
@@ -206,3 +249,3 @@ return this.transport.send( | ||
firstRound ? 0x00 : 0x80, | ||
newTransaction ? 0x00 : 0x80, | ||
newTransaction ? (segwit ? 0x02 : 0x00) : 0x80, | ||
transactionData | ||
@@ -215,3 +258,4 @@ ); | ||
transaction: Transaction, | ||
inputs: Array<{ trustedInput: boolean, value: Buffer }> | ||
inputs: Array<{ trustedInput: boolean, value: Buffer }>, | ||
segwit?: boolean = false | ||
) { | ||
@@ -225,15 +269,16 @@ let data = Buffer.concat([ | ||
true, | ||
data | ||
data, | ||
segwit | ||
).then(() => { | ||
let i = 0; | ||
return eachSeries(transaction.inputs, input => { | ||
// TODO : segwit | ||
let prefix; | ||
if (inputs[i].trustedInput) { | ||
prefix = Buffer.alloc(2); | ||
prefix[0] = 0x01; | ||
prefix[1] = inputs[i].value.length; | ||
if (segwit) { | ||
prefix = Buffer.from([0x02]); | ||
} else { | ||
prefix = Buffer.from([0x01, inputs[i].value.length]); | ||
} | ||
} else { | ||
prefix = Buffer.alloc(1); | ||
prefix[0] = 0x00; | ||
prefix = Buffer.from([0x00]); | ||
} | ||
@@ -248,3 +293,4 @@ data = Buffer.concat([ | ||
false, | ||
data | ||
data, | ||
segwit | ||
).then(() => { | ||
@@ -280,3 +326,4 @@ let scriptBlocks = []; | ||
false, | ||
scriptBlock | ||
scriptBlock, | ||
segwit | ||
); | ||
@@ -422,2 +469,3 @@ }).then(() => { | ||
* * sequence is the sequence number to use for this input (when using RBF), or non present | ||
* @param segwit is a boolean indicating wether to use segwit or not | ||
* @param associatedKeysets is an array of BIP 32 paths pointing to the path to the private key used for each UTXO | ||
@@ -443,3 +491,4 @@ * @param changePath is an optional BIP 32 path pointing to the path to the public key used to compute the change address | ||
lockTime?: number = DEFAULT_LOCKTIME, | ||
sigHashType?: number = SIGHASH_ALL | ||
sigHashType?: number = SIGHASH_ALL, | ||
segwit?: boolean = false | ||
) { | ||
@@ -462,8 +511,10 @@ // Inputs are provided as arrays of [transaction, output_index, optional redeem script, optional sequence] | ||
}; | ||
const getTrustedInputCall = segwit | ||
? this.getTrustedInputBIP143.bind(this) | ||
: this.getTrustedInput.bind(this); | ||
const outputScript = Buffer.from(outputScriptHex, "hex"); | ||
return foreach(inputs, input => | ||
doIf(!resuming, () => | ||
this.getTrustedInput(input[1], input[0]).then(trustedInput => { | ||
return foreach(inputs, input => { | ||
return doIf(!resuming, () => | ||
getTrustedInputCall(input[1], input[0]).then(trustedInput => { | ||
trustedInputs.push({ | ||
@@ -474,28 +525,29 @@ trustedInput: true, | ||
}) | ||
).then(() => { | ||
const { outputs } = input[0]; | ||
const index = input[1]; | ||
if (outputs && index <= outputs.length - 1) { | ||
regularOutputs.push(outputs[index]); | ||
} | ||
}) | ||
) | ||
.then(() => { | ||
for (let i = 0; i < inputs.length; i++) { | ||
let sequence = Buffer.alloc(4); | ||
sequence.writeUInt32LE( | ||
inputs[i].length >= 4 && typeof inputs[i][3] === "number" | ||
? inputs[i][3] | ||
: DEFAULT_SEQUENCE, | ||
0 | ||
); | ||
targetTransaction.inputs.push({ | ||
script: nullScript, | ||
prevout: nullPrevout, | ||
sequence | ||
}); | ||
} | ||
}) | ||
.then(() => { | ||
return doIf(!resuming, () => | ||
) | ||
.then(() => { | ||
const { outputs } = input[0]; | ||
const index = input[1]; | ||
if (outputs && index <= outputs.length - 1) { | ||
regularOutputs.push(outputs[index]); | ||
} | ||
}) | ||
.then(() => { | ||
for (let i = 0; i < inputs.length; i++) { | ||
let sequence = Buffer.alloc(4); | ||
sequence.writeUInt32LE( | ||
inputs[i].length >= 4 && typeof inputs[i][3] === "number" | ||
? inputs[i][3] | ||
: DEFAULT_SEQUENCE, | ||
0 | ||
); | ||
targetTransaction.inputs.push({ | ||
script: nullScript, | ||
prevout: nullPrevout, | ||
sequence | ||
}); | ||
} | ||
}); | ||
}) | ||
.then(() => | ||
doIf(!resuming, () => | ||
// Collect public keys | ||
@@ -513,5 +565,21 @@ foreach(inputs, (input, i) => | ||
}) | ||
); | ||
}) | ||
) | ||
) | ||
.then(() => | ||
doIf(segwit, () => | ||
// Do the first run with all inputs | ||
this.startUntrustedHashTransactionInput( | ||
true, | ||
targetTransaction, | ||
trustedInputs, | ||
segwit | ||
).then(() => | ||
doIf(!resuming && typeof changePath != "undefined", function() { | ||
return this.provideOutputFullChangePath(changePath); | ||
}).then(() => this.hashOutputFull(outputScript)) | ||
) | ||
) | ||
) | ||
.then(() => | ||
// Do the second run with the individual transaction | ||
foreach(inputs, (input, i) => { | ||
@@ -521,29 +589,32 @@ targetTransaction.inputs[i].script = | ||
? Buffer.from(inputs[i][2], "hex") | ||
: regularOutputs[i].script; | ||
: !segwit | ||
? regularOutputs[i].script | ||
: Buffer.concat([ | ||
Buffer.from([OP_PUSHDATA1, OP_HASH160, HASH_SIZE]), | ||
this.hashPublicKey(publicKeys[i]), | ||
Buffer.from([OP_EQUALVERIFY, OP_CHECKSIG]) | ||
]); | ||
return this.startUntrustedHashTransactionInput( | ||
firstRun, | ||
!segwit && firstRun, | ||
targetTransaction, | ||
trustedInputs | ||
).then(() => | ||
Promise.resolve() | ||
.then(() => { | ||
if (!resuming && typeof changePath !== "undefined") { | ||
trustedInputs, | ||
segwit | ||
) | ||
.then(() => | ||
doIf(!segwit, () => | ||
doIf(!resuming && typeof changePath != "undefined", function() { | ||
return this.provideOutputFullChangePath(changePath); | ||
} | ||
}) | ||
.then(() => this.hashOutputFull(outputScript)) | ||
.then(() => | ||
this.signTransaction( | ||
associatedKeysets[i], | ||
lockTime, | ||
sigHashType | ||
).then(signature => { | ||
signatures.push(signature); | ||
targetTransaction.inputs[i].script = nullScript; | ||
if (firstRun) { | ||
firstRun = false; | ||
} | ||
}) | ||
}).then(() => this.hashOutputFull(outputScript)) | ||
) | ||
); | ||
) | ||
.then(() => | ||
this.signTransaction(associatedKeysets[i], lockTime, sigHashType) | ||
) | ||
.then(signature => { | ||
signatures.push(signature); | ||
targetTransaction.inputs[i].script = nullScript; | ||
if (firstRun) { | ||
firstRun = false; | ||
} | ||
}); | ||
}) | ||
@@ -554,12 +625,20 @@ ) | ||
for (let i = 0; i < inputs.length; i++) { | ||
const signatureSize = Buffer.alloc(1); | ||
const 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] | ||
]); | ||
if (segwit) { | ||
targetTransaction.inputs[i].script = Buffer.concat([ | ||
Buffer.from("160014", "hex"), | ||
this.hashPublicKey(publicKeys[i]) | ||
]); | ||
} else { | ||
const signatureSize = Buffer.alloc(1); | ||
const 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].value.slice( | ||
@@ -574,8 +653,24 @@ 4, | ||
const result = Buffer.concat([ | ||
this.serializeTransaction(targetTransaction), | ||
outputScript, | ||
lockTimeBuffer | ||
var result = Buffer.concat([ | ||
this.serializeTransaction(targetTransaction, false), | ||
outputScript | ||
]); | ||
if (segwit) { | ||
var witness = Buffer.alloc(0); | ||
for (var i = 0; i < inputs.length; i++) { | ||
var tmpScriptData = Buffer.concat([ | ||
Buffer.from("02", "hex"), | ||
Buffer.from([signatures[i].length]), | ||
signatures[i], | ||
Buffer.from([publicKeys[i].length]), | ||
publicKeys[i] | ||
]); | ||
witness = Buffer.concat([witness, tmpScriptData]); | ||
} | ||
result = Buffer.concat([result, witness]); | ||
} | ||
result = Buffer.concat([result, lockTimeBuffer]); | ||
return result.toString("hex"); | ||
@@ -672,3 +767,4 @@ }); | ||
targetTransaction, | ||
trustedInputs | ||
trustedInputs, | ||
false | ||
) | ||
@@ -730,5 +826,9 @@ .then(() => this.hashOutputFull(outputScript)) | ||
*/ | ||
splitTransaction(transactionHex: string): Transaction { | ||
splitTransaction( | ||
transactionHex: string, | ||
isSegwitSupported: boolean | ||
): Transaction { | ||
const inputs = []; | ||
const outputs = []; | ||
var witness = false; | ||
let offset = 0; | ||
@@ -738,2 +838,9 @@ const transaction = Buffer.from(transactionHex, "hex"); | ||
offset += 4; | ||
if ( | ||
isSegwitSupported && | ||
(transaction[offset] === 0 && transaction[offset + 1] !== 0) | ||
) { | ||
offset += 2; | ||
witness = true; | ||
} | ||
let varint = this.getVarint(transaction, offset); | ||
@@ -765,4 +872,10 @@ const numberInputs = varint[0]; | ||
} | ||
let locktime = transaction.slice(offset, offset + 4); | ||
return { version, inputs, outputs, locktime }; | ||
var witnessScript, locktime; | ||
if (witness) { | ||
witnessScript = transaction.slice(offset, -4); | ||
locktime = transaction.slice(transaction.length - 4); | ||
} else { | ||
locktime = transaction.slice(offset, offset + 4); | ||
} | ||
return { version, inputs, outputs, locktime, witness: witnessScript }; | ||
} | ||
@@ -796,4 +909,6 @@ | ||
*/ | ||
serializeTransaction(transaction: Transaction) { | ||
serializeTransaction(transaction: Transaction, skipWitness: boolean) { | ||
let inputBuffer = Buffer.alloc(0); | ||
let useWitness = | ||
typeof transaction["witness"] != "undefined" && !skipWitness; | ||
transaction.inputs.forEach(input => { | ||
@@ -814,3 +929,7 @@ inputBuffer = Buffer.concat([ | ||
) { | ||
outputBuffer = Buffer.concat([outputBuffer, transaction.locktime]); | ||
outputBuffer = Buffer.concat([ | ||
outputBuffer, | ||
(useWitness && transaction.witness) || Buffer.alloc(0), | ||
transaction.locktime | ||
]); | ||
} | ||
@@ -820,2 +939,3 @@ | ||
transaction.version, | ||
useWitness ? Buffer.from("0001", "hex") : Buffer.alloc(0), | ||
this.createVarint(transaction.inputs.length), | ||
@@ -871,3 +991,4 @@ inputBuffer, | ||
outputs?: TransactionOutput[], | ||
locktime?: Buffer | ||
locktime?: Buffer, | ||
witness?: Buffer | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
187085
15
2180
2
1
1
+ Addedcreate-hash@^1.1.3
+ Addedcipher-base@1.0.6(transitive)
+ Addedcreate-hash@1.2.0(transitive)
+ Addedhash-base@3.1.0(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedmd5.js@1.3.5(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedripemd160@2.0.2(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedsha.js@2.4.11(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedutil-deprecate@1.0.2(transitive)