@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
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
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)