UNPKG

hive-multisign

Version:

A typescript utility for multi-signature transactions on the Hive blockchain

273 lines (272 loc) 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resetOriginalKey = exports.demo = exports.sendTrx = exports.hasEnoughSignatures = exports.prepareTrx = exports.assignUnifromAuth = exports.updateAuths = exports.generateAuthChangeObject = exports.generatePublicKeys = exports.generatePrivateKeys = exports.getUserAuthorities = void 0; const dhive_1 = require("@hiveio/dhive"); const buffer_1 = require("buffer"); const client = new dhive_1.Client(['https://hive-api.arcange.eu', 'https://hived.emre.sh', 'https://api.hive.blog', 'https://api.openhive.network']); /** * Get account information * @param usernames account usernames * @returns */ const getAccounts = async (usernames) => { return client.call('condenser_api', 'get_accounts', [usernames]); }; /** * Get user authorities (owner, active, posting) * @param username * @returns */ const getUserAuthorities = async (username) => { const info = await getAccounts([username]); if (info.length == 0) { throw new Error(`Account @${username} not found`); } const account = info[0]; return { owner: account.owner, active: account.active, posting: account.posting }; }; exports.getUserAuthorities = getUserAuthorities; /** * Get the public memo key for the specified accounts * @param username account username * @returns */ const getMemoAndAccount = async (username) => { let res = []; const info = await getAccounts([username]); if (info.length == 0) { throw new Error(`Account @${username} not found`); } return { owner: info[0].owner, active: info[0].active, posting: info[0].posting, memo: info[0].memo_key }; }; /** * Generate many private keys for the account. Each private key is associated with one password. * @param username account username * @param passwords array of passwords, one of which must be the account's master password * @param role key role (e.g. 'posting') */ const generatePrivateKeys = (username, passwords, role) => { return passwords.map((pass) => dhive_1.PrivateKey.fromLogin(username, pass, role)); }; exports.generatePrivateKeys = generatePrivateKeys; /** * Generate the public keys associated with the specified private keys. * @param privateKeys private keys * @returns */ const generatePublicKeys = (privateKeys) => { return privateKeys.map((key) => dhive_1.PrivateKey.from(key.toString()).createPublic().toString()); }; exports.generatePublicKeys = generatePublicKeys; /** * Generate the object needed to send an account authority update. * @param username account username * @param threshold weight threshold * @param accountAuths account authorities [username, weight] * @param keyAuths key authorities [public_key, weight] * @param role key role (e.g. 'posting') * @param replace Replace the old authorities instead of appending to them * @returns */ const generateAuthChangeObject = async (username, threshold, accountAuths, keyAuths, role, replace) => { const accountInfo = await getMemoAndAccount(username); let newAccountAuths = Array.from(accountAuths); let newKeyAuths = Array.from(keyAuths); // Make sure the user is not sending public keys for (let i = 0; i < newKeyAuths.length; i++) { if (newKeyAuths[i][0][0] == '5') { newKeyAuths[i][0] = (0, exports.generatePublicKeys)([newKeyAuths[i][0]])[0]; } } // If shouldn't replace account auths merge with the old ones if (!(replace === null || replace === void 0 ? void 0 : replace.account)) { const oldAccountAuths = accountInfo[role].account_auths; newAccountAuths = mergeAuthWithoutDuplicates(oldAccountAuths, accountAuths); } // If shouldn't replace key auths merge with the old ones if (!(replace === null || replace === void 0 ? void 0 : replace.key)) { const oldKeyAuths = accountInfo[role].key_auths; newKeyAuths = mergeAuthWithoutDuplicates(oldKeyAuths, keyAuths); } return { account: username, [role]: { weight_threshold: threshold, account_auths: newAccountAuths.sort((a, b) => (a[0] > b[0] ? 1 : -1)), key_auths: newKeyAuths.sort((a, b) => (a[0] > b[0] ? 1 : -1)) }, json_metadata: '', memo_key: accountInfo.memo }; }; exports.generateAuthChangeObject = generateAuthChangeObject; /** * Merge authority pairs (account auths or key auths) considering newAuths the correct value in case of duplicates. * @param oldAuths old authority pairs * @param newAuths new authority pairs * @returns */ const mergeAuthWithoutDuplicates = (oldAuths, newAuths) => { const merged = Array.from(newAuths); const setNew = new Set(newAuths.map((pair) => pair[0])); // Add old auths that aren't duplicates for (const pair of oldAuths) { if (!setNew.has(pair[0])) { merged.push(pair); } } return merged; }; /** * Replace the chosen authority with the keys specified. * @param username account username * @param privateActive private active key * @param threshold wight threshold required to broadcast a transaction * @param keyAuths [public_key, weight] * @param replace Replace the old authorities instead of appending to them * @returns */ const updateAuths = async (username, privateActive, threshold, accountAuths, keyAuths, role, replace) => { let sum = 0; for (const keyWeight of keyAuths) { sum += keyWeight[1]; } if (sum < threshold) { throw new Error(`Threshold (${threshold}) cannot be smaller than the sum of the weights (${sum})`); } const payload = await (0, exports.generateAuthChangeObject)(username, threshold, [], keyAuths, role, replace); return client.broadcast.updateAccount(payload, dhive_1.PrivateKey.from(privateActive)); }; exports.updateAuths = updateAuths; /** * Add an authority for each password. Set the threshold such that a signature from each of them is required. * @param username account username * @param privateActive private active key * @param passwords array of passowrds * @param role key role (e.g. 'posting') * @returns */ const assignUnifromAuth = (username, privateActive, passwords, role) => { const keyAuths = (0, exports.generatePrivateKeys)(username, passwords, role).map((key) => [dhive_1.PrivateKey.from(key.toString()).createPublic().toString(), 1]); return (0, exports.updateAuths)(username, privateActive, passwords.length, [], keyAuths, role, { key: true }); }; exports.assignUnifromAuth = assignUnifromAuth; /** * Prepare a transaction that needs to be signed. * @param operations operations * @param expireTime expire time [ms] in [0, 3590*1000] * @returns */ const prepareTrx = async (operations, expireTime = 1000 * 3590) => { const props = await client.database.getDynamicGlobalProperties(); const ref_block_num = props.head_block_number & 0xffff; const ref_block_prefix = buffer_1.Buffer.from(props.head_block_id, 'hex').readUInt32LE(4); const expiration = new Date(Date.now() + expireTime).toISOString().slice(0, -5); const extensions = []; return { expiration, extensions, operations, ref_block_num, ref_block_prefix }; }; exports.prepareTrx = prepareTrx; /** * Check if the specified transaction has enough signatures to be broadcasted to the blockchain. * @param trx singed transaction * @returns */ const hasEnoughSignatures = async (trx) => { try { return await client.database.verifyAuthority(trx); } catch (err) { return false; } }; exports.hasEnoughSignatures = hasEnoughSignatures; /** * Sign transaction. * @param trx transaction * @param key private key * @returns */ const signTrx = (trx, key) => { return client.broadcast.sign(trx, key); }; /** * Broadcast a signed transaction. * @param signedTrx signed transaction * @returns */ const sendTrx = async (signedTrx) => { return client.broadcast.send(signedTrx); }; exports.sendTrx = sendTrx; /** * Test the multisign system. We reccomend testing on a disposable account. * The test changes the posting authorities, requiring 2 signatures to send transactions and sends a test transaction to * verify it works. * IMPORTANT: this method will change your posting authority keys, if you want to go back to the original one you need to run * the function 'resetOriginalKey'. * @param username account username * @param privateActive private active key * @returns */ const demo = async (username, privateActive) => { // Choose some passwords const passwords = ['test123', 'test456']; // Create private keys for those passwords const newPrivates = (0, exports.generatePrivateKeys)(username, passwords, 'posting'); // Find the associated public keys const newPublics = newPrivates.map((key) => dhive_1.PrivateKey.from(key.toString()).createPublic().toString()); // Create an array of [public_key, weight], where the weight specifies how much the key's vote is worth // In this case our 2 keys will have both a weight of 2 const keyAuths = newPublics.map((key) => [key, 2]); // Update the account. Use threshold = 4, so both signatures (2+2) are required for the transaction to be sent await (0, exports.updateAuths)(username, privateActive, 4, [], keyAuths, 'posting', { key: true }); // Create an operation to check that the system works const operation = [ 'custom_json', { required_auths: [], required_posting_auths: [username], id: 'hive-multisign test', json: JSON.stringify({ message: 'Transaction broadcasted with multiple signatures' }) } ]; // Prepare the transaction to be sent const trx = await (0, exports.prepareTrx)([operation]); // Sign the transaction with all the keys let signedTrx = {}; // Sign with the first private key signedTrx = client.broadcast.sign(trx, dhive_1.PrivateKey.from(newPrivates[0].toString())); // Sign with the second private key the partially signed trx signedTrx = client.broadcast.sign(signedTrx, dhive_1.PrivateKey.from(newPrivates[1].toString())); // Send the transaction return client.broadcast.send(signedTrx); }; exports.demo = demo; /** * Reset your posting key to the original. If you can't find your master password don't worry, choose a password now and * then go to your wallet and change your posting key to the one generated here. * @param username account username * @param privateActive private active key * @param masterPassword master password, it was given to you along with the other keys */ const resetOriginalKey = async (username, privateActive, masterPassword) => { const newPrivate = (0, exports.generatePrivateKeys)(username, [masterPassword], 'posting')[0]; await (0, exports.updateAuths)(username, privateActive, 1, [], [[dhive_1.PrivateKey.from(newPrivate.toString()).createPublic().toString(), 1]], 'posting', { key: true }); console.log(`Your new private key is: ${newPrivate.toString()}`); }; exports.resetOriginalKey = resetOriginalKey;