Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: private export of internal utils and constants for monkey-patching #45

Merged
merged 6 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ Tx.create({ sign, getPrivateKey });
tx.legacy.draftSingleOutput({ utxos, inputs, output });
tx.legacy.finalizePresorted(txDraft, keys);

Tx.createDonationOutput();

Tx.appraise({ inputs, outputs });
Tx.getId(txHex);

Expand Down
162 changes: 99 additions & 63 deletions dashtx.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* @prop {TxToDash} toDash
* @prop {TxToSats} toSats
* @prop {TxCreate} create
* @prop {TxCreateDonationOutput} createDonationOutput
* @prop {TxCreateRaw} createRaw
* @prop {TxCreateHashable} createHashable
* @prop {TxCreateSigned} createSigned
Expand All @@ -35,6 +36,8 @@
* @prop {Function} _hashAndSignAll
* @prop {Function} _legacyMustSelectInputs
* @prop {Function} _legacySelectOptimalUtxos
* @prop {Function} _packInputs
* @prop {Function} _packOutputs
*/

/**
Expand Down Expand Up @@ -101,10 +104,24 @@ var DashTx = ("object" === typeof module && exports) || {};
const PKH_SIZE = (20).toString(16); // 0x14
const PKH_SCRIPT_SIZE = (25).toString(16); // 0x19

//@ts-ignore - for debug only
Tx._OP_DUP_HEX = OP_DUP;
//@ts-ignore - for debug only
Tx._OP_HASH160_HEX = OP_HASH160;
//@ts-ignore - for debug only
Tx._OP_EQUALVERIFY_HEX = OP_EQUALVERIFY;
//@ts-ignore - for debug only
Tx._OP_CHECKSIG_HEX = OP_CHECKSIG;
//@ts-ignore - for debug only
Tx._PKH_SIZE_HEX = PKH_SIZE;
//@ts-ignore - for debug only
Tx._PKH_SCRIPT_SIZE_HEX = PKH_SCRIPT_SIZE;

const E_LITTLE_INT =
"JavaScript 'Number's only go up to uint53, you must use 'BigInt' (ex: `let amount = 18014398509481984n`) for larger values";
const E_TOO_BIG_INT =
"JavaScript 'BigInt's are arbitrarily large, but you may only use up to UINT64 for transactions";
const E_NO_OUTPUTS = `'outputs' list must not be empty; use .createDonationOutput({ message }) to burn inputs`;

Tx.SATOSHIS = SATOSHIS;
Tx.LEGACY_DUST = 2000;
Expand Down Expand Up @@ -177,6 +194,11 @@ var DashTx = ("object" === typeof module && exports) || {};
let size = 0;

for (let output of outputs) {
if (output.message) {
if (!output.memo) {
output.memo = TxUtils.strToHex(output.message);
}
}
if (output.memo) {
let memoSize = output.memo.length / 2;
if (memoSize > MAX_U8) {
Expand Down Expand Up @@ -915,10 +937,6 @@ var DashTx = ("object" === typeof module && exports) || {};
inputs: [],
outputs: txInfo.outputs,
version: txInfo.version || CURRENT_VERSION,
//@ts-ignore - debug only
_DANGER_donate: txInfo._DANGER_donate,
//@ts-ignore
_donation_memo: txInfo._donation_memo,
};

// temp shim
Expand Down Expand Up @@ -1078,32 +1096,41 @@ var DashTx = ("object" === typeof module && exports) || {};
outputs,
/* maxFee = 10000, */
_debug = false,
//@ts-ignore - debug only
_DANGER_donate = false,
//@ts-ignore
_donation_memo,
}) {
let sep = "";
let _sep = "";
if (_debug) {
sep = "\n";
_sep = "\n";
}

// let txMap = {};

/** @type Array<String> */
let tx = [];
let v = toUint32LE(version);
let v = TxUtils._toUint32LE(version);
tx.push(v);
// txMap.version = v;

void Tx._packInputs({ tx, inputs, _sep });
void Tx._packOutputs({ tx, outputs, _sep });

let locktimeHex = TxUtils._toUint32LE(locktime);
tx.push(locktimeHex);

let txHex = tx.join(_sep);
return txHex;
};

/**
* Privately exported for use by DashJoin.js
* @param {Object} opts
* @param {Array<String>} opts.tx
* @param {Array<TxInputRaw|TxInputHashable|TxInputSigned>} opts.inputs
* @param {String} [opts._sep] - string to join hex segements ('' or '\n')
* @returns {Array<String>} - tx (original is modified if provided)
*/
Tx._packInputs = function ({ tx = [], inputs, _sep = "" }) {
let nInputs = Tx.utils.toVarInt(inputs.length);
tx.push(nInputs);
// txMap.input_count = nInputs;
// txMap.inputs = [];

for (let input of inputs) {
let inputHex = [];
// txMap.inputs.push(inputHex);

let txId = input.txId;
if (!txId) {
Expand All @@ -1127,7 +1154,7 @@ var DashTx = ("object" === typeof module && exports) || {};
"expected utxo property 'outputIndex' to be an integer representing this input's previous output index",
);
}
let reverseVout = toUint32LE(voutIndex);
let reverseVout = TxUtils._toUint32LE(voutIndex);
inputHex.push(reverseVout);

//@ts-ignore - enum types not handled properly here
Expand Down Expand Up @@ -1165,41 +1192,37 @@ var DashTx = ("object" === typeof module && exports) || {};
let sequence = "ffffffff";
inputHex.push(sequence);

let txIn = inputHex.join(sep);
let txIn = inputHex.join(_sep);
tx.push(txIn);
}

if (!outputs.length) {
if (!_DANGER_donate) {
throw new Error(
`'outputs' list must not be empty - use the developer debug option '_DANGER_donate: true' to bypass`,
);
}

let memo = _donation_memo;
if (!memo) {
let encoder = new TextEncoder();
let gifts = ["💸", "🎁", "🧧"];
let indexIsh = Math.random() * 3;
let index = Math.floor(indexIsh);
let gift = encoder.encode(gifts[index]);
memo = TxUtils.bytesToHex(gift);
}
return tx;
};

outputs.push({
satoshis: 0,
memo: memo,
});
/**
* Privately exported for use by DashJoin.js
* @param {Object} opts
* @param {Array<String>} opts.tx
* @param {Array<TxOutput>} opts.outputs
* @param {String} [opts._sep] - string to join hex segements ('' or '\n')
* @returns {Array<String>} - tx (original is modified if provided)
*/
Tx._packOutputs = function ({ tx = [], outputs, _sep = "" }) {
if (!outputs.length) {
throw new Error(E_NO_OUTPUTS);
}

let nOutputs = Tx.utils.toVarInt(outputs.length);
tx.push(nOutputs);
// txMap.output_count = nOutputs;
// txMap.outputs = [];

for (let i = 0; i < outputs.length; i += 1) {
let output = outputs[i];

if (output.message) {
if (!output.memo) {
output.memo = TxUtils.strToHex(output.message);
}
}
if (output.memo) {
let invalid = output.satoshis || output.address || output.pubKeyHash;
if (invalid) {
Expand All @@ -1209,21 +1232,19 @@ var DashTx = ("object" === typeof module && exports) || {};
}

let outputHex = Tx._createMemoScript(output.memo, i);
// txMap.outputs.push(outputHex);

let txOut = outputHex.join(sep);
let txOut = outputHex.join(_sep);
tx.push(txOut);
continue;
}

/** @type {Array<String>} */
let outputHex = [];
// txMap.outputs.push(outputHex);

if (!output.satoshis) {
throw new Error(`every output must have 'satoshis'`);
}
let satoshis = toUint64LE(output.satoshis);
let satoshis = TxUtils._toUint64LE(output.satoshis);
outputHex.push(satoshis);

if (!output.pubKeyHash) {
Expand All @@ -1243,18 +1264,25 @@ var DashTx = ("object" === typeof module && exports) || {};
outputHex.push(output.pubKeyHash);
outputHex.push(`${OP_EQUALVERIFY}${OP_CHECKSIG}`);

let txOut = outputHex.join(sep);
let txOut = outputHex.join(_sep);
tx.push(txOut);
}

let locktimeHex = toUint32LE(locktime);
tx.push(locktimeHex);
// txMap.locktime = locktimeHex;
return tx;
};

// console.log("DEBUG txMap", txMap);
Tx.createDonationOutput = function (opts) {
let satoshis = 0;
let message = opts?.message;
if (!message) {
let gifts = ["💸", "🎁", "🧧"];
let indexIsh = Math.random() * 3;
let index = Math.floor(indexIsh);
message = gifts[index];
}

let txHex = tx.join(sep);
return txHex;
let output = { satoshis, message };
return output;
};

/**
Expand All @@ -1263,7 +1291,7 @@ var DashTx = ("object" === typeof module && exports) || {};
*/
Tx._createMemoScript = function (memoHex, i = 0) {
let outputHex = [];
let satoshis = toUint64LE(0);
let satoshis = TxUtils._toUint64LE(0);
outputHex.push(satoshis);

assertHex(memoHex, `output[${i}].memo`);
Expand Down Expand Up @@ -1346,7 +1374,7 @@ var DashTx = ("object" === typeof module && exports) || {};
Tx.hashPartial = async function (txHex, sigHashType = Tx.SIGHASH_ALL) {
let txSignable = txHex;
if (sigHashType) {
let sigHashTypeHex = toUint32LE(sigHashType);
let sigHashTypeHex = TxUtils._toUint32LE(sigHashType);
txSignable = `${txSignable}${sigHashTypeHex}`;
}
//console.log("Signable Tx Hex");
Expand Down Expand Up @@ -1495,17 +1523,17 @@ var DashTx = ("object" === typeof module && exports) || {};

//@ts-ignore
if (n <= MAX_U16) {
return "fd" + toUint32LE(n).slice(0, 4);
return "fd" + TxUtils._toUint32LE(n).slice(0, 4);
}

//@ts-ignore
if (n <= MAX_U32) {
return "fe" + toUint32LE(n);
return "fe" + TxUtils._toUint32LE(n);
}

//@ts-ignore
if (n <= MAX_U53) {
return "ff" + toUint64LE(n);
return "ff" + TxUtils._toUint64LE(n);
}

if ("bigint" !== typeof n) {
Expand All @@ -1515,7 +1543,7 @@ var DashTx = ("object" === typeof module && exports) || {};
}

if (n <= MAX_U64) {
return "ff" + toUint64LE(n);
return "ff" + TxUtils._toUint64LE(n);
}

let err = new Error(E_TOO_BIG_INT);
Expand All @@ -1528,7 +1556,7 @@ var DashTx = ("object" === typeof module && exports) || {};
* which is true in practice, and much simpler.
* @param {BigInt|Number} n - 32-bit positive int to encode
*/
function toUint32LE(n) {
TxUtils._toUint32LE = function (n) {
// make sure n is uint32/int53, not int32
//n = n >>> 0;

Expand All @@ -1537,15 +1565,15 @@ var DashTx = ("object" === typeof module && exports) || {};

let hexLE = Tx.utils.reverseHex(hex);
return hexLE;
}
};

/**
* This can handle Big-Endian CPUs, which don't exist,
* and looks too complicated.
* @param {BigInt|Number} n - 64-bit BigInt or <= 53-bit Number to encode
* @returns {String} - 8 Little-Endian bytes
*/
function toUint64LE(n) {
TxUtils._toUint64LE = function (n) {
let bn;
if ("bigint" === typeof n) {
bn = n;
Expand All @@ -1569,7 +1597,7 @@ var DashTx = ("object" === typeof module && exports) || {};
let hex = hexArr.join("");

return hex;
}
};

/** @type TxToVarIntSize */
TxUtils.toVarIntSize = function (n) {
Expand Down Expand Up @@ -1766,6 +1794,7 @@ if ("object" === typeof module) {
/**
* @typedef TxOutput
* @prop {String} [memo] - hex bytes of a memo (incompatible with pubKeyHash / address)
* @prop {String} [message] - memo, but as a UTF-8 string
* @prop {String} [address] - payAddr as Base58Check (human-friendly)
* @prop {String} [pubKeyHash] - payAddr's raw hex value (decoded, not Base58Check)
* @prop {Uint53} satoshis - the number of smallest units of the currency
Expand Down Expand Up @@ -1818,6 +1847,13 @@ if ("object" === typeof module) {
* @returns {Uint32}
*/

/**
* Create a donation output with a nice message.
* @callback TxCreateDonationOutput
* @param {Object} [opts]
* @param {String} [opts.message] - UTF-8 Memo String
*/

/**
* @callback TxCreateHashable
* @param {TxInfo} txInfo
Expand Down Expand Up @@ -1973,5 +2009,5 @@ if ("object" === typeof module) {
/**
* @callback TxStringToHex
* @param {String} utf8
* @returns {String}
* @returns {String} - encoded bytes as hex
*/
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dashtx",
"version": "0.14.3",
"version": "0.14.4",
"description": "Create DASH Transactions with Vanilla JS (0 deps, cross-platform)",
"main": "index.js",
"files": [
Expand Down
Loading
Loading