Skip to content

Commit 664fd27

Browse files
committed
WIP: feat: hash individually
1 parent 5ce62d7 commit 664fd27

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
@@ -40,6 +40,7 @@
4040
* @prop {Function} _debugPrint
4141
* @prop {Function} _hash
4242
* @prop {Function} _hashAndSignAll
43+
* @prop {Function} _hashAndSignOne
4344
* @prop {Function} _legacyMustSelectInputs
4445
* @prop {Function} _legacySelectOptimalUtxos
4546
* @prop {Function} _packInputs
@@ -66,6 +67,7 @@
6667
/**
6768
* @typedef tx
6869
* @prop {TxHashAndSignAll} hashAndSignAll
70+
* @prop {TxHashAndSignOne} hashAndSignOne
6971
*/
7072

7173
/** @type {Tx} */
@@ -250,7 +252,60 @@ var DashTx = ("object" === typeof module && exports) || {};
250252
}
251253
void Tx._addPrivKeyUtils(privUtils, keys);
252254

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

256311
txInst.legacy = {};
@@ -897,6 +952,7 @@ var DashTx = ("object" === typeof module && exports) || {};
897952
};
898953

899954
/**
955+
* @param {TxInfoSigned} txInfoSigned
900956
* @param {TxInfo} txInfo
901957
* TODO _param {Array<TxInputRaw>} txInfo.inputs - needs type narrowing check
902958
* TODO _param {Array<TxOutput>} txInfo.outputs
@@ -905,128 +961,105 @@ var DashTx = ("object" === typeof module && exports) || {};
905961
* @param {TxDeps} keyUtils
906962
* @returns {Promise<TxInfoSigned>}
907963
*/
908-
Tx._hashAndSignAll = async function (txInfo, keyUtils) {
909-
let sortedInputs = txInfo.inputs.slice(0);
910-
sortedInputs.sort(Tx.sortInputs);
911-
for (let i = 0; i < sortedInputs.length; i += 1) {
912-
let isSelf = sortedInputs[i] === txInfo.inputs[i];
913-
if (!isSelf) {
914-
console.warn(
915-
`txInfo.inputs are not ordered correctly, use txInfo.inputs.sort(Tx.sortInputs)\n(this will be an exception in the next version)`,
916-
);
917-
break;
918-
}
964+
Tx._hashAndSignAll = async function (txInfoSigned, txInfo, keyUtils) {
965+
for (let i = 0; i < txInfo.inputs.length; i += 1) {
966+
let txInputSigned = await DashTx._hashAndSignOne(keyUtils, txInfo, i);
967+
txInfoSigned.inputs[i] = txInputSigned;
919968
}
920969

921-
let sortedOutputs = txInfo.outputs.slice(0);
922-
sortedOutputs.sort(Tx.sortOutputs);
923-
for (let i = 0; i < sortedOutputs.length; i += 1) {
924-
let isSelf = sortedOutputs[i] === txInfo.outputs[i];
925-
if (!isSelf) {
926-
console.warn(
927-
`txInfo.outputs are not ordered correctly, use txInfo.outputs.sort(Tx.sortOutputs)\n(this will be an exception in the next version)`,
928-
);
929-
break;
930-
}
931-
}
970+
let transaction = Tx.createSigned(txInfoSigned);
932971

933-
/** @type {TxInfoSigned} */
934-
let txInfoSigned = {
935-
/** @type {Array<TxInputSigned>} */
936-
inputs: [],
972+
return {
973+
//@ts-ignore - tsc doesn't support an enum here
974+
inputs: txInfo.inputs,
975+
locktime: txInfo.locktime || 0x0,
937976
outputs: txInfo.outputs,
977+
transaction: transaction,
938978
version: txInfo.version || CURRENT_VERSION,
939-
locktime: txInfo.locktime || 0x00,
940-
transaction: "",
941979
};
980+
};
942981

943-
/**
944-
* @param {TxPrivateKey} privKey
945-
*/
946-
function createGetPrivateKey(privKey) {
947-
return function () {
948-
return privKey;
949-
};
950-
}
951-
952-
function throwGetPrivateKeyError() {
953-
if (false) {
954-
return new Uint8Array(0);
955-
}
982+
/**
983+
* @param {TxPrivateKey} privKey
984+
*/
985+
function createGetPrivateKey(privKey) {
986+
return function () {
987+
return privKey;
988+
};
989+
}
956990

957-
const msg =
958-
"getPrivateKey() is only valid for the lifetime of transaction signing process";
959-
throw new Error(msg);
991+
function throwGetPrivateKeyError() {
992+
if (false) {
993+
return new Uint8Array(0);
960994
}
961995

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

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

1020-
let transaction = Tx.createSigned(txInfoSigned);
1025+
let pubKeyHex = txInput.publicKey;
1026+
if (!pubKeyHex) {
1027+
let getPrivateKey = createGetPrivateKey(privKey);
1028+
let _txInput = Object.assign({}, { getPrivateKey }, txInput);
1029+
let pubKey = await keyUtils.getPublicKey(_txInput, i, txInfo.inputs);
1030+
pubKeyHex = Tx.utils.bytesToHex(pubKey);
1031+
_txInput.getPrivateKey = throwGetPrivateKeyError;
1032+
}
1033+
if ("string" !== typeof pubKeyHex) {
1034+
let warn = new Error("stack");
1035+
console.warn(
1036+
`utxo inputs should be plain JSON and use hex rather than buffers for 'publicKey'`,
1037+
warn.stack,
1038+
);
1039+
pubKeyHex = Tx.utils.bytesToHex(pubKeyHex);
1040+
}
10211041

1022-
return {
1023-
//@ts-ignore - tsc doesn't support an enum here
1024-
inputs: txInfo.inputs,
1025-
locktime: txInfo.locktime || 0x0,
1026-
outputs: txInfo.outputs,
1027-
transaction: transaction,
1028-
version: txInfo.version || CURRENT_VERSION,
1042+
/** @type TxInputSigned */
1043+
let txInputSigned = {
1044+
txId: txInput.txId || txInput.txid,
1045+
txid: txInput.txId || txInput.txid,
1046+
outputIndex: txInput.outputIndex,
1047+
signature: sigHex,
1048+
publicKey: pubKeyHex,
1049+
sigHashType: _sigHashType,
10291050
};
1051+
1052+
// expose _actual_ values used, for debugging
1053+
let txHashHex = Tx.utils.bytesToHex(txHashBuf);
1054+
Object.assign({
1055+
_hash: txHashHex,
1056+
_signature: sigHex.toString(),
1057+
_lockScript: txInfo.inputs[i].script,
1058+
_publicKey: pubKeyHex,
1059+
_sigHashType: _sigHashType,
1060+
});
1061+
1062+
return txInputSigned;
10301063
};
10311064

10321065
Tx.createRaw = function (opts) {
@@ -1860,6 +1893,12 @@ if ("object" === typeof module) {
18601893
* @param {Array<TxPrivateKey>} [keys]
18611894
*/
18621895

1896+
/**
1897+
* @callback TxHashAndSignOne
1898+
* @param {TxInfo} txInfo
1899+
* @param {Number} i - which to sign
1900+
*/
1901+
18631902
/**
18641903
* @callback TxHashPartial
18651904
* @param {String} txHex - signable tx hex (like raw tx, but with (lock)script)

0 commit comments

Comments
 (0)