Skip to content

Commit f8e63fd

Browse files
committed
WIP: feat: hash individually
1 parent c34acbc commit f8e63fd

File tree

1 file changed

+147
-108
lines changed

1 file changed

+147
-108
lines changed

dashtx.js

+147-108
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* @prop {Function} _createMemoScript
3535
* @prop {Function} _hash
3636
* @prop {Function} _hashAndSignAll
37+
* @prop {Function} _hashAndSignOne
3738
* @prop {Function} _legacyMustSelectInputs
3839
* @prop {Function} _legacySelectOptimalUtxos
3940
* @prop {Function} _packInputs
@@ -59,6 +60,7 @@
5960
/**
6061
* @typedef tx
6162
* @prop {TxHashAndSignAll} hashAndSignAll
63+
* @prop {TxHashAndSignOne} hashAndSignOne
6264
*/
6365

6466
/** @type {Tx} */
@@ -242,7 +244,60 @@ var DashTx = ("object" === typeof module && exports) || {};
242244
}
243245
void Tx._addPrivKeyUtils(privUtils, keys);
244246

245-
return await Tx._hashAndSignAll(txInfo, privUtils);
247+
let sortedInputs = txInfo.inputs.slice(0);
248+
sortedInputs.sort(Tx.sortInputs);
249+
for (let i = 0; i < sortedInputs.length; i += 1) {
250+
let isSelf = sortedInputs[i] === txInfo.inputs[i];
251+
if (!isSelf) {
252+
console.warn(
253+
`txInfo.inputs are not ordered correctly, use txInfo.inputs.sort(Tx.sortInputs)\n(this will be an exception in the next version)`,
254+
);
255+
break;
256+
}
257+
}
258+
259+
let sortedOutputs = txInfo.outputs.slice(0);
260+
sortedOutputs.sort(Tx.sortOutputs);
261+
for (let i = 0; i < sortedOutputs.length; i += 1) {
262+
let isSelf = sortedOutputs[i] === txInfo.outputs[i];
263+
if (!isSelf) {
264+
console.warn(
265+
`txInfo.outputs are not ordered correctly, use txInfo.outputs.sort(Tx.sortOutputs)\n(this will be an exception in the next version)`,
266+
);
267+
break;
268+
}
269+
}
270+
271+
/** @type {TxInfoSigned} */
272+
let txInfoSigned = {
273+
/** @type {Array<TxInputSigned>} */
274+
inputs: [],
275+
outputs: txInfo.outputs,
276+
version: txInfo.version || CURRENT_VERSION,
277+
locktime: txInfo.locktime || 0x00,
278+
transaction: "",
279+
};
280+
281+
return await Tx._hashAndSignAll(txInfoSigned, txInfo, privUtils);
282+
};
283+
284+
/** @type {TxHashAndSignAll} */
285+
txInst.hashAndSignOne = async function (txInfo, i) {
286+
let privUtils = Object.assign({}, keyUtils);
287+
void Tx._addPrivKeyUtils(keyUtils, null);
288+
289+
/** @type {TxInfoSigned} */
290+
let txInfoSigned = {
291+
/** @type {Array<TxInputSigned>} */
292+
inputs: [],
293+
outputs: txInfo.outputs,
294+
version: txInfo.version || CURRENT_VERSION,
295+
locktime: txInfo.locktime || 0x00,
296+
transaction: "",
297+
};
298+
299+
let txInputSigned = await Tx._hashAndSignOne(privUtils, txInfo, i);
300+
txInfoSigned.inputs[i] = txInputSigned;
246301
};
247302

248303
txInst.legacy = {};
@@ -889,6 +944,7 @@ var DashTx = ("object" === typeof module && exports) || {};
889944
};
890945

891946
/**
947+
* @param {TxInfoSigned} txInfoSigned
892948
* @param {TxInfo} txInfo
893949
* TODO _param {Array<TxInputRaw>} txInfo.inputs - needs type narrowing check
894950
* TODO _param {Array<TxOutput>} txInfo.outputs
@@ -897,128 +953,105 @@ var DashTx = ("object" === typeof module && exports) || {};
897953
* @param {TxDeps} keyUtils
898954
* @returns {Promise<TxInfoSigned>}
899955
*/
900-
Tx._hashAndSignAll = async function (txInfo, keyUtils) {
901-
let sortedInputs = txInfo.inputs.slice(0);
902-
sortedInputs.sort(Tx.sortInputs);
903-
for (let i = 0; i < sortedInputs.length; i += 1) {
904-
let isSelf = sortedInputs[i] === txInfo.inputs[i];
905-
if (!isSelf) {
906-
console.warn(
907-
`txInfo.inputs are not ordered correctly, use txInfo.inputs.sort(Tx.sortInputs)\n(this will be an exception in the next version)`,
908-
);
909-
break;
910-
}
956+
Tx._hashAndSignAll = async function (txInfoSigned, txInfo, keyUtils) {
957+
for (let i = 0; i < txInfo.inputs.length; i += 1) {
958+
let txInputSigned = await DashTx._hashAndSignOne(keyUtils, txInfo, i);
959+
txInfoSigned.inputs[i] = txInputSigned;
911960
}
912961

913-
let sortedOutputs = txInfo.outputs.slice(0);
914-
sortedOutputs.sort(Tx.sortOutputs);
915-
for (let i = 0; i < sortedOutputs.length; i += 1) {
916-
let isSelf = sortedOutputs[i] === txInfo.outputs[i];
917-
if (!isSelf) {
918-
console.warn(
919-
`txInfo.outputs are not ordered correctly, use txInfo.outputs.sort(Tx.sortOutputs)\n(this will be an exception in the next version)`,
920-
);
921-
break;
922-
}
923-
}
962+
let transaction = Tx.createSigned(txInfoSigned);
924963

925-
/** @type {TxInfoSigned} */
926-
let txInfoSigned = {
927-
/** @type {Array<TxInputSigned>} */
928-
inputs: [],
964+
return {
965+
//@ts-ignore - tsc doesn't support an enum here
966+
inputs: txInfo.inputs,
967+
locktime: txInfo.locktime || 0x0,
929968
outputs: txInfo.outputs,
969+
transaction: transaction,
930970
version: txInfo.version || CURRENT_VERSION,
931-
locktime: txInfo.locktime || 0x00,
932-
transaction: "",
933971
};
972+
};
934973

935-
/**
936-
* @param {TxPrivateKey} privKey
937-
*/
938-
function createGetPrivateKey(privKey) {
939-
return function () {
940-
return privKey;
941-
};
942-
}
943-
944-
function throwGetPrivateKeyError() {
945-
if (false) {
946-
return new Uint8Array(0);
947-
}
974+
/**
975+
* @param {TxPrivateKey} privKey
976+
*/
977+
function createGetPrivateKey(privKey) {
978+
return function () {
979+
return privKey;
980+
};
981+
}
948982

949-
const msg =
950-
"getPrivateKey() is only valid for the lifetime of transaction signing process";
951-
throw new Error(msg);
983+
function throwGetPrivateKeyError() {
984+
if (false) {
985+
return new Uint8Array(0);
952986
}
953987

954-
for (let i = 0; i < txInfo.inputs.length; i += 1) {
955-
let txInput = txInfo.inputs[i];
956-
// TODO script -> lockScript, sigScript
957-
//let lockScriptHex = txInput.script;
958-
let _sigHashType = txInput.sigHashType ?? Tx.SIGHASH_ALL;
959-
let txHashable = Tx.createHashable(txInfo, i);
960-
let txHashBuf = await Tx.hashPartial(txHashable, _sigHashType);
961-
let privKey = await keyUtils.getPrivateKey(txInput, i, txInfo.inputs);
962-
963-
let sigBuf = await keyUtils.sign(privKey, txHashBuf);
964-
let sigHex = "";
965-
if ("string" === typeof sigBuf) {
966-
console.warn(`sign() should return a Uint8Array of an ASN.1 signature`);
967-
sigHex = sigBuf;
968-
} else {
969-
sigHex = Tx.utils.bytesToHex(sigBuf);
970-
}
971-
972-
let pubKeyHex = txInput.publicKey;
973-
if (!pubKeyHex) {
974-
let getPrivateKey = createGetPrivateKey(privKey);
975-
let _txInput = Object.assign({}, { getPrivateKey }, txInput);
976-
let pubKey = await keyUtils.getPublicKey(_txInput, i, txInfo.inputs);
977-
pubKeyHex = Tx.utils.bytesToHex(pubKey);
978-
_txInput.getPrivateKey = throwGetPrivateKeyError;
979-
}
980-
if ("string" !== typeof pubKeyHex) {
981-
let warn = new Error("stack");
982-
console.warn(
983-
`utxo inputs should be plain JSON and use hex rather than buffers for 'publicKey'`,
984-
warn.stack,
985-
);
986-
pubKeyHex = Tx.utils.bytesToHex(pubKeyHex);
987-
}
988-
989-
/** @type TxInputSigned */
990-
let txInputSigned = {
991-
txId: txInput.txId || txInput.txid,
992-
txid: txInput.txId || txInput.txid,
993-
outputIndex: txInput.outputIndex,
994-
signature: sigHex,
995-
publicKey: pubKeyHex,
996-
sigHashType: _sigHashType,
997-
};
998-
999-
// expose _actual_ values used, for debugging
1000-
let txHashHex = Tx.utils.bytesToHex(txHashBuf);
1001-
Object.assign({
1002-
_hash: txHashHex,
1003-
_signature: sigHex.toString(),
1004-
_lockScript: txInfo.inputs[i].script,
1005-
_publicKey: pubKeyHex,
1006-
_sigHashType: _sigHashType,
1007-
});
988+
const msg =
989+
"getPrivateKey() is only valid for the lifetime of transaction signing process";
990+
throw new Error(msg);
991+
}
1008992

1009-
txInfoSigned.inputs[i] = txInputSigned;
993+
/**
994+
* @param {TxInfo} txInfo
995+
* @param {TxDeps} keyUtils
996+
* @param {Number} i - which input to try to sign
997+
* @returns {Promise<TxInputSigned>}
998+
*/
999+
Tx._hashAndSignOne = async function (keyUtils, txInfo, i) {
1000+
let txInput = txInfo.inputs[i];
1001+
// TODO script -> lockScript, sigScript
1002+
//let lockScriptHex = txInput.script;
1003+
let _sigHashType = txInput.sigHashType ?? Tx.SIGHASH_ALL;
1004+
let txHashable = Tx.createHashable(txInfo, i);
1005+
let txHashBuf = await Tx.hashPartial(txHashable, _sigHashType);
1006+
let privKey = await keyUtils.getPrivateKey(txInput, i, txInfo.inputs);
1007+
1008+
let sigBuf = await keyUtils.sign(privKey, txHashBuf);
1009+
let sigHex = "";
1010+
if ("string" === typeof sigBuf) {
1011+
console.warn(`sign() should return a Uint8Array of an ASN.1 signature`);
1012+
sigHex = sigBuf;
1013+
} else {
1014+
sigHex = Tx.utils.bytesToHex(sigBuf);
10101015
}
10111016

1012-
let transaction = Tx.createSigned(txInfoSigned);
1017+
let pubKeyHex = txInput.publicKey;
1018+
if (!pubKeyHex) {
1019+
let getPrivateKey = createGetPrivateKey(privKey);
1020+
let _txInput = Object.assign({}, { getPrivateKey }, txInput);
1021+
let pubKey = await keyUtils.getPublicKey(_txInput, i, txInfo.inputs);
1022+
pubKeyHex = Tx.utils.bytesToHex(pubKey);
1023+
_txInput.getPrivateKey = throwGetPrivateKeyError;
1024+
}
1025+
if ("string" !== typeof pubKeyHex) {
1026+
let warn = new Error("stack");
1027+
console.warn(
1028+
`utxo inputs should be plain JSON and use hex rather than buffers for 'publicKey'`,
1029+
warn.stack,
1030+
);
1031+
pubKeyHex = Tx.utils.bytesToHex(pubKeyHex);
1032+
}
10131033

1014-
return {
1015-
//@ts-ignore - tsc doesn't support an enum here
1016-
inputs: txInfo.inputs,
1017-
locktime: txInfo.locktime || 0x0,
1018-
outputs: txInfo.outputs,
1019-
transaction: transaction,
1020-
version: txInfo.version || CURRENT_VERSION,
1034+
/** @type TxInputSigned */
1035+
let txInputSigned = {
1036+
txId: txInput.txId || txInput.txid,
1037+
txid: txInput.txId || txInput.txid,
1038+
outputIndex: txInput.outputIndex,
1039+
signature: sigHex,
1040+
publicKey: pubKeyHex,
1041+
sigHashType: _sigHashType,
10211042
};
1043+
1044+
// expose _actual_ values used, for debugging
1045+
let txHashHex = Tx.utils.bytesToHex(txHashBuf);
1046+
Object.assign({
1047+
_hash: txHashHex,
1048+
_signature: sigHex.toString(),
1049+
_lockScript: txInfo.inputs[i].script,
1050+
_publicKey: pubKeyHex,
1051+
_sigHashType: _sigHashType,
1052+
});
1053+
1054+
return txInputSigned;
10221055
};
10231056

10241057
Tx.createRaw = function (opts) {
@@ -1851,6 +1884,12 @@ if ("object" === typeof module) {
18511884
* @param {Array<TxPrivateKey>} [keys]
18521885
*/
18531886

1887+
/**
1888+
* @callback TxHashAndSignOne
1889+
* @param {TxInfo} txInfo
1890+
* @param {Number} i - which to sign
1891+
*/
1892+
18541893
/**
18551894
* @callback TxHashPartial
18561895
* @param {String} txHex - signable tx hex (like raw tx, but with (lock)script)

0 commit comments

Comments
 (0)