From bb3b1024f6784848992f41e4f0e5d93dfda15fc6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 9 Apr 2024 22:30:19 +0000 Subject: [PATCH] feat: implement CoinJoin in JavaScript - use rpc to select from MNs - getverack response - parse p2p message header - parse p2p version response - send verack - read full messages - get wallet + address info - fill and denominate wallet - create collateral tx - ping/pong + unit test - compose DSA and get responses - test DSA packing - senddsq - add fixtures from observed-working regtest traffic - pack DSI message - split denominations - create and test DSSU parser - create and test DSQ parser - add salt to wallets to create different 'users' - sort evonodes so all 'users' join the same evonode - add string time output for easy comparison / debugging - create and test packDsi - allow arbitrary whitespace in hex of test fixtures - get and parse DSF, add test fixtures - add known-working transaction to test fixtures - add Packer.packDss() - add test fixtures for known-good dsa, dsf, and dss messages - add first-ever ALL|ANYONECANPAY test fixtures --- coinjoin.js | 53 + demo.js | 1316 ++++++++++++++++++++++++ fixtures/anyonecanpay.0.dsa.hex | 1 + fixtures/anyonecanpay.0.input-key.hex | 1 + fixtures/anyonecanpay.0.input.addr | 2 + fixtures/anyonecanpay.1.dsf.hex | 1 + fixtures/anyonecanpay.1.tx-raw.hex | 1 + fixtures/anyonecanpay.2.input-hash.hex | 1 + fixtures/anyonecanpay.2.input-sig.hex | 1 + fixtures/anyonecanpay.2.input-tx.hex | 2 + fixtures/anyonecanpay.3.dss.hex | 3 + fixtures/anyonecanpay.3.tx.hex | 1 + fixtures/anyonecanpay.4.dsc.hex | 4 + fixtures/dsa.hex | 2 + fixtures/dsf.hex | 41 + fixtures/dsi.hex | 78 ++ fixtures/dsq.hex | 2 + fixtures/dsq.json | 11 + fixtures/dssu.hex | 2 + fixtures/dssu.json | 9 + fixtures/he-1-asn1-sig.hex | 2 + fixtures/he-1-hashtx.hex | 41 + fixtures/he-1-p2pkh.hex | 1 + fixtures/he-1-privkey.hex | 1 + fixtures/he-1-sighash.hex | 1 + fixtures/he-1-utxo.json | 5 + fixtures/he-cj-full.log | 58 ++ fixtures/he-dsf.hex | 30 + fixtures/he-dss-partial.hex | 13 + fixtures/he-dss.hex | 49 + fixtures/mnauth.hex | 2 + fixtures/ping.hex | 2 + fixtures/sendaddrv2.hex | 1 + fixtures/senddsq.hex | 2 + fixtures/verack.hex | 1 + fixtures/version.hex | 2 + package-lock.json | 491 ++------- package.json | 10 +- packer.js | 748 ++++++++++++++ parser.js | 397 +++++++ tests/dsa.js | 52 + tests/dsf.js | 60 ++ tests/dsi.js | 100 ++ tests/dsq.js | 62 ++ tests/dss.sighashall.js | 154 +++ tests/dssu.js | 55 + tests/ping-pong.js | 155 +++ 47 files changed, 3604 insertions(+), 423 deletions(-) create mode 100644 coinjoin.js create mode 100644 demo.js create mode 100644 fixtures/anyonecanpay.0.dsa.hex create mode 100644 fixtures/anyonecanpay.0.input-key.hex create mode 100644 fixtures/anyonecanpay.0.input.addr create mode 100644 fixtures/anyonecanpay.1.dsf.hex create mode 100644 fixtures/anyonecanpay.1.tx-raw.hex create mode 100644 fixtures/anyonecanpay.2.input-hash.hex create mode 100644 fixtures/anyonecanpay.2.input-sig.hex create mode 100644 fixtures/anyonecanpay.2.input-tx.hex create mode 100644 fixtures/anyonecanpay.3.dss.hex create mode 100644 fixtures/anyonecanpay.3.tx.hex create mode 100644 fixtures/anyonecanpay.4.dsc.hex create mode 100644 fixtures/dsa.hex create mode 100644 fixtures/dsf.hex create mode 100644 fixtures/dsi.hex create mode 100644 fixtures/dsq.hex create mode 100644 fixtures/dsq.json create mode 100644 fixtures/dssu.hex create mode 100644 fixtures/dssu.json create mode 100644 fixtures/he-1-asn1-sig.hex create mode 100644 fixtures/he-1-hashtx.hex create mode 100644 fixtures/he-1-p2pkh.hex create mode 100644 fixtures/he-1-privkey.hex create mode 100644 fixtures/he-1-sighash.hex create mode 100644 fixtures/he-1-utxo.json create mode 100644 fixtures/he-cj-full.log create mode 100644 fixtures/he-dsf.hex create mode 100644 fixtures/he-dss-partial.hex create mode 100644 fixtures/he-dss.hex create mode 100644 fixtures/mnauth.hex create mode 100644 fixtures/ping.hex create mode 100644 fixtures/sendaddrv2.hex create mode 100644 fixtures/senddsq.hex create mode 100644 fixtures/verack.hex create mode 100644 fixtures/version.hex create mode 100644 packer.js create mode 100644 parser.js create mode 100644 tests/dsa.js create mode 100644 tests/dsf.js create mode 100644 tests/dsi.js create mode 100644 tests/dsq.js create mode 100644 tests/dss.sighashall.js create mode 100644 tests/dssu.js create mode 100644 tests/ping-pong.js diff --git a/coinjoin.js b/coinjoin.js new file mode 100644 index 0000000..7c2de2d --- /dev/null +++ b/coinjoin.js @@ -0,0 +1,53 @@ +'use strict'; + +let CoinJoin = module.exports; + +CoinJoin.STANDARD_DENOMINATIONS = [ + // 0.00100001 + 100001, + // 0.01000010 + 1000010, + // 0.10000100 + 10000100, + // 1.00001000 + 100001000, + // 10.0000100 + 1000010000, +]; + +// TODO the spec seems to be more of an ID, though +// the implementation makes it look more like a mask... +CoinJoin.STANDARD_DENOMINATION_MASKS = { + // 0.00100001 + 100001: 0b00010000, + // 0.01000010 + 1000010: 0b00001000, + // 0.10000100 + 10000100: 0b00000100, + // 1.00001000 + 100001000: 0b00000010, + // 10.00010000 + 1000010000: 0b00000001, +}; + +CoinJoin.STANDARD_DENOMINATIONS_MAP = { + // 0.00100001 + 0b00010000: 100001, + // 0.01000010 + 0b00001000: 1000010, + // 0.10000100 + 0b00000100: 10000100, + // 1.00001000 + 0b00000010: 100001000, + // 10.00010000 + 0b00000001: 1000010000, +}; + +// (STANDARD_DENOMINATIONS[0] / 10).floor(); +CoinJoin.COLLATERAL = 10000; +// COLLATERAL * 4 +CoinJoin.MAX_COLLATERAL = 40000; + +CoinJoin.isDenominated = function (sats) { + return CoinJoin.STANDARD_DENOMINATIONS.includes(sats); +}; diff --git a/demo.js b/demo.js new file mode 100644 index 0000000..e40a6b1 --- /dev/null +++ b/demo.js @@ -0,0 +1,1316 @@ +'use strict'; + +let DotEnv = require('dotenv'); +void DotEnv.config({ path: '.env' }); +void DotEnv.config({ path: '.env.secret' }); + +//@ts-ignore - ts can't understand JSON, still... +let pkg = require('./package.json'); + +let Net = require('node:net'); + +let CoinJoin = require('./coinjoin.js'); +let Packer = require('./packer.js'); // TODO rename packer +let Parser = require('./parser.js'); + +let DashPhrase = require('dashphrase'); +let DashHd = require('dashhd'); +let DashKeys = require('dashkeys'); +let DashRpc = require('dashrpc'); +let DashTx = require('dashtx'); +let Secp256k1 = require('@dashincubator/secp256k1'); + +const DENOM_LOWEST = 100001; +const PREDENOM_MIN = DENOM_LOWEST + 193; +// const MIN_UNUSED = 2500; +const MIN_UNUSED = 1000; +const MIN_BALANCE = 100001 * 1000; +const MIN_DENOMINATED = 200; + +// https://github.com/dashpay/dash/blob/v19.x/src/coinjoin/coinjoin.h#L39 +// const COINJOIN_ENTRY_MAX_SIZE = 9; // real +const COINJOIN_ENTRY_MAX_SIZE = 2; // just for testing right now + +let rpcConfig = { + protocol: 'http', // https for remote, http for local / private networking + user: process.env.DASHD_RPC_USER, + pass: process.env.DASHD_RPC_PASS || process.env.DASHD_RPC_PASSWORD, + host: process.env.DASHD_RPC_HOST || '127.0.0.1', + port: process.env.DASHD_RPC_PORT || '19898', // mainnet=9998, testnet=19998, regtest=19898 + timeout: 10 * 1000, // bump default from 5s to 10s for up to 10k addresses +}; +if (process.env.DASHD_RPC_TIMEOUT) { + let rpcTimeoutSec = parseFloat(process.env.DASHD_RPC_TIMEOUT); + rpcConfig.timeout = rpcTimeoutSec * 1000; +} + +async function main() { + /* jshint maxstatements: 1000 */ + /* jshint maxcomplexity: 100 */ + + let walletSalt = process.argv[2] || ''; + let isHelp = walletSalt === 'help' || walletSalt === '--help'; + if (isHelp) { + throw new Error( + `USAGE\n ${process.argv[1]} [wallet-salt]\n\nEXAMPLE\n ${process.argv[1]} 'luke|han|chewie'`, + ); + } + + let walletPhrase = process.env.DASH_WALLET_PHRASE || ''; + if (!walletPhrase) { + throw new Error('missing DASH_WALLET_PHRASE'); + } + + let network = 'regtest'; + // let minimumParticipants = Packer.NETWORKS[network].minimumParticiparts; + rpcConfig.onconnected = async function () { + let rpc = this; + console.info(`[info] rpc client connected ${rpc.host}`); + }; + + let rpc = new DashRpc(rpcConfig); + rpc.onconnected = rpcConfig.onconnected; + let height = await rpc.init(rpc); + console.info(`[info] rpc server is ready. Height = ${height}`); + + let keyUtils = { + sign: async function (privKeyBytes, hashBytes) { + let sigOpts = { canonical: true, extraEntropy: true }; + let sigBytes = await Secp256k1.sign(hashBytes, privKeyBytes, sigOpts); + return sigBytes; + }, + getPrivateKey: async function (input) { + if (!input.address) { + //throw new Error('should put the address on the input there buddy...'); + console.warn('missing address:', input.txid, input.outputIndex); + return null; + } + let data = keysMap[input.address]; + let isUint = data.index > -1; + if (!isUint) { + throw new Error(`missing 'index'`); + } + // TODO map xkey by walletid + let addressKey = await xreceiveKey.deriveAddress(data.index); + + { + // sanity check + let privKeyHex = DashTx.utils.bytesToHex(addressKey.privateKey); + if (data._privKeyHex !== privKeyHex) { + if (data._privKeyHex) { + console.log(data._privKeyHex); + console.log(privKeyHex); + throw new Error('mismatch key bytes'); + } + data._privKeyHex = privKeyHex; + } + } + return addressKey.privateKey; + }, + toPublicKey: async function (privKeyBytes) { + // TODO use secp256k1 directly + return await DashKeys.utils.toPublicKey(privKeyBytes); + }, + }; + let dashTx = DashTx.create(keyUtils); + + let testCoin = '1'; + let seedBytes = await DashPhrase.toSeed(walletPhrase, walletSalt); + let walletKey = await DashHd.fromSeed(seedBytes, { + coinType: testCoin, + versions: DashHd.TESTNET, + }); + let walletId = await DashHd.toId(walletKey); + + let accountHdpath = `m/44'/1'/0'`; + let accountKey = await walletKey.deriveAccount(0); + let xreceiveKey = await accountKey.deriveXKey(walletKey, 0); //jshint ignore:line + // let xchangeKey = await accountKey.deriveXKey(walletKey, 1); + // let xprvHdpath = `m/44'/5'/0'/0`; + // let xprvKey = await DashHd.derivePath(walletKey, xprvHdpath); + + // generate bunches of keys + // remove the leading `m/` or `m'/` + let partialPath = accountHdpath.replace(/^m'?\//, ''); + let totalBalance = 0; + let keysMap = {}; //jshint ignore:line + let used = []; + let addresses = []; + let unusedMap = {}; + let index = 0; + let numAddresses = 100; + for (;;) { + let uncheckedAddresses = []; + for (let i = 0; i < numAddresses; i += 1) { + let addressKey = await xreceiveKey.deriveAddress(index); + + // Descriptors are in the form of + // - pkh(xpub123...abc/2) - for the 3rd address of a receiving or change xpub + // - pkh(xpub456...def/0/2) - for the 3rd receive address of an account xpub + // - pkh([walletid/44'/0'/0']xpub123...abc/0/2) - same, plus wallet & hd info + // - pkh([walletid/44'/0'/0'/0/2]Xaddr...#checksum) - same, but the address + // See also: https://github.com/dashpay/dash/blob/master/doc/descriptors.md + // TODO sort out sha vs double-sha vs fingerprint + let descriptor = `pkh([${walletId}/${partialPath}/0/${index}])`; + let address = await DashHd.toAddr(addressKey.publicKey, { + version: 'testnet', + }); + // let utxosRpc = await rpc.getAddressUtxos({ addresses: [address] }); + // let utxos = utxosRpc.result; + // console.log('utxosRpc.result.length', utxosRpc.result.length); + + let data = keysMap[address]; + if (!data) { + data = { + walletId: walletId, + prefix: "m/44'/1'", + account: 0, + usage: 0, + index: index, + descriptor: descriptor, + address: address, + // uxtos: utxos, + used: false, + reserved: 0, + satoshis: 0, + }; + // console.log('[debug] addr info', data); + addresses.push(address); + uncheckedAddresses.push(address); + } + keysMap[index] = data; + keysMap[address] = data; + // console.log('[DEBUG] address:', address); + if (!data.used) { + unusedMap[address] = data; + } + + index += 1; + } + // console.log('[debug] addresses.length', addresses.length); + // console.log('[debug] uncheckedAddresses.length', uncheckedAddresses.length); + + // TODO segment unused addresses + // let unusedAddresses = Object.keys(unusedMap); + // console.log('[debug] unusedAddresses.length', unusedAddresses.length); + + let mempooldeltas = await rpc.getAddressMempool({ + addresses: uncheckedAddresses, + // addresses: unusedAddresses, + }); + // console.log( + // '[debug] mempooldeltas.result.length', + // mempooldeltas.result.length, + // ); + // TODO check that we have a duplicate in both deltas by using txid, vin/vout + for (let delta of mempooldeltas.result) { + totalBalance += delta.satoshis; + + let data = keysMap[delta.address]; + data.satoshis += delta.satoshis; + data.used = true; + if (!used.includes(data)) { + used.push(data); + } + delete unusedMap[data.address]; + } + + let deltas = await rpc.getAddressDeltas({ + addresses: uncheckedAddresses, + }); + // console.log('[debug] deltas.result.length', deltas.result.length); + for (let delta of deltas.result) { + totalBalance += delta.satoshis; + + let data = keysMap[delta.address]; + data.satoshis += delta.satoshis; + data.used = true; + if (!used.includes(data)) { + used.push(data); + } + delete unusedMap[data.address]; + } + + let numUnused = addresses.length - used.length; + if (numUnused >= MIN_UNUSED) { + // console.log('[debug] addresses.length', addresses.length); + // console.log('[debug] used.length', used.length); + break; + } + } + console.log('[debug] wallet balance:', totalBalance); + + let denomination = 100001 * 1; + + void (await generateMinBalance()); + void (await generateDenominations()); + + // TODO sort denominated + // for (let addr of addresses) { ... } + + async function generateMinBalance() { + for (let addr of addresses) { + // console.log('[debug] totalBalance:', totalBalance); + if (totalBalance >= MIN_BALANCE) { + break; + } + + let data = keysMap[addr]; + let isAvailable = !data.used && !data.reserved; + if (!isAvailable) { + continue; + } + + void (await generateToAddressAndUpdateBalance(data)); + } + } + + async function generateDenominations() { + // jshint maxcomplexity: 25 + let denomCount = 0; + let denominable = []; + let denominated = {}; + for (let addr of addresses) { + let data = keysMap[addr]; + if (data.reserved) { + continue; + } + if (data.satoshis === 0) { + continue; + } + + // TODO denominations.includes(data.satoshis) + let isUndenominated = data.satoshis % DENOM_LOWEST; + if (isUndenominated) { + if (data.satoshis >= PREDENOM_MIN) { + denominable.push(data); + } + continue; + } + + if (!denominated[data.satoshis]) { + denominated[data.satoshis] = []; + } + denomCount += 1; + denominated[data.satoshis].push(data); + } + + // CAVEAT: this fee-approximation strategy that guarantees + // to denominate all coins _correctly_, but in some cases will + // create _smaller_ denominations than necessary - specifically + // 10 x 100001 instead of 1 x 1000010 when the lowest order of + // coin is near the single coin value (i.e. 551000010) + // (because 551000010 / 100194 yields 5499 x 100001 coins + full fees, + // but we actually only generate 5 + 4 + 9 + 9 = 27 coins, leaving + // well over 5472 * 193 extra value) + for (let data of denominable) { + // console.log('[debug] denominable', data); + if (denomCount >= MIN_DENOMINATED) { + break; + } + + let fee = data.satoshis; + + // 123 means + // - 3 x 100001 + // - 2 x 1000010 + // - 1 x 10000100 + let order = data.satoshis / PREDENOM_MIN; + order = Math.floor(order); + let orderStr = order.toString(); + // TODO mod and divide to loop and shift positions, rather than stringify + let orders = orderStr.split(''); + orders.reverse(); + + // TODO Math.min(orders.length, STANDARD_DENOMS.length); + // let numOutputs = 0; + let denomOutputs = []; + // let magnitudes = [0]; + for (let i = 0; i < orders.length; i += 1) { + let order = orders[i]; + let count = parseInt(order, 10); + let orderSingle = DENOM_LOWEST * Math.pow(10, i); + // let orderTotal = count * orderSingle; + // numOutputs += count; + for (let i = 0; i < count; i += 1) { + fee -= orderSingle; + denomOutputs.push({ + satoshis: orderSingle, + }); + } + // magnitudes.push(count); + } + // example: + // [ 0, 3, 2, 1 ] + // - 0 x 100001 * 0 + // - 3 x 100001 * 1 + // - 2 x 100001 * 10 + // - 1 x 100001 * 100 + + // console.log('[debug] denom outputs', denomOutputs); + // console.log('[debug] fee', fee); + // Note: this is where we reconcile the difference between + // the number of the smallest denom, and the number of actual denoms + // (and where we may end up with 10 x LOWEST, which we could carry + // over into the next tier, but won't right now for simplicity). + for (;;) { + let numInputs = 1; + let fees = DashTx._appraiseCounts(numInputs, denomOutputs.length + 1); + let nextCoinCost = DENOM_LOWEST + fees.max; + if (fee < nextCoinCost) { + // TODO split out 10200 (or 10193) collaterals as well + break; + } + fee -= DashTx.OUTPUT_SIZE; + fee -= DENOM_LOWEST; + denomOutputs.push({ + satoshis: DENOM_LOWEST, + }); + // numOutputs += 1; + // magnitudes[1] += 1; + } + // console.log('[debug] denom outputs', denomOutputs); + + let changes = []; + for (let addr of addresses) { + if (denomOutputs.length === 0) { + break; + } + + let unused = unusedMap[addr]; + if (!unused) { + continue; + } + + unused.reserved = Date.now(); + delete unusedMap[addr]; + + let denomValue = denomOutputs.pop(); + if (!denomValue) { + break; + } + + unused.satoshis = denomValue.satoshis; + changes.push(unused); + } + + let txInfo; + { + let utxosRpc = await rpc.getAddressUtxos({ addresses: [data.address] }); + let utxos = utxosRpc.result; + for (let utxo of utxos) { + console.log('[debug] input utxo', utxo); + // utxo.sigHashType = 0x01; + utxo.address = data.address; + if (utxo.txid) { + // TODO fix in dashtx + utxo.txId = utxo.txid; + } + } + for (let change of changes) { + let pubKeyHashBytes = await DashKeys.addrToPkh(change.address, { + version: 'testnet', + }); + change.pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + } + + txInfo = { + version: 3, + inputs: utxos, + outputs: changes, + locktime: 0, + }; + txInfo.inputs.sort(DashTx.sortInputs); + txInfo.outputs.sort(DashTx.sortOutputs); + } + + let keys = []; + for (let input of txInfo.inputs) { + let data = keysMap[input.address]; + let addressKey = await xreceiveKey.deriveAddress(data.index); + keys.push(addressKey.privateKey); + // DEBUG check pkh hex + let pubKeyHashBytes = await DashKeys.addrToPkh(data.address, { + version: 'testnet', + }); + data.pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + console.log(data); + } + let txInfoSigned = await dashTx.hashAndSignAll(txInfo); + + console.log('[debug], txInfo, keys, txSigned'); + console.log(txInfo); + console.log(keys); + console.log(txInfoSigned); + await sleep(150); + let txRpc = await rpc.sendRawTransaction(txInfoSigned.transaction); + await sleep(150); + console.log('[debug] txRpc.result', txRpc.result); + + // TODO don't add collateral coins + for (let change of changes) { + denomCount += 1; + if (!denominated[change.satoshis]) { + denominated[change.satoshis] = []; + } + denominated[change.satoshis].push(change); + change.reserved = 0; + } + } + } + + async function generateToAddressAndUpdateBalance(data) { + let numBlocks = 1; + await sleep(150); + void (await rpc.generateToAddress(numBlocks, data.address)); + await sleep(150); + // let blocksRpc = await rpc.generateToAddress(numBlocks, addr); + // console.log('[debug] blocksRpc', blocksRpc); + + // let deltas = await rpc.getAddressMempool({ addresses: [addr] }); + // console.log('[debug] generatetoaddress mempool', deltas); + // let deltas2 = await rpc.getAddressDeltas({ addresses: [addr] }); + // console.log('[debug] generatetoaddress deltas', deltas); + // let results = deltas.result.concat(deltas2.result); + // for (let delta of results) { + // totalBalance += delta.satoshis; + // keysMap[delta.address].used = true; + // delete unusedMap[delta.address]; + // } + + let utxosRpc = await rpc.getAddressUtxos({ addresses: [data.address] }); + let utxos = utxosRpc.result; + for (let utxo of utxos) { + // console.log(data.index, '[debug] utxo.satoshis', utxo.satoshis); + data.satoshis += utxo.satoshis; + totalBalance += utxo.satoshis; + keysMap[utxo.address].used = true; + delete unusedMap[utxo.address]; + } + } + + // TODO unreserve collateral after positive response + // (and check for use 30 seconds after failure message) + async function getCollateralTx() { + let barelyEnoughest = { satoshis: Infinity, reserved: 0 }; + for (let addr of addresses) { + let data = keysMap[addr]; + if (data.reserved > 0) { + continue; + } + + if (!data.satoshis) { + continue; + } + + if (barelyEnoughest.reserved > 0) { + let isDenom = data.satoshis % DENOM_LOWEST === 0; + if (isDenom) { + continue; + } + } + + if (data.satoshis < CoinJoin.COLLATERAL) { + continue; + } + + if (data.satoshis < barelyEnoughest.satoshis) { + barelyEnoughest = data; + barelyEnoughest.reserved = Date.now(); + } + } + console.log('[debug] barelyEnoughest coin:', barelyEnoughest); + + let collateralTxInfo; + { + let addr = barelyEnoughest.address; + let utxosRpc = await rpc.getAddressUtxos({ addresses: [addr] }); + let utxos = utxosRpc.result; + for (let utxo of utxos) { + console.log('[debug] input utxo', utxo); + // utxo.sigHashType = 0x01; + utxo.address = addr; + if (utxo.txid) { + // TODO fix in dashtx + utxo.txId = utxo.txid; + } + } + + let output; + let leftover = barelyEnoughest.satoshis - CoinJoin.COLLATERAL; + if (leftover >= CoinJoin.COLLATERAL) { + let change = await reserveChangeAddress(); + output = Object.assign({}, change); + // TODO change.used = true; + // change.reserved = 0; + let pubKeyHashBytes = await DashKeys.addrToPkh(output.address, { + version: 'testnet', + }); + output.pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + output.satoshis = leftover; + } else { + output = DashTx.createDonationOutput(); + // TODO 0-byte memo? no outputs (bypassing the normal restriction)? + } + + console.log('[debug] change or memo', output); + let txInfo = { + version: 3, + inputs: utxos, + outputs: [output], + locktime: 0, + }; + txInfo.inputs.sort(DashTx.sortInputs); + txInfo.outputs.sort(DashTx.sortOutputs); + + collateralTxInfo = txInfo; + } + + console.log('[debug] ds* collateral tx', collateralTxInfo); + return collateralTxInfo; + } + + async function reserveChangeAddress() { + for (let addr of addresses) { + let data = keysMap[addr]; + + let isAvailable = !data.used && !data.reserved; + if (!isAvailable) { + continue; + } + + data.reserved = Date.now(); + return data; + } + + let msg = + 'sanity fail: ran out of addresses despite having 500+ unused extra'; + throw new Error(msg); + } + + // async function getPrivateKeys(inputs) { + // let keys = []; + // for (let input of inputs) { + // let privKeyBytes = await keyUtils.getPrivateKey(input); + // keys.push(privKeyBytes); + // } + + // return keys; + // } + + let evonodes = []; + { + let resp = await rpc.masternodelist(); + let evonodesMap = resp.result; + let evonodeProTxIds = Object.keys(evonodesMap); + for (let id of evonodeProTxIds) { + let evonode = evonodesMap[id]; + if (evonode.status === 'ENABLED') { + let hostParts = evonode.address.split(':'); + let evodata = { + id: evonode.id, + hostname: hostParts[0], + port: hostParts[1], + type: evonode.type, + }; + evonodes.push(evodata); + } + } + if (!evonodes.length) { + throw new Error('Sanity Fail: no evonodes online'); + } + } + + // void shuffle(evonodes); + evonodes.sort(byId); + let evonode = evonodes.at(-1); + console.info('[info] chosen evonode:'); + console.log(JSON.stringify(evonode, null, 2)); + + let conn = Net.createConnection({ + host: evonode.hostname, + port: evonode.port, + keepAlive: true, + keepAliveInitialDelay: 3, + //localAddress: rpc.host, + }); + + /** @type {Array} */ + let chunks = []; + let chunksLength = 0; + let errReject; + + function onError(err) { + console.error('Error:'); + console.error(err); + conn.removeListener('error', onError); + errReject(err); + } + function onEnd() { + console.info('[info] disconnected from server'); + } + conn.on('error', onError); + conn.once('end', onEnd); + conn.setMaxListeners(2); + let dataCount = 0; + conn.on('data', function (data) { + console.log('[DEBUG] data'); + console.log(dataCount, data.length, data.toString('hex')); + dataCount += 1; + }); + + /** @type {Array} */ + let messages = []; + /** @type {Object} */ + let listenerMap = {}; + async function goRead() { + let pongSize = Packer.HEADER_SIZE + Packer.PING_SIZE; + let pongMessageBytes = new Uint8Array(pongSize); + for (;;) { + console.log('[debug] readMessage()'); + let msg = await readMessage(); + + if (msg.command === 'ping') { + void Packer.packPong({ + network: network, + message: pongMessageBytes, + nonce: msg.payload, + }); + conn.write(pongMessageBytes); + console.log('[debug] sent pong'); + continue; + } + + if (msg.command === 'dssu') { + let dssu = await Parser.parseDssu(msg.payload); + console.log('[debug] dssu', dssu); + continue; + } + + let i = messages.length; + messages.push(msg); + let listeners = Object.values(listenerMap); + for (let ln of listeners) { + void ln(msg, i, messages); + } + } + } + void goRead(); + + /** + * Reads a for a full 24 bytes, parses those bytes as a header, + * and then reads the length of the payload. Any excess bytes will + * be saved for the next cycle - meaning it can handle multiple + * messages in a single packet. + */ + async function readMessage() { + const HEADER_SIZE = 24; + const PAYLOAD_SIZE_MAX = 4 * 1024 * 1024; + + // TODO setTimeout + let _resolve; + let _reject; + let p = new Promise(function (__resolve, __reject) { + _resolve = __resolve; + _reject = __reject; + }); + + let header; + + function cleanup() { + // console.log("[debug] readMessage handlers: remove 'onReadableHeader'"); + conn.removeListener('data', onReadableHeader); + conn.removeListener('readable', onReadableHeader); + + // console.log("[debug] readMessage handlers: remove 'onReadablePayload'"); + conn.removeListener('data', onReadablePayload); + conn.removeListener('readable', onReadablePayload); + } + + function resolve(data) { + cleanup(); + _resolve(data); + } + + function reject(err) { + cleanup(); + _reject(err); + } + + function onReadableHeader(data) { + let size = data?.length || 0; + console.log('State: reading header', size); + let chunk; + for (;;) { + chunk = data; + // chunk = conn.read(); // TODO reenable + if (!chunk) { + break; + } + chunks.push(chunk); + chunksLength += chunk.byteLength; + data = null; // TODO nix + } + if (chunksLength < HEADER_SIZE) { + return; + } + if (chunks.length > 1) { + chunk = Buffer.concat(chunks, chunksLength); + } else { + chunk = chunks[0]; + } + chunks = []; + chunksLength = 0; + if (chunk.byteLength > HEADER_SIZE) { + let extra = chunk.slice(HEADER_SIZE); + chunks.push(extra); + chunksLength += chunk.byteLength; + chunk = chunk.slice(0, HEADER_SIZE); + } + header = Parser.parseHeader(chunk); + if (header.payloadSize > PAYLOAD_SIZE_MAX) { + throw new Error('too big you are, handle you I cannot'); + } + // console.log('DEBUG header', header); + conn.removeListener('readable', onReadableHeader); + conn.removeListener('data', onReadableHeader); + + if (header.payloadSize === 0) { + resolve(header); + return; + } + + // console.log("[debug] readMessage handlers: add 'onReadablePayload'"); + //conn.on('readable', onReadablePayload); + conn.on('data', onReadablePayload); + onReadablePayload(null); + } + + function onReadablePayload(data) { + let size = data?.length || 0; + console.log('State: reading payload', size); + let chunk; + for (;;) { + chunk = data; + // chunk = conn.read(); // TODO revert + if (!chunk) { + break; + } + chunks.push(chunk); + chunksLength += chunk.byteLength; + data = null; // TODO nix + } + if (chunksLength < header.payloadSize) { + return; + } + if (chunks.length > 1) { + chunk = Buffer.concat(chunks, chunksLength); + } else if (chunks.length === 1) { + chunk = chunks[0]; + } else { + console.log("[warn] 'chunk' is 'null' (probably the debug null)"); + return; + } + chunks = []; + chunksLength = 0; + if (chunk.byteLength > header.payloadSize) { + let extra = chunk.slice(header.payloadSize); + chunks.push(extra); + chunksLength += chunk.byteLength; + chunk = chunk.slice(0, header.payloadSize); + } + header.payload = chunk; + conn.removeListener('readable', onReadablePayload); + conn.removeListener('data', onReadablePayload); + resolve(header); + } + + errReject = reject; + + // console.log("[debug] readMessage handlers: add 'onReadableHeader'"); + //conn.on('readable', onReadableHeader); + conn.on('data', onReadableHeader); + + if (chunks.length) { + onReadableHeader(null); + } + + let msg = await p; + return msg; + } + + async function waitForConnect() { + // connect / connected + // TODO setTimeout + await new Promise(function (_resolve, _reject) { + function cleanup() { + conn.removeListener('readable', onReadable); + conn.removeListener('data', onReadable); + } + + function resolve(data) { + cleanup(); + _resolve(data); + } + + function reject(err) { + cleanup(); + _reject(err); + } + + function onConnect() { + resolve(); + } + + function onReadable() { + // checking an impossible condition, just in case + throw new Error('unexpected response before request'); + } + + errReject = reject; + conn.once('connect', onConnect); + //conn.on('readable', onReadable); + conn.on('data', onReadable); + }); + } + + await waitForConnect(); + console.log('connected'); + + // + // version / verack + // + let versionMsg = Packer.version({ + network: network, // Packer.NETWORKS.regtest, + //protocol_version: Packer.PROTOCOL_VERSION, + //addr_recv_services: [Packer.IDENTIFIER_SERVICES.NETWORK], + addr_recv_ip: evonode.hostname, + addr_recv_port: evonode.port, + //addr_trans_services: [], + //addr_trans_ip: '127.0.01', + //addr_trans_port: null, + // addr_trans_ip: conn.localAddress, + // addr_trans_port: conn.localPort, + start_height: height, + //nonce: null, + user_agent: `DashJoin.js/${pkg.version}`, + // optional-ish + relay: false, + mnauth_challenge: null, + mn_connection: false, + }); + + // let versionBuffer = Buffer.from(versionMsg); + // console.log('version', versionBuffer.toString('hex')); + // console.log(Parser.parseHeader(versionBuffer.slice(0, 24))); + // console.log(Parser.parseVerack(versionBuffer.slice(24))); + + { + let versionP = new Promise(function (resolve, reject) { + listenerMap['version'] = async function (message) { + let versionResp = await Parser.parseVersion(message.payload); + console.log('DEBUG version', versionResp.version); + resolve(null); + listenerMap['version'] = null; + delete listenerMap['version']; + }; + }); + await sleep(150); + conn.write(versionMsg); + + await versionP; + } + + { + let verackP = await new Promise(function (resolve, reject) { + listenerMap['verack'] = async function (message) { + if (message.command !== 'verack') { + return; + } + + console.log('DEBUG verack', message); + resolve(); + listenerMap['verack'] = null; + delete listenerMap['verack']; + }; + }); + let verackBytes = Packer.packMessage({ + network, + command: 'verack', + payload: null, + }); + await sleep(150); + conn.write(verackBytes); + + await verackP; + } + + { + let mnauthP = new Promise(function (resolve, reject) { + listenerMap['mnauth'] = async function (message) { + if (message.command !== 'mnauth') { + return; + } + + resolve(); + listenerMap['mnauth'] = null; + delete listenerMap['mnauth']; + }; + }); + + let senddsqP = new Promise(function (resolve, reject) { + listenerMap['senddsq'] = async function (message) { + if (message.command !== 'senddsq') { + return; + } + + let sendDsqMessage = Packer.packSendDsq({ + network: network, + send: true, + }); + await sleep(150); + conn.write(sendDsqMessage); + console.log("[debug] sending 'senddsq':", sendDsqMessage); + + resolve(); + listenerMap['senddsq'] = null; + delete listenerMap['senddsq']; + }; + }); + + await mnauthP; + await senddsqP; + } + + { + let dsqPromise = new Promise(readDsq); + // + // dsa / dssu + dsq + // + //for (let i = 0; i < minimumParticipants; i += 1) + let collateralTx; + { + void (await generateMinBalance()); + void (await generateDenominations()); + + void (await generateMinBalance()); + let collateralTxInfo = await getCollateralTx(); + let txInfoSigned = await dashTx.hashAndSignAll(collateralTxInfo); + collateralTx = DashTx.utils.hexToBytes(txInfoSigned.transaction); + } + let dsaMsg = await Packer.packAllow({ + network, + denomination, + collateralTx, + }); + await sleep(150); + conn.write(dsaMsg); + + let dsaBuf = Buffer.from(dsaMsg); + console.log('[debug] dsa', dsaBuf.toString('hex')); + + let dsq = await dsqPromise; + for (; !dsq.ready; ) { + dsq = await new Promise(readDsq); + if (dsq.ready) { + break; + } + } + } + + function readDsq(resolve, reject) { + listenerMap['dsq'] = async function (message) { + if (message.command !== 'dsq') { + return; + } + + let dsq = await Parser.parseDsq(message.payload); + console.log('DEBUG dsq', dsq); + + resolve(dsq); + listenerMap['dsq'] = null; + delete listenerMap['dsq']; + }; + } + + let dsfP = new Promise(function (resolve, reject) { + listenerMap['dsf'] = async function (message) { + if (message.command !== 'dsf') { + return; + } + + let dsf = Parser.parseDsf(message.payload); + resolve(dsf); + listenerMap['dsf'] = null; + delete listenerMap['dsf']; + }; + }); + + let dscP = new Promise(function (resolve, reject) { + listenerMap['dsc'] = async function (message) { + if (message.command !== 'dsc') { + return; + } + + console.log('[debug] DSC Status:', message.payload.slice(4)); + // let dsc = Parser.parseDsc(message.payload); + // resolve(dsc); + resolve(); + listenerMap['dsc'] = null; + delete listenerMap['dsc']; + }; + }); + + let inputs = []; + let outputs = []; + { + // build utxo inputs from addrs + for (let addr of addresses) { + if (inputs.length >= COINJOIN_ENTRY_MAX_SIZE) { + break; + } + + let data = keysMap[addr]; + // Note: we'd need to look at utxos (not total address balance) + // to be wholly accurate, but this is good enough for now + if (data.satoshis !== denomination) { + continue; + } + if (data.reserved) { + continue; + } + + data.reserved = Date.now(); + let utxosRpc = await rpc.getAddressUtxos({ addresses: [data.address] }); + let utxos = utxosRpc.result; + for (let utxo of utxos) { + // utxo.sigHashType = 0x01; + utxo.address = data.address; + utxo.index = data.index; + // TODO fix in dashtx + utxo.txId = utxo.txId || utxo.txid; + utxo.txid = utxo.txId || utxo.txid; + + // must have pubKeyHash for script to sign + let pubKeyHashBytes = await DashKeys.addrToPkh(data.address, { + version: 'testnet', + }); + utxo.pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + + console.log('[debug] input utxo', utxo); + inputs.push(utxo); + } + } + + // build output addrs + for (let addr of addresses) { + if (outputs.length >= inputs.length) { + break; + } + + let data = keysMap[addr]; + + let isFree = !data.used && !data.reserved; + if (!isFree) { + continue; + } + + data.reserved = Date.now(); + let pubKeyHashBytes = await DashKeys.addrToPkh(data.address, { + version: 'testnet', + }); + let pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + + let output = { + pubKeyHash: pubKeyHash, + satoshis: denomination, + }; + + outputs.push(output); + } + // inputs.sort(DashTx.sortInputs); + // outputs.sort(DashTx.sortOutputs); + } + + console.log('sanity check 1: inputs', inputs); + let dsf; + { + void (await generateMinBalance()); + let collateralTxInfo = await getCollateralTx(); + let txInfoSigned = await dashTx.hashAndSignAll(collateralTxInfo); + let collateralTx = DashTx.utils.hexToBytes(txInfoSigned.transaction); + + let dsiMessageBytes = Packer.packDsi({ + network, + inputs, + collateralTx, + outputs, + }); + await sleep(150); + conn.write(dsiMessageBytes); + dsf = await dsfP; + } + + console.log('sanity check 2: inputs', inputs); + { + let txRequest = dsf.transaction_unsigned; + console.log('[debug] tx request (unsigned)', txRequest); + let sigHashType = DashTx.SIGHASH_ALL | DashTx.SIGHASH_ANYONECANPAY; //jshint ignore:line + // let sigHashType = DashTx.SIGHASH_ALL; + let txInfo = DashTx.parseUnknown(txRequest); + console.log('[debug] DashTx.parseRequest(dsfTxRequest)'); + console.log(txInfo); + for (let input of inputs) { + console.log('sanity check 3: input', input); + let privKeyBytes = await keyUtils.getPrivateKey(input); + let pubKeyBytes = await keyUtils.toPublicKey(privKeyBytes); + let publicKey = DashTx.utils.bytesToHex(pubKeyBytes); + + { + // sanity check + let addr = await DashKeys.pubkeyToAddr(pubKeyBytes, { + version: 'testnet', + }); + if (addr !== input.address) { + console.error(`privKeyBytes => 'addr': ${addr}`); + console.error(`'input.address': ${input.address}`); + throw new Error('sanity fail: address mismatch'); + } + } + + // let sighashInputs = []; + for (let sighashInput of txInfo.inputs) { + if (sighashInput.txid !== input.txid) { + continue; + } + if (sighashInput.outputIndex !== input.outputIndex) { + continue; + } + + sighashInput.index = input.index; + sighashInput.address = input.address; + sighashInput.satoshis = input.satoshis; + sighashInput.pubKeyHash = input.pubKeyHash; + // sighashInput.script = input.script; + sighashInput.publicKey = publicKey; + sighashInput.sigHashType = sigHashType; + console.log('[debug] YES, CAN HAZ INPUTS!!!', sighashInput); + // sighashInputs.push({ + // txId: input.txId || input.txid, + // txid: input.txid || input.txId, + // outputIndex: input.outputIndex, + // pubKeyHash: input.pubKeyHash, + // sigHashType: input.sigHashType, + // }); + break; + } + // if (sighashInputs.length !== 1) { + // let msg = + // 'expected exactly one selected input to match one tx request input'; + // throw new Error(msg); + // } + // let anyonecanpayIndex = 0; + // let txHashable = DashTx.createHashable( + // { + // version: txInfo.version, + // inputs: sighashInputs, // exactly 1 + // outputs: txInfo.outputs, + // locktime: txInfo.locktime, + // }, + // anyonecanpayIndex, + // ); + // console.log('[debug] txHashable (pre-sighashbyte)', txHashable); + + // let signableHashBytes = await DashTx.hashPartial(txHashable, sigHashType); + // let signableHashHex = DashTx.utils.bytesToHex(signableHashBytes); + // console.log('[debug] signableHashHex', signableHashHex); + // let sigBuf = await keyUtils.sign(privKeyBytes, signableHashBytes); + // let signature = DashTx.utils.bytesToHex(sigBuf); + // Object.assign(input, { publicKey, sigHashType, signature }); + } + + // for (let input of txInfo.inputs) { + // let inputs = Tx.selectSigHashInputs(txInfo, i, _sigHashType); + // let outputs = Tx.selectSigHashOutputs(txInfo, i, _sigHashType); + // let txForSig = Object.assign({}, txInfo, { inputs, outputs }); + // } + // let txSigned = await dashTx.hashAndSignAll(txForSig); + let txSigned = await dashTx.hashAndSignAll(txInfo); + console.log('[debug] txSigned', txSigned); + let signedInputs = []; + for (let input of txSigned.inputs) { + if (!input?.signature) { + continue; + } + signedInputs.push(input); + } + console.log('[debug] signed inputs', signedInputs); + + let dssMessageBytes = Packer.packDss({ + network: network, + inputs: signedInputs, + }); + console.log('[debug] dss =>', dssMessageBytes.length); + console.log(dssMessageBytes); + let dssHex = DashTx.utils.bytesToHex(dssMessageBytes); + console.log(dssHex); + await sleep(150); + conn.write(dssMessageBytes); + await dscP; + } + + console.log('Sweet, sweet victory!'); +} + +/** + * @param {Object} a + * @param {String} a.id + * @param {Object} b + * @param {String} b.id + */ +function byId(a, b) { + if (a.id > b.id) { + return 1; + } + if (a.id < b.id) { + return -1; + } + return 0; +} + +// http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array +// function shuffle(arr) { +// let currentIndex = arr.length; + +// // While there remain elements to shuffle... +// for (; currentIndex !== 0; ) { +// // Pick a remaining element... +// let randomIndexFloat = Math.random() * currentIndex; +// let randomIndex = Math.floor(randomIndexFloat); +// currentIndex -= 1; + +// // And swap it with the current element. +// let temporaryValue = arr[currentIndex]; +// arr[currentIndex] = arr[randomIndex]; +// arr[randomIndex] = temporaryValue; +// } + +// return arr; +// } + +function sleep(ms) { + return new Promise(function (resolve) { + setTimeout(resolve, ms); + }); +} + +main() + .then(function () { + console.info('Done'); + process.exit(0); + }) + .catch(function (err) { + console.error('Fail:'); + console.error(err.stack); + process.exit(1); + }); diff --git a/fixtures/anyonecanpay.0.dsa.hex b/fixtures/anyonecanpay.0.dsa.hex new file mode 100644 index 0000000..9f7cce8 --- /dev/null +++ b/fixtures/anyonecanpay.0.dsa.hex @@ -0,0 +1 @@ +fcc1b7dc647361000000000000000000c300000094172680100000000300000001520aa672368ee5b328eea6975f8ed85462f6ca6000a7eca571b00b5b9812cf83000000006a47304402204dfe8089d22da2130a8236d9c9cb4361bf29f664de47b7c3f7ea1c6523b0d47802204cb5c2938cda0c964f5d05f3ab1ee951b6bd827ce35ecd2890e9b9c74ddf43d40121035efd987d60495fc8180351558e4ee58b8c5d86eb36498e0182b9d1d26d22198bffffffff0179951d06000000001976a914da16dbe25379eb63ec0a1866cef414ee0f318a5b88ac00000000 diff --git a/fixtures/anyonecanpay.0.input-key.hex b/fixtures/anyonecanpay.0.input-key.hex new file mode 100644 index 0000000..be620b9 --- /dev/null +++ b/fixtures/anyonecanpay.0.input-key.hex @@ -0,0 +1 @@ +cVT5fWT5F4V4EkCK9d57LT1g3CoG18aqz1E2DGMy6KH1Cc5LnjkS diff --git a/fixtures/anyonecanpay.0.input.addr b/fixtures/anyonecanpay.0.input.addr new file mode 100644 index 0000000..6c0a6bc --- /dev/null +++ b/fixtures/anyonecanpay.0.input.addr @@ -0,0 +1,2 @@ +# index: 116 +yZV88MAMnV5Tw2NYQHLmz6NYCCVMX5vvpD diff --git a/fixtures/anyonecanpay.1.dsf.hex b/fixtures/anyonecanpay.1.dsf.hex new file mode 100644 index 0000000..3ddf06b --- /dev/null +++ b/fixtures/anyonecanpay.1.dsf.hex @@ -0,0 +1 @@ +0200000004172754369ee634872efbc09f603c47c77770cd54dbd4fb533cefdcc0dbeb2601030000001976a914ac7b14bcd04d544914d90d01e0c1a1d4caba6abf88acfffffffff60f3e363041c9ba2130f07fd9fe32ad4a3f5034bdcf4a388b27f6c5f646d14f040000001976a914f3dc0395df80b4640d9940c3277bccc6f148421588acffffffff0db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000001976a914906e2ab1d4fdb9a5f4ff4f2e6538607a697dbb0e88acffffffff5d02ec3c1f151f4459bd3bea8ee7e05b94866b6702ecb31daf8363294c6c8f6b000000001976a9140b937639d4add223ac30d3ed70c62fe5f271bd0c88acffffffff04a1860100000000001976a914393357d3c0244c6934c5177ab4bcf84cd8d7a10a88aca1860100000000001976a914416298463aa890595e6b5a9c633ff237d6664eef88aca1860100000000001976a9144877d5d7132a9a2a9987445bb109213a5b7d461188aca1860100000000001976a9145488fda54d3e7469d19aca28a226ea521a9e920988ac00000000 diff --git a/fixtures/anyonecanpay.1.tx-raw.hex b/fixtures/anyonecanpay.1.tx-raw.hex new file mode 100644 index 0000000..3ddf06b --- /dev/null +++ b/fixtures/anyonecanpay.1.tx-raw.hex @@ -0,0 +1 @@ +0200000004172754369ee634872efbc09f603c47c77770cd54dbd4fb533cefdcc0dbeb2601030000001976a914ac7b14bcd04d544914d90d01e0c1a1d4caba6abf88acfffffffff60f3e363041c9ba2130f07fd9fe32ad4a3f5034bdcf4a388b27f6c5f646d14f040000001976a914f3dc0395df80b4640d9940c3277bccc6f148421588acffffffff0db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000001976a914906e2ab1d4fdb9a5f4ff4f2e6538607a697dbb0e88acffffffff5d02ec3c1f151f4459bd3bea8ee7e05b94866b6702ecb31daf8363294c6c8f6b000000001976a9140b937639d4add223ac30d3ed70c62fe5f271bd0c88acffffffff04a1860100000000001976a914393357d3c0244c6934c5177ab4bcf84cd8d7a10a88aca1860100000000001976a914416298463aa890595e6b5a9c633ff237d6664eef88aca1860100000000001976a9144877d5d7132a9a2a9987445bb109213a5b7d461188aca1860100000000001976a9145488fda54d3e7469d19aca28a226ea521a9e920988ac00000000 diff --git a/fixtures/anyonecanpay.2.input-hash.hex b/fixtures/anyonecanpay.2.input-hash.hex new file mode 100644 index 0000000..8bd6aac --- /dev/null +++ b/fixtures/anyonecanpay.2.input-hash.hex @@ -0,0 +1 @@ +c9c9b979ecf34b1c6c4091cf8e83d8ae8b0823886b749b924dea75e5656bbe3f diff --git a/fixtures/anyonecanpay.2.input-sig.hex b/fixtures/anyonecanpay.2.input-sig.hex new file mode 100644 index 0000000..4c60fed --- /dev/null +++ b/fixtures/anyonecanpay.2.input-sig.hex @@ -0,0 +1 @@ +3045022100b46cdff770bb50ad4248af8c5c6169236b99d2a3f050951e6b84c9ab3d316f9202207f0370b7a7c31def382955a27c9234ad74538c1e07ad4e2dfa32902f8982acf5 diff --git a/fixtures/anyonecanpay.2.input-tx.hex b/fixtures/anyonecanpay.2.input-tx.hex new file mode 100644 index 0000000..541e538 --- /dev/null +++ b/fixtures/anyonecanpay.2.input-tx.hex @@ -0,0 +1,2 @@ +02000000010db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000001976a914906e2ab1d4fdb9a5f4ff4f2e6538607a697dbb0e88acffffffff04a1860100000000001976a914393357d3c0244c6934c5177ab4bcf84cd8d7a10a88aca1860100000000001976a914416298463aa890595e6b5a9c633ff237d6664eef88aca1860100000000001976a9144877d5d7132a9a2a9987445bb109213a5b7d461188aca1860100000000001976a9145488fda54d3e7469d19aca28a226ea521a9e920988ac00000000 +81000000 diff --git a/fixtures/anyonecanpay.3.dss.hex b/fixtures/anyonecanpay.3.dss.hex new file mode 100644 index 0000000..5a6fa67 --- /dev/null +++ b/fixtures/anyonecanpay.3.dss.hex @@ -0,0 +1,3 @@ +fcc1b7dc6473730000000000000000002901000093ebbbfb02f60f3e363041c9ba2130f07fd9fe32ad4a3f5034bdcf4a388b27f6c5f646d14f040000006b483045022100ba34 +5735b58b47536c6534b8832ef727a525c1010e862781ad5263f91d185c9a022076744d504d3153b716fbb5c46f86628482a472eb23d061510aa8cb8970c6d8158121024de8fc8b47173306579f4b39b7f8c9a4491d0487f968a89074e44ddf397fc1b7ffffffff +0db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000006b483045022100b46cdff770bb50ad4248af8c5c6169236b99d2a3f050951e6b84c9ab3d316f9202207f0370b7a7c31def382955a27c9234ad74538c1e07ad4e2dfa32902f8982acf58121026bd10466e924ab4ab8be5f0a0daf65f6468e20c8a205f2df4fbdf510010a8ba0ffffffff diff --git a/fixtures/anyonecanpay.3.tx.hex b/fixtures/anyonecanpay.3.tx.hex new file mode 100644 index 0000000..98260ee --- /dev/null +++ b/fixtures/anyonecanpay.3.tx.hex @@ -0,0 +1 @@ +0200000004172754369ee634872efbc09f603c47c77770cd54dbd4fb533cefdcc0dbeb2601030000001976a914ac7b14bcd04d544914d90d01e0c1a1d4caba6abf88acfffffffff60f3e363041c9ba2130f07fd9fe32ad4a3f5034bdcf4a388b27f6c5f646d14f040000006b483045022100ba345735b58b47536c6534b8832ef727a525c1010e862781ad5263f91d185c9a022076744d504d3153b716fbb5c46f86628482a472eb23d061510aa8cb8970c6d8158121024de8fc8b47173306579f4b39b7f8c9a4491d0487f968a89074e44ddf397fc1b7ffffffff0db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000006b483045022100b46cdff770bb50ad4248af8c5c6169236b99d2a3f050951e6b84c9ab3d316f9202207f0370b7a7c31def382955a27c9234ad74538c1e07ad4e2dfa32902f8982acf58121026bd10466e924ab4ab8be5f0a0daf65f6468e20c8a205f2df4fbdf510010a8ba0ffffffff5d02ec3c1f151f4459bd3bea8ee7e05b94866b6702ecb31daf8363294c6c8f6b000000001976a9140b937639d4add223ac30d3ed70c62fe5f271bd0c88acffffffff04a1860100000000001976a914393357d3c0244c6934c5177ab4bcf84cd8d7a10a88aca1860100000000001976a914416298463aa890595e6b5a9c633ff237d6664eef88aca1860100000000001976a9144877d5d7132a9a2a9987445bb109213a5b7d461188aca1860100000000001976a9145488fda54d3e7469d19aca28a226ea521a9e920988ac00000000 diff --git a/fixtures/anyonecanpay.4.dsc.hex b/fixtures/anyonecanpay.4.dsc.hex new file mode 100644 index 0000000..a7bc570 --- /dev/null +++ b/fixtures/anyonecanpay.4.dsc.hex @@ -0,0 +1,4 @@ +fcc1b7dc +647363000000000000000000080000006265a450 +6ab00000 +14000000 diff --git a/fixtures/dsa.hex b/fixtures/dsa.hex new file mode 100644 index 0000000..526cf6d --- /dev/null +++ b/fixtures/dsa.hex @@ -0,0 +1,2 @@ +fcc1b7dc647361000000000000000000c40000004caa3c88 +04000000030000000127c03b36308e0925666682de270cdaa212b696776ac3529242851e338c627a1c000000006b4830450221008e1c7087a46e80e0bef0175b09a060f6b38f19dace07019819d4ab9addc0b8560220284e59145ec926e794d8f9150df9c5568fe7add6d54712208e607891be9719f30121026215ac5afb5a890d06e4eb07ed6383fb4cd46ca0de222cca4c4f8c9fcf9a6023ffffffff01bbae9606000000001976a914ef46d2e30c714916c43676364b27657ed753ef0788ac00000000 diff --git a/fixtures/dsf.hex b/fixtures/dsf.hex new file mode 100644 index 0000000..25af44d --- /dev/null +++ b/fixtures/dsf.hex @@ -0,0 +1,41 @@ +fcc1b7dc 647366000000000000000000 54050000 58eecedc574b0400 +02000000 +12 +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 00000000 00 ffffffff +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 01000000 00 ffffffff +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 02000000 00 ffffffff +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 03000000 00 ffffffff +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 04000000 00 ffffffff +191e8802ddc8345b2cfac9fa765dce581e178dc1d1c7a4c32a1c40f36607ec45 01000000 00 ffffffff +191e8802ddc8345b2cfac9fa765dce581e178dc1d1c7a4c32a1c40f36607ec45 02000000 00 ffffffff +191e8802ddc8345b2cfac9fa765dce581e178dc1d1c7a4c32a1c40f36607ec45 03000000 00 ffffffff +191e8802ddc8345b2cfac9fa765dce581e178dc1d1c7a4c32a1c40f36607ec45 04000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 00000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 01000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 02000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 03000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 04000000 00 ffffffff +0e94d350ef62f7e11b2e5e2ad7d5c1fd029a46bbce5900aa26329188839e61b2 01000000 00 ffffffff +0e94d350ef62f7e11b2e5e2ad7d5c1fd029a46bbce5900aa26329188839e61b2 02000000 00 ffffffff +0e94d350ef62f7e11b2e5e2ad7d5c1fd029a46bbce5900aa26329188839e61b2 03000000 00 ffffffff +0e94d350ef62f7e11b2e5e2ad7d5c1fd029a46bbce5900aa26329188839e61b2 04000000 00 ffffffff +12 +a186010000000000 19 7619 14 2180e2e082e504e2ff99300bb47b74d0bbe6fa5a 88ac +a186010000000000 19 7619 14 3a2142b82222413e45a469d975f185023a33ef2d 88ac +a186010000000000 19 7619 14 4a271dd96a7e096db0174fef0a95b1ab2131cabf 88ac +a186010000000000 19 7619 14 4ae6f64be49c8a9dad05091c33843d5c30ba7126 88ac +a186010000000000 19 7619 14 580c7812093a680367e2ea24f9503ee8c3731e6d 88ac +a186010000000000 19 7619 14 61ce8d35674e020bd91f63d175ad666cfd273b76 88ac +a186010000000000 19 7619 14 660f50b55e635ea347efd22314666024fd25990c 88ac +a186010000000000 19 7619 14 6dbe907ca08a5f893752449a6e5c67e0b8110938 88ac +a186010000000000 19 7619 14 74bc30a19f16aee3e8b48a8b5b7ccdde445bc8c6 88ac +a186010000000000 19 7619 14 7568a7c69ef2357459376f8c3d638bd205705fb6 88ac +a186010000000000 19 7619 14 8ec49f7e482b6ceb658156d9669d4b348843a179 88ac +a186010000000000 19 7619 14 94c1d34dfd35b5e5c108aab7dc8a82c6d046c0e4 88ac +a186010000000000 19 7619 14 9a2bb835bd557437fcdbdb09b3e1de86bf529cdf 88ac +a186010000000000 19 7619 14 9f7fc53ec2c2849da00d6b1ec6ded26d5a6409fa 88ac +a186010000000000 19 7619 14 af44b235ce58918edcb47c957f942f3fcb2ae1d8 88ac +a186010000000000 19 7619 14 b6055ef1bd4266a0b284fc7e34241cc4211f80d6 88ac +a186010000000000 19 7619 14 d547b11f8612213e7a22f2aefb5669a5e4242fc9 88ac +a186010000000000 19 7619 14 e3d4e7e53b80c1cc45268acc82921923fadb029e 88ac +00000000 diff --git a/fixtures/dsi.hex b/fixtures/dsi.hex new file mode 100644 index 0000000..b31c05d --- /dev/null +++ b/fixtures/dsi.hex @@ -0,0 +1,78 @@ +fcc1b7dc647369000000000000000000a3010000ed146dfd + +03 + 36bdc3796c5630225f2c86c946e2221a + 9958378f5d08da380895c2656730b5c0 + 02000000 + 00 + ffffffff + + 36bdc3796c5630225f2c86c946e2221a + 9958378f5d08da380895c2656730b5c0 + 0f000000 + 00 + ffffffff + + 36bdc3796c5630225f2c86c946e2221a + 9958378f5d08da380895c2656730b5c0 + 0d000000 + 00 + ffffffff + +01000000 + 01 + 83bd1980c71c38f035db9b14d7f934f7 + d595181b3436e36289902619f3f7d383 + 00000000 + 6b + 483045022100f4d8fa0ae4132235fecd540a + 62715ccfb1c9a97f8698d066656e30bb1e1e + 06b90220301b4cc93f38950a69396ed89dfc + c08e72ec8e6e7169463592a0bf504946d98b + 812102fa4b9c0f9e76e06d57c75cab9c8368 + a62a1ce8db6eb0c25c3e0719ddd9ab549c + ffffffff + + 01 + e093040000000000 + 19 + 76 + a9 + 14 + f8956a4eb0e53b05ee6b30edfd2770b5 + 26c1f1bb + 88 + ac + + 00000000 + +03 + e8e4f50500000000 + 19 + 76 + a9 + 14 + 14826d7ba05cf76588a5503c03951dc9 + 14c91b6c + 88 + ac + + e8e4f50500000000 + 19 + 76 + a9 + 14 + f01197177de2358928196a543b2bbd97 + 3c2ab002 + 88 + ac + + e8e4f50500000000 + 19 + 76 + a9 + 14 + 426614716e94812d483bca32374f6ac8 + cd121b0d + 88 + ac diff --git a/fixtures/dsq.hex b/fixtures/dsq.hex new file mode 100644 index 0000000..3159e8e --- /dev/null +++ b/fixtures/dsq.hex @@ -0,0 +1,2 @@ +fcc1b7dc6473710000000000000000008e000000968ff020 +04000000d03171667a17b053a987bb519cff4fb73fa8718eb7bc68c51cc0f3b44d5f9f58b9dc2d66000000000160ab6e501a57e993271ab86e230177b50b32a7cab720016c79b8acf2eb4dfa5a8b3a930fa5c1e2dd1d92111b377b0c40a80e06083f9f06bb7a78ec87a085c8c67ee4a9655d2e14ba4c8caac0116484fe2a19813f6cf7065fead64efd7f1c4139a7 diff --git a/fixtures/dsq.json b/fixtures/dsq.json new file mode 100644 index 0000000..a6301d8 --- /dev/null +++ b/fixtures/dsq.json @@ -0,0 +1,11 @@ +{ + "denomination_id": 4, + "denomination": 10000100, + "protxhash_bytes": null, + "protxhash": "d03171667a17b053a987bb519cff4fb73fa8718eb7bc68c51cc0f3b44d5f9f58", + "timestamp_unix": 1714281657, + "timestamp": "2024-04-28T05:20:57.000Z", + "ready": true, + "signature_bytes": null, + "signature": "60ab6e501a57e993271ab86e230177b50b32a7cab720016c79b8acf2eb4dfa5a8b3a930fa5c1e2dd1d92111b377b0c40a80e06083f9f06bb7a78ec87a085c8c67ee4a9655d2e14ba4c8caac0116484fe2a19813f6cf7065fead64efd7f1c4139a7" +} diff --git a/fixtures/dssu.hex b/fixtures/dssu.hex new file mode 100644 index 0000000..8760484 --- /dev/null +++ b/fixtures/dssu.hex @@ -0,0 +1,2 @@ +fcc1b7dc64737375000000000000000010000000889be7ee +5ecd0a00010000000100000013000000 diff --git a/fixtures/dssu.json b/fixtures/dssu.json new file mode 100644 index 0000000..355e4a9 --- /dev/null +++ b/fixtures/dssu.json @@ -0,0 +1,9 @@ +{ + "session_id": 707934, + "state_id": 1, + "state": "QUEUE", + "status_id": 1, + "status": "ACCEPTED", + "message_id": 19, + "message": "MSG_NOERR" +} diff --git a/fixtures/he-1-asn1-sig.hex b/fixtures/he-1-asn1-sig.hex new file mode 100644 index 0000000..afc7698 --- /dev/null +++ b/fixtures/he-1-asn1-sig.hex @@ -0,0 +1,2 @@ +30 45 02 21 0085e8bd03a92422fc8f656ded2c19ceb70c68707a1699dd54782c4395532022b6 + 02 20 7730c221bcf818c0b0445f576b36d2ee0698edc4d3fe872aeaff772122e4251d diff --git a/fixtures/he-1-hashtx.hex b/fixtures/he-1-hashtx.hex new file mode 100644 index 0000000..2073560 --- /dev/null +++ b/fixtures/he-1-hashtx.hex @@ -0,0 +1,41 @@ +02000000 + +0b +82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c00 00000000 00 +ffffffff +c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f 03000000 +19 76 a9 14 37b00a500178dfb1bb95d66fe7ba10d9baf9d14e 88 ac ffffffff +c0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46 04000000 00 +ffffffff +204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58 01000000 00 +ffffffff +86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d77 0a000000 00 +ffffffff +e6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95 09000000 00 +ffffffff +a2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f38195 07000000 00 +ffffffff +c05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a 09000000 00 +ffffffff +697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa 08000000 00 +ffffffff +177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb7 02000000 00 +ffffffff +e2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6 07000000 00 +ffffffff + +0b +a186010000000000 19 76 a9 14 1e624bcdc64f72e4e68eacc59d40ab7a3400aeae 88 ac +a186010000000000 19 76 a9 14 335194d41dbc6c7e71b263711f0319a6bef5b452 88 ac +a186010000000000 19 76 a9 14 75922991d66988fd0defee70a60753728882d59b 88 ac +a186010000000000 19 76 a9 14 7c47c66f6c7a09e20be326692b42f43576a6249e 88 ac +a186010000000000 19 76 a9 14 93a314141f390ddd111f9be2b550e8e74b703043 88 ac +a186010000000000 19 76 a9 14 b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c 88 ac +a186010000000000 19 76 a9 14 cfd97c2953dbda72b66d1e37d8e8f8acef4e3d49 88 ac +a186010000000000 19 76 a9 14 d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e 88 ac +a186010000000000 19 76 a9 14 df35875deec179fa1d39d7182fe2d746ff1a2e21 88 ac +a186010000000000 19 76 a9 14 e58de7fe595532146be33e08493ed2b26f18251c 88 ac +a186010000000000 19 76 a9 14 fdebd30c80543b3c72b5cfff7c788e63d63d29a6 88 ac + +00000000 +01000000 diff --git a/fixtures/he-1-p2pkh.hex b/fixtures/he-1-p2pkh.hex new file mode 100644 index 0000000..6d9981a --- /dev/null +++ b/fixtures/he-1-p2pkh.hex @@ -0,0 +1 @@ +76a91437b00a500178dfb1bb95d66fe7ba10d9baf9d14e88ac diff --git a/fixtures/he-1-privkey.hex b/fixtures/he-1-privkey.hex new file mode 100644 index 0000000..2e49c48 --- /dev/null +++ b/fixtures/he-1-privkey.hex @@ -0,0 +1 @@ +107da4635cb63a387ec4c17258d600c8813599b7ec72893d5bab0bf3aa514788 diff --git a/fixtures/he-1-sighash.hex b/fixtures/he-1-sighash.hex new file mode 100644 index 0000000..67623b4 --- /dev/null +++ b/fixtures/he-1-sighash.hex @@ -0,0 +1 @@ +35a639ce51073d0be3531fa724745fa3c1bf1c609132cff4c98e8045ccce235f diff --git a/fixtures/he-1-utxo.json b/fixtures/he-1-utxo.json new file mode 100644 index 0000000..0674e7f --- /dev/null +++ b/fixtures/he-1-utxo.json @@ -0,0 +1,5 @@ +{ + "_txid": "c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f", + "txid": "3f807423b29a738f31be9af3fff0fb1a66f87d6952336bd3d62c68a904b647c5", + "outputIndex": 3 +} diff --git a/fixtures/he-cj-full.log b/fixtures/he-cj-full.log new file mode 100644 index 0000000..99376d3 --- /dev/null +++ b/fixtures/he-cj-full.log @@ -0,0 +1,58 @@ +Known-working, coinjoin transaction, broadcast at: +https://insight.testnet.networks.dash.org:3002/insight/address/yRPtuFHK7Czn7GhfpGLQAYvsGjGkojhzS4 + +08:47:22 27 CoinJoinClientSession.processFinalTransaction: DSFINALTX: +dsf: +80eb0100020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac00000000 + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a91437b00a500178dfb1bb95d66fe7ba10d9baf9d14e88ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 1 with key 107da4635cb63a387ec4c17258d600c8813599b7ec72893d5bab0bf3aa514788 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end: +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f030000001976a91437b00a500178dfb1bb95d66fe7ba10d9baf9d14e88acffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash 35a639ce51073d0be3531fa724745fa3c1bf1c609132cff4c98e8045ccce235f + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914786381547e752eaa9a06f70bb1265145e685753988ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 2 with key 325c50fff4c6cb307ec5eda1310e2766d632091d984ff19ad3848e1e65db742a +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46040000001976a914786381547e752eaa9a06f70bb1265145e685753988acffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash 5c564906aa2496f00b789665a042d03c1135ee3e7c9a129822697c85ac68667a + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a91419c171c953f1766d325a9181909380b0a31595fd88ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 3 with key b1ae60833f531c642dbda4d509462e6f5dde064d217c88547ceef18360b8bb54 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58010000001976a91419c171c953f1766d325a9181909380b0a31595fd88acffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash c9ca6800a866e63d31eebe62ab9d31e49bccb0b08b19af8b0b53031396f0280b + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914ccf7c90b25ecf67011e160bd7ca99fadaa8d272888ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 4 with key 5dc84baacdc67aea283c64270500b072d65b7aa2bced91998f9c2fe599378d17 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a0000001976a914ccf7c90b25ecf67011e160bd7ca99fadaa8d272888acffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash 9ab23bb0d34ba3b1520ffdd7f838d41dd183762ee5330b7c983c7a8ea9be5d57 + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914ffe035d9e39100e9d179601ae3ee2f2223c864fd88ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 5 with key 33479550caf0a09153fd3d22e884581e2a0e28f397b5a56c02a846409e4e7bd7 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95090000001976a914ffe035d9e39100e9d179601ae3ee2f2223c864fd88acffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash e8798f8b97df67ea033bdc2ea896e15919f99f4ac8a3984afd0cdf2ff8bee69f + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914b1c59bf3ce5bd944c5213259c9398a0edbaa724188ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 7 with key 9e38eadc76529b88f1cb983f894fea4dd93042e18f47717c3c90f0229abc0909 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a090000001976a914b1c59bf3ce5bd944c5213259c9398a0edbaa724188acffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash 5427e074bc5c932998cee33f660dc5cbf5a158f734e7a87b13236ad2caf06a29 + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a91483b7f4eb6e8f004ae4c37dfca20c15b4daac746488ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 8 with key b8df0a2e512cad7a13dc83816eea7d35a2e972c9b11ed04e56fc619ca974cefe +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa080000001976a91483b7f4eb6e8f004ae4c37dfca20c15b4daac746488acffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash eaa607b7b47db553f0ab6a8214c621b6a055b58b76c87b23c51f22193a888d78 + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914903dda3fb6996c5788ae855b696f0c276a80a6dd88ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 10 with key 0ff7f48db747dcdf5607186460d143dfb0233c083c1cbc6722bb32acaf9b54dd +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6070000001976a914903dda3fb6996c5788ae855b696f0c276a80a6dd88acffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash c443f57d11d30ff9b9e7c3f4f02b545d6cfb1edf91d51a488141a43af58f5835 + +08:47:22 27 CoinJoinClientSession.signFinalTransaction: DSFINAL: +dss: +08c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f030000006b48304502210085e8bd03a92422fc8f656ded2c19ceb70c68707a1699dd54782c4395532022b602207730c221bcf818c0b0445f576b36d2ee0698edc4d3fe872aeaff772122e4251d0121030c467477c7d4aefb377cbe11c48765669676c556d0149c06da25fd0c2ade39ecffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46040000006a47304402204316f56e90d27505ecb938e52391cc9b0b77474b67283983a63463d0823a813a022058bcb6eab28c2f4f936ba85183e750d010808e1bb11201fab5bae6d5a25391ba0121023c2ff18948d68270f5e150456caa8c1fcb0e3ed33e68340b5e087fd6651cbe7dffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58010000006a47304402200b049725050b28a4a42f80db28bd8e48f266e2a3460cf6a798457420fa54bd0d02204edd66bf280cfda43019f48394439009cb234ee5871c2ec490d4d7ba84e6a461012102dde437be25a79dedc6dbf4cf73ac8e3eefd90112ddae827c9042b43d0e8d9bf6ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a0000006b4830450221009048e55404378c68962bdb4c2acb4ea6592a040ac17be5f5cc2a957b087930620220694e165128a6c40c3bc4f77b369949d80606b9bdcf34e2dd8622107b9a87e3ff0121025064ef597ec1520e2e3b7836261457eb79522299314703a7beeea2bbf42b9175ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95090000006a473044022059c43a7d1327d36adce6cfef92e5e2159c83dc9ebc6b28c5ac8c1ec46e6cdcb2022050dea4f95e4a8a6c4a06d71e6c7e5fc9b7249a967f09bd4283c2ed08a1225ed601210257f68289f77638b808706bced459895b349001a5d86641da619886474e9b417cffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a090000006a47304402205b3398b3ea59a6ebe5cad2be234b64ddc852057197aaf7071c5621739e54ed5f02201330850ddd04f318cc5cffb8ce8464dc0d17dcc8e8d37c84db8bdef0b980363f01210297f200b4f0dc3967d77d822189276d60e6baad8075d954be933724dfa7ded74fffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa080000006a47304402203c3023ee03dee54982ef2176c6b26c81be06d8c09fb19e98ef98c257210f3c0e0220193b4f24aa9cc5720186dd02c05c9257a46383eed23586a3348d4b9af2d3f304012103e943423c41299842d5532a7800d0e7f6d4a4e89a68b992f071988e3325ca3e82ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6070000006b483045022100de784c07605de564af098672900651b901bbbfd05a9daa51b51d50f414073e16022020881559ada92c99777d1ec103dded571cbb8a9cfc0052262fbd3bcf14ac9dfe01210336d148cfc9a3a3603fa27aca926a73e2d47861194871f1473d9cc173c9ee5df0ffffffff diff --git a/fixtures/he-dsf.hex b/fixtures/he-dsf.hex new file mode 100644 index 0000000..3efe7b0 --- /dev/null +++ b/fixtures/he-dsf.hex @@ -0,0 +1,30 @@ +80eb0100 +02000000 + +0b +82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c00 00000000 00 ffffffff +c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f 03000000 00 ffffffff +c0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46 04000000 00 ffffffff +204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58 01000000 00 ffffffff +86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d77 0a000000 00 ffffffff +e6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95 09000000 00 ffffffff +a2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f38195 07000000 00 ffffffff +c05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a 09000000 00 ffffffff +697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa 08000000 00 ffffffff +177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb7 02000000 00 ffffffff +e2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6 07000000 00 ffffffff + +0b +a186010000000000 19 76 a9 14 1e624bcdc64f72e4e68eacc59d40ab7a3400aeae 88 ac +a186010000000000 19 76 a9 14 335194d41dbc6c7e71b263711f0319a6bef5b452 88 ac +a186010000000000 19 76 a9 14 75922991d66988fd0defee70a60753728882d59b 88 ac +a186010000000000 19 76 a9 14 7c47c66f6c7a09e20be326692b42f43576a6249e 88 ac +a186010000000000 19 76 a9 14 93a314141f390ddd111f9be2b550e8e74b703043 88 ac +a186010000000000 19 76 a9 14 b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c 88 ac +a186010000000000 19 76 a9 14 cfd97c2953dbda72b66d1e37d8e8f8acef4e3d49 88 ac +a186010000000000 19 76 a9 14 d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e 88 ac +a186010000000000 19 76 a9 14 df35875deec179fa1d39d7182fe2d746ff1a2e21 88 ac +a186010000000000 19 76 a9 14 e58de7fe595532146be33e08493ed2b26f18251c 88 ac +a186010000000000 19 76 a9 14 fdebd30c80543b3c72b5cfff7c788e63d63d29a6 88 ac + +00000000 diff --git a/fixtures/he-dss-partial.hex b/fixtures/he-dss-partial.hex new file mode 100644 index 0000000..b772d2c --- /dev/null +++ b/fixtures/he-dss-partial.hex @@ -0,0 +1,13 @@ +02 + +c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f 03000000 +6b 48 30 45 02 21 0085e8bd03a92422fc8f656ded2c19ceb70c68707a1699dd54782c4395532022b6 + 02 20 7730c221bcf818c0b0445f576b36d2ee0698edc4d3fe872aeaff772122e4251d + 01 21 030c467477c7d4aefb377cbe11c48765669676c556d0149c06da25fd0c2ade39ec +ffffffff + +c0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46 04000000 +6a 47 30 44 02 20 4316f56e90d27505ecb938e52391cc9b0b77474b67283983a63463d0823a813a + 02 20 58bcb6eab28c2f4f936ba85183e750d010808e1bb11201fab5bae6d5a25391ba + 01 21 023c2ff18948d68270f5e150456caa8c1fcb0e3ed33e68340b5e087fd6651cbe7d +ffffffff diff --git a/fixtures/he-dss.hex b/fixtures/he-dss.hex new file mode 100644 index 0000000..5dfb63e --- /dev/null +++ b/fixtures/he-dss.hex @@ -0,0 +1,49 @@ +08 + +c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f 03000000 +6b 48 30 45 02 21 0085e8bd03a92422fc8f656ded2c19ceb70c68707a1699dd54782c4395532022b6 + 02 20 7730c221bcf818c0b0445f576b36d2ee0698edc4d3fe872aeaff772122e4251d + 01 21 030c467477c7d4aefb377cbe11c48765669676c556d0149c06da25fd0c2ade39ec +ffffffff + +c0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46 04000000 +6a 47 30 44 02 20 4316f56e90d27505ecb938e52391cc9b0b77474b67283983a63463d0823a813a + 02 20 58bcb6eab28c2f4f936ba85183e750d010808e1bb11201fab5bae6d5a25391ba + 01 21 023c2ff18948d68270f5e150456caa8c1fcb0e3ed33e68340b5e087fd6651cbe7d +ffffffff + +204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58 01000000 +6a 47 30 44 02 20 0b049725050b28a4a42f80db28bd8e48f266e2a3460cf6a798457420fa54bd0d + 02 20 4edd66bf280cfda43019f48394439009cb234ee5871c2ec490d4d7ba84e6a461 + 01 21 02dde437be25a79dedc6dbf4cf73ac8e3eefd90112ddae827c9042b43d0e8d9bf6 +ffffffff + +86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d77 0a000000 +6b 48 30 45 02 21 009048e55404378c68962bdb4c2acb4ea6592a040ac17be5f5cc2a957b08793062 + 02 20 694e165128a6c40c3bc4f77b369949d80606b9bdcf34e2dd8622107b9a87e3ff + 01 21 025064ef597ec1520e2e3b7836261457eb79522299314703a7beeea2bbf42b9175 +ffffffff + +e6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95 09000000 +6a 47 30 44 02 20 59c43a7d1327d36adce6cfef92e5e2159c83dc9ebc6b28c5ac8c1ec46e6cdcb2 + 02 20 50dea4f95e4a8a6c4a06d71e6c7e5fc9b7249a967f09bd4283c2ed08a1225ed6 + 01 21 0257f68289f77638b808706bced459895b349001a5d86641da619886474e9b417c +ffffffff + +c05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a 09000000 +6a 47 30 44 02 20 5b3398b3ea59a6ebe5cad2be234b64ddc852057197aaf7071c5621739e54ed5f + 02 20 1330850ddd04f318cc5cffb8ce8464dc0d17dcc8e8d37c84db8bdef0b980363f + 01 21 0297f200b4f0dc3967d77d822189276d60e6baad8075d954be933724dfa7ded74f +ffffffff + +697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa 08000000 +6a 47 30 44 02 20 3c3023ee03dee54982ef2176c6b26c81be06d8c09fb19e98ef98c257210f3c0e + 02 20 193b4f24aa9cc5720186dd02c05c9257a46383eed23586a3348d4b9af2d3f304 + 01 21 03e943423c41299842d5532a7800d0e7f6d4a4e89a68b992f071988e3325ca3e82 +ffffffff + +e2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6 07000000 +6b 48 30 45 02 21 00de784c07605de564af098672900651b901bbbfd05a9daa51b51d50f414073e16 + 02 20 20881559ada92c99777d1ec103dded571cbb8a9cfc0052262fbd3bcf14ac9dfe + 01 21 0336d148cfc9a3a3603fa27aca926a73e2d47861194871f1473d9cc173c9ee5df0 +ffffffff diff --git a/fixtures/mnauth.hex b/fixtures/mnauth.hex new file mode 100644 index 0000000..04bf001 --- /dev/null +++ b/fixtures/mnauth.hex @@ -0,0 +1,2 @@ +fcc1b7dc6d6e6175746800000000000080000000feef82b8 +7451ba14649d8bcc2268aa661e065202cda23fe78e190d26f0f16341c930f7c78bed254584fcdeb02ca90f05cf4851be9debfb5557c7bada57040b6cc85062009342c636decd5af9fadf3e5bff55eb150b4df12e122547b3cfc697e69afa7d2b86a743f64146ac4c253e998de946b0a4e99c9c521a862fbed91bc3bc5e14b749fcc1b7dc73656e646865616465727300000000005df6e0e2fcc1b7dc73656e64636d70637400000009000000ccfe104a000100000000000000fcc1b7dc73656e646473710000000000010000009c12cfdc01 diff --git a/fixtures/ping.hex b/fixtures/ping.hex new file mode 100644 index 0000000..4a00387 --- /dev/null +++ b/fixtures/ping.hex @@ -0,0 +1,2 @@ +fcc1b7dc70696e670000000000000000080000001c97a722 +adb85b0a56e96472 diff --git a/fixtures/sendaddrv2.hex b/fixtures/sendaddrv2.hex new file mode 100644 index 0000000..3de971e --- /dev/null +++ b/fixtures/sendaddrv2.hex @@ -0,0 +1 @@ +fcc1b7dc73656e646164647276320000000000005df6e0e2 diff --git a/fixtures/senddsq.hex b/fixtures/senddsq.hex new file mode 100644 index 0000000..50da297 --- /dev/null +++ b/fixtures/senddsq.hex @@ -0,0 +1,2 @@ +fcc1b7dc73656e646473710000000000010000009c12cfdc +01 diff --git a/fixtures/verack.hex b/fixtures/verack.hex new file mode 100644 index 0000000..cd473b1 --- /dev/null +++ b/fixtures/verack.hex @@ -0,0 +1 @@ +fcc1b7dc76657261636b000000000000000000005df6e0e2 diff --git a/fixtures/version.hex b/fixtures/version.hex new file mode 100644 index 0000000..fa99198 --- /dev/null +++ b/fixtures/version.hex @@ -0,0 +1,2 @@ +fcc1b7dc76657273696f6e0000000000890000002f9b4c82 +57120100050c00000000000023dc2d66000000000000000000000000000000000000000000000000000000000000050c000000000000000000000000000000000000000000000000d354bb37d36fa13c122f4461736820436f72653a32302e312e302f892600000179b1a12ee108929deac59da9996060a153519bca1ef85af184e58a3f75198f6900 diff --git a/package-lock.json b/package-lock.json index 07164b8..4420166 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,67 +9,23 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@dashevo/dashcore-lib": "^0.20.6", "@dashincubator/secp256k1": "^1.7.1-5", - "@mentoc/xtract": "^1.0.3", - "dashtx": "^0.9.0", - "node-lmdb": "^0.9.7" + "dashhd": "^3.3.3", + "dashkeys": "^1.1.4", + "dashphrase": "^1.4.0", + "dashrpc": "^19.0.1", + "dashtx": "^0.18.1", + "dotenv": "^16.4.5" }, "devDependencies": { "mocha": "^10.2.0" } }, - "node_modules/@dashevo/bls": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@dashevo/bls/-/bls-1.2.9.tgz", - "integrity": "sha512-7+j0tQMi8fPmgcLgxnfvML2GW/A6xYA1cTLDLN2prfvV6V1OpH6EiwVGKTXBquJT0OQX++upClOKYUlIEQ8+0A==", - "dependencies": { - "binascii": "0.0.2" - } - }, - "node_modules/@dashevo/dashcore-lib": { - "version": "0.20.6", - "resolved": "https://registry.npmjs.org/@dashevo/dashcore-lib/-/dashcore-lib-0.20.6.tgz", - "integrity": "sha512-GDiQJZ8ZV3kyYSMdRgJ0kaNyzBj5xJa0+IKyQdSqLv5v+25E9MCfTrza8hxci29/Hm3yN/nnkGv0a/0BmV24wA==", - "dependencies": { - "@dashevo/bls": "~1.2.9", - "@dashevo/x11-hash-js": "^1.0.2", - "@types/node": "^12.12.47", - "bloom-filter": "^0.2.0", - "bn.js": "^4.12.0", - "bs58": "=4.0.1", - "elliptic": "^6.5.4", - "inherits": "=2.0.1", - "lodash": "^4.17.20", - "ripemd160": "^2.0.2", - "unorm": "^1.6.0" - } - }, - "node_modules/@dashevo/dashcore-lib/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==" - }, - "node_modules/@dashevo/x11-hash-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@dashevo/x11-hash-js/-/x11-hash-js-1.0.2.tgz", - "integrity": "sha512-3vvnZweBca4URBXHF+FTrM4sdTpp3IMt73G1zUKQEdYm/kJkIKN94qpFai7YZDl87k64RCH+ckRZk6ruQPz5KQ==" - }, "node_modules/@dashincubator/secp256k1": { "version": "1.7.1-5", "resolved": "https://registry.npmjs.org/@dashincubator/secp256k1/-/secp256k1-1.7.1-5.tgz", "integrity": "sha512-3iA+RDZrJsRFPpWhlYkp3EdoFAlKjdqkNFiRwajMrzcpA/G/IBX0AnC1pwRLkTrM+tUowcyGrkJfT03U4ETZeg==" }, - "node_modules/@mentoc/xtract": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@mentoc/xtract/-/xtract-1.0.3.tgz", - "integrity": "sha512-0Fcz116CLucafGiXFhovFVo3QH+e7Cerxg5g6GBhmgARUfskF+q7dt6Izab0LdbgAe35ju0HJk9tA7bq0bf9bQ==" - }, - "node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -128,14 +84,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -145,21 +93,6 @@ "node": ">=8" } }, - "node_modules/binascii": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/binascii/-/binascii-0.0.2.tgz", - "integrity": "sha512-rA2CrUl1+6yKrn+XgLs8Hdy18OER1UW146nM+ixzhQXDY+Bd3ySkyIJGwF2a4I45JwbvF1mDL/nWkqBwpOcdBA==" - }, - "node_modules/bloom-filter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bloom-filter/-/bloom-filter-0.2.0.tgz", - "integrity": "sha512-RMG2RpnKczVzRsEYSPaT5rKsyj0w5/wpQRjaW4vOMe1WyUDQpoqxjNc10uROEjdhu63ytRt6aFRPXFePi/Rd7A==" - }, - "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -182,25 +115,12 @@ "node": ">=8" } }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dependencies": { - "base-x": "^3.0.2" - } - }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -303,10 +223,30 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/dashhd": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dashhd/-/dashhd-3.3.3.tgz", + "integrity": "sha512-sbhLV8EtmebnlIdx/d1hcbnxdfka/0rcLx+UO5y44kZdu5tyJ5ftBFbhhIb38vd+T+Xfcwpeo0z+0ZDznRkfaw==" + }, + "node_modules/dashkeys": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/dashkeys/-/dashkeys-1.1.4.tgz", + "integrity": "sha512-y62hg+r8V56gqUfvnyNqCmQ0MvA/wGmRPWfuKFKDCZY02dvWkhCD0zBRVpBGPjfk+T2gWKbkcrHOCrb2RXZ9cw==" + }, + "node_modules/dashphrase": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dashphrase/-/dashphrase-1.4.0.tgz", + "integrity": "sha512-o+LdiPkiYmg07kXBE+2bbcJzBmeTQVPn1GS2XlQeo8lene+KknAprSyiYi5XtqV/QVgNjvzOV7qBst2MijSPAA==" + }, + "node_modules/dashrpc": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/dashrpc/-/dashrpc-19.0.1.tgz", + "integrity": "sha512-1BLXnYZPHHRwvehIF6HqLLSfv2bTZlU97dq/8XJ2F0cBEk3ofi9/fbxYVmDwWtVjqtIJPfdXhSFx7fJu2hJNPA==" + }, "node_modules/dashtx": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.9.0.tgz", - "integrity": "sha512-DDbH5vPChUpOrYMOoM+6g/Iy99KqG4nkJ6f8TphnGibzAY7mitjMgtFSc62YzbZdoPGYeSPm8N4jmz+Mbwm7Eg==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.18.1.tgz", + "integrity": "sha512-5PA8E9tg5ykv6fm6E7fy345gz7pZSrZ+Ao1F8Sq+9CzSIKFPEVBoJm/1i+58An24VBvrrRJUSVq5v7KSGmwm9g==", "bin": { "dashtx-inspect": "bin/inspect.js" } @@ -355,18 +295,15 @@ "node": ">=0.3.1" } }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/emoji-regex": { @@ -515,28 +452,6 @@ "node": ">=8" } }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -546,16 +461,6 @@ "he": "bin/he" } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -569,7 +474,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -670,11 +576,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -691,16 +592,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, "node_modules/minimatch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", @@ -768,11 +659,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" - }, "node_modules/nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", @@ -785,26 +671,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-lmdb": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/node-lmdb/-/node-lmdb-0.9.7.tgz", - "integrity": "sha512-RyYSB3kTByjp1yCKE474yuoBxi/+8kKvjYFOVuDBVoS31D6JvzjwMgNnQrMwoHSbZ6RwucW7jmF93Cm9ltziJQ==", - "hasInstallScript": true, - "dependencies": { - "nan": "^2.14.1", - "node-gyp-build": "^4.2.3" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -892,19 +758,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -926,19 +779,11 @@ "node": ">=0.10.0" } }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -963,14 +808,6 @@ "randombytes": "^2.1.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -1036,19 +873,6 @@ "node": ">=8.0" } }, - "node_modules/unorm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", - "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", @@ -1143,59 +967,11 @@ } }, "dependencies": { - "@dashevo/bls": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@dashevo/bls/-/bls-1.2.9.tgz", - "integrity": "sha512-7+j0tQMi8fPmgcLgxnfvML2GW/A6xYA1cTLDLN2prfvV6V1OpH6EiwVGKTXBquJT0OQX++upClOKYUlIEQ8+0A==", - "requires": { - "binascii": "0.0.2" - } - }, - "@dashevo/dashcore-lib": { - "version": "0.20.6", - "resolved": "https://registry.npmjs.org/@dashevo/dashcore-lib/-/dashcore-lib-0.20.6.tgz", - "integrity": "sha512-GDiQJZ8ZV3kyYSMdRgJ0kaNyzBj5xJa0+IKyQdSqLv5v+25E9MCfTrza8hxci29/Hm3yN/nnkGv0a/0BmV24wA==", - "requires": { - "@dashevo/bls": "~1.2.9", - "@dashevo/x11-hash-js": "^1.0.2", - "@types/node": "^12.12.47", - "bloom-filter": "^0.2.0", - "bn.js": "^4.12.0", - "bs58": "=4.0.1", - "elliptic": "^6.5.4", - "inherits": "=2.0.1", - "lodash": "^4.17.20", - "ripemd160": "^2.0.2", - "unorm": "^1.6.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==" - } - } - }, - "@dashevo/x11-hash-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@dashevo/x11-hash-js/-/x11-hash-js-1.0.2.tgz", - "integrity": "sha512-3vvnZweBca4URBXHF+FTrM4sdTpp3IMt73G1zUKQEdYm/kJkIKN94qpFai7YZDl87k64RCH+ckRZk6ruQPz5KQ==" - }, "@dashincubator/secp256k1": { "version": "1.7.1-5", "resolved": "https://registry.npmjs.org/@dashincubator/secp256k1/-/secp256k1-1.7.1-5.tgz", "integrity": "sha512-3iA+RDZrJsRFPpWhlYkp3EdoFAlKjdqkNFiRwajMrzcpA/G/IBX0AnC1pwRLkTrM+tUowcyGrkJfT03U4ETZeg==" }, - "@mentoc/xtract": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@mentoc/xtract/-/xtract-1.0.3.tgz", - "integrity": "sha512-0Fcz116CLucafGiXFhovFVo3QH+e7Cerxg5g6GBhmgARUfskF+q7dt6Izab0LdbgAe35ju0HJk9tA7bq0bf9bQ==" - }, - "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -1239,35 +1015,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "binascii": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/binascii/-/binascii-0.0.2.tgz", - "integrity": "sha512-rA2CrUl1+6yKrn+XgLs8Hdy18OER1UW146nM+ixzhQXDY+Bd3ySkyIJGwF2a4I45JwbvF1mDL/nWkqBwpOcdBA==" - }, - "bloom-filter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bloom-filter/-/bloom-filter-0.2.0.tgz", - "integrity": "sha512-RMG2RpnKczVzRsEYSPaT5rKsyj0w5/wpQRjaW4vOMe1WyUDQpoqxjNc10uROEjdhu63ytRt6aFRPXFePi/Rd7A==" - }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1287,25 +1040,12 @@ "fill-range": "^7.0.1" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "requires": { - "base-x": "^3.0.2" - } - }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -1381,10 +1121,30 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "dashhd": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dashhd/-/dashhd-3.3.3.tgz", + "integrity": "sha512-sbhLV8EtmebnlIdx/d1hcbnxdfka/0rcLx+UO5y44kZdu5tyJ5ftBFbhhIb38vd+T+Xfcwpeo0z+0ZDznRkfaw==" + }, + "dashkeys": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/dashkeys/-/dashkeys-1.1.4.tgz", + "integrity": "sha512-y62hg+r8V56gqUfvnyNqCmQ0MvA/wGmRPWfuKFKDCZY02dvWkhCD0zBRVpBGPjfk+T2gWKbkcrHOCrb2RXZ9cw==" + }, + "dashphrase": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dashphrase/-/dashphrase-1.4.0.tgz", + "integrity": "sha512-o+LdiPkiYmg07kXBE+2bbcJzBmeTQVPn1GS2XlQeo8lene+KknAprSyiYi5XtqV/QVgNjvzOV7qBst2MijSPAA==" + }, + "dashrpc": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/dashrpc/-/dashrpc-19.0.1.tgz", + "integrity": "sha512-1BLXnYZPHHRwvehIF6HqLLSfv2bTZlU97dq/8XJ2F0cBEk3ofi9/fbxYVmDwWtVjqtIJPfdXhSFx7fJu2hJNPA==" + }, "dashtx": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.9.0.tgz", - "integrity": "sha512-DDbH5vPChUpOrYMOoM+6g/Iy99KqG4nkJ6f8TphnGibzAY7mitjMgtFSc62YzbZdoPGYeSPm8N4jmz+Mbwm7Eg==" + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.18.1.tgz", + "integrity": "sha512-5PA8E9tg5ykv6fm6E7fy345gz7pZSrZ+Ao1F8Sq+9CzSIKFPEVBoJm/1i+58An24VBvrrRJUSVq5v7KSGmwm9g==" }, "debug": { "version": "4.3.4", @@ -1415,19 +1175,10 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } + "dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==" }, "emoji-regex": { "version": "8.0.0", @@ -1531,41 +1282,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1579,7 +1301,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "is-binary-path": { "version": "2.1.0", @@ -1647,11 +1370,6 @@ "p-locate": "^5.0.0" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -1662,16 +1380,6 @@ "is-unicode-supported": "^0.1.0" } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, "minimatch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", @@ -1727,31 +1435,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" - }, "nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, - "node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==" - }, - "node-lmdb": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/node-lmdb/-/node-lmdb-0.9.7.tgz", - "integrity": "sha512-RyYSB3kTByjp1yCKE474yuoBxi/+8kKvjYFOVuDBVoS31D6JvzjwMgNnQrMwoHSbZ6RwucW7jmF93Cm9ltziJQ==", - "requires": { - "nan": "^2.14.1", - "node-gyp-build": "^4.2.3" - } - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1812,16 +1501,6 @@ "safe-buffer": "^5.1.0" } }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1837,19 +1516,11 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true }, "serialize-javascript": { "version": "6.0.0", @@ -1860,14 +1531,6 @@ "randombytes": "^2.1.0" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -1912,16 +1575,6 @@ "is-number": "^7.0.0" } }, - "unorm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", - "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, "workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", diff --git a/package.json b/package.json index f269f0c..2ef8115 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,12 @@ }, "homepage": "https://github.com/dashhive/DashJoin.js#readme", "dependencies": { - "@dashevo/dashcore-lib": "^0.20.6", "@dashincubator/secp256k1": "^1.7.1-5", - "@mentoc/xtract": "^1.0.3", - "dashtx": "^0.9.0", - "node-lmdb": "^0.9.7" + "dashhd": "^3.3.3", + "dashkeys": "^1.1.4", + "dashphrase": "^1.4.0", + "dashrpc": "^19.0.1", + "dashtx": "^0.18.1", + "dotenv": "^16.4.5" } } diff --git a/packer.js b/packer.js new file mode 100644 index 0000000..7a9693c --- /dev/null +++ b/packer.js @@ -0,0 +1,748 @@ +'use strict'; + +let Packer = module.exports; + +let Crypto = require('node:crypto'); + +let CoinJoin = require('./coinjoin.js'); + +Packer.PROTOCOL_VERSION = 70227; + +Packer.FIELD_SIZES = { + VERSION: 4, + SERVICES: 8, + TIMESTAMP: 8, + ADDR_RECV_SERVICES: 8, + ADDR_RECV_IP: 16, + ADDR_RECV_PORT: 2, + ADDR_TRANS_SERVICES: 8, + ADDR_TRANS_IP: 16, + ADDR_TRANS_PORT: 2, + NONCE: 8, + USER_AGENT_BYTES: 1, // can be skipped + USER_AGENT_STRING: 0, + START_HEIGHT: 4, + // The following 2 fields are OPTIONAL + RELAY: 0, + RELAY_NONEMPTY: 1, + MNAUTH_CHALLENGE: 0, + MNAUTH_CHALLENGE_NONEMPTY: 32, + MN_CONNECTION: 0, + MN_CONNECTION_NONEMPTY: 1, +}; + +Packer.RELAY_PROTOCOL_VERSION_INTRODUCTION = 70001; +Packer.MNAUTH_PROTOCOL_VERSION_INTRODUCTION = 70214; + +let textEncoder = new TextEncoder(); + +let SIZES = { + MAGIC_BYTES: 4, + COMMAND_NAME: 12, + PAYLOAD_SIZE: 4, + CHECKSUM: 4, +}; +const TOTAL_HEADER_SIZE = + SIZES.MAGIC_BYTES + SIZES.COMMAND_NAME + SIZES.PAYLOAD_SIZE + SIZES.CHECKSUM; +Packer.HEADER_SIZE = TOTAL_HEADER_SIZE; + +Packer.PING_SIZE = Packer.FIELD_SIZES.NONCE; +Packer.DSQ_SIZE = 1; // bool + +const EMPTY_CHECKSUM = [0x5d, 0xf6, 0xe0, 0xe2]; + +/** + * @typedef {"mainnet"|"testnet"|"regtest"|"devnet"} NetworkName + */ + +Packer.NETWORKS = {}; +Packer.NETWORKS.mainnet = { + port: 9999, + magic: new Uint8Array([ + //0xBD6B0CBF, + 0xbf, 0x0c, 0x6b, 0xbd, + ]), + start: 0xbf0c6bbd, + nBits: 0x1e0ffff0, + minimumParticiparts: 3, +}; +Packer.NETWORKS.testnet = { + port: 19999, + magic: new Uint8Array([ + //0xFFCAE2CE, + 0xce, 0xe2, 0xca, 0xff, + ]), + start: 0xcee2caff, + nBits: 0x1e0ffff0, + minimumParticiparts: 2, +}; +Packer.NETWORKS.regtest = { + port: 19899, + magic: new Uint8Array([ + //0xDCB7C1FC, + 0xfc, 0xc1, 0xb7, 0xdc, + ]), + start: 0xfcc1b7dc, + nBits: 0x207fffff, + minimumParticiparts: 2, +}; +Packer.NETWORKS.devnet = { + port: 19799, + magic: new Uint8Array([ + //0xCEFFCAE2, + 0xe2, 0xca, 0xff, 0xce, + ]), + start: 0xe2caffce, + nBits: 0x207fffff, + minimumParticiparts: 2, +}; + +/** + * @typedef {0x01|0x02|0x04|0x400} ServiceBitmask + * @typedef {"NETWORK"|"GETUTXO "|"BLOOM"|"NETWORK_LIMITED"} ServiceName + */ + +/** @type {Object.} */ +let SERVICE_IDENTIFIERS = {}; + +/** + * 0x00 is the default - not a full node, no guarantees + */ + +/** + * NODE_NETWORK: + * This is a full node and can be asked for full + * blocks. It should implement all protocol features + * available in its self-reported protocol version. + */ +SERVICE_IDENTIFIERS.NETWORK = 0x01; + +/** + * NODE_GETUTXO: + * This node is capable of responding to the getutxo + * protocol request. Dash Core does not support + * this service. + */ +SERVICE_IDENTIFIERS.GETUTXO = 0x02; + +/** + * NODE_BLOOM: + * This node is capable and willing to handle bloom- + * filtered connections. Dash Core nodes used to support + * this by default, without advertising this bit, but + * no longer do as of protocol version 70201 + * (= NO_BLOOM_VERSION) + */ +SERVICE_IDENTIFIERS.BLOOM = 0x04; + +/** + * 0x08 is not supported by Dash + */ + +/** + * NODE_NETWORK_LIMITED: + * This is the same as NODE_NETWORK with the + * limitation of only serving the last 288 blocks. + * Not supported prior to Dash Core 0.16.0 + */ +SERVICE_IDENTIFIERS.NETWORK_LIMITED = 0x400; + +/** + * @typedef VersionOpts + * @prop {NetworkName} network - "mainnet", "testnet", etc + * @prop {Uint32?} [protocol_version] - features (default: Packer.PROTOCOL_VERSION) + * @prop {Array?} [addr_recv_services] - default: NETWORK + * @prop {String} addr_recv_ip - ipv6 address (can be 'ipv4-mapped') of the server + * @prop {Uint16} addr_recv_port - 9999, 19999, etc (can be arbitrary on testnet) + * @prop {Array?} [addr_trans_services] - default: NONE + * @prop {String?} [addr_trans_ip]- null, or the external ipv6 or ipv4-mapped address + * @prop {Uint16} [addr_trans_port] - null, or the external port (ignored for tcp?) + * @prop {Uint32} start_height - start height of your best block + * @prop {Uint8Array?} [nonce] - 8 random bytes to identify this transmission + * @prop {String?} [user_agent] - ex: "DashJoin/1.0 request/1.0 node/20.0.0 macos/14.0" + * @prop {Boolean?} [relay] - request all network tx & inv messages to be relayed to you + * @prop {Uint8Array?} [mnauth_challenge] - 32 bytes for the masternode to sign as proof + */ + +/** + * Constructs a version message, with fields in the correct byte order. + * @param {VersionOpts} opts + * + * See also: + * - https://dashcore.readme.io/docs/core-ref-p2p-network-control-messages#version + */ +/* jshint maxcomplexity: 9001 */ +/* jshint maxstatements:150 */ +/* (it's simply very complex, okay?) */ +Packer.version = function ({ + network, + protocol_version = Packer.PROTOCOL_VERSION, + // alias of addr_trans_services + //services, + addr_recv_services = [SERVICE_IDENTIFIERS.NETWORK], + addr_recv_ip, + addr_recv_port, + addr_trans_services = [], + addr_trans_ip = '127.0.0.1', + addr_trans_port = 65535, + start_height, + nonce = null, + user_agent = null, + relay = null, + mnauth_challenge = null, +}) { + const command = 'version'; + + let args = { + network, + protocol_version, + addr_recv_services, + addr_recv_ip, + addr_recv_port, + addr_trans_services, + addr_trans_ip, + addr_trans_port, + start_height, + nonce, + user_agent, + relay, + mnauth_challenge, + }; + let SIZES = Object.assign({}, Packer.FIELD_SIZES); + + if (!Packer.NETWORKS[args.network]) { + throw new Error(`"network" '${args.network}' is invalid.`); + } + if (!Array.isArray(args.addr_recv_services)) { + throw new Error('"addr_recv_services" must be an array'); + } + if ( + //@ts-ignore - protocol_version has a default value + args.protocol_version < Packer.RELAY_PROTOCOL_VERSION_INTRODUCTION && + args.relay !== null + ) { + throw new Error( + `"relay" field is not supported in protocol versions prior to ${Packer.RELAY_PROTOCOL_VERSION_INTRODUCTION}`, + ); + } + if ( + //@ts-ignore - protocol_version has a default value + args.protocol_version < Packer.MNAUTH_PROTOCOL_VERSION_INTRODUCTION && + args.mnauth_challenge !== null + ) { + throw new Error( + '"mnauth_challenge" field is not supported in protocol versions prior to MNAUTH_CHALLENGE_OFFSET', + ); + } + if (args.mnauth_challenge !== null) { + if (!(args.mnauth_challenge instanceof Uint8Array)) { + throw new Error('"mnauth_challenge" field must be a Uint8Array'); + } + if ( + args.mnauth_challenge.length !== Packer.SIZES.MNAUTH_CHALLENGE_NONEMPTY + ) { + throw new Error( + `"mnauth_challenge" field must be ${Packer.SIZES.MNAUTH_CHALLENGE_NONEMPTY} bytes long`, + ); + } + } + SIZES.USER_AGENT_STRING = args.user_agent?.length || 0; + if (args.relay !== null) { + SIZES.RELAY = Packer.FIELD_SIZES.RELAY_NONEMPTY; + } + // if (args.mnauth_challenge !== null) { + SIZES.MNAUTH_CHALLENGE = Packer.FIELD_SIZES.MNAUTH_CHALLENGE_NONEMPTY; + // } + SIZES.MN_CONNECTION = Packer.FIELD_SIZES.MN_CONNECTION_NONEMPTY; + + let TOTAL_SIZE = + SIZES.VERSION + + SIZES.SERVICES + + SIZES.TIMESTAMP + + SIZES.ADDR_RECV_SERVICES + + SIZES.ADDR_RECV_IP + + SIZES.ADDR_RECV_PORT + + SIZES.ADDR_TRANS_SERVICES + + SIZES.ADDR_TRANS_IP + + SIZES.ADDR_TRANS_PORT + + SIZES.NONCE + + SIZES.USER_AGENT_BYTES + + SIZES.USER_AGENT_STRING + + SIZES.START_HEIGHT + + SIZES.RELAY + + SIZES.MNAUTH_CHALLENGE + + SIZES.MN_CONNECTION; + let payload = new Uint8Array(TOTAL_SIZE); + // Protocol version + + //@ts-ignore - protocol_version has a default value + let versionBytes = uint32ToBytesLE(args.protocol_version); + payload.set(versionBytes, 0); + + /** + * Set services to NODE_NETWORK (1) + NODE_BLOOM (4) + */ + const SERVICES_OFFSET = SIZES.VERSION; + let senderServicesBytes; + { + let senderServicesMask = 0n; + //@ts-ignore - addr_trans_services has a default value of [] + for (const serviceBit of addr_trans_services) { + senderServicesMask += BigInt(serviceBit); + } + let senderServices64 = new BigInt64Array([senderServicesMask]); // jshint ignore:line + senderServicesBytes = new Uint8Array(senderServices64.buffer); + payload.set(senderServicesBytes, SERVICES_OFFSET); + } + + const TIMESTAMP_OFFSET = SERVICES_OFFSET + SIZES.SERVICES; + { + let tsBytes = uint32ToBytesLE(Date.now()); + payload.set(tsBytes, TIMESTAMP_OFFSET); + } + + let ADDR_RECV_SERVICES_OFFSET = TIMESTAMP_OFFSET + SIZES.TIMESTAMP; + { + let serverServicesMask = 0n; + //@ts-ignore - addr_recv_services has a default value + for (const serviceBit of addr_recv_services) { + serverServicesMask += BigInt(serviceBit); + } + let serverServices64 = new BigInt64Array([serverServicesMask]); // jshint ignore:line + let serverServicesBytes = new Uint8Array(serverServices64.buffer); + payload.set(serverServicesBytes, ADDR_RECV_SERVICES_OFFSET); + } + + /** + * "ADDR_RECV" means the host that we're sending this traffic to. + * So, in other words, it's the master node + */ + let ADDR_RECV_IP_OFFSET = + ADDR_RECV_SERVICES_OFFSET + SIZES.ADDR_RECV_SERVICES; + { + let ipBytesBE = ipv4ToBytesBE(args.addr_recv_ip); + payload.set([0xff, 0xff], ADDR_RECV_IP_OFFSET + 10); + payload.set(ipBytesBE, ADDR_RECV_IP_OFFSET + 12); + } + + /** + * Copy address recv port + */ + let ADDR_RECV_PORT_OFFSET = ADDR_RECV_IP_OFFSET + SIZES.ADDR_RECV_IP; + { + let portBytes16 = Uint16Array.from([args.addr_recv_port]); + let portBytes = new Uint8Array(portBytes16.buffer); + portBytes.reverse(); + payload.set(portBytes, ADDR_RECV_PORT_OFFSET); + } + + /** + * Copy address transmitted services + */ + let ADDR_TRANS_SERVICES_OFFSET = ADDR_RECV_PORT_OFFSET + SIZES.ADDR_RECV_PORT; + payload.set(senderServicesBytes, ADDR_TRANS_SERVICES_OFFSET); + + /** + * We add the extra 10, so that we can encode an ipv4-mapped ipv6 address + */ + let ADDR_TRANS_IP_OFFSET = + ADDR_TRANS_SERVICES_OFFSET + SIZES.ADDR_TRANS_SERVICES; + { + //@ts-ignore - addr_trans_ip has a default value + if (is_ipv6_mapped_ipv4(args.addr_trans_ip)) { + //@ts-ignore - addr_trans_ip has a default value + let ipv6Parts = args.addr_trans_ip.split(':'); + let ipv4Str = ipv6Parts.at(-1); + //@ts-ignore - guaranteed to be defined, actually + let ipBytesBE = ipv4ToBytesBE(ipv4Str); + payload.set(ipBytesBE, ADDR_TRANS_IP_OFFSET + 12); + payload.set([0xff, 0xff], ADDR_TRANS_IP_OFFSET + 10); // we add the 10 so that we can fill the latter 6 bytes + } else { + /** TODO: ipv4-only & ipv6-only */ + //@ts-ignore - addr_trans_ip has a default value + let ipBytesBE = ipv4ToBytesBE(args.addr_trans_ip); + payload.set(ipBytesBE, ADDR_TRANS_IP_OFFSET + 12); + payload.set([0xff, 0xff], ADDR_TRANS_IP_OFFSET + 10); // we add the 10 so that we can fill the latter 6 bytes + } + } + + let ADDR_TRANS_PORT_OFFSET = ADDR_TRANS_IP_OFFSET + SIZES.ADDR_TRANS_IP; + { + let portBytes16 = Uint16Array.from([args.addr_trans_port]); + let portBytes = new Uint8Array(portBytes16.buffer); + portBytes.reverse(); + payload.set(portBytes, ADDR_TRANS_PORT_OFFSET); + } + + // TODO we should set this to prevent duplicate broadcast + // this can be left zero + let NONCE_OFFSET = ADDR_TRANS_PORT_OFFSET + SIZES.ADDR_TRANS_PORT; + if (!args.nonce) { + args.nonce = new Uint8Array(SIZES.NONCE); + Crypto.getRandomValues(args.nonce); + } + payload.set(args.nonce, NONCE_OFFSET); + + let USER_AGENT_BYTES_OFFSET = NONCE_OFFSET + SIZES.NONCE; + if (null !== args.user_agent && typeof args.user_agent === 'string') { + let userAgentSize = args.user_agent.length; + payload.set([userAgentSize], USER_AGENT_BYTES_OFFSET); + let uaBytes = textEncoder.encode(args.user_agent); + payload.set(uaBytes, USER_AGENT_BYTES_OFFSET + 1); + } else { + payload.set([0x0], USER_AGENT_BYTES_OFFSET); + } + + let START_HEIGHT_OFFSET = + USER_AGENT_BYTES_OFFSET + SIZES.USER_AGENT_BYTES + SIZES.USER_AGENT_STRING; + { + let heightBytes = uint32ToBytesLE(args.start_height); + payload.set(heightBytes, START_HEIGHT_OFFSET); + } + + let RELAY_OFFSET = START_HEIGHT_OFFSET + SIZES.START_HEIGHT; + if (args.relay !== null) { + let bytes = [0x00]; + if (args.relay) { + bytes[0] = 0x01; + } + payload.set(bytes, RELAY_OFFSET); + } + + let MNAUTH_CHALLENGE_OFFSET = RELAY_OFFSET + SIZES.RELAY; + if (!args.mnauth_challenge) { + let rnd = new Uint8Array(32); + Crypto.getRandomValues(rnd); + args.mnauth_challenge = rnd; + } + payload.set(args.mnauth_challenge, MNAUTH_CHALLENGE_OFFSET); + + // let MNAUTH_CONNECTION_OFFSET = MNAUTH_CHALLENGE_OFFSET + SIZES.MN_CONNECTION; + // if (args.mn_connection) { + // payload.set([0x01], MNAUTH_CONNECTION_OFFSET); + // } + + payload = Packer.packMessage({ network, command, payload }); + return payload; +}; + +/** + * In this case the only bytes are the nonce + * Use a .subarray(offset) to define an offset. + * (a manual offset will not work consistently, and .byteOffset is context-sensitive) + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Uint8Array?} [opts.message] + * @param {Uint8Array?} [opts.nonce] + */ +Packer.packPing = function ({ network, message = null, nonce = null }) { + const command = 'ping'; + + if (!message) { + let pingSize = Packer.HEADER_SIZE + Packer.PING_SIZE; + message = new Uint8Array(pingSize); + } + let payload = message.subarray(Packer.HEADER_SIZE); + + if (!nonce) { + nonce = payload; + Crypto.getRandomValues(nonce); + } else { + payload.set(nonce, 0); + } + + void Packer.packMessage({ network, command, bytes: message }); + return message; +}; + +/** + * In this case the only bytes are the nonce + * Use a .subarray(offset) to define an offset. + * (a manual offset will not work consistently, and .byteOffset is context-sensitive) + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Uint8Array?} [opts.message] + * @param {Uint8Array} opts.nonce + */ +Packer.packPong = function ({ network, message = null, nonce }) { + const command = 'pong'; + + if (!message) { + let pongSize = Packer.HEADER_SIZE + Packer.PING_SIZE; + message = new Uint8Array(pongSize); + } + let payload = message.subarray(Packer.HEADER_SIZE); + payload.set(nonce, 0); + + void Packer.packMessage({ network, command, bytes: message }); + return message; +}; + +/** + * Turns on or off DSQ messages (necessary for CoinJoin, but off by default) + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Uint8Array?} [opts.message] + * @param {Boolean?} [opts.send] + */ +Packer.packSendDsq = function ({ network, message = null, send = true }) { + const command = 'senddsq'; + + if (!message) { + let dsqSize = Packer.HEADER_SIZE + Packer.DSQ_SIZE; + message = new Uint8Array(dsqSize); + } + + let sendByte = [0x01]; + if (!send) { + sendByte = [0x00]; + } + let payload = message.subarray(Packer.HEADER_SIZE); + payload.set(sendByte, 0); + + void Packer.packMessage({ network, command, bytes: message }); + + return message; +}; + +/** + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Uint32} opts.denomination + * @param {Uint8Array} opts.collateralTx + */ +Packer.packAllow = function ({ network, denomination, collateralTx }) { + const command = 'dsa'; + const DENOMINATION_SIZE = 4; + + //@ts-ignore - numbers can be used as map keys + let denomMask = CoinJoin.STANDARD_DENOMINATION_MASKS[denomination]; + if (!denomMask) { + throw new Error( + `contact your local Dash representative to vote for denominations of '${denomination}'`, + ); + } + + let totalLength = DENOMINATION_SIZE + collateralTx.length; + let payload = new Uint8Array(totalLength); + let dv = new DataView(payload.buffer); + let offset = 0; + + let DV_LITTLE_ENDIAN = true; + dv.setUint32(offset, denomMask, DV_LITTLE_ENDIAN); + offset += DENOMINATION_SIZE; + + payload.set(collateralTx, offset); + + let message = Packer.packMessage({ network, command, payload }); + return message; +}; + +let DashTx = require('dashtx'); + +/** + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Array} opts.inputs + * @param {Array} opts.outputs + * @param {Uint8Array} opts.collateralTx + */ +Packer.packDsi = function ({ network, inputs, collateralTx, outputs }) { + const command = 'dsi'; + + let neutered = []; + for (let input of inputs) { + let _input = { + txId: input.txId || input.txid, + txid: input.txid || input.txId, + outputIndex: input.outputIndex, + }; + neutered.push(_input); + } + + let inputsHex = DashTx.serializeInputs(inputs); + let inputHex = inputsHex.join(''); + let outputsHex = DashTx.serializeOutputs(outputs); + let outputHex = outputsHex.join(''); + + let len = collateralTx.length; + len += inputHex.length / 2; + len += outputHex.length / 2; + let bytes = new Uint8Array(Packer.HEADER_SIZE + len); + + let offset = Packer.HEADER_SIZE; + + { + let inputsPayload = bytes.subarray(offset); + let j = 0; + for (let i = 0; i < inputHex.length; i += 2) { + let end = i + 2; + let hex = inputHex.slice(i, end); + inputsPayload[j] = parseInt(hex, 16); + j += 1; + } + offset += inputHex.length / 2; + } + + bytes.set(collateralTx, offset); + offset += collateralTx.length; + + { + let outputsPayload = bytes.subarray(offset); + let j = 0; + for (let i = 0; i < outputHex.length; i += 2) { + let end = i + 2; + let hex = outputHex.slice(i, end); + outputsPayload[j] = parseInt(hex, 16); + j += 1; + } + offset += outputHex.length / 2; + } + + void Packer.packMessage({ network, command, bytes }); + return bytes; +}; + +/** + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Array} [opts.inputs] + */ +Packer.packDss = function ({ network, inputs }) { + const command = 'dss'; + + if (!inputs?.length) { + // TODO make better + throw new Error('you must provide some inputs'); + } + + let txInputsHex = DashTx.serializeInputs(inputs); + let txInputHex = txInputsHex.join(''); + let payload = DashTx.utils.hexToBytes(txInputHex); + + // TODO prealloc bytes + let bytes = Packer.packMessage({ network, command, payload }); + return bytes; +}; + +/** + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {String} opts.command + * @param {Uint8Array?} [opts.bytes] + * @param {Uint8Array?} [opts.payload] + */ +Packer.packMessage = function ({ + network, + command, + bytes = null, + payload = null, +}) { + let payloadLength = payload?.byteLength || 0; + let messageSize = Packer.HEADER_SIZE + payloadLength; + let offset = 0; + + let embeddedPayload = false; + let message = bytes; + if (message) { + if (!payload) { + payload = message.subarray(Packer.HEADER_SIZE); + payloadLength = payload.byteLength; + messageSize = Packer.HEADER_SIZE + payloadLength; + embeddedPayload = true; + } + } else { + message = new Uint8Array(messageSize); + } + if (message.length !== messageSize) { + throw new Error( + `expected bytes of length ${messageSize}, but got ${message.length}`, + ); + } + message.set(Packer.NETWORKS[network].magic, offset); + offset += SIZES.MAGIC_BYTES; + + // Set command_name (char[12]) + let nameBytes = textEncoder.encode(command); + message.set(nameBytes, offset); + offset += SIZES.COMMAND_NAME; + + // Finally, append the payload to the header + if (!payload) { + // skip because it's already initialized to 0 + //message.set(payloadLength, offset); + offset += SIZES.PAYLOAD_SIZE; + + message.set(EMPTY_CHECKSUM, offset); + return message; + } + + let payloadSizeBytes = uint32ToBytesLE(payloadLength); + message.set(payloadSizeBytes, offset); + offset += SIZES.PAYLOAD_SIZE; + + let checksum = compute_checksum(payload); + message.set(checksum, offset); + offset += SIZES.CHECKSUM; + + if (!embeddedPayload) { + message.set(payload, offset); + } + return message; +}; + +/** + * First 4 bytes of SHA256(SHA256(payload)) in internal byte order. + * @param {Uint8Array} payload + */ +function compute_checksum(payload) { + // TODO this should be node-specific in node for performance reasons + let hash = Crypto.createHash('sha256').update(payload).digest(); + let hashOfHash = Crypto.createHash('sha256').update(hash).digest(); + return hashOfHash.slice(0, 4); +} + +/** + * @param {String} ipv4 + */ +function ipv4ToBytesBE(ipv4) { + let u8s = []; + // let u8s = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff /*,0,0,0,0*/]; + + let octets = ipv4.split('.'); + for (let octet of octets) { + let int8 = parseInt(octet); + u8s.push(int8); + } + + let bytes = Uint8Array.from(u8s); + return bytes; +} + +/** + * @param {Uint32} n + */ +function uint32ToBytesLE(n) { + let u32 = new Uint32Array([n]); + let u8 = new Uint8Array(u32.buffer); + return u8; +} + +/** + * @param {String} ip + */ +function is_ipv6_mapped_ipv4(ip) { + return !!ip.match(/^[:]{2}[f]{4}[:]{1}.*$/); +} + +/** + * @typedef {Number} Uint64 + * @typedef {Number} Uint32 + * @typedef {Number} Uint16 + */ + +/** + * + * addr_recv_ip is the ipv6 address of the master node (can be 'ipv4-mapped') + * @typedef {String} Ipv6Addr + */ diff --git a/parser.js b/parser.js new file mode 100644 index 0000000..f2d7179 --- /dev/null +++ b/parser.js @@ -0,0 +1,397 @@ +'use strict'; + +let Parser = module.exports; + +const DV_LITTLE_ENDIAN = true; +// const DV_BIG_ENDIAN = false; +//let EMPTY_HASH = Buffer.from('5df6e0e2', 'hex'); + +Parser.HEADER_SIZE = 24; +Parser.DSSU_SIZE = 16; +Parser.DSQ_SIZE = 142; +Parser.SESSION_ID_SIZE = 4; + +let CoinJoin = require('./coinjoin.js'); +let DashTx = require('dashtx'); + +/** + * Parse the 24-byte P2P Message Header + * - 4 byte magic bytes (delimiter) (possibly intended for non-tcp messages?) + * - 12 byte string (stop at first null) + * - 4 byte payload size + * - 4 byte checksum + * + * See also: + * - https://docs.dash.org/projects/core/en/stable/docs/reference/p2p-network-message-headers.html#message-headers + * @param {Uint8Array} bytes + */ +Parser.parseHeader = function (bytes) { + let buffer = Buffer.from(bytes); + // console.log( + // new Date(), + // '[debug] parseHeader(bytes)', + // buffer.length, + // buffer.toString('hex'), + // ); + // console.log(buffer.toString('utf8')); + + bytes = new Uint8Array(buffer); + if (bytes.length < Parser.HEADER_SIZE) { + let msg = `developer error: header should be ${Parser.HEADER_SIZE}+ bytes (optional payload), not ${bytes.length}`; + throw new Error(msg); + } + let dv = new DataView(bytes.buffer); + + let commandStart = 4; + let payloadSizeStart = 16; + let checksumStart = 20; + + let magicBytes = buffer.slice(0, commandStart); + + let commandEnd = buffer.indexOf(0x00, commandStart); + if (commandEnd >= payloadSizeStart) { + throw new Error('command name longer than 12 bytes'); + } + let commandBuf = buffer.slice(commandStart, commandEnd); + let command = commandBuf.toString('utf8'); + + let payloadSize = dv.getUint32(payloadSizeStart, DV_LITTLE_ENDIAN); + let checksum = buffer.slice(checksumStart, checksumStart + 4); + + let headerMessage = { + magicBytes, + command, + payloadSize, + checksum, + }; + + // if (command !== 'inv') { + // console.log(new Date(), headerMessage); + // } + // console.log(); + return headerMessage; +}; + +/** + * @param {Uint8Array} bytes + */ +Parser.parseVersion = function (bytes) { + let buffer = Buffer.from(bytes); + // console.log( + // '[debug] parseVersion(bytes)', + // buffer.length, + // buffer.toString('hex'), + // ); + // console.log(buffer.toString('utf8')); + + bytes = new Uint8Array(buffer); + let dv = new DataView(bytes.buffer); + + let versionStart = 0; + let version = dv.getUint32(versionStart, DV_LITTLE_ENDIAN); + + let servicesStart = versionStart + 4; // + SIZES.VERSION (4) + let servicesMask = dv.getBigUint64(servicesStart, DV_LITTLE_ENDIAN); + + let timestampStart = servicesStart + 8; // + SIZES.SERVICES (8) + let timestamp64n = dv.getBigInt64(timestampStart, DV_LITTLE_ENDIAN); + let timestamp64 = Number(timestamp64n); + let timestampMs = timestamp64 * 1000; + let timestamp = new Date(timestampMs); + + let addrRecvServicesStart = timestampStart + 8; // + SIZES.TIMESTAMP (8) + let addrRecvServicesMask = dv.getBigUint64( + addrRecvServicesStart, + DV_LITTLE_ENDIAN, + ); + + let addrRecvAddressStart = addrRecvServicesStart + 8; // + SIZES.SERVICES (8) + let addrRecvAddress = buffer.slice( + addrRecvAddressStart, + addrRecvAddressStart + 16, + ); + + let addrRecvPortStart = addrRecvAddressStart + 16; // + SIZES.IPV6 (16) + let addrRecvPort = dv.getUint16(addrRecvPortStart, DV_LITTLE_ENDIAN); + + let addrTransServicesStart = addrRecvPortStart + 2; // + SIZES.PORT (2) + let addrTransServicesMask = dv.getBigUint64( + addrTransServicesStart, + DV_LITTLE_ENDIAN, + ); + + let addrTransAddressStart = addrTransServicesStart + 8; // + SIZES.SERVICES (8) + let addrTransAddress = buffer.slice( + addrTransAddressStart, + addrTransAddressStart + 16, + ); + + let addrTransPortStart = addrTransAddressStart + 16; // + SIZES.IPV6 (16) + let addrTransPort = dv.getUint16(addrTransPortStart, DV_LITTLE_ENDIAN); + + let nonceStart = addrTransPortStart + 2; // + SIZES.PORT (2) + let nonce = buffer.slice(nonceStart, nonceStart + 8); + + let uaSizeStart = 80; // + SIZES.PORT (2) + let uaSize = buffer[uaSizeStart]; + + let uaStart = uaSizeStart + 1; + let uaBytes = buffer.slice(uaStart, uaStart + uaSize); + let ua = uaBytes.toString('utf8'); + + let startHeightStart = uaStart + uaSize; + let startHeight = dv.getUint32(startHeightStart, DV_LITTLE_ENDIAN); + + let relayStart = startHeightStart + 4; + /** @type {Boolean?} */ + let relay = null; + if (buffer.length > relayStart) { + relay = buffer[relayStart] > 0; + } + + let mnAuthChStart = relayStart + 1; + /** @type {Uint8Array?} */ + let mnAuthChallenge = null; + if (buffer.length > mnAuthChStart) { + mnAuthChallenge = buffer.slice(mnAuthChStart, mnAuthChStart + 32); + } + + let mnConnStart = mnAuthChStart + 32; + /** @type {Boolean?} */ + let mnConn = null; + if (buffer.length > mnConnStart) { + mnConn = buffer[mnConnStart] > 0; + } + + let versionMessage = { + version, + servicesMask, + timestamp, + addrRecvServicesMask, + addrRecvAddress, + addrRecvPort, + addrTransServicesMask, + addrTransAddress, + addrTransPort, + nonce, + ua, + startHeight, + relay, + mnAuthChallenge, + mnConn, + }; + + // console.log(versionMessage); + // console.log(); + return versionMessage; +}; + +Parser._DSSU_MESSAGE_IDS = { + 0x00: 'ERR_ALREADY_HAVE', + 0x01: 'ERR_DENOM', + 0x02: 'ERR_ENTRIES_FULL', + 0x03: 'ERR_EXISTING_TX', + 0x04: 'ERR_FEES', + 0x05: 'ERR_INVALID_COLLATERAL', + 0x06: 'ERR_INVALID_INPUT', + 0x07: 'ERR_INVALID_SCRIPT', + 0x08: 'ERR_INVALID_TX', + 0x09: 'ERR_MAXIMUM', + 0x0a: 'ERR_MN_LIST', // <-- + 0x0b: 'ERR_MODE', + 0x0c: 'ERR_NON_STANDARD_PUBKEY', // (Not used) + 0x0d: 'ERR_NOT_A_MN', //(Not used) + 0x0e: 'ERR_QUEUE_FULL', + 0x0f: 'ERR_RECENT', + 0x10: 'ERR_SESSION', + 0x11: 'ERR_MISSING_TX', + 0x12: 'ERR_VERSION', + 0x13: 'MSG_NOERR', + 0x14: 'MSG_SUCCESS', + 0x15: 'MSG_ENTRIES_ADDED', + 0x16: 'ERR_SIZE_MISMATCH', +}; + +Parser._DSSU_STATES = { + 0x00: 'IDLE', + 0x01: 'QUEUE', + 0x02: 'ACCEPTING_ENTRIES', + 0x03: 'SIGNING', + 0x04: 'ERROR', + 0x05: 'SUCCESS', +}; + +Parser._DSSU_STATUSES = { + 0x00: 'REJECTED', + 0x01: 'ACCEPTED', +}; + +/** + * @param {Uint8Array} bytes + */ +Parser.parseDssu = function (bytes) { + let buffer = Buffer.from(bytes); + + bytes = new Uint8Array(buffer); + let dv = new DataView(bytes.buffer); + // console.log('[debug] parseDssu(bytes)', bytes.length, buffer.toString('hex')); + // console.log(buffer.toString('utf8')); + if (bytes.length !== Parser.DSSU_SIZE) { + let msg = `developer error: a 'dssu' message is 16 bytes, but got ${bytes.length}`; + throw new Error(msg); + } + + /** + * 4 nMsgSessionID - Required - Session ID + * 4 nMsgState - Required - Current state of processing + * 4 nMsgEntriesCount - Required - Number of entries in the pool (deprecated) + * 4 nMsgStatusUpdate - Required - Update state and/or signal if entry was accepted or not + * 4 nMsgMessageID - Required - ID of the typical masternode reply message + */ + const SIZES = { + SESSION_ID: Parser.SESSION_ID_SIZE, + STATE: 4, + ENTRIES_COUNT: 4, + STATUS_UPDATE: 4, + MESSAGE_ID: 4, + }; + + let offset = 0; + + let session_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + offset += SIZES.SESSION_ID; + + let state_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + offset += SIZES.STATE; + + ///** + // * Grab the entries count + // * Not parsed because apparently master nodes no longer send + // * the entries count. + // */ + //parsed.entries_count = dv.getUint32(offset, DV_LITTLE_ENDIAN); + //offset += SIZES.ENTRIES_COUNT; + + let status_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + offset += SIZES.STATUS_UPDATE; + + let message_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + + let dssuMessage = { + session_id: session_id, + state_id: state_id, + state: Parser._DSSU_STATES[state_id], + // entries_count: 0, + status_id: status_id, + status: Parser._DSSU_STATUSES[status_id], + message_id: message_id, + message: Parser._DSSU_MESSAGE_IDS[message_id], + }; + + // console.log(dssuMessage); + // console.log(); + return dssuMessage; +}; + +/** + * @param {Uint8Array} bytes + */ +Parser.parseDsq = function (bytes) { + let buffer = Buffer.from(bytes); + + bytes = new Uint8Array(buffer); + if (bytes.length !== Parser.DSQ_SIZE) { + let msg = `developer error: 'dsq' messages are ${Parser.DSQ_SIZE} bytes, not ${bytes.length}`; + throw new Error(msg); + } + let dv = new DataView(bytes.buffer); + // console.log('[debug] parseDsq(bytes)', bytes.length, buffer.toString('hex')); + // console.log(buffer.toString('utf8')); + + const SIZES = { + DENOM: 4, + PROTX: 32, + TIME: 8, + READY: 1, + SIG: 97, + }; + + let offset = 0; + + let denomination_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + offset += SIZES.DENOM; + + //@ts-ignore - correctness of denomination must be checked higher up + let denomination = CoinJoin.STANDARD_DENOMINATIONS_MAP[denomination_id]; + + /** + * Grab the protxhash + */ + let protxhash_bytes = bytes.slice(offset, offset + SIZES.PROTX); + offset += SIZES.PROTX; + + /** + * Grab the time + */ + let timestamp64n = dv.getBigInt64(offset, DV_LITTLE_ENDIAN); + offset += SIZES.TIME; + let timestamp_unix = Number(timestamp64n); + let timestampMs = timestamp_unix * 1000; + let timestampDate = new Date(timestampMs); + let timestamp = timestampDate.toISOString(); + + /** + * Grab the fReady + */ + let ready = bytes[offset] > 0x00; + offset += SIZES.READY; + + let signature_bytes = bytes.slice(offset, offset + SIZES.SIG); + + let dsqMessage = { + denomination_id, + denomination, + protxhash_bytes, + // protxhash: '', + timestamp_unix, + timestamp, + ready, + signature_bytes, + // signature: '', + }; + + // console.log(dsqMessage); + // console.log(); + return dsqMessage; +}; + +/** + * @param {Uint8Array} bytes + */ +Parser.parseDsf = function (bytes) { + // console.log( + // new Date(), + // '[debug] parseDsf (msg len)', + // bytes.length, + // bytes.toString('hex'), + // ); + + let offset = 0; + let sessionId = bytes.subarray(offset, Parser.SESSION_ID_SIZE); + let session_id = DashTx.utils.bytesToHex(sessionId); + offset += Parser.SESSION_ID_SIZE; + + // TODO parse transaction completely with DashTx + let transactionUnsigned = bytes.subarray(offset); + let transaction_unsigned = DashTx.utils.bytesToHex(transactionUnsigned); + + // let txLen = transaction_unsigned.length / 2; + // console.log( + // new Date(), + // '[debug] parseDsf (tx len)', + // txLen, + // transaction_unsigned, + // ); + + return { session_id, transaction_unsigned }; +}; diff --git a/tests/dsa.js b/tests/dsa.js new file mode 100644 index 0000000..745c412 --- /dev/null +++ b/tests/dsa.js @@ -0,0 +1,52 @@ +'use strict'; + +let Packer = require('../packer.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +// regtest magic network bytes +let network = 'regtest'; +let regtest = 'fcc1b7dc'; + +// dsa\0\0\0\0\0\0\0\0\0 +let command = '647361000000000000000000'; + +// collateralTx.length (195 or 0xc3) as LITTLE ENDIAN +let payloadSize = 'c3000000'; + +// sha256(sha256(collateralTx)) +let checksum = 'edf0d8b1'; + +// 0.10000100 +// 10000100: 0b00000100 as LITTLE_ENDIAN +let denomination = 10000100; +let denomMask = '04000000'; + +// 10000 fee, +// (decode at https://live.blockcypher.com/dash/decodetx/) +let collateralTxHex = + '030000000127c03b36308e0925666682de270cdaa212b696776ac3529242851e338c627a1c000000006a47304402203d8a4f9d25dbff1b7c4711411d6102c6bd8c868a393081d262480f9df89c428a022079a61ce32d26fc522b05eebbceae17a056bf58518de7b6e17e89ff7ee980b64f0121026215ac5afb5a890d06e4eb07ed6383fb4cd46ca0de222cca4c4f8c9fcf9a6023ffffffff01bbae9606000000001976a914ef46d2e30c714916c43676364b27657ed753ef0788ac00000000'; + +function test() { + let expectedHex = `${regtest}${command}${payloadSize}${checksum}${denomMask}${collateralTxHex}`; + let collateralTx = DashKeys.utils.hexToBytes(collateralTxHex); + let message = Packer.packAllow({ network, denomination, collateralTx }); + let messageHex = DashKeys.utils.bytesToHex(message); + if (expectedHex.length !== messageHex.length) { + let length = expectedHex.length / 2; + throw new Error( + `expected message.length to be ${length}, but actual message is ${message.length}`, + ); + } + if (expectedHex !== messageHex) { + throw new Error( + 'bytes of dsa (allow / join request) messages do not match', + ); + } + + console.info( + `PASS: Packer.packAllow({ network, denomination, collateralTx }) matches`, + ); +} + +test(); diff --git a/tests/dsf.js b/tests/dsf.js new file mode 100644 index 0000000..2779413 --- /dev/null +++ b/tests/dsf.js @@ -0,0 +1,60 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +let Parser = require('../parser.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +async function test() { + let fixtureDsfBytes = await readFixtureHex('dsf'); + // let fixtureDsqJson = require('../fixtures/dsf.json'); + + let header = Parser.parseHeader(fixtureDsfBytes); + if (header.command !== 'dsf') { + throw new Error( + `sanity fail: should have loaded 'dsf' fixture, but got '${header.command}'`, + ); + } + + let payload = fixtureDsfBytes.subarray(Parser.HEADER_SIZE); + // TODO verify + // - a chosen subset of our offered inputs (e.g. we offer 9, but 3 are selected) + // - that equally many of our offered outputs are selected (e.g. 3 = 3) + // - that the satoshi values of our outputs match coin for coin + let dsf = Parser.parseDsf(payload); + console.log(new Date(), '[debug] dsf obj:', dsf); + if ('string' !== typeof dsf.transaction_unsigned) { + throw new Error("'.transactionUnsigned' should exist as a hex string"); + } + let txLen = dsf.transaction_unsigned.length / 2; + let expectedLen = header.payloadSize - Parser.SESSION_ID_SIZE; + console.log(new Date(), '[debug] dsf len:', txLen); + if (txLen !== expectedLen) { + throw new Error( + `expected '.transactionUnsigned' to represent ${header.payloadSize} bytes, but got ${txLen}`, + ); + } + + // Assert.deepEqual(dsq, fixtureDsqJson); + + // console.info('PASS: dsf correctly parsed and values match'); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + let bytes = DashKeys.utils.hexToBytes(fixtureStr); + return bytes; +} + +test().catch(function (err) { + console.error(err); + process.exit(1); +}); diff --git a/tests/dsi.js b/tests/dsi.js new file mode 100644 index 0000000..11fbb65 --- /dev/null +++ b/tests/dsi.js @@ -0,0 +1,100 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +let Packer = require('../packer.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +async function test() { + let expectedHex = await readFixtureHex('dsi'); + + let network = 'regtest'; + + // Canonical documentation example + // See + + let denomination = 100001 * 1000; + + let inputs = [ + { + txid: 'c0b5306765c2950838da085d8f3758991a22e246c9862c5f2230566c79c3bd36', + outputIndex: 2, + }, + { + txid: 'c0b5306765c2950838da085d8f3758991a22e246c9862c5f2230566c79c3bd36', + outputIndex: 15, + }, + { + txid: 'c0b5306765c2950838da085d8f3758991a22e246c9862c5f2230566c79c3bd36', + outputIndex: 13, + }, + ]; + + let collateralTxHexes = [ + '01000000', + '01', + '83bd1980c71c38f035db9b14d7f934f7', + 'd595181b3436e36289902619f3f7d383', + '00000000', + '6b', + '483045022100f4d8fa0ae4132235fecd540a', + '62715ccfb1c9a97f8698d066656e30bb1e1e', + '06b90220301b4cc93f38950a69396ed89dfc', + 'c08e72ec8e6e7169463592a0bf504946d98b', + '812102fa4b9c0f9e76e06d57c75cab9c8368', + 'a62a1ce8db6eb0c25c3e0719ddd9ab549c', + 'ffffffff', + '01', + 'e093040000000000', + '19', + '76', + 'a9', + '14', + 'f8956a4eb0e53b05ee6b30edfd2770b5', + '26c1f1bb', + '88', + 'ac', + '00000000', + ]; + let collateralTxHex = collateralTxHexes.join(''); + let collateralTx = DashKeys.utils.hexToBytes(collateralTxHex); + + let outputs = [ + { + pubKeyHash: '14826d7ba05cf76588a5503c03951dc914c91b6c', + satoshis: denomination, + }, + { + pubKeyHash: 'f01197177de2358928196a543b2bbd973c2ab002', + satoshis: denomination, + }, + { + pubKeyHash: '426614716e94812d483bca32374f6ac8cd121b0d', + satoshis: denomination, + }, + ]; + + let message = Packer.packDsi({ network, inputs, collateralTx, outputs }); + let messageHex = DashKeys.utils.bytesToHex(message); + + Assert.equal(messageHex, expectedHex); + + console.info( + `PASS: Packer.packDsi({ network, inputs, collateralTx, outputs }) matches`, + ); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for readability / debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + return fixtureStr; +} + +test(); diff --git a/tests/dsq.js b/tests/dsq.js new file mode 100644 index 0000000..9cc9746 --- /dev/null +++ b/tests/dsq.js @@ -0,0 +1,62 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +let Parser = require('../parser.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +async function test() { + let totalSize = Parser.HEADER_SIZE + Parser.DSQ_SIZE; + let fixtureDsqBytes = await readFixtureHex('dsq'); + let fixtureDsqJson = require('../fixtures/dsq.json'); + + if (fixtureDsqBytes.length !== totalSize) { + let msg = `sanity fail: fixture should be ${totalSize} bytes, not ${fixtureDsqBytes.length}`; + throw new Error(msg); + } + + let header = Parser.parseHeader(fixtureDsqBytes); + if (header.command !== 'dsq') { + throw new Error('sanity fail: loaded incorrect fixture'); + } + + if (header.payloadSize !== Parser.DSQ_SIZE) { + throw new Error('sanity fail: wrong payload size in header'); + } + + let payload = fixtureDsqBytes.subarray(Parser.HEADER_SIZE); + if (payload.length !== Parser.DSQ_SIZE) { + throw new Error('sanity fail: payload has trailing bytes'); + } + + let dsq = Parser.parseDsq(payload); + + // JSON-ify + dsq.protxhash = DashKeys.utils.bytesToHex(dsq.protxhash_bytes); + dsq.protxhash_bytes = null; + dsq.signature = DashKeys.utils.bytesToHex(dsq.signature_bytes); + dsq.signature_bytes = null; + + Assert.deepEqual(dsq, fixtureDsqJson); + + console.info('PASS: dsq correctly parsed and values match'); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + let bytes = DashKeys.utils.hexToBytes(fixtureStr); + return bytes; +} + +test().catch(function (err) { + console.error(err); + process.exit(1); +}); diff --git a/tests/dss.sighashall.js b/tests/dss.sighashall.js new file mode 100644 index 0000000..1cc79cd --- /dev/null +++ b/tests/dss.sighashall.js @@ -0,0 +1,154 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +// let Parser = require('../parser.js'); +let DashKeys = require('dashkeys'); +let DashTx = require('dashtx'); +require('dashtx'); + +let Secp256k1 = require('@dashincubator/secp256k1'); + +async function signNonRandom(privKeyBytes, hashBytes) { + let sigOpts = { canonical: true /*extraEntropy: true*/ }; + let sigBytes = await Secp256k1.sign(hashBytes, privKeyBytes, sigOpts); + return sigBytes; +} + +async function sign(privKeyBytes, hashBytes) { + let sigOpts = { canonical: true, extraEntropy: true }; + let sigBytes = await Secp256k1.sign(hashBytes, privKeyBytes, sigOpts); + return sigBytes; +} + +async function test() { + let privHex = await readFixtureHex('he-1-privkey'); + let privBytes = DashKeys.utils.hexToBytes(privHex); + + let pubBytes = await DashKeys.utils.toPublicKey(privBytes); + // let pubHex = DashKeys.utils.bytesToHex(pubBytes); + + let p2pkhHex = await readFixtureHex('he-1-p2pkh'); + let originalPkhHex = p2pkhHex.slice(6, -4); + + let pkhBytes = await DashKeys.pubkeyToPkh(pubBytes); + let pkhHex = DashKeys.utils.bytesToHex(pkhBytes); + + Assert.equal(originalPkhHex, pkhHex); + + let hashTxHex = await readFixtureHex('he-1-hashtx'); + let hashTxBytes = DashKeys.utils.hexToBytes(hashTxHex); + { + // let hashTxHead = hashTxHex.slice(0, 24); + // let hashTxFoot = hashTxHex.slice(-24); + //console.log(`[debug] hashTx hex ${hashTxHex.length} ${hashTxHead}...${hashTxFoot}`); + } + + let sigHashHexExp = await readFixtureHex('he-1-sighash'); + + let sigHashBytes = await DashTx.doubleSha256(hashTxBytes); + let sigHashHex = DashKeys.utils.bytesToHex(sigHashBytes); + Assert.equal(sigHashHex, sigHashHexExp); + + let sigHexExp = await readFixtureHex('he-1-asn1-sig'); + + // non-random for the purpose of testing + { + let sigBytes = await signNonRandom(privBytes, sigHashBytes); + let sigHex = DashKeys.utils.bytesToHex(sigBytes); + + Assert.equal(sigHex, sigHexExp); + + let verified = await Secp256k1.verify(sigBytes, sigHashBytes, pubBytes); + Assert.equal(verified, true); + } + + { + let sigRndBytes = await sign(privBytes, sigHashBytes); + let sigRndHex = DashKeys.utils.bytesToHex(sigRndBytes); + Assert.notEqual(sigRndHex, sigHexExp); + + let verified = await Secp256k1.verify(sigRndBytes, sigHashBytes, pubBytes); + Assert.equal(verified, true); + } + + { + let dsfHex = await readFixtureHex('he-dsf'); + + // let dsfSerial = dsfHex.slice(0, 8); + // console.log('[debug] dsf serial', dsfSerial); + + let txRequestHex = dsfHex.slice(8); + let dsfTxInfo = DashTx.parseUnknown(txRequestHex); + let txRequestHex2 = DashTx.serialize(dsfTxInfo); + + Assert.equal(txRequestHex, txRequestHex2); + + let coin = require('../fixtures/he-1-utxo.json'); + let inputIndex = -1; + let version = dsfTxInfo.version; + let inputs = []; + for (let i = 0; i < dsfTxInfo.inputs.length; i += 1) { + let _input = dsfTxInfo.inputs[i]; + let input = { + txid: _input.txid || _input.txId, + txId: _input.txid || _input.txId, + outputIndex: _input.outputIndex, + pubKeyHash: '', + }; + inputs.push(input); + if (_input.txid !== coin.txid) { + continue; + } + if (_input.outputIndex !== coin.outputIndex) { + continue; + } + input.pubKeyHash = pkhHex; + inputIndex = i; + } + let outputs = []; + for (let _output of dsfTxInfo.outputs) { + let output = { + satoshis: _output.satoshis, + pubKeyHash: _output.pubKeyHash, + }; + outputs.push(output); + } + let locktime = dsfTxInfo.locktime; + + let txInfo = { + version, + inputs, + outputs, + locktime, + }; + let hasIndex = inputIndex > -1; + if (!hasIndex) { + throw new Error('selected coin not found in selected inputs'); + } + let sigHashType = 0x01; + let txForSig = DashTx.createForSig(txInfo, inputIndex, sigHashType); + let hashTxHex2 = DashTx.serializeForSig(txForSig, sigHashType); + + Assert.equal(hashTxHex2, hashTxHex); + } + + console.info("PASS: verify signature of 'he' fixture"); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + return fixtureStr; +} + +test().catch(function (err) { + console.error(err); + process.exit(1); +}); diff --git a/tests/dssu.js b/tests/dssu.js new file mode 100644 index 0000000..5e253c7 --- /dev/null +++ b/tests/dssu.js @@ -0,0 +1,55 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +let Parser = require('../parser.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +async function test() { + let totalSize = Parser.HEADER_SIZE + Parser.DSSU_SIZE; + let fixtureDssuBytes = await readFixtureHex('dssu'); + let fixtureDssuJson = require('../fixtures/dssu.json'); + + if (fixtureDssuBytes.length !== totalSize) { + let msg = `sanity fail: fixture should be ${totalSize} bytes, not ${fixtureDssuBytes.length}`; + throw new Error(msg); + } + + let header = Parser.parseHeader(fixtureDssuBytes); + if (header.command !== 'dssu') { + throw new Error('sanity fail: loaded incorrect fixture'); + } + + if (header.payloadSize !== Parser.DSSU_SIZE) { + throw new Error('sanity fail: wrong payload size in header'); + } + + let payload = fixtureDssuBytes.subarray(Parser.HEADER_SIZE); + if (payload.length !== Parser.DSSU_SIZE) { + throw new Error('sanity fail: payload has trailing bytes'); + } + + let dssu = Parser.parseDssu(payload); + Assert.deepEqual(dssu, fixtureDssuJson); + + console.info('PASS: dssu correctly parsed and values match'); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + let bytes = DashKeys.utils.hexToBytes(fixtureStr); + return bytes; +} + +test().catch(function (err) { + console.error(err); + process.exit(1); +}); diff --git a/tests/ping-pong.js b/tests/ping-pong.js new file mode 100644 index 0000000..8befc48 --- /dev/null +++ b/tests/ping-pong.js @@ -0,0 +1,155 @@ +'use strict'; + +let Packer = require('../packer.js'); + +function test() { + let network = 'regtest'; + let messageSize = Packer.HEADER_SIZE + Packer.PING_SIZE; + let message = new Uint8Array(messageSize); + + // These two are the same, but the latter is more intuitive to use: + // + // - this is ONLY a reference IF the underlying ArrayBuffer is used + // - the OFFSET is ONLY a property, it DOES NOT affect .set() + //let pingBytes = new Uint8Array(bytes.buffer, offset); + // - this is a reference, as you'd expect + // - .set() works from the offset + let pingBytes = message.subarray(Packer.HEADER_SIZE); + let zeroNonceStr = [0, 0, 0, 0, 0, 0, 0, 0].toString(); + if (zeroNonceStr !== pingBytes.toString()) { + throw new Error('sanity fail: nonce bytes should be initialized to 0'); + } + void Packer.packPing({ network, message }); + let randNonceStr = pingBytes.toString(); + if (zeroNonceStr === randNonceStr) { + throw new Error('nonce bytes should be randomized, not 0'); + } + let randNonceBytes2 = message.slice(Packer.HEADER_SIZE); + let randNonceStr2 = randNonceBytes2.toString(); + if (randNonceStr !== randNonceStr2) { + throw new Error( + 'nonce bytes subarray and nonce bytes slice should be equal', + ); + } + + let nonce = new Uint8Array(Packer.FIELD_SIZES.NONCE); + nonce[0] = 0xab; + nonce[1] = 0xcd; + nonce[2] = 0xef; + nonce[3] = 0x12; + nonce[4] = 0x34; + nonce[5] = 0x56; + nonce[6] = 0x78; + nonce[7] = 0x90; + let staticNonceStr = nonce.toString(); + void Packer.packPing({ network, message, nonce }); + let staticNonceStr2 = nonce.toString(); + if (staticNonceStr !== staticNonceStr2) { + throw new Error('static nonce bytes should NOT have been overwritten'); + } + + let nonceBytes3 = pingBytes.slice(0); + let staticNonceStr3 = nonceBytes3.toString(); + if (staticNonceStr !== staticNonceStr3) { + throw new Error( + 'ping nonce bytes and static nonce bytes should have been identical', + ); + } + + let nonceBytes4 = message.slice(Packer.HEADER_SIZE); + let staticNonceStr4 = nonceBytes4.toString(); + if (staticNonceStr !== staticNonceStr4) { + throw new Error( + 'message bytes at ping nonce offset should have matched static nonce', + ); + } + + { + void Packer.packMessage({ + network: 'regtest', + command: 'ping', + bytes: message, + }); + let headerInts = [ + // regtest + 252, 193, 183, 220, + // 4-char "ping" + 8 trailing nulls + 112, 105, 110, 103, + // + 0, 0, 0, 0, + // + 0, 0, 0, 0, + // little-endian Uint32 payload size + 8, 0, 0, 0, + // first 4 bytes of the sha256(sha256(staticNonce)) + 97, 172, 101, 125, + ]; + let headerStr = headerInts.toString(); + let expectedStr = `${headerStr},${staticNonceStr}`; + let messageStr = message.toString(); + if (expectedStr !== messageStr) { + throw new Error('complete messages did not match'); + } + } + + { + let messageBytes = Packer.packMessage({ + network: 'regtest', + command: 'ping', + payload: pingBytes, + }); + let headerInts = [ + // regtest + 252, 193, 183, 220, + // 4-char "ping" + 8 trailing nulls + 112, 105, 110, 103, + // + 0, 0, 0, 0, + // + 0, 0, 0, 0, + // little-endian Uint32 payload size + 8, 0, 0, 0, + // first 4 bytes of the sha256(sha256(staticNonce)) + 97, 172, 101, 125, + ]; + let headerStr = headerInts.toString(); + let expectedStr = `${headerStr},${staticNonceStr}`; + let messageStr = messageBytes.toString(); + if (expectedStr !== messageStr) { + throw new Error('complete messages did not match'); + } + } + + console.log(`PASS: headers and ping pack as expected`); + + { + let messageBytes = Packer.packPong({ + network, + nonce, + }); + let headerInts = [ + // regtest + 252, 193, 183, 220, + // 4-char "pong" + 8 trailing nulls + 112, 111, 110, 103, + // + 0, 0, 0, 0, + // + 0, 0, 0, 0, + // little-endian Uint32 payload size + 8, 0, 0, 0, + // first 4 bytes of the sha256(sha256(staticNonce)) + 97, 172, 101, 125, + ]; + let headerStr = headerInts.toString(); + let expectedStr = `${headerStr},${staticNonceStr}`; + let messageStr = messageBytes.toString(); + if (expectedStr !== messageStr) { + throw new Error('complete pong messages did not match'); + } + } + + console.log(`PASS: headers and pong pack as expected`); +} + +test();