Skip to content

Commit d5a125c

Browse files
committed
feat: work through high-level coinjoin flow
1 parent 8e199ea commit d5a125c

File tree

1 file changed

+194
-4
lines changed

1 file changed

+194
-4
lines changed

public/wallet-app.js

Lines changed: 194 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@
10141014
return f.toFixed(d);
10151015
}
10161016

1017-
async function connectToPeer(height, evonode) {
1017+
async function connectToPeer(evonode, height) {
10181018
if (App.peers[evonode.host]) {
10191019
return App.peers[evonode.host];
10201020
}
@@ -1040,7 +1040,7 @@
10401040

10411041
let senddsqBytes = DashJoin.packers.senddsq({ network: network });
10421042
console.log('[REQ: %csenddsq%c]', 'color: $55daba', 'color: inherit');
1043-
wsc.send(senddsqBytes);
1043+
p2p.send(senddsqBytes);
10441044

10451045
void p2p.createSubscriber(['dsq'], async function (evstream) {
10461046
let msg = await evstream.once('dsq');
@@ -1069,10 +1069,185 @@
10691069
}
10701070
wsc.addEventListener('error', cleanup);
10711071

1072-
App.peers[evonode.host] = { p2p };
1072+
App.peers[evonode.host] = p2p;
10731073
return App.peers[evonode.host];
10741074
}
10751075

1076+
// 0. 'dsq' broadcast puts a node in the local in-memory pool
1077+
// 1. 'dsa' requests to be allowed to join a session
1078+
// 2. 'dssu' accepts
1079+
// + 'dsq' marks ready (in either order)
1080+
// 3. 'dsi' signals desired coins in and out
1081+
// 4. 'dsf' accepts specific coins in and out
1082+
// 5. 'dss' sends signed inputs paired to trusted outputs
1083+
// 6. 'dssu' updates status
1084+
// + 'dsc' confirms the tx will broadcast soon
1085+
async function createCoinJoinSession(
1086+
evonode, // { host }
1087+
inputs, // [{address, txid, pubKeyHash, ...getPrivateKeyInfo }]
1088+
outputs, // [{ pubKeyHash, satoshis }]
1089+
collateralTxes, // (for dsa and dsi) any 2 txes having fees >=0.00010000 more than necessary
1090+
) {
1091+
let p2p = App.peers[evonode.host];
1092+
if (!p2p) {
1093+
throw new Error(`'${evonode.host}' is not connected`);
1094+
}
1095+
1096+
let denomination = inputs[0].satoshis;
1097+
for (let input of inputs) {
1098+
if (input.satoshis !== denomination) {
1099+
let msg = `utxo.satoshis (${input.satoshis}) must match requested denomination ${denomination}`;
1100+
throw new Error(msg);
1101+
}
1102+
}
1103+
for (let output of outputs) {
1104+
if (!output.sateshis) {
1105+
output.satoshis = denomination;
1106+
continue;
1107+
}
1108+
if (output.satoshis !== denomination) {
1109+
let msg = `output.satoshis (${output.satoshis}) must match requested denomination ${denomination}`;
1110+
throw new Error(msg);
1111+
}
1112+
}
1113+
1114+
// todo: pick a smaller size that matches the dss
1115+
let message = new Uint8Array(DashP2P.PAYLOAD_SIZE_MAX);
1116+
let evstream = p2p.createSubscriber(['dssu', 'dsq', 'dsf', 'dsc']);
1117+
1118+
{
1119+
let collateralTx = collateralTxes.shift();
1120+
let dsaBytes = DashJoin.packers.dsa({
1121+
network,
1122+
message,
1123+
denomination,
1124+
collateralTx,
1125+
});
1126+
p2p.send(dsaBytes);
1127+
for (;;) {
1128+
let msg = await evstream.once();
1129+
1130+
if (msg.command === 'dsq') {
1131+
let dsq = DashJoin.parsers.dsq(msg.payload);
1132+
if (dsq.denomination !== denomination) {
1133+
continue;
1134+
}
1135+
if (!dsq.ready) {
1136+
continue;
1137+
}
1138+
break;
1139+
}
1140+
1141+
if (msg.command === 'dssu') {
1142+
let dssu = DashJoin.parsers.dssu(msg.payload);
1143+
if (dssu.state === 'ERROR') {
1144+
evstream.close();
1145+
throw new Error();
1146+
}
1147+
}
1148+
}
1149+
}
1150+
1151+
let dsfTxRequest;
1152+
{
1153+
let collateralTx = collateralTxes.shift();
1154+
let dsiBytes = DashJoin.packers.dsi({
1155+
network,
1156+
message,
1157+
inputs,
1158+
collateralTx,
1159+
outputs,
1160+
});
1161+
p2p.send(dsiBytes);
1162+
let msg = await evstream.once('dsf');
1163+
let dsf = DashJoin.parsers.dsf(msg.payload);
1164+
1165+
dsfTxRequest = DashTx.parseUnknown(dsf.transaction_unsigned);
1166+
makeSelectedInputsSignable(dsfTxRequest, inputs);
1167+
let txSigned = await dashTx.hashAndSignAll(dsfTxRequest);
1168+
1169+
let signedInputs = [];
1170+
for (let input of txSigned.inputs) {
1171+
if (!input?.signature) {
1172+
continue;
1173+
}
1174+
signedInputs.push(input);
1175+
}
1176+
assertSelectedOutputs(dsfTxRequest, outputs, inputs.length);
1177+
1178+
let dssBytes = DashJoin.packers.dss({
1179+
network: network,
1180+
message: message,
1181+
inputs: signedInputs,
1182+
});
1183+
p2p.send(dssBytes);
1184+
}
1185+
1186+
return dsfTxRequest;
1187+
}
1188+
1189+
function makeSelectedInputsSignable(txRequest, inputs) {
1190+
// let selected = [];
1191+
1192+
for (let input of inputs) {
1193+
if (!input.publicKey) {
1194+
let msg = `coin '${input.address}:${input.txid}:${input.outputIndex}' is missing 'input.publicKey'`;
1195+
throw new Error(msg);
1196+
}
1197+
for (let sighashInput of txRequest.inputs) {
1198+
if (sighashInput.txid !== input.txid) {
1199+
continue;
1200+
}
1201+
if (sighashInput.outputIndex !== input.outputIndex) {
1202+
continue;
1203+
}
1204+
1205+
let sigHashType = DashTx.SIGHASH_ALL | DashTx.SIGHASH_ANYONECANPAY; //jshint ignore:line
1206+
1207+
sighashInput.index = input.index;
1208+
sighashInput.address = input.address;
1209+
sighashInput.satoshis = input.satoshis;
1210+
sighashInput.pubKeyHash = input.pubKeyHash;
1211+
// sighashInput.script = input.script;
1212+
sighashInput.publicKey = input.publicKey;
1213+
sighashInput.sigHashType = sigHashType;
1214+
// sighashInputs.push({
1215+
// txId: input.txId || input.txid,
1216+
// txid: input.txid || input.txId,
1217+
// outputIndex: input.outputIndex,
1218+
// pubKeyHash: input.pubKeyHash,
1219+
// sigHashType: input.sigHashType,
1220+
// });
1221+
1222+
// selected.push(input);
1223+
break;
1224+
}
1225+
}
1226+
1227+
// return selected;
1228+
}
1229+
1230+
function assertSelectedOutputs(txRequest, outputs, count) {
1231+
let _count = 0;
1232+
for (let output of outputs) {
1233+
for (let sighashOutput of txRequest.outputs) {
1234+
if (sighashOutput.pubKeyHash !== output.pubKeyHash) {
1235+
continue;
1236+
}
1237+
if (sighashOutput.satoshis !== output.satoshis) {
1238+
continue;
1239+
}
1240+
1241+
_count += 1;
1242+
}
1243+
}
1244+
1245+
if (count !== _count) {
1246+
let msg = `expected ${count} matching outputs but found found ${_count}`;
1247+
throw new Error(msg);
1248+
}
1249+
}
1250+
10761251
async function main() {
10771252
if (network === `testnet`) {
10781253
let $testnets = $$('[data-network=testnet]');
@@ -1104,7 +1279,22 @@
11041279
};
11051280
App.peers = {};
11061281

1107-
void (await connectToPeer(App._chaininfo.blocks, App._evonode));
1282+
void (await connectToPeer(App._evonode, App._chaininfo.blocks));
1283+
1284+
// collateral, denominated
1285+
// let collateralTxInfo = await getCollateralTx();
1286+
// // let keys = await getPrivateKeys(collateralTxInfo.inputs);
1287+
// // let txInfoSigned = await dashTx.hashAndSignAll(collateralTxInfo, keys);
1288+
// let txInfoSigned = await dashTx.hashAndSignAll(collateralTxInfo);
1289+
// let collateralTx = DashTx.utils.hexToBytes(txInfoSigned.transaction);
1290+
1291+
// await createCoinJoinSession(
1292+
// App.evonode,
1293+
// // inputs, // [{address, txid, pubKeyHash, ...getPrivateKeyInfo }]
1294+
// // outputs, // [{ pubKeyHash, satoshis }]
1295+
// // dsaCollateralTx, // any tx with fee >= 0.00010000
1296+
// // dsiCollateralTx, // any tx with fee >= 0.00010000
1297+
// );
11081298
}
11091299

11101300
main().catch(function (err) {

0 commit comments

Comments
 (0)