Skip to content

Commit fb6ba07

Browse files
committed
Fix a bunch of stuff to get identity create working
1 parent 861f8bd commit fb6ba07

File tree

13 files changed

+3266
-650
lines changed

13 files changed

+3266
-650
lines changed

1-create-asset-lock.js

Lines changed: 779 additions & 0 deletions
Large diffs are not rendered by default.

1.8.1/generated_bincode.d.ts

Lines changed: 620 additions & 0 deletions
Large diffs are not rendered by default.

1.8.1/generated_bincode.js

Lines changed: 326 additions & 23 deletions
Large diffs are not rendered by default.

2-create-identity-transition.js

Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
import Fs from "node:fs/promises";
2+
3+
// import DashKeys from "dashkeys";
4+
import * as DashTx from "dashtx/dashtx.js";
5+
6+
import * as Bincode from "./bincode.ts";
7+
import * as DashBincode from "./1.8.1/generated_bincode.js";
8+
import * as KeyUtils from "./key-utils.js";
9+
import baseX from "base-x";
10+
11+
const ST_CREATE_IDENTITY = 2;
12+
const L2_VERSION_PLATFORM = 1; // actually constant "0" ??
13+
14+
let KEY_LEVELS = {
15+
0: "MASTER",
16+
1: "CRITICAL",
17+
2: "HIGH",
18+
3: "MEDIUM",
19+
MASTER: 0,
20+
CRITICAL: 1,
21+
HIGH: 2,
22+
MEDIUM: 3,
23+
};
24+
25+
let KEY_PURPOSES = {
26+
0: "AUTHENTICATION",
27+
1: "ENCRYPTION",
28+
2: "DECRYPTION",
29+
3: "TRANSFER",
30+
4: "SYSTEM",
31+
5: "VOTING",
32+
AUTHENTICATION: 0,
33+
ENCRYPTION: 1,
34+
DECRYPTION: 2,
35+
TRANSFER: 3,
36+
SYSTEM: 4,
37+
VOTING: 5,
38+
};
39+
40+
let KEY_TYPES = {
41+
0: "ECDSA_SECP256K1",
42+
ECDSA_SECP256K1: 0,
43+
};
44+
45+
const BASE58 = `123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz`;
46+
let base58 = baseX(BASE58);
47+
48+
let Thingy = {};
49+
50+
/**
51+
* @typedef AssetLockChainProof
52+
* @prop {Number} core_chain_locked_height
53+
* @prop {Object} out_point
54+
* @prop {String} out_point.txid
55+
* @prop {Number} out_point.vout
56+
*/
57+
58+
/**
59+
* @param {import('dashhd').HDWallet} assetKey
60+
* @param {import('dashhd').HDWallet} masterKey
61+
* @param {import('dashhd').HDWallet} otherKey
62+
* @param {String} identityIdHex
63+
* @param {String} txidHex
64+
* @param {String} [txlocksigHex]
65+
* @param {import('dashtx').TxInfo} [txCore]
66+
*/
67+
Thingy.doStuff = async function (
68+
assetKey,
69+
masterKey,
70+
otherKey,
71+
identityIdHex,
72+
txidHex,
73+
txlocksigHex,
74+
txCore,
75+
) {
76+
// const INSTANT_ALP = 0;
77+
// const CHAIN_ALP = 1;
78+
79+
/** @type {DashBincode.AssetLockProof} */
80+
let assetLockProof;
81+
if (txlocksigHex) {
82+
assetLockProof = await getAssetLockInstantProof(txlocksigHex);
83+
} else {
84+
assetLockProof = await getAssetLockChainProof(txidHex, txCore);
85+
}
86+
87+
if (!masterKey.privateKey) {
88+
throw new Error("'masterKey' is missing 'privateKey'");
89+
}
90+
if (!otherKey.privateKey) {
91+
throw new Error("'otherKey' is missing 'privateKey'");
92+
}
93+
let identityKeys = await getKnownIdentityKeys(
94+
{ privateKey: masterKey.privateKey, publicKey: masterKey.publicKey },
95+
{ privateKey: otherKey.privateKey, publicKey: otherKey.publicKey },
96+
);
97+
let stKeys = getIdentityTransitionKeys(identityKeys);
98+
99+
let identityCreate = DashBincode.IdentityCreateTransitionV0({
100+
//protocolVersion: L2_VERSION_PLATFORM,
101+
// $version: L2_VERSION_PLATFORM.toString(),
102+
// type: ST_CREATE_IDENTITY,
103+
// ecdsaSig(assetLockPrivateKey, CBOR(thisStateTransition))
104+
// "signature":"IBTTgge+/VDa/9+n2q3pb4tAqZYI48AX8X3H/uedRLH5dN8Ekh/sxRRQQS9LaOPwZSCVED6XIYD+vravF2dhYOE=",
105+
asset_lock_proof: assetLockProof,
106+
// publicKeys: stKeys,
107+
public_keys: stKeys,
108+
// [
109+
// {
110+
// id: 0,
111+
// type: 0,
112+
// purpose: 0,
113+
// securityLevel: 0,
114+
// data: "AkWRfl3DJiyyy6YPUDQnNx5KERRnR8CoTiFUvfdaYSDS",
115+
// readOnly: false,
116+
// },
117+
// ],
118+
identity_id: DashBincode.Identifier(DashBincode.IdentifierBytes32(DashTx.utils.hexToBytes(identityIdHex))),
119+
user_fee_increase: 0,
120+
signature: DashBincode.BinaryData(new Uint8Array),
121+
})
122+
123+
let stateTransition = DashBincode.StateTransition.IdentityCreate(
124+
DashBincode.IdentityCreateTransition.V0(identityCreate));
125+
console.log(`stKeys:`);
126+
console.log(stKeys);
127+
128+
let nullSigTransition = new Uint8Array(Bincode.encode(
129+
DashBincode.StateTransition,
130+
stateTransition,
131+
{
132+
signable: true,
133+
},
134+
));
135+
console.log();
136+
console.log(`nullSigTransition (ready-to-sign by identity keys):`);
137+
console.log('(hex)', DashTx.utils.bytesToHex(nullSigTransition));
138+
console.log('(base64)', bytesToBase64(nullSigTransition));
139+
140+
let nullSigMagicHash = await KeyUtils.doubleSha256(nullSigTransition);
141+
142+
if (!assetKey.privateKey) {
143+
throw new Error("'assetKey' is missing 'privateKey'");
144+
}
145+
{
146+
let magicSigBytes = await KeyUtils.magicSign({
147+
privKeyBytes: assetKey.privateKey,
148+
doubleSha256Bytes: nullSigMagicHash,
149+
});
150+
151+
identityCreate.signature[0] = magicSigBytes
152+
}
153+
154+
for (let i = 0; i < identityKeys.length; i += 1) {
155+
let key = identityKeys[i];
156+
let stPub = identityCreate.public_keys[i];
157+
let magicSigBytes = await KeyUtils.magicSign({
158+
privKeyBytes: key.privateKey,
159+
doubleSha256Bytes: nullSigMagicHash,
160+
});
161+
162+
Bincode.match(stPub, {
163+
V0: ({0: stPub0}) => {
164+
stPub0.signature[0] = magicSigBytes
165+
}
166+
})
167+
}
168+
169+
console.log();
170+
console.log(JSON.stringify(stateTransition, (key, val) => {
171+
if (val instanceof Uint8Array || val instanceof ArrayBuffer) {
172+
return {'@Uint8Array hex': DashTx.utils.bytesToHex(new Uint8Array(val))}
173+
}
174+
return val
175+
}, 2));
176+
177+
let grpcTransition = "";
178+
let transitionHashHex = "";
179+
{
180+
let fullSigTransition = new Uint8Array(Bincode.encode(
181+
DashBincode.StateTransition,
182+
stateTransition,
183+
{
184+
signable: false,
185+
},
186+
));
187+
console.log();
188+
console.log(`transition (fully signed):`);
189+
console.log(DashTx.utils.bytesToHex(fullSigTransition));
190+
let transitionHash = await KeyUtils.sha256(fullSigTransition);
191+
transitionHashHex = DashTx.utils.bytesToHex(transitionHash);
192+
grpcTransition = bytesToBase64(fullSigTransition);
193+
}
194+
195+
console.log();
196+
console.log();
197+
console.log(`grpcurl -plaintext -d '{
198+
"stateTransition": "${grpcTransition}"
199+
}' seed-2.testnet.networks.dash.org:1443 org.dash.platform.dapi.v0.Platform.broadcastStateTransition`);
200+
console.log();
201+
let identityIdBytes = DashTx.utils.hexToBytes(identityIdHex);
202+
let identity = base58.encode(identityIdBytes);
203+
console.log(`https://testnet.platform-explorer.com/identity/${identity}`);
204+
console.log(
205+
`https://testnet.platform-explorer.com/transaction/${transitionHashHex}`,
206+
);
207+
};
208+
209+
export default Thingy;
210+
211+
/** @param {HexString} txlocksigHex */
212+
async function getAssetLockInstantProof(txlocksigHex) {
213+
{
214+
let len = txlocksigHex.length / 2;
215+
console.log();
216+
console.log(`Tx Lock Sig Hex (${len}):`);
217+
console.log(txlocksigHex);
218+
}
219+
220+
let vout = -1;
221+
let instantLockTxHex = "";
222+
let instantLockSigHex = "";
223+
{
224+
let txlocksig = DashTx.parseUnknown(txlocksigHex);
225+
vout = 0;
226+
//vout = txlocksig.extraPayload.outputs.findIndex(function (output) {
227+
// //@ts-expect-error
228+
// return output.script === "6a00";
229+
//});
230+
// console.log(txlocksig.extraPayload.outputs);
231+
//@ts-expect-error
232+
instantLockSigHex = txlocksig.sigHashTypeHex;
233+
let isLen = instantLockSigHex.length / 2;
234+
let len = txlocksigHex.length / 2;
235+
len -= isLen;
236+
instantLockTxHex = txlocksigHex.slice(0, len * 2);
237+
console.log();
238+
console.log(`Tx Hex (${len})`);
239+
console.log(instantLockTxHex);
240+
console.log();
241+
console.log(`Tx Lock Sig Instant Lock Hex (${isLen})`);
242+
//@ts-expect-error
243+
console.log(txlocksig.sigHashTypeHex);
244+
}
245+
246+
let assetLockInstantProof = DashBincode.RawInstantLockProof({
247+
instant_lock: DashBincode.BinaryData(DashTx.utils.hexToBytes(instantLockSigHex)),
248+
transaction: DashBincode.BinaryData(DashTx.utils.hexToBytes(instantLockTxHex)), // TODO this may need the proof, not the signed tx
249+
output_index: vout,
250+
});
251+
return DashBincode.AssetLockProof.Instant(assetLockInstantProof);
252+
}
253+
254+
/**
255+
* @param {HexString} txidHex
256+
* @param {any} txInfo - TODO CoreTx
257+
*/
258+
async function getAssetLockChainProof(txidHex, txInfo) {
259+
//@ts-expect-error
260+
let vout = txInfo.vout.findIndex(voutInfo =>
261+
voutInfo.scriptPubKey?.hex === "6a00" // TODO match the burn
262+
);
263+
264+
let assetLockChainProof = DashBincode.ChainAssetLockProof({
265+
core_chain_locked_height: txInfo.height,
266+
out_point: {
267+
txid: DashBincode.Txid(DashTx.utils.hexToBytes(txidHex)),
268+
vout: vout,
269+
},
270+
});
271+
272+
return DashBincode.AssetLockProof.Chain(assetLockChainProof);
273+
}
274+
275+
/**
276+
* @param {Required<Pick<import('dashhd').HDXKey, "privateKey"|"publicKey">>} masterKey
277+
* @param {Required<Pick<import('dashhd').HDXKey, "privateKey"|"publicKey">>} otherKey
278+
* @returns {Promise<Array<EvoKey>>}
279+
*/
280+
async function getKnownIdentityKeys(masterKey, otherKey) {
281+
if (!masterKey.privateKey) {
282+
throw new Error("linter fail");
283+
}
284+
if (!otherKey.privateKey) {
285+
throw new Error("linter fail");
286+
}
287+
let keyDescs = [
288+
// {"$version":"0","id":0,"purpose":0,"securityLevel":0,"contractBounds":null,"type":0,"readOnly":false,"data":[3,58,154,139,30,76,88,26,25,135,114,76,102,151,19,93,49,192,126,231,172,130,126,106,89,206,192,34,176,77,81,5,95],"disabledAt":null}
289+
{
290+
id: 0,
291+
type: DashBincode.KeyType.ECDSA_SECP256K1(),
292+
purpose: DashBincode.Purpose.AUTHENTICATION(),
293+
securityLevel: DashBincode.SecurityLevel.MASTER(),
294+
readOnly: false,
295+
publicKey: masterKey.publicKey,
296+
privateKey: masterKey.privateKey,
297+
data: "",
298+
},
299+
// {"$version":"0","id":1,"purpose":0,"securityLevel":1,"contractBounds":null,"type":0,"readOnly":false,"data":[2,1,70,3,1,141,196,55,100,45,218,22,244,199,252,80,228,130,221,35,226,70,128,188,179,165,150,108,59,52,56,72,226],"disabledAt":null}
300+
{
301+
id: 1,
302+
type: DashBincode.KeyType.ECDSA_SECP256K1(),
303+
purpose: DashBincode.Purpose.AUTHENTICATION(),
304+
securityLevel: DashBincode.SecurityLevel.CRITICAL(),
305+
readOnly: false,
306+
privateKey: otherKey.privateKey,
307+
publicKey: otherKey.publicKey,
308+
data: "",
309+
},
310+
];
311+
return keyDescs;
312+
}
313+
314+
/**
315+
* @typedef EvoKey
316+
* @prop {Uint8} id
317+
* @prop {DashBincode.KeyType} type - TODO constrain to members of KEY_TYPES
318+
* @prop {DashBincode.Purpose} purpose - TODO constrain to members of KEY_PURPOSES
319+
* @prop {DashBincode.SecurityLevel} securityLevel - TODO constrain to members of KEY_LEVELS
320+
* @prop {Boolean} readOnly
321+
* @prop {Uint8Array} publicKey
322+
* @prop {Uint8Array} privateKey
323+
*/
324+
325+
/**
326+
* @typedef STKey
327+
* @prop {Uint8} id
328+
* @prop {DashBincode.KeyType} type - TODO constrain to members of KEY_TYPES
329+
* @prop {DashBincode.Purpose} purpose - TODO constrain to members of KEY_PURPOSES
330+
* @prop {Base64} data - base64-encoded publicKey (compact)
331+
* @prop {DashBincode.SecurityLevel} securityLevel - TODO constrain to members of KEY_LEVELS
332+
* @prop {Boolean} readOnly
333+
*/
334+
335+
/**
336+
* @param {Array<EvoKey>} identityKeys - TODO
337+
* @returns {Array<DashBincode.IdentityPublicKeyInCreation>}
338+
*/
339+
function getIdentityTransitionKeys(identityKeys) {
340+
let stKeys = [];
341+
for (let key of identityKeys) {
342+
let stKey = DashBincode.IdentityPublicKeyInCreation.V0(DashBincode.IdentityPublicKeyInCreationV0({
343+
id: key.id,
344+
key_type: key.type,
345+
purpose: key.purpose,
346+
security_level: key.securityLevel,
347+
contract_bounds: undefined,
348+
read_only: key.readOnly || false,
349+
data: DashBincode.BinaryData(key.publicKey),
350+
signature: DashBincode.BinaryData(new Uint8Array),
351+
}));
352+
stKeys.push(stKey);
353+
}
354+
return stKeys;
355+
}
356+
357+
/**
358+
* @param {Uint8Array} bytes
359+
*/
360+
function bytesToBase64(bytes) {
361+
// @ts-expect-error Uint8Array is close enough to number[] for this to work
362+
return btoa(String.fromCharCode.apply(null, bytes));
363+
}
364+
365+
/**
366+
* Reads a hex file as text, stripping comments (anything including and after a non-hex character), removing whitespace, and joining as a single string
367+
* @param {String} path
368+
*/
369+
async function readHex(path) {
370+
let text = await Fs.readFile(path, "utf8");
371+
let lines = text.split("\n");
372+
let hexes = [];
373+
for (let line of lines) {
374+
line = line.replace(/\s/g, "");
375+
line = line.replace(/[^0-9a-f].*/i, "");
376+
hexes.push(line);
377+
}
378+
379+
let hex = hexes.join("");
380+
return hex;
381+
}
382+
383+
/**
384+
* @param {String} path
385+
*/
386+
async function readWif(path) {
387+
let wif = await Fs.readFile(path, "utf8");
388+
wif = wif.trim();
389+
390+
return wif;
391+
}
392+
393+
/** @typedef {String} Base58 */
394+
/** @typedef {String} Base64 */
395+
/** @typedef {String} HexString */
396+
/** @typedef {Number} Uint53 */
397+
/** @typedef {Number} Uint32 */
398+
/** @typedef {Number} Uint8 */

0 commit comments

Comments
 (0)